mminer-apple2/src/apple2/willy.inc

542 lines
23 KiB
PHP

;-----------------------------------------------------------------------------
; willy.inc
; Part of manic miner, the zx spectrum game, made for Apple II
;
; Stefan Wessels, 2020
; This is free and unencumbered software released into the public domain.
;-----------------------------------------------------------------------------
.segment "CODE"
;-----------------------------------------------------------------------------
; Moves willy. Incorporates user desire (userKeyMask) and conveyor direction
; in movement and does movement and collision resolution. This code was juggled a
; whole lot to make the outcome match that of the original game. It's quite
; possibly sub-optimal, but it does what it is meant to do.
.proc willyMove
willyYPosBackup = tmpBot + 1
willyXPosBackup = tmpBot + 2
willyFrameBackup = tmpBot + 3
willyFloor = tmpBot + 4
lda movementMask
bit bit2Mask ; MASK_AIR
beq checkHorzKeys ; on ground - check horizontal keys
jmp selectDirection ; in air - check horizontal motion
checkHorzKeys:
lda userKeyMask ; get the user Key
ldx conveyorMask ; and get the conveyor direction
beq notOnConveyor ; ignore conveyor code if not on a conveyor
and conveyorMask ; and user desire with conveyor
bne onConveyor ; if same, user conveyor direction for actual
lda userKeyMask ; get the user mask and
and #<~MASK_AIR ; if it's got no horizontal component
beq onConveyor ; willy is following the conveyor
lda movementMask ; user key horiz and not same as conveyor
jmp selectDirection ; so simply keep the current movement going
onConveyor:
lda movementMask ; actual is now conveyor direction
and #<~(MASK_LEFT | MASK_RIGHT) ; clear current
ora conveyorMask ; add conveyor direction
sta movementMask ; store as actual
jmp selectDirection ; use the conveyor for sideways
notOnConveyor:
and #<~MASK_AIR ; clear the jump desire
sta movementMask ; make actual user desire
selectDirection:
and #(MASK_LEFT | MASK_RIGHT) ; see if willy is moving horizontally
bne :+ ; yes - handle horizontal movement
jmp vertical
:
and #MASK_RIGHT ; check MASK_RIGHT
bne right ; if set, move to the right
left:
lda willyDir ; see if already heading left
bne moveLeft ; if so, keep moving left
lda #0 ; when turning, no direction
sta movementMask
lda #1 ; was facing right, so turn around
sta willyDir
lda willyFrame ; flip the facing frame to left
ora #4
sta willyFrame
bne vertical
moveLeft:
ldx willyFrame ; get the frame
stx willyFrameBackup ; back it up
dex ; move one left
cpx #4 ; wrapped?
bcc :+ ; yes, move to previous column
stx willyFrame ; not wrapped, save the frame
bcc :+
jmp vertical
:
ldx #7 ; keep going, load right most frame
stx willyFrame ; set frame
ldx willyXPos ; get the column
stx willyXPosBackup ; save it
dex ; previous column
stx willyXPos ; make that current
ldy #0 ; check side (left) collisions column
beq hCollision ; BRA
right:
lda willyDir ; see if willy's already facing right
beq moveRight ; if so, move
lda #0 ; turn willy to the right
sta willyDir
sta movementMask ; and clear the movement mask
lda willyFrame
and #3 ; set right facing frame
sta willyFrame
jmp vertical
moveRight:
ldx willyFrame ; back up the animation frame
stx willyFrameBackup
inx ; next frame (to the right)
cpx #4 ; wrapped?
bcs :+ ; yes, move to next (right) column
stx willyFrame ; save the frame
bcc vertical
:
ldx #0
stx willyFrame ; set frame 0
ldx willyXPos ; back up the column
stx willyXPosBackup
inx ; move right
stx willyXPos ; save this column
ldy #1 ; check the right-hand column for collisions
hCollision:
jsr willySetWorldPtr ; set up the world pointer to check collisions
ldx #2 ; assume checking 2 rows
lda willyYPos ; get the height in pixels
and #7 ; see if willy is aligned
beq colLoop ; yes, go with 2 rows
inx ; if not aligned, willy crosses 3 rows
colLoop:
lda (srcPtrL), y ; load the world byte
beq :+ ; if air then no collision
jsr willyWorldCollision ; resolve the collision if needed
bcc :+ ; if carry clear, can move (didn't hit a wall)
lda willyXPosBackup ; hit a wall, so restore position (column)
sta willyXPos
lda willyFrameBackup ; and also restore the frame
sta willyFrame
jmp vertical
:
tya ; Y is 0 or 1 (left or right), put in a
adc #32 ; move down a row
tay ; and put back in y
dex ; x has the number of rows remaining to check
bne colLoop ; do till all rows checked
vertical:
lda movementMask
bit bit2Mask ; MASK_AIR
bne vertMove ; in the air already then move vertically
lda userKeyMask ; get the user desire
bit bit2Mask ; MASK_AIR see if user wants to jump
beq willyCollisionFeet ; if not then check feet
and #<~MASK_AIR ; clear jump from desire
sta userKeyMask ; and save
lda movementMask ; update actual
ora #MASK_AIR ; by adding a jump
sta movementMask
lda #1 ; mark this as a fall starting
sta willyFallFromJump ; from a jump - willy dies easier
vertMove:
lda willyYPos ; save the current Y position
sta willyYPosBackup
lda willyJumpCounter ; get the current jump counter for height calculation
cmp #18 ; see sbc #4 below for why 18 (up/down curve length)
bcs falling ; if jump counter gt 17 then falling (0-17 is jump)
lsr ; / 2
sec
sbc #4 ; -4, so -4, -4, -3, -3, -2, -2, -1, -1, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4
clc
adc willyYPos ; adjust willy height
sta willyYPos
ldx willyJumpCounter ; get the current jump counter for height calculation
lda jumpFreq, x
ldy #$08 ; duration for the freq
jsr audioPlayNote::freq ; make a sound of this freq and duration
inc willyJumpCounter
lda willyJumpCounter
cmp #9
bcs willyCollisionFeet ; and move the jump counter along
willyCollisionTop:
lda willyYPosBackup ; see where willy was
and #7 ; if aligned with a row, now entering a new row so check collision
bne willyCollisionFeet ; not aligned, no collision, go to feet check
jsr willySetWorldPtr ; set the srcPtr to point into the world
ldy #0 ; start with the head left
lda (srcPtrL), y ; read the world byte
beq :+ ; if air then do nothing
jsr willyWorldCollision ; not air so handle the potential collision
bcs hitHead ; carry set means wall collision, so fall
:
ldy #1 ; check the right as well
lda (srcPtrL), y
beq willyCollisionFeet ; if air, ignore
jsr willyWorldCollision
bcc willyCollisionFeet ; carry clear means no collision
hitHead:
lda willyYPosBackup
sta willyYPos
lda #18
sta willyJumpCounter
jmp willyCollisionFeet ; and check for landing immediately
falling:
lda willyJumpCounter ; get the current jump counter for height calculation
and #$0f
tax
lda fallFreq, x
ldy #$08 ; duration for the freq
jsr audioPlayNote::freq ; make a sound of this freq and duration
inc willyJumpCounter ; and move the jump counter along
lda movementMask ; strip horizontal from actual
and #<~(MASK_LEFT | MASK_RIGHT)
sta movementMask
lda willyYPos ; get willy's height
clc
adc #4 ; move him 4 down (fall speed)
sta willyYPos
willyCollisionFeet:
lda #0 ; assume not on a conveyor
sta conveyorMask
lda willyYPos ; see if willy is level with a floor
and #7
bne positionScreen ; if not, don't check the feet
lda willyJumpCounter ; see if still going up in jump
beq :+ ; if 0 then not jumping
cmp #10 ; if still going up in the jump, don't check the feet
bcc positionScreen
:
jsr willySetWorldPtr ; set up the world pointer
ldy #64 ; check the ground below willy, left
lda (srcPtrL), y
sta willyFloor ; store the byte
beq :+ ; if air, don't resolve
jsr willyFloorCollision ; resolve the collision
:
ldy #65 ; check the floor right
lda (srcPtrL), y
beq checkFloor ; if air, done with feet
tax ; save the byte
ora willyFloor ; and update the floor
sta willyFloor
txa ; restore the world byte
jsr willyFloorCollision ; and resolve it
checkFloor:
lda willyFloor ; was there something under willy's feet
bne positionScreen ; if yes, then position willy
lda willyJumpCounter ; see if willy is in a jump or fall
bne positionScreen ; yes, position screen
lda #18 ; willy was walking, so set him to fall
sta willyJumpCounter
lda movementMask ; strip horizontal from actual
and #<~(MASK_LEFT | MASK_RIGHT) ; clear left and right
ora #MASK_AIR ; set the mask that he's now in the air (fall)
sta movementMask
positionScreen: ; this is also called from gameInitStage
lda cameraMode ; see which "camera mode" is active
bne cameraScroll ; non zero is scroll mode
camera3Zone: ; zone 1: 0-19, 2: 6-25, 3: 12-31
lda willyXPos ; see where Willy is
cmp #10 ; 10 divides zones 1 and 2
bcs zone2or3 ; ge 10 means zone 2 or 3
lda leftEdge ; zone 1 - see where the edge is
cmp #0 ; if it's at 0 all is well
beq done
cameraLess:
dec leftEdge ; edge is gt 0, so move it left
bpl moveName ; BRA
zone2or3:
cmp #20 ; 20 divides zones 2 and 3
bcs cameraRight ; ge 20 means zone 3
lda leftEdge ; see where the edge is (in zone 2)
cmp #6 ; zone 2 edge wants to be at 6
beq done ; if it's there, all is well
bcs cameraLess ; if it's gt 6, then move it left
inc leftEdge ; move the edge right
bne moveName ; BRA
cameraRight:
lda leftEdge ; zone 3, see where the edge is
cmp #$0c ; if it's at 12
beq done ; then it's all good
inc leftEdge ; otherwise move it right towards 12
moveName: ; if the edge moves the text needs to move as well
lda #UI_COMPONENT_NAME ; and mark the name as needing to scroll too
jsr uiUpdateComponent
rts
cameraScroll:
lda willyXPos
sec ; col is in accumulator
sbc #$0a ; see if willy is past column 10
bcs :+
lda #0 ; not, so set the left edge to the left
bne :++ ; BRA
:
cmp #$0d ; see if the col is less than 13
bcc :+
lda #$0c ; col is 13 or greater, so clamp to 12
:
cmp leftEdge ; see if the edge needs to move
beq done ; don't move
sta leftEdge ; set the new left edge
lda #UI_COMPONENT_NAME ; and mark the name as needing to scroll too
jsr uiUpdateComponent
done:
rts ; done with willy's movement
.endproc
;-----------------------------------------------------------------------------
; resolves collisions for willy. Used by feet but foot collision entry is
; from willyFloorCollision. on exit, carry set means wall collision
.proc willyWorldCollision
clc
cmp #DATA_BUSH ; bushes kill willy
beq willyDies
cmp #DATA_ROCK ; rocks kill willy
bne :+
willyDies:
lda #EVENT_DIED ; simply set the die event
ora eventState
sta eventState
rts
:
cmp #DATA_WALL ; walls block willy
bne :+
rts ; carry was set by the cmp and equality. set means wall collision
:
cmp #DATA_KEY ; key's need to be counted
bne :+
jmp willyHitKey ; clears carry
:
cmp #DATA_DOOR ; added dynamically when last key found
bne :+
lda #EVENT_NEXT_LEVEL ; set event to move to next cavern/level
ora eventState
sta eventState
rts
:
cmp #DATA_SWITCH1 ; added at level init for kong screens
bne :+
jmp willyHitSwitch1
:
cmp #DATA_SWITCH2 ; added at level init for kong screens
bne done
jmp willyHitSwitch2
done:
clc ; for unhandled (floor tiles), just clear carry
rts
.endproc
;-----------------------------------------------------------------------------
.proc willyFloorCollision
cmp #DATA_FLOOR1 ; floors are landed on
beq landed
cmp #DATA_WALL ; walls can be walked on
beq landed
cmp #DATA_FLOOR2 ; special can be walked on
bne :+
landed:
lda movementMask ; see landing from a jump/fall
and #MASK_AIR
beq notFromAir ; just a foot-fall from walking, no action
clc
lda willyFallFromJump ; see if a jump
adc willyJumpCounter ; and how far
cmp #18+9 ; compare to death height
bcc fellNotTooFar ; not too far
jmp willyWorldCollision::willyDies ; fell to far, kill willy
fellNotTooFar:
lda #0 ; reset the fall from jump
sta willyFallFromJump
sta willyJumpCounter ; and reset the willy jump counter
lda #<~MASK_AIR ; clear the air bit
and movementMask
sta movementMask
notFromAir:
rts
:
cmp #DATA_CONVEYOR ; landed on a conveyor
bne :+
ldx currLevel ; get the level
lda conveyorDirections, x ; get the direction of the conveyor
sta conveyorMask ; set it as the conveyor mask (which is reset each frame)
bne landed ; and do landing code
:
cmp #DATA_COLLAPSE ; check for collapsing tiles
bcc willyWorldCollision ; less than, not collapsing
cmp #DATA_COLLAPSE + 9 ; in the collapse range
bcs willyWorldCollision ; no, check non-walk/floor tiles
jsr willyCollapse ; collapse a platform tile, returns with carry clear
bcc landed ; BRA to land code
.endproc
;-----------------------------------------------------------------------------
.proc willyHitSwitch1
clc
adc #1 ; DATA_SWITCH1 becomes DATA_SWITCH1_OPEN
sta (srcPtrL), y ; make this switch draw open
lda #0
sta levelLayout+11*32+17 ; make a hole in the wall
sta levelLayout+12*32+17
ldx #1 ; sprite 1 barrel needs to go further
lda #19 ; this is the new max for return-kong
extend:
sta spriteMax, x ; make the new max active
clc ; must leave with carry clear - not a wall collision
rts
.endproc
;-----------------------------------------------------------------------------
.proc willyHitSwitch2
clc
adc #1 ; DATA_SWITCH2 becomes DATA_SWITCH2_OPEN
sta (srcPtrL), y ; make this switch draw open
lda #0
sta levelLayout+2*32+15 ; remove kong's platform
sta levelLayout+2*32+16
ldx #3 ; kong is at index 3
lda #14*8-5 ; put the fallen-down destination for kong in place
sta spriteMax, x
lda #2 ; turn kong upside down
sta spriteFrame, x
lda #0 ; set kong's direction to down
sta spriteDir, x
rts
.endproc
;-----------------------------------------------------------------------------
.proc willyCollapse
clc
adc #1 ; move the tile-top one down
cmp #DATA_COLLAPSE + 7 ; is it all the way down
bcc :+ ; not yet
lda #0 ; yes, so erase the tile
clc
:
sta (srcPtrL), y ; make the change in the level
rts
.endproc
;-----------------------------------------------------------------------------
.proc willyHitKey
tempX = tmpBot + 5
stx tempX ; save x - it may be an index in left/right col
lda #0 ; erase the key tile in the level
sta (srcPtrL), y
ldx #3 ; add 100 (digit 3 of 6, zero based 000100)
lda #1
jsr textAddScore
dec keysToCollect ; 1 less key to collect
bne done ; all keys collected?
ldx currLevel ; get the level
lda doorL, x ; get the door location in the level (lo)
sta putDoor + 1
lda doorH, x ; and hi
sta putDoor + 2
lda #DATA_DOOR ; get the door tile
putDoor:
sta PLACEHOLDER ; and add the door tile to the level
done:
ldx tempX ; restore the saved x
clc ; make sure carry is clear
rts
.endproc
;-----------------------------------------------------------------------------
.proc willySetWorldPtr
lda willyYPos ; get the height
lsr ; divide by 8
lsr
lsr
sta willyYRow ; save the row willy's in
tax ; put the row in Y
clc
lda mult32L, x ; row * 32
adc willyXPos ; and add the X position (levelLayout is aligned so no need to add lo)
sta srcPtrL ; the low byte of the pos in the level
lda mult32H, x ; get the high byte
adc #>levelLayout ; and offset into the level
sta srcPtrH ; and now srcPtr points at willy in the level
rts
.endproc