mminer-orca/src/willy.asm

534 lines
27 KiB
NASM

;-----------------------------------------------------------------------------
; 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.
;-----------------------------------------------------------------------------
65816 OFF
copy src/defs.s
WILLY start
using LOWMEM
using ROSYSTEM
using ROLEVELS
;-----------------------------------------------------------------------------
; 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.
willyMove entry
willyYPosBackup equ tmpBot+1
willyXPosBackup equ tmpBot+2
willyFrameBackup equ tmpBot+3
willyFloor equ tmpBot+4
lda movementMask
bit bit2Mask ; MASK_AIR
beq checkHorzKeys ; on ground - check horizontal keys
jmp selectDirection ; in air - check horizontal motion
checkHorzKeys anop
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 #$FB ; <~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 anop
lda movementMask ; actual is now conveyor direction
and #$FC ;<~(MASK_LEFT | MASK_RIGHT) ; clear current
ora conveyorMask ; add conveyor direction
sta movementMask ; store as actual
jmp selectDirection ; use the conveyor for sideways
notOnConveyor anop
and #$FB ; <~MASK_AIR ; clear the jump desire
sta movementMask ; make actual user desire
selectDirection anop
and #3 ;(MASK_LEFT | MASK_RIGHT) ; see if willy is moving horizontally
bne wm00 ; yes - handle horizontal movement
jmp vertical
wm00 and #MASK_RIGHT ; check MASK_RIGHT
bne right ; if set, move to the right
left anop
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 anop
ldx willyFrame ; get the frame
stx willyFrameBackup ; back it up
dex ; move one left
cpx #4 ; wrapped?
bcc wm01 ; yes, move to previous column
stx willyFrame ; not wrapped, save the frame
bcc wm01
jmp vertical
wm01 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 anop
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 anop
ldx willyFrame ; back up the animation frame
stx willyFrameBackup
inx ; next frame (to the right)
cpx #4 ; wrapped?
bcs wm02 ; yes, move to next (right) column
stx willyFrame ; save the frame
bcc vertical
wm02 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 anop
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 anop
lda (srcPtrL),y ; load the world byte
beq wm03 ; if air then no collision
jsr willyWorldCollision ; resolve the collision if needed
bcc wm03 ; 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
wm03 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 anop
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 #$FB ; <~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 anop
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 a ; / 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 freq ; make a sound of this freq and duration
inc willyJumpCounter
lda willyJumpCounter
cmp #9
bcs willyCollisionFeet ; and move the jump counter along
willyCollisionTop anop
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 wm04 ; if air then do nothing
jsr willyWorldCollision ; not air so handle the potential collision
bcs hitHead ; carry set means wall collision, so fall
wm04 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 anop
lda willyYPosBackup
sta willyYPos
lda #18
sta willyJumpCounter
jmp willyCollisionFeet ; and check for landing immediately
falling anop
lda willyJumpCounter ; get the current jump counter for height calculation
and #$0f
tax
lda fallFreq,x
ldy #$08 ; duration for the freq
jsr freq ; make a sound of this freq and duration
inc willyJumpCounter ; and move the jump counter along
lda movementMask ; strip horizontal from actual
and #$FC ; <~(MASK_LEFT | MASK_RIGHT)
sta movementMask
lda willyYPos ; get willy's height
clc
adc #4 ; move him 4 down (fall speed)
sta willyYPos
willyCollisionFeet anop
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 wm05 ; if 0 then not jumping
cmp #10 ; if still going up in the jump, don't check the feet
bcc positionScreen
wm05 jsr willySetWorldPtr ; set up the world pointer
ldy #64 ; check the ground below willy, left
lda (srcPtrL),y
sta willyFloor ; store the byte
beq wm06 ; if air, don't resolve
jsr willyFloorCollision ; resolve the collision
wm06 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 anop
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 #$FC ; <~(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 entry ; this is also called from gameInitStage
lda cameraMode ; see which "camera mode" is active
bne cameraScroll ; non zero is scroll mode
camera3Zone anop ; 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 done0
cameraLess anop
dec leftEdge ; edge is gt 0, so move it left
bpl moveName ; BRA
zone2or3 anop
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 done0 ; 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 anop
lda leftEdge ; zone 3, see where the edge is
cmp #$0c ; if it's at 12
beq done0 ; then it's all good
inc leftEdge ; otherwise move it right towards 12
moveName anop ; 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 anop
lda willyXPos
sec ; col is in accumulator
sbc #$0a ; see if willy is past column 10
bcs wm07
lda #0 ; not, so set the left edge to the left
bne wm08 ; BRA
wm07 cmp #$0d ; see if the col is less than 13
bcc wm08
lda #$0c ; col is 13 or greater, so clamp to 12
wm08 cmp leftEdge ; see if the edge needs to move
beq done0 ; 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
done0 anop
rts ; done with willy's movement
;-----------------------------------------------------------------------------
; resolves collisions for willy. Used by feet but foot collision entry is
; from willyFloorCollision. on exit, carry set means wall collision
willyWorldCollision entry
clc
cmp #DATA_BUSH ; bushes kill willy
beq willyDies
cmp #DATA_ROCK ; rocks kill willy
bne wwc0
willyDies entry
lda #EVENT_DIED ; simply set the die event
ora eventState
sta eventState
rts
wwc0 cmp #DATA_WALL ; walls block willy
bne wwc1
rts ; carry was set by the cmp and equality. set means wall collision
wwc1 cmp #DATA_KEY ; key's need to be counted
bne wwc2
jmp willyHitKey ; clears carry
wwc2 cmp #DATA_DOOR ; added dynamically when last key found
bne wwc3
lda #EVENT_NEXT_LEVEL ; set event to move to next cavern/level
ora eventState
sta eventState
rts
wwc3 cmp #DATA_SWITCH1 ; added at level init for kong screens
bne wwc4
jmp willyHitSwitch1
wwc4 cmp #DATA_SWITCH2 ; added at level init for kong screens
bne done1
jmp willyHitSwitch2
done1 anop
clc ; for unhandled (floor tiles), just clear carry
rts
;-----------------------------------------------------------------------------
willyFloorCollision entry
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 wfc0
landed anop
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 willyDies ; fell to far, kill willy
fellNotTooFar anop
lda #0 ; reset the fall from jump
sta willyFallFromJump
sta willyJumpCounter ; and reset the willy jump counter
lda #$FB ; <~MASK_AIR ; clear the air bit
and movementMask
sta movementMask
notFromAir anop
rts
wfc0 cmp #DATA_CONVEYOR ; landed on a conveyor
bne wfc1
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
wfc1 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
;-----------------------------------------------------------------------------
willyHitSwitch1 entry
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 anop
sta spriteMax,x ; make the new max active
clc ; must leave with carry clear - not a wall collision
rts
;-----------------------------------------------------------------------------
willyHitSwitch2 entry
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
;-----------------------------------------------------------------------------
willyCollapse entry
clc
adc #1 ; move the tile-top one down
cmp #DATA_COLLAPSE+7 ; is it all the way down
bcc wc00 ; not yet
lda #0 ; yes, so erase the tile
clc
wc00 sta (srcPtrL),y ; make the change in the level
rts
;-----------------------------------------------------------------------------
willyHitKey entry
tempX equ 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 done2 ; 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 anop
sta PLACEHOLDER ; and add the door tile to the level
done2 anop
ldx tempX ; restore the saved x
clc ; make sure carry is clear
rts
;-----------------------------------------------------------------------------
willySetWorldPtr entry
lda willyYPos ; get the height
lsr a ; divide by 8
lsr a
lsr a
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
end