850 lines
35 KiB
PHP
850 lines
35 KiB
PHP
;-----------------------------------------------------------------------------
|
|
; game.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"
|
|
|
|
;-----------------------------------------------------------------------------
|
|
.proc gameLoop
|
|
|
|
jsr gameNewGame ; init things that need 1-time game init
|
|
|
|
nextLevel:
|
|
jsr tilesPrepForLevel ; move 8 tiles for this level to tilesInstances
|
|
|
|
restart:
|
|
jsr gameInitStage ; init the stage, incl unpacking the level, tile prep etc
|
|
|
|
playLoop:
|
|
jsr inputGet ; read the keyboard
|
|
|
|
lda demoMode ; check for demo mode
|
|
beq :+ ; if not demo, move willy
|
|
jsr gameDemoTick ; tick the demo if demo mode
|
|
jmp :++ ; skip moving willy
|
|
:
|
|
jsr willyMove ; move the main character based on user input (or conveyor or gravity)
|
|
:
|
|
jsr gameAI ; run all the monster AI
|
|
|
|
ldx fullScreenClearCount ; get the flag for what level of screen clear is needed
|
|
jsr screenClear ; and clear the screen
|
|
|
|
jsr tilesAnimateKeys ; copy the 16 bytes for the next "key" frame into place
|
|
jsr tilesAnimateConveyor ; update the conveyor tile
|
|
|
|
jsr screenDrawSprites ; draw all the enemies
|
|
|
|
lda demoMode ; get the event state
|
|
bne :+ ; if so skip showing willy
|
|
jsr screenDrawWilly ; not demo - draw willy with collision detection
|
|
bcc :+ ; carry set is a collision, clear is no problem
|
|
lda eventState ; on collision, set the die event
|
|
ora #EVENT_DIED
|
|
sta eventState
|
|
:
|
|
jsr screenDrawLevel ; show all the tiles
|
|
|
|
lda updateUICount ; see if the UI needs an update
|
|
beq :+
|
|
jsr uiUpdate ; if needed, update the appropriate UI components
|
|
|
|
:
|
|
ldx numSprites ; The door's index
|
|
jsr screenDrawSprite ; render the door over everything else, no collision
|
|
|
|
jsr screenSwap ; swap to the newly rendered screen
|
|
jsr audioPlayNote ; play in-game music if needed
|
|
|
|
lda eventState ; see if any events fired
|
|
beq playLoop ; keep looping while no events
|
|
|
|
jsr gameEvent ; process the event, return value in a
|
|
bit bit1Mask ; EVENT_LEVEL_RESTART
|
|
bne restart
|
|
bit bit2Mask ; EVENT_NEXT_LEVEL
|
|
bne nextLevel
|
|
|
|
|
|
lda demoMode ; no Game Over when the demo ends
|
|
bne :+
|
|
jsr gameOver ; wasn't a demo, show Game Over
|
|
:
|
|
jsr textCheckHighScore ; game over - see if a new high score was set
|
|
rts
|
|
|
|
.endproc
|
|
|
|
;-----------------------------------------------------------------------------
|
|
.proc gameNewGame
|
|
|
|
lda #START_LIVES ; init player number of lives
|
|
sta lives
|
|
|
|
lda #LEVEL_Central_Cavern ; Init these game vars to 0
|
|
sta currLevel
|
|
|
|
lda #0 ; audioPlayNote uses muiscL as well
|
|
sta musicL ; track progress through in-game song
|
|
|
|
ldx #5 ; six, zero-based score digits
|
|
lda #'0' ; load a with '0'
|
|
:
|
|
sta score, x ; set all 6 score digits to '0'
|
|
dex
|
|
bpl :-
|
|
|
|
rts
|
|
|
|
.endproc
|
|
|
|
;-----------------------------------------------------------------------------
|
|
.proc gameInitStage
|
|
|
|
count = tmpBot + 0
|
|
spriteIdx = tmpBot + 1
|
|
instanceIdx = tmpBot + 2
|
|
|
|
ldy currLevel ; Skip some stuff for "Game Over" pseudo-level
|
|
cpy #20
|
|
bcs notDemo
|
|
|
|
jsr levelUnpack ; decompress a level to levelLayout
|
|
jsr levelPlaceKeys ; put the keys into the world
|
|
jsr textSetLevelText ; create the gfx cache for the name
|
|
|
|
ldy currLevel ; tables are indexed by level
|
|
lda demoMode ; see if this is demo mode
|
|
beq notDemo ; branch if game
|
|
lda willyx ; use willy screen 0 for willyXPos in demo (force leftEdge to 0)
|
|
bne :+
|
|
|
|
notDemo:
|
|
lda willyx, y ; set willy X up
|
|
:
|
|
sta willyXPos
|
|
ldx cameraMode
|
|
lda #CAMERA_MODE_SCROLL
|
|
sta cameraMode
|
|
jsr willyMove::positionScreen ; set the screen position based on willy's X
|
|
stx cameraMode
|
|
lda willyy, y ; set willy's Y
|
|
sta willyYPos
|
|
lda willyStartDir, y ; and see what facing direction
|
|
sta willyDir
|
|
beq :+ ; if right (0) then set that as the animFrame
|
|
lda #4 ; if left (1) set the anim frame to 4
|
|
:
|
|
sta willyFrame ; and save the anim frame
|
|
|
|
postWilly:
|
|
lda #3 ; AI will count down and runs before clearScreen
|
|
sta fullScreenClearCount ; so set to 3 to get full clear on both buffers
|
|
|
|
lda #AIR_SPEED ; set counter for ticking down air
|
|
sta airFlow
|
|
|
|
lda #32 ; set how many air cols there are
|
|
sta airCols
|
|
|
|
lda #$7f ; the air "bar" tip is a character that draws less
|
|
sta airTipGfx ; bar over time, till it rolls to the prev column
|
|
|
|
lda #$ff ; match all masks
|
|
jsr uiUpdateComponent ; so force all UI elements to draw
|
|
|
|
lda level_sprites_count, y ; get how many enemy sprites the level has
|
|
sta count ; save for init
|
|
sta numSprites ; save for the game to use
|
|
dec numSprites ; make this count 0 based
|
|
lda level_sprites_offset, y ; see where in the sprite tables this sprite starts
|
|
tay ; move the offset into x - sprite tables indexed by this
|
|
|
|
ldx #0 ; y will be an enemy instance index
|
|
stx eventState ; init he event state
|
|
stx livesFrame ; but use this 0 to init some variables
|
|
stx movementMask ; current movement for willy
|
|
stx conveyorMask ; direction conveyor under willy is moving (0=no conveyor under willy)
|
|
stx userKeyMask ; keys the user has pressed to move willy
|
|
stx willyJumpCounter ; 0 means no jump, 1-18 is in a jump, beyond is a fall height
|
|
stx spriteFramesIdx
|
|
|
|
clc ; clear carry - will always enter below with carry clear
|
|
next:
|
|
sty spriteIdx ; save which sprite is being processed (x clobbered)
|
|
stx instanceIdx ; save y, also clobbered
|
|
|
|
lda #0
|
|
sta spriteFrame, x ; init the sprite specific frame counter to 0
|
|
|
|
lda sprites_x, y ; load the basic sprite variables and put into zero page
|
|
sta spriteXPos, x ; from 0 .. numSprites - 1
|
|
lda sprites_y, y ; the addressing is unfortunate - sta ,x is zero-page 2 byte
|
|
sta spriteYPos, x ; but it's not worth swapping x and y for this bit of code
|
|
lda sprites_min, y
|
|
sta spriteMin, x
|
|
lda sprites_max, y
|
|
sta spriteMax, x
|
|
lda sprites_speed, y
|
|
sta spriteSpeed, x
|
|
sta spriteTick, x
|
|
lda sprites_dir, y
|
|
sta spriteDir, x
|
|
lda sprites_class, y
|
|
sta spriteClass, x
|
|
lda sprites_colors, y ; get the color
|
|
sta spriteColor, x
|
|
|
|
lda sprites_bitmaps, y ; get the index into the sprite data for this sprite's images
|
|
jsr spriteInstanceSpriteFrames
|
|
|
|
ldx instanceIdx ; restore y
|
|
cpx numSprites
|
|
beq prep
|
|
|
|
lda spriteClass, x
|
|
bit bit1Mask ; CLASS_FOUR_FRAME
|
|
beq :+
|
|
lda #4
|
|
bne :++
|
|
:
|
|
lda #8
|
|
:
|
|
adc spriteFramesIdx, x
|
|
sta spriteFramesIdx + 1, x
|
|
|
|
ldy spriteIdx
|
|
iny ; next sprite in sprite table
|
|
inx ; next instance
|
|
dec count
|
|
beq prep
|
|
jmp next
|
|
|
|
prep:
|
|
ldx currLevel ; some levels need special handling
|
|
cpx #LEVEL_Eugenes_Lair ; eugene needs colored copies
|
|
bne :+
|
|
jsr spriteEugeneSetup
|
|
jmp fixDoor
|
|
|
|
:
|
|
cpx #LEVEL_Miner_Willy_meets_the_Kong ; kong levels need switches
|
|
beq kong
|
|
cpx #LEVEL_Return_of_the_Alien_Kong_Beast
|
|
bne :+
|
|
kong:
|
|
lda #DATA_SWITCH1
|
|
sta levelLayout + 6
|
|
|
|
lda #DATA_SWITCH2
|
|
sta levelLayout + 18
|
|
jmp fixDoor
|
|
|
|
:
|
|
cpx #LEVEL_Skylab_Landing_Bay ; skylab needs satellites to get X values
|
|
bne :+
|
|
ldx #2 ; init the falling satellites
|
|
skyLabPos:
|
|
lda skylabXPos, x
|
|
sta spriteXPos, x
|
|
txa
|
|
sta spriteTick, x
|
|
dex
|
|
bpl skyLabPos
|
|
|
|
:
|
|
cpx #LEVEL_The_Final_Barrier
|
|
bne fixDoor
|
|
dec numSprites ; hide the victory door sprite
|
|
|
|
fixDoor:
|
|
jmp spriteDoorSetup ; sprites are set up - door is special
|
|
|
|
.endproc
|
|
|
|
;-----------------------------------------------------------------------------
|
|
.proc gameAI
|
|
|
|
lda fullScreenClearCount ; get the state
|
|
beq :+ ; if it's zero, move on
|
|
dec fullScreenClearCount ; count this down
|
|
:
|
|
dec airFlow ; deal with the air countdown. airFlow is "time" counter
|
|
bne airDone
|
|
jsr gameAirDecrease
|
|
|
|
airDone:
|
|
inc livesFrame ; move the lives anim frame along
|
|
lda livesFrame ; through the first 4 frames only
|
|
cmp #16 ; by dividing by 4 (in essence) to get a slower animation
|
|
bcc :+
|
|
lda #0 ; start at frame 0 when wrapping
|
|
:
|
|
sta livesFrame
|
|
and #3 ; only update every 4 frames (not & 3)
|
|
bne :+
|
|
lda #UI_COMPONENT_LIVES ; tell ui to update lives
|
|
jsr uiUpdateComponent
|
|
|
|
:
|
|
ldx numSprites ; now set up all the sprites for this level
|
|
loop:
|
|
lda spriteClass, x ; start with the class
|
|
bit CLASS_DOOR ; and if it's a door, treat that separate
|
|
beq :+
|
|
jmp door
|
|
:
|
|
bit CLASS_MOVE_Y ; see if it moves vertically
|
|
beq horizontal ; no - then it moves horizontally
|
|
jmp vertical ; yes - them move it vertically
|
|
|
|
horizontal:
|
|
lda spriteSpeed, x ; get speed
|
|
bit bit0Mask ; Is the speed 1
|
|
beq rtick ; if not, advance this sprite
|
|
dec spriteTick, x ; dec the ticker
|
|
bpl next ; if ge 0 then ignore sprite
|
|
sta spriteTick, x ; reset ticker to speed and fall through to run
|
|
|
|
rtick:
|
|
lda spriteDir, x ; get the direction
|
|
bne left ; 1 is left, 0 is right
|
|
|
|
right:
|
|
inc spriteFrame, x ; move the frame
|
|
lda spriteFrame, x ; load it
|
|
cmp #4 ; see if it's 4
|
|
bcc next ; if 4+, done here
|
|
inc spriteXPos, x ; up the x position
|
|
lda spriteXPos, x ; load it
|
|
cmp spriteMax, x ; see if it's ge max
|
|
bcs rightEnd ; if yes, end of going right
|
|
lda #0 ; no, reset
|
|
sta spriteFrame, x ; frame to 0
|
|
beq next ; BRA. done with this sprite
|
|
rightEnd:
|
|
dec spriteXPos, x ; set back to last valid x
|
|
lda #1 ; load left
|
|
sta spriteDir, x ; and set direction left
|
|
lda #7 ; 7 is most right position of sprite
|
|
sta spriteFrame, x ; and set the frame to that
|
|
|
|
fix4:
|
|
lda spriteClass, x ; get the class
|
|
bit CLASS_FOUR_FRAME ; see if it has the (only) 4 frames flag set
|
|
beq next ; if not, done with this sprite (frame 7 is good)
|
|
lda #3 ; drop the 7 to 3
|
|
sta spriteFrame, x ; set the frame
|
|
jmp next
|
|
|
|
left:
|
|
dec spriteFrame, x ; move the frame
|
|
lda spriteFrame ,x ; load it
|
|
cmp #$ff ; see if it is now lt 0 (3,2,1 -> 0 overflow)
|
|
beq stepLeft ; if it is, move the col left
|
|
cmp #3 ; if it's 3, (7,6,5,4 -> 3 overflow)
|
|
bne next ; if not, done with this sprite
|
|
|
|
stepLeft:
|
|
dec spriteXPos, x ; move the column left
|
|
lda spriteXPos, x ; load it
|
|
cmp spriteMin, x ; compare to minimum
|
|
bcc leftEnd ; if less than minimum, too far, past the left edge
|
|
lda #7 ; keep going, load 7
|
|
sta spriteFrame ,x ; set frame back to 7
|
|
bne fix4 ; BRA, and check for a 4-frame sprite
|
|
|
|
leftEnd:
|
|
inc spriteXPos, x ; put the column back to in range
|
|
lda #0 ; load 0
|
|
sta spriteDir, x ; set as direction (right)
|
|
sta spriteFrame, x ; and frame (most left frame)
|
|
beq next ; BRA, done with this sprite
|
|
|
|
postVMove:
|
|
lda spriteClass, x ; after a vertical update check these special cases
|
|
bit CLASS_EUGENE ; Eugene
|
|
bne eugene
|
|
bit CLASS_KONG ; Kong
|
|
beq :+
|
|
jmp kong
|
|
:
|
|
bit CLASS_SKYLAB ; skylab
|
|
beq :+
|
|
jmp skylab
|
|
:
|
|
inc spriteFrame, x ; otherwise go to next frame
|
|
lda spriteFrame, x
|
|
and #3
|
|
sta spriteFrame, x
|
|
|
|
next:
|
|
dex ; get previous sprite
|
|
bpl goTop ; if ge 0 then still a sprite to process
|
|
rts ; all sprites done - exit
|
|
|
|
goTop:
|
|
jmp loop
|
|
|
|
vertical:
|
|
lda spriteDir, x ; get direction 1 = UP, 0 = DOWN
|
|
beq down
|
|
bmi postVMove ; if the spriteDir is lt $ff, stationary sprite
|
|
|
|
up:
|
|
lda spriteYPos, x ; get the Y position
|
|
sec
|
|
sbc spriteSpeed, x ; move up by the speed
|
|
cmp spriteMin, x ; see if at top
|
|
bcc upEnd ; overshot top
|
|
sta spriteYPos, x ; update Y position
|
|
bcs postVMove ; BRA
|
|
|
|
upEnd:
|
|
lda spriteClass, x ; get the class
|
|
bit CLASS_HOLDATEND ; should it stop or bounce
|
|
bne stop ; HOLDATEND means stop
|
|
lda #0 ; change direction
|
|
sta spriteDir, x ; to DOWN (0)
|
|
beq postVMove ; BRA
|
|
|
|
down:
|
|
lda spriteYPos, x ; get the Y
|
|
clc
|
|
adc spriteSpeed, x ; add the speed
|
|
cmp spriteMax, x ; see of at end
|
|
bcs downEnd ; at or past end
|
|
sta spriteYPos, x ; still good, update Y position
|
|
bcc postVMove ; BRA
|
|
|
|
downEnd:
|
|
lda spriteClass, x ; same as upEnd
|
|
bit CLASS_HOLDATEND
|
|
bne stop
|
|
lda #1 ; but mark for moving UP (1)
|
|
sta spriteDir, x
|
|
bne postVMove ; BRA maybe down?
|
|
stop:
|
|
lda #$ff ; set the direction to -1 (lt 0)
|
|
sta spriteDir, x
|
|
bne postVMove ; BRA
|
|
|
|
door:
|
|
lda keysToCollect ; check if all keys have been collected
|
|
bne next ; no - nothing more to do
|
|
frameToggle:
|
|
dec spriteTick, x ; count down for animation
|
|
bpl next ; gt 0, nothing more
|
|
lda spriteFrame, x ; get the frame
|
|
eor #1 ; toggle between 1 and 0
|
|
sta spriteFrame, x ; update the frame
|
|
lda spriteSpeed, x ; get the anim speed
|
|
sta spriteTick, x ; save it to the tick
|
|
jmp next
|
|
|
|
eugene:
|
|
lda keysToCollect ; eugene changes behavior when all keys collected
|
|
bne eugeneNormal ; not all keys, carry on
|
|
lda #0 ; all keys - force eugene down
|
|
sta spriteDir, x
|
|
inc spriteFrame, x ; cycle through the 5 colors
|
|
lda spriteFrame, x
|
|
cmp #4
|
|
bcc :+
|
|
lda #0
|
|
:
|
|
sta spriteFrame, x ; save the new frame
|
|
eugeneNormal:
|
|
jmp next
|
|
|
|
kong:
|
|
lda spriteMax, x ; if kong's max is 0 he's still up
|
|
beq frameToggle
|
|
lda spriteDir, x ; if he's not up see what his direction is
|
|
bpl kongFall ; gt 0, then he's still falling
|
|
cmp #$FE ; $fe he's been erased so done with him
|
|
beq kongDone ; $ff he's just reached the bottom
|
|
dec spriteDir, x ; turn $ff into $fe
|
|
txa ; put the sprite index into a
|
|
pha ; and save it
|
|
lda spriteFramesIdx, x ; get the index to the 1st kong frame
|
|
tax ; put that in x
|
|
inx ; and skip the 2 frames where
|
|
inx ; kong is standing
|
|
ldy #2 ; want to clear 2 frames
|
|
jsr spriteClearFrames ; and make the falling frames blank
|
|
pla ; get the sprite index
|
|
tax ; and put it back in x
|
|
kongDone:
|
|
jmp next
|
|
kongFall:
|
|
ldx #3 ; digit 3 (100's)
|
|
lda #1 ; add 1
|
|
jsr textAddScore ; add to score
|
|
lda #UI_COMPONENT_SCORE ; show changes
|
|
jsr uiUpdateComponent
|
|
jmp frameToggle
|
|
|
|
skylab:
|
|
lda spriteDir, x ; get the direction of the falling satellite
|
|
cmp #$ff ; see if it's reached its end
|
|
beq :+ ; yes it has
|
|
jmp next
|
|
:
|
|
inc spriteFrame, x ; advance the collapsing frame
|
|
lda spriteFrame, x ; load that frame
|
|
cmp #8 ; see if it's the last
|
|
bcs :+ ; yes
|
|
jmp next
|
|
:
|
|
lda spriteTick, x ; get the tick (hich is an index in this case)
|
|
clc
|
|
adc #3 ; advance by 3 (3 satellites at a time) so next index for this satellite
|
|
cmp #12 ; (3*4 is 12) - there are 4 stating locations per satellite
|
|
bcc :+ ; not rolled over
|
|
and #3 ; reset this satellite to 1st starting location (index)
|
|
:
|
|
sta spriteTick, x ; save the tick
|
|
tay ; put into Y
|
|
lda skylabXPos, y ; get the actual start position, based on y, for this satellite
|
|
sta spriteXPos, x ; put that into the satellite
|
|
lda #0 ; reset the frame, position and direction all to 0
|
|
sta spriteFrame, x
|
|
sta spriteYPos, x
|
|
sta spriteDir, x
|
|
jmp next
|
|
|
|
.endproc
|
|
|
|
|
|
;-----------------------------------------------------------------------------
|
|
.proc gameDemoTick
|
|
|
|
dec demoTimer ; timer counts down
|
|
bne :+ ; if not yet zero, nothing to do
|
|
lda #DEMO_TIMER_DURATION ; reset the timer
|
|
sta demoTimer
|
|
lda #UI_COMPONENT_NAME ; mark the level name as needing an update
|
|
jsr uiUpdateComponent
|
|
lda leftEdge ; scroll the screen
|
|
clc
|
|
adc demoDirection ; based on the scrolling direction
|
|
sta leftEdge
|
|
beq nextDemoLevel ; if the edge is 0 then done with level
|
|
cmp #12 ; at 12, the level flips scroll direction
|
|
bne :+
|
|
lda #$ff ; at 12, the scroll direction becomes -1
|
|
sta demoDirection
|
|
:
|
|
rts
|
|
|
|
nextDemoLevel:
|
|
lda #DEMO_TIMER_INITAL ; set for a longer initial hold at a new level
|
|
sta demoTimer
|
|
lda #1 ; set the scroll direction to be right (1)
|
|
sta demoDirection
|
|
lda #0 ; set the edge to be the very left
|
|
sta leftEdge
|
|
lda #EVENT_NEXT_LEVEL ; fire a new level event
|
|
ora eventState
|
|
sta eventState
|
|
rts
|
|
|
|
.endproc
|
|
|
|
;-----------------------------------------------------------------------------
|
|
.proc gameEvent
|
|
|
|
bit bit0Mask ; EVENT_DIED
|
|
beq :+ ; if not, must be end of level
|
|
dec lives ; died, so take a life
|
|
bmi died ; all lives lost ends the game
|
|
lda #0
|
|
sta tmpBot
|
|
jsr screenInvertVisibleScreen
|
|
lda #EVENT_LEVEL_RESTART ; still alive so restart the level
|
|
rts
|
|
|
|
:
|
|
bit bit4Mask ; EVENT_CHEAT_JUMP
|
|
bne done ; if jumping, just go
|
|
lda currLevel ; check the level
|
|
cmp #19 ; is this the last level
|
|
bcc :+ ; if not, go to screen invert
|
|
lda cheatActive ; last level - got here by cheating?
|
|
bne :+ ; yes - go screen invert
|
|
lda demoMode ; is this demo mode?
|
|
bne :+ ; yes - go to screen invert
|
|
jsr gameVictory ; played properly through, get the victory
|
|
:
|
|
ldx #8 ; do an inverse screen effect
|
|
:
|
|
stx sizeL
|
|
txa
|
|
and #3
|
|
clc
|
|
adc #1
|
|
sta tmpBot
|
|
jsr screenInvertVisibleScreen
|
|
ldx sizeL
|
|
dex
|
|
bne :-
|
|
|
|
lda demoMode ; is this demo mode
|
|
bne incLevel ; skip the air/score, just go to next level
|
|
|
|
nextLevel:
|
|
jsr screenSwap::valueSwap ; do the air countdown/add score routine now, on front screen
|
|
|
|
airLoop:
|
|
ldx #5 ; digit 5 (1's)
|
|
lda #7 ; add 7
|
|
jsr textAddScore ; add to score
|
|
ldx #4 ; digit 4 (10's)
|
|
lda #1 ; add 1 (so add 17 per tick)
|
|
jsr textAddScore ; add to score
|
|
jsr gameAirDecrease ; run the decrease air
|
|
lda airCols ; get the remaining bar length
|
|
asl ; mult * 4
|
|
asl
|
|
eor #$7f ; and reverse (ignore MSB which is 0) - this is the freq
|
|
ldy #6 ; duration for the freq
|
|
jsr audioPlayNote::freq ; make a sound of this freq and duration
|
|
jsr uiUpdate ; and show the updates
|
|
lda eventState ; get the event state
|
|
bit bit0Mask ; check for EVENT_DIED
|
|
beq airLoop ; not dead means more air left
|
|
|
|
jsr screenSwap::valueSwap ; go back to the back screen
|
|
|
|
incLevel:
|
|
inc currLevel ; set the current level to the next
|
|
ldx currLevel
|
|
cpx #20 ; see if this is the last level + 1
|
|
bcc done ; if not, all is well
|
|
lda demoMode ; check again for demo
|
|
beq :+ ; if not, roll over to level 1 and keep playing
|
|
died:
|
|
lda #EVENT_DIED ; demo also ends with a death event
|
|
rts
|
|
:
|
|
ldx #0 ; not demo, past last level, start at 1st level again
|
|
stx currLevel
|
|
|
|
done:
|
|
lda #EVENT_NEXT_LEVEL ; return in a the action (next level)
|
|
rts
|
|
|
|
.endproc
|
|
|
|
;-----------------------------------------------------------------------------
|
|
.proc gameAirDecrease
|
|
|
|
lda #AIR_SPEED
|
|
sta airFlow
|
|
lda #UI_COMPONENT_AIR ; tick the air down, so update the UI
|
|
jsr uiUpdateComponent
|
|
ldx airCols ; see if it's an odd or even column
|
|
inx ; but the air draws from an odd column so the 1 bit is backwards
|
|
txa
|
|
and #1
|
|
tax ; x 0 is even, x 1 is odd
|
|
lda airTipGfx ; see what the tip looks like
|
|
cmp maskGreen, x ; if it's all green, time to drop another column
|
|
beq colDec
|
|
lsr ; not all green, so adjust the tip by dropping 2 bits (1 white pixel)
|
|
lsr
|
|
ora maskGreenHi, x ; and replace that with a green pixel (appropriate for odd/even column)
|
|
bne airOk
|
|
|
|
colDec:
|
|
dec airCols ; one less bar
|
|
bpl :+
|
|
lda eventState ; out of air causes death
|
|
ora #EVENT_DIED
|
|
sta eventState
|
|
lda #0
|
|
sta airCols ; lock airCols at 0
|
|
lda maskGreen, x ; lock to all green for the tip
|
|
bne airOk
|
|
:
|
|
lda maskNewTip, x ; start a new (mostly white) tip, appropriate for odd/even
|
|
|
|
airOk:
|
|
sta airTipGfx
|
|
|
|
rts
|
|
|
|
.endproc
|
|
|
|
;-----------------------------------------------------------------------------
|
|
.proc gameOver
|
|
|
|
iter = currLevel ; how many times to loop
|
|
|
|
bootPart:
|
|
ldx #20 ; game over level is 20 (0 based)
|
|
stx currLevel
|
|
jsr gameInitStage
|
|
|
|
ldx #0 ; clear the top part of the screen
|
|
jsr screenClear
|
|
|
|
ldx #0 ; draw the boot
|
|
jsr screenDrawSprite
|
|
|
|
ldx #$60 ; start of boot-drop freq
|
|
stx iter
|
|
|
|
lda #1 ; pretend there's a key so Boot (EUGENE) doesn't animate
|
|
sta keysToCollect ; and the "door" doesn't switch to the second frame
|
|
|
|
tax ; also draw the pedestal (door)
|
|
jsr screenDrawSprite ; draw the boot
|
|
|
|
lda #2 ; set willy to frame 2
|
|
sta willyFrame
|
|
|
|
jsr screenDrawWilly ; show willy on the pedestal
|
|
jsr screenSwap ; make it all visible
|
|
jsr screenSwap::valueSwap ; fake the front as the back
|
|
bootLoop:
|
|
jsr gameAI ; run the AI to move the boot down
|
|
|
|
dec iter ; raise freq
|
|
dec iter
|
|
|
|
lda audioMask ; see if the audio will play or skip
|
|
and #AUDIO_SOUND
|
|
beq otherDelay ; audio won't delay so "fake" an audio delay
|
|
|
|
lda iter ; get the freq
|
|
ldy #$80 ; duration for the freq (also slows the boot down)
|
|
jsr audioPlayNote::freq ; make a sound of this freq and duration
|
|
jmp :+
|
|
|
|
otherDelay:
|
|
lda iter
|
|
lsr
|
|
lsr
|
|
tay
|
|
jsr uiDelay::ySet
|
|
:
|
|
|
|
ldx #0 ; go draw the boot
|
|
jsr screenDrawSprite
|
|
|
|
lda spriteDir ; see if the boot has reached the pedestal
|
|
bpl bootLoop ; net yet, keep going
|
|
|
|
gameOverPart:
|
|
color = tmpBot + 1 ; index into color masks arrays
|
|
xPos = tmpBot + 2 ; x for string
|
|
yPos = tmpBot + 3 ; y for string
|
|
textL = tmpBot + 4 ; string pointer
|
|
textH = tmpBot + 5
|
|
len = tmpBot + 6 ; how many characters (0 based)
|
|
|
|
lda #$20
|
|
sta iter ; how many times to loop
|
|
lda #(7*8) ; Y for string
|
|
sta yPos
|
|
lda #4 ; starting color
|
|
sta color
|
|
cycleLoop:
|
|
lda #4 ; print GAME at x 4
|
|
sta xPos
|
|
lda #<roTextGame ; point at GAME text
|
|
sta textL
|
|
lda #>roTextGame
|
|
sta textH
|
|
lda #4 ; 0-3 characters
|
|
sta len
|
|
jsr textColorCycle ; show the text in color
|
|
|
|
lda #13 ; print OVER at x 13
|
|
sta xPos
|
|
lda #<roTextOver ; point at OVER text
|
|
sta textL
|
|
lda #>roTextOver
|
|
sta textH
|
|
lda #4 ; also 0-3 characters in length
|
|
sta len
|
|
jsr textColorCycle ; and show over with color
|
|
|
|
ldy #$30 ; delay the iteration of color
|
|
jsr uiDelay::ySet
|
|
dec iter ; one less iteration to do
|
|
bpl cycleLoop ; do all iterations
|
|
|
|
rts
|
|
|
|
.endproc
|
|
|
|
;-----------------------------------------------------------------------------
|
|
.proc gameVictory
|
|
|
|
lda #19 ; put willy above the door
|
|
sta willyXPos ; outside the caverns
|
|
lda #0
|
|
sta willyFrame
|
|
lda #2*8
|
|
sta willyYPos
|
|
|
|
ldx fullScreenClearCount ; get the flag for what level of screen clear is needed
|
|
jsr screenClear ; and clear the screen
|
|
|
|
jsr screenDrawSprites ; draw all the enemies
|
|
jsr screenDrawWilly ; not demo - draw willy with collision detection
|
|
jsr screenDrawLevel ; show all the tiles
|
|
ldx numSprites ; The door's index
|
|
inx
|
|
jsr screenDrawSprite ; render the door over everything else, no collision
|
|
jsr screenSwap ; swap to the newly rendered screen
|
|
|
|
audioPart:
|
|
freq = tmpBot + 0 ; freq
|
|
duration = tmpBot + 1 ; duration
|
|
iteration = tmpBot + 2 ; iteration
|
|
|
|
lda #50 ; 50 iterations
|
|
sta iteration
|
|
lda #0 ; init freq and duration
|
|
sta freq
|
|
sta duration
|
|
|
|
loop:
|
|
lda duration ; start with the duration
|
|
adc iteration ; add the iteration counter * 3
|
|
adc iteration
|
|
adc iteration
|
|
sta freq ; save as the freq
|
|
ldy duration ; put duration in Y
|
|
|
|
lda audioMask ; see if the audio will play or skip
|
|
and #AUDIO_SOUND
|
|
bne audioOn ; if on, use the freq "API"
|
|
:
|
|
ldx freq ; a bit ridiculous to redo playNote stuff here
|
|
:
|
|
dex ; but I want the audio code to all go through the
|
|
bne :- ; same "API" for consistency
|
|
dey
|
|
bne :--
|
|
beq postFreq
|
|
|
|
audioOn:
|
|
lda freq
|
|
jsr audioPlayNote::freq ; make the sound if sound enabled
|
|
|
|
postFreq:
|
|
dec iteration ; dec the iterations
|
|
bne loop ; loop till all iterations done
|
|
rts
|
|
|
|
.endproc
|