542 lines
23 KiB
PHP
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
|