1213 lines
45 KiB
PHP
1213 lines
45 KiB
PHP
;-----------------------------------------------------------------------------
|
|
; game.inc
|
|
; Part of penetrator, the zx spectrum game, made for Apple II
|
|
;
|
|
; Stefan Wessels, 2019
|
|
; This is free and unencumbered software released into the public domain.
|
|
|
|
;-----------------------------------------------------------------------------
|
|
.segment "CODE"
|
|
|
|
;-----------------------------------------------------------------------------
|
|
.proc gamePlay
|
|
|
|
jsr gameInit
|
|
|
|
outerloop:
|
|
lda lives
|
|
bne :+
|
|
rts ; no lives left = Game Over
|
|
:
|
|
jsr gameLevelInit ; prep whatever level the active player is at
|
|
jsr gameShowPlayer ; show the pre-play who's turn message
|
|
jsr uiShowGameLabels ; show the lines for score, danger, etc.
|
|
|
|
preamble: ; bring terrain on-screen with no clock delay
|
|
jsr drawPresent
|
|
jsr gameWorldMove
|
|
jsr gameWorldMove
|
|
jsr terrainShow ; show terrain 1st because it collides with nothing
|
|
jsr drawEnemies ; enemy missiles collide with terrain
|
|
|
|
dec terrainOrigin ; get the start of terrain drawing to col 0
|
|
dec terrainOrigin ; get the start of terrain drawing to col 0
|
|
bpl preamble ; and draw even at 0
|
|
inc terrainOrigin ; origin is now -1 so set it back to 0
|
|
jsr gamePlaceShip ; find a safe spot for the player to spawn
|
|
jsr drawPlayer ; now show the player ship
|
|
jsr drawPresent ; flip layers 0/1 (bring backLayer to visible)
|
|
|
|
bit KBDSTRB
|
|
wait #$40 ; give the user a moment to get ready
|
|
|
|
loop:
|
|
jsr inputGame ; read joystick and move player, check for pause key
|
|
cmp #$9b ; check for ESC
|
|
bne skip ; if not, check for pause
|
|
rts ; on ESC, just quit
|
|
|
|
skip:
|
|
lda playerDead ; if the player is dead the world doesn't move
|
|
bne :+
|
|
jsr gameWorldMove ; scroll the world
|
|
inc bufferDraw ; and move the draw start also
|
|
jsr gameCheckStage ; see if a stage is ending
|
|
:
|
|
lda flipFlop ; the flipFlop is 0->1->0->1, etc.
|
|
eor #1
|
|
sta flipFlop
|
|
|
|
jsr gameAI ; run the "AI" - this moves enemies and animates everything, etc.
|
|
|
|
next:
|
|
dec bulletIndex ; Bullets move every frame (2x for 1 world move)
|
|
dec bulletIndex
|
|
|
|
draw:
|
|
jsr terrainShow ; show terrain 1st because it collides with nothing
|
|
jsr drawEnemies ; enemy missiles collide with terrain
|
|
jsr drawPlayer ; player collides with enemies only
|
|
jsr drawBullets ; checks for enemy collision
|
|
jsr drawBombs ; checks for enemy collision (but won't destroy player)
|
|
jsr drawExplosions ; explosions draw over everything and collide with nothing
|
|
|
|
lda updateHUD ; only update the hud when it changes
|
|
beq delay
|
|
jsr uiUpdateGameHud
|
|
|
|
delay:
|
|
jsr drawPresent ; flip layers 0/1 (bring backLayer to visible)
|
|
|
|
lda victory ; when set, either a nuke or back home
|
|
bne win
|
|
jsr serviceAudio ; run the audio
|
|
lda playerDead
|
|
beq loop ; playerDead = 0 means alive so keep going
|
|
|
|
died:
|
|
dec playerDead ; the counter holds the explosion state
|
|
bne skip ; don't get user input
|
|
jsr gameNextPlayer ; switch players if a 2p game
|
|
jmp outerloop
|
|
|
|
win:
|
|
jsr gameWinSequence ; flash screen and show the win screen (wow or home)
|
|
jmp outerloop
|
|
|
|
.endproc
|
|
|
|
;-----------------------------------------------------------------------------
|
|
.proc gameInit
|
|
|
|
jsr drawClearScreen ; make sure the screen is clear before adjusting the
|
|
|
|
lda #NUM_LIVES ; lives to start
|
|
sta lives
|
|
|
|
lda #0 ; init some variables - stage is set before this
|
|
sta direction ; go to the right
|
|
sta activePlayer ; player 1 is always 1st to go
|
|
sta score
|
|
sta score + 1
|
|
sta score + 2
|
|
|
|
ldx #NUM_STAGES-1 ; mark all stages as all rockets
|
|
:
|
|
sta makeMonster, x
|
|
dex
|
|
bpl :-
|
|
|
|
lda #NUM_SKIP_MONST ; make this stage (NUM_SKIP_MONST) be a monster stage
|
|
sta monsterCnt
|
|
|
|
ldx #3 ; set stage 3 (zero based) as some rockets become monsters
|
|
sta makeMonster, x
|
|
|
|
|
|
ldy #0 ; init player 1
|
|
jsr gameStatsToPlayer
|
|
|
|
ldy #1 ; init player 2
|
|
jmp gameStatsToPlayer
|
|
|
|
.endproc
|
|
|
|
;-----------------------------------------------------------------------------
|
|
.proc gameLevelInit
|
|
|
|
lda #0 ; Lots of values need a 0 init
|
|
sta victory
|
|
sta playerDead
|
|
sta zCollision
|
|
sta stopScrolling
|
|
sta bufferInsert
|
|
sta dangerTickIdx
|
|
sta moveHorz
|
|
sta moveVert
|
|
sta flipFlop
|
|
sta pause
|
|
sta fireCoolDown
|
|
|
|
tax ; clear the buffers for tracking objects
|
|
:
|
|
sta worldBBuffer, x
|
|
sta worldTBuffer, x
|
|
sta enemyBuffer, x
|
|
sta enemyHgtBuffer, x
|
|
sta explosionBuffer, x
|
|
sta bulletsBuffer, x
|
|
dex
|
|
bne :-
|
|
|
|
ldx #(bombDir + (NUM_BOMBS - 1) - bombX) ; clear the bomb area
|
|
:
|
|
sta bombX, x
|
|
dex
|
|
bpl :-
|
|
|
|
lda #XSIZE-1 ; place where terrain will start drawing (right hand)
|
|
sta terrainOrigin
|
|
|
|
lda #AUDIO_MOTOR_PLAY ; Init th eaudio-frame with the engine
|
|
sta audioFrame
|
|
|
|
lda #$ff
|
|
sta lastInput ; set no keys down all 1's
|
|
sta nextStage
|
|
|
|
lda #DANGER_TICKS
|
|
sta dangerTickCount
|
|
|
|
ldx #(textEnd-textStage)-1 ; empty the text areas to spaces
|
|
store:
|
|
lda textStage, x
|
|
beq :+ ; skip the null terminators
|
|
lda #$20 ; load the space (" ") character
|
|
sta textStage, x ; write it to the text areas
|
|
:
|
|
dex
|
|
bpl store
|
|
|
|
jsr gameFindStage ; point zWorldPtr at the stage start
|
|
jsr gameWorldMove ; prime the draw buffers
|
|
lda bufferInsert
|
|
sta bufferDraw ; point the tail at the head
|
|
inc bufferDraw ; advance the tail (this will correct itself)
|
|
|
|
rts
|
|
|
|
.endproc
|
|
|
|
;-----------------------------------------------------------------------------
|
|
.proc gameShowPlayer
|
|
|
|
jsr drawClearScreen
|
|
|
|
print textPlayer, #(08), #(8*08)
|
|
lda activePlayer
|
|
clc
|
|
adc #$31
|
|
sta textPlayerNum
|
|
print textPlayerNum, #(23), #(8*08)
|
|
jsr drawPresent
|
|
|
|
wait #$40
|
|
|
|
jmp drawClearScreen
|
|
|
|
.endproc
|
|
|
|
;-----------------------------------------------------------------------------
|
|
.proc gamePlaceShip
|
|
|
|
bottom = tempBlock
|
|
top = tempBlock + 1
|
|
|
|
lda #$ff ; init markers with extremes
|
|
sta bottom
|
|
lda #0
|
|
sta top
|
|
|
|
lda #(XSIZE/3) ; some way into the screen
|
|
sta playerShipX ; put the ship
|
|
clc
|
|
adc bufferDraw ; make worldBBuffer relative to screen col 0
|
|
tay
|
|
ldx #SHIP_WIDTH
|
|
loop:
|
|
lda worldBBuffer, y ; get the bottom at this col under the ship
|
|
cmp bottom ; see if it's higher than the marker
|
|
bcs :+
|
|
sta bottom ; is higher, update the marker
|
|
:
|
|
lda worldTBuffer, y ; get the top above the ship
|
|
cmp top ; see if it's lower than the marker
|
|
bcc :+
|
|
sta top ; if lower, update the top marker
|
|
:
|
|
iny
|
|
dex
|
|
bne loop ; do for all terrain under/above the ship
|
|
lda bottom ; put the ship SHIP_START above the bottom
|
|
sec
|
|
sbc #SHIP_START
|
|
sta playerShipY
|
|
sec ; see how high the top is now
|
|
sbc #SHIP_HEIGHT
|
|
cmp top ; see if the top is now above the world top
|
|
bcs done
|
|
lda #SHIP_HEIGHT ; if it is, adjust the ship down to
|
|
adc top ; be directly under the top - this happens in narrow passages
|
|
sta playerShipY
|
|
|
|
done:
|
|
rts
|
|
|
|
.endproc
|
|
|
|
;-----------------------------------------------------------------------------
|
|
; Manage the nextStage counter based on the stage variable changing
|
|
; This variable is needed because the stage changes relative to the marker
|
|
; based on direction.
|
|
.proc gameCheckStage
|
|
|
|
ldy #2 ; check for the current stage end marker
|
|
lda (zWorldPtr), y
|
|
bit Bit7Mask ; end of/start of stage marker
|
|
beq noMarker ; not set means no marker
|
|
|
|
lda direction ; there is a marker - see which direction
|
|
bne :+
|
|
lda #XSIZE
|
|
:
|
|
sta nextStage
|
|
|
|
noMarker:
|
|
lda nextStage ; now deal with the nextStage marker counter, if set
|
|
bmi done ; -1 means nothing to do
|
|
|
|
lda stopScrolling ; if stopScrolling is set
|
|
bne done ; then the stage counter does not count down
|
|
dec nextStage ; count down to -1 eventually
|
|
bne done ; non-zero - nothing to do
|
|
|
|
dec nextStage ; at zero - take action and set to -1 so this inly triggers once
|
|
sty updateHUD ; y = 2 so force HUD update draws for the 2 buffers
|
|
lda direction ; based on direction, adjust the stage counter
|
|
beq stageUp ; going right
|
|
|
|
stagedown:
|
|
dec stage ; going left so dec stage
|
|
bpl done ; stage 0 and above - keep going
|
|
|
|
lda #0 ; stage -1 means back home,
|
|
sta enemyHit ; add score for a non-flying nuke (1000 pts)
|
|
jsr gameAddScore
|
|
|
|
lda #1 ; mark the "home run" as a victory
|
|
sta victory
|
|
rts
|
|
|
|
stageUp:
|
|
inc stage ; going right - stage up
|
|
|
|
done:
|
|
rts
|
|
.endproc
|
|
|
|
;-----------------------------------------------------------------------------
|
|
; This processes the buffers for enemies, bullets, explosions and moves the bombs.
|
|
.proc gameAI
|
|
|
|
zaBombHeight = tempBlock + 1
|
|
zaTempFrame = tempBlock + 2
|
|
zaTempHeight = tempBlock + 2
|
|
zaEnd = tempBlock + 3
|
|
|
|
lda inFlight + 1 ; launches counted last frame
|
|
sta inFlight ; use it this frame
|
|
lda #0 ; reset the counter from last frame
|
|
sta inFlight + 1 ; to count again in this frame
|
|
|
|
ldy bufferInsert ; this is the end of the world
|
|
iny ; but radar's can extend up to 3 beyond
|
|
iny ; and the end is 1 past the last
|
|
iny ; thing to process
|
|
iny
|
|
sty zaEnd ; so mark the end of processing
|
|
ldy bufferDraw ; make enemyBuffer relative to screen col 0
|
|
|
|
loop:
|
|
lda enemyBuffer, y
|
|
beq explosion ; ignore blank columns
|
|
|
|
bit Bit12Mask ; check if it needs animating
|
|
beq :+ ; ignore monsters (%nnnnnn00)
|
|
|
|
adc #%00010000 ; advance to next anim frame (radars & missiles)
|
|
and #%01110000 ; restrict to these 3 bits
|
|
sta zaTempFrame ; save only the frame counter
|
|
lda enemyBuffer, y ; get the original
|
|
and #%10001111 ; mask out the frame
|
|
ora zaTempFrame ; add in the new frame (and thus preserve bit 8)
|
|
sta enemyBuffer, y ; save this enemy now (post anim update)
|
|
|
|
:
|
|
bit Bit8432Mask ; check if this is a radar, 2nd column of a missile or a launched missile
|
|
bne doHeight ; skip it if it is one of those things
|
|
|
|
lda inFlight ; see how many rockets are in-flight
|
|
cmp #NUM_INFLIGHT * 2 ; limit to 3 (both halves count so looks like 6 launches)
|
|
bcs :+
|
|
|
|
lda enemyHgtBuffer, y ; this is the left hand column of a missile - see if it needs to launch
|
|
sec
|
|
sbc playerShipY ; get the distance from the player
|
|
lsr ; put it in the same units as the X
|
|
lsr
|
|
cmp #31 ; distance cut off - > delta then rockets don't launch
|
|
bcs :+
|
|
sta tempBlock + 4 ; approx 4/3 * height
|
|
lsr tempBlock + 4 ; since the missiles move 3 rows up for every
|
|
lsr tempBlock + 4 ; 4 pixels the world moves
|
|
clc
|
|
adc tempBlock + 4
|
|
sta zaTempHeight ; save it
|
|
tya ; get the distance of this rocket from the player in X
|
|
sec ; start by calculating the screen X position of the rocket
|
|
sbc bufferDraw ; make enemyBuffer relative to screen col 0
|
|
sbc playerShipX ; and then subtracting the screen x position of the ship
|
|
bcc :+ ; player is to the right so ignore
|
|
cmp zaTempHeight ; is dist y to missile = dist x to missile (in column units?)
|
|
beq launch ; if equal, do a launch
|
|
:
|
|
lda enemyBuffer, y
|
|
bne doHeight ; no match, so no launch
|
|
|
|
launch:
|
|
iny ; mark the right hand column as launched
|
|
lda enemyBuffer, y
|
|
ora #%10000000
|
|
sta enemyBuffer, y
|
|
dey
|
|
|
|
lda enemyBuffer, y ; mark left column as launched
|
|
ora #%10000000
|
|
sta enemyBuffer, y
|
|
|
|
doHeight:
|
|
bit Bit8Mask ; see if this needs height adjustment
|
|
beq explosion ; don't move non-launched stuff
|
|
|
|
inc inFlight + 1 ; count launched rockets
|
|
bit Bit1Mask ; see if this is a missile
|
|
bne :+ ; this is a missile
|
|
bit Bit34Mask ; don't do anything unless this is
|
|
bne explosion ; the 1st column of the monster
|
|
jsr gameMonsterAI ; This is a monster - run its AI
|
|
jmp explosion
|
|
|
|
:
|
|
lda enemyHgtBuffer, y
|
|
sec
|
|
sbc #3
|
|
cmp #WORLD_START
|
|
bcs :+ ; out of existence at the top, kill it off
|
|
lda #0
|
|
sta enemyBuffer, y
|
|
:
|
|
sta enemyHgtBuffer, y
|
|
|
|
explosion:
|
|
lda explosionBuffer, y ; advance the explosion sprite frame
|
|
beq nextBufferEntry
|
|
clc
|
|
adc #1
|
|
bit Bit3Mask ; at frame 4, stop the explosion
|
|
beq :+
|
|
lda #0
|
|
:
|
|
sta explosionBuffer, y
|
|
|
|
nextBufferEntry: ; advance processing to the next enemy
|
|
iny
|
|
cpy zaEnd ; see if all enemies processed
|
|
clc
|
|
beq doBombs
|
|
jmp loop
|
|
|
|
doBombs:
|
|
ldx #NUM_BOMBS - 1 ; move the bombs
|
|
|
|
loopb:
|
|
lda bombY, x
|
|
beq nextb ; 0 in Y means inactive
|
|
clc
|
|
adc #3 ; move down 4 rows
|
|
sta bombY, x
|
|
|
|
lda bombDir, x ; get the travel direction
|
|
beq :+ ; if 0, it's going straight down
|
|
dec bombDir, x ; decrease the columns it will move forward for
|
|
inc bombX, x ; <> 0, still moving forward, inc it's x position
|
|
bne skbd ; always (jmp)
|
|
:
|
|
lda stopScrolling ; see if the world is moving
|
|
bne skbd ; if not, don't adjust the bomb X
|
|
dec bombX, x ; scroll bomb with world to go straight down
|
|
skbd:
|
|
lda bombX, x ; get the screen column
|
|
bmi bol ; if < 0 then bomb out left hand side of screen
|
|
adc bufferDraw ; make enemyBuffer relative to screen col 0
|
|
tay
|
|
lda worldBBuffer, y ; get the terrain bottom height in this col
|
|
sec
|
|
sbc bombY, x ; subtract the bomb height
|
|
bcs nextb ; if the bomb < terrain (carry set), all is well
|
|
bol:
|
|
lda #0 ; bomb too low, write 0 into the bomb height
|
|
sta bombY, x
|
|
|
|
nextb:
|
|
dex
|
|
bpl loopb ; repeat for all bombs
|
|
|
|
clc
|
|
rts
|
|
.endproc
|
|
|
|
|
|
;-----------------------------------------------------------------------------
|
|
; This moves the monster up and down between the top and bottom of the world
|
|
; where it will pause for a moment. When the monster overlaps the player, it
|
|
; will also pause for a moment before going back to the bounce
|
|
; called with y the index in enemyBuffer to the 1st strip of the monster and
|
|
; acc the value in enemyBuffer, y
|
|
.proc gameMonsterAI
|
|
|
|
zaTempIndex = tempBlock + 2 ; y reg saved as Index to 1st strip in monster
|
|
|
|
and #%01100000 ; check if the stop counter has a value
|
|
beq moving ; if not, monster is moving
|
|
|
|
paused: ; the monster is at top/bottom or overlapped player
|
|
lda #%00100000
|
|
adc enemyBuffer, y ; add to the monster value
|
|
ora #%10000000 ; make sure the flying bit stays on
|
|
sta enemyBuffer, y ; save the update
|
|
rts
|
|
|
|
moving: ; the monster is bouncing up or down
|
|
sty zaTempIndex ; save the index - need to work on 3 strips
|
|
ldx #MONSTER_WIDTH ; load x with the width (3)
|
|
lda enemyBuffer, y ; reload the enemy (acc destroyed by and above)
|
|
bit Bit5Mask ; bit 5 is direction - 0 is up and 1 is down
|
|
bne down
|
|
|
|
up:
|
|
lda enemyHgtBuffer, y ; monster is at same height in all its columns
|
|
sec
|
|
sbc #MONSTER_SPEED ; move up to where it would be
|
|
sbc #MONSTER_HEIGHT ; but need the top of the monster to check if it hit the ceiling
|
|
|
|
dey ; adjust for alignment with world buffer
|
|
|
|
chktop:
|
|
cmp worldTBuffer, y ; compare to the world ceiling height
|
|
beq changedir ; if equal, change direction don't go up
|
|
bcc changedir ; if less than, also don't go up but change dir
|
|
iny ; do for all the monster strips
|
|
dex
|
|
bne chktop
|
|
|
|
ldx #MONSTER_WIDTH ; not in ceiling collision so adjust height
|
|
ldy zaTempIndex ; start at the 1st strip
|
|
|
|
:
|
|
lda enemyHgtBuffer, y ; move the strips upwards
|
|
sec
|
|
sbc #MONSTER_SPEED ; speed at which monster moves
|
|
sta enemyHgtBuffer, y ; save updated position
|
|
iny ; repeat for all strips
|
|
dex
|
|
bne :-
|
|
|
|
chkplayerhgt: ; see if the monster is now overlapping the enemy ship
|
|
clc
|
|
ldy zaTempIndex ; get the 1st strip index
|
|
ldx #MONSTER_SPEED ; check as many columns as moved (faster for than overlap calc)
|
|
:
|
|
cmp playerShipY ; is it matching the player
|
|
beq stop ; if yes, stop the monster
|
|
adc #1 ; try each position in the step
|
|
dex ; and repeat for all increments in a monster step (speed)
|
|
bne :-
|
|
rts
|
|
|
|
changedir:
|
|
ldy zaTempIndex ; go back to the 1st strip
|
|
lda enemyBuffer, y ; get the enemy
|
|
eor #%00110000 ; turn on stop and change dir in one go
|
|
sta enemyBuffer, y ; save
|
|
rts
|
|
|
|
stop:
|
|
lda enemyBuffer, y ; get the 1st strip
|
|
ora #%00100000 ; set stop counter to 1
|
|
sta enemyBuffer, y ; save
|
|
rts
|
|
|
|
down:
|
|
lda enemyHgtBuffer, y ; monster is at same height in all its columns
|
|
clc
|
|
adc #MONSTER_SPEED ; height down by speed to where it would go
|
|
|
|
ldx #MONSTER_WIDTH ; check for a bottom collision
|
|
dey ; adjust for the world offset
|
|
chkbot:
|
|
cmp worldBBuffer, y ; get the world below this strip
|
|
bcs changedir ; if the world is higher than the base of the monster, change direction
|
|
iny ; check all strips
|
|
dex
|
|
bne chkbot
|
|
|
|
ldx #MONSTER_WIDTH ; height okay, so finally move the monster
|
|
ldy zaTempIndex ; starting at strip 1
|
|
|
|
:
|
|
lda enemyHgtBuffer, y ; get the strip height
|
|
clc
|
|
adc #MONSTER_SPEED ; adjust the height
|
|
sta enemyHgtBuffer, y ; save it
|
|
iny ; do for all strips
|
|
dex
|
|
bne :-
|
|
beq chkplayerhgt ; JuMP to see if the monster and player now overlap
|
|
|
|
done:
|
|
rts
|
|
|
|
.endproc
|
|
|
|
;-----------------------------------------------------------------------------
|
|
.proc gameWorldMove
|
|
|
|
jsr gameUpdateDangerBar ; when the world moves, the danger bar needs to be updated
|
|
|
|
clc
|
|
inc bufferInsert ; advance where buffer data is inserted
|
|
ldx bufferInsert ; get the new right edge
|
|
inx ; step past where radar columns might be
|
|
inx
|
|
inx
|
|
lda #0 ; write a null to kill old circular data
|
|
sta enemyBuffer, x
|
|
sta enemyHgtBuffer, x
|
|
ldx bufferInsert ; get the right edge again
|
|
|
|
lda direction ; world prt moves based on level forward/reverse direction
|
|
bne left
|
|
|
|
clc ; moving through to the right, advance the world 1 column
|
|
lda zWorldPtr
|
|
adc #3
|
|
sta zWorldPtr
|
|
bcc :+
|
|
inc zWorldPtr + 1
|
|
clc
|
|
:
|
|
ldy #0 ; get the world bottom
|
|
lda (zWorldPtr), y
|
|
sta worldBBuffer, x
|
|
|
|
iny ; get the world top
|
|
lda (zWorldPtr), y
|
|
sta worldTBuffer, x
|
|
|
|
lda terrainOrigin ; if the terrain is still scrolling in
|
|
bne done ; then don't populate enemies
|
|
|
|
iny ; get the enemy flags
|
|
lda (zWorldPtr), y ; get the enemy flag
|
|
beq done ; no enemies, carry on
|
|
jmp gameWorldFlag ; populate the world with enemies
|
|
|
|
left: ; to the left (in the data, back to front), 1 column "subtract"
|
|
sec
|
|
lda zWorldPtr
|
|
sbc #$03
|
|
sta zWorldPtr
|
|
bcs :+
|
|
dec zWorldPtr + 1
|
|
|
|
:
|
|
clc ; make carry clear since gameWorldFlag expects to be called with carry clear
|
|
ldy #6 ; get the world bottom
|
|
lda (zWorldPtr), y
|
|
sta worldBBuffer, x
|
|
|
|
iny ; get the world top
|
|
lda (zWorldPtr), y
|
|
sta worldTBuffer, x
|
|
|
|
lda terrainOrigin ; if the terrain is still scrolling in
|
|
bne done ; then don't populate enemies
|
|
|
|
ldy #8 ; look ahead 1 col
|
|
lda (zWorldPtr), y ; get the enemy flag
|
|
bit Bit1Mask ; is this a missile
|
|
beq radar ; not a missile, maybe there's a radar coming?
|
|
jmp gameWorldFlag ; this is a missile - go add it
|
|
|
|
radar:
|
|
ldy #2 ; Look ahead even further for a radar coming on-screen
|
|
lda (zWorldPtr), y ; get the enemy flag
|
|
bit Bit2Mask ; is this a radar
|
|
beq done ; no, then nothing more to do
|
|
jmp gameWorldFlag ; yes - add the radar
|
|
|
|
done:
|
|
rts
|
|
|
|
.endproc
|
|
|
|
;-----------------------------------------------------------------------------
|
|
.proc gameFindStage
|
|
|
|
zaStage = tempBlock + 1
|
|
|
|
ldy #2 ; world data is bottom, top and then flags. need flags in this routine
|
|
|
|
lda direction ; world prt moves based on level forward/reverse direction
|
|
bne left
|
|
|
|
lda #<worldDataStart ; forward (right) - init with the start of the data
|
|
sta zWorldPtr
|
|
lda #>worldDataStart
|
|
sta zWorldPtr + 1
|
|
|
|
lda stage ; see how many stage markers to skip
|
|
beq done ; stage 0 - skip none, start of data is good
|
|
sta zaStage
|
|
|
|
rloop:
|
|
lda (zWorldPtr), y ; get the flags
|
|
bit Bit7Mask ; see if it's a stage marker
|
|
beq :+
|
|
dec zaStage ; found a stage but step once more not to trigger in-game
|
|
|
|
:
|
|
lda zWorldPtr ; step the pointer over the data triplet
|
|
clc
|
|
adc #3
|
|
sta zWorldPtr
|
|
bcc :+
|
|
inc zWorldPtr + 1
|
|
:
|
|
jsr gameUpdateDangerBar ; update the danger meter
|
|
lda zaStage ; now check if the stage is found
|
|
beq done ; and exit if it has
|
|
bne rloop
|
|
|
|
left: ; world is moving backwards so
|
|
lda #<(worldDataEnd - 3) ; start at the end of the data, looking
|
|
sta zWorldPtr ; at the 1st triplet of data
|
|
lda #>(worldDataEnd - 3)
|
|
sta zWorldPtr + 1
|
|
|
|
lda #(NUM_STAGES-1) ; see how many stage markers to skip
|
|
sec
|
|
sbc stage
|
|
beq done
|
|
sta zaStage ; save the counter
|
|
|
|
lloop:
|
|
lda (zWorldPtr), y ; get the flags
|
|
bit Bit7Mask ; and see if it's a stage flag
|
|
beq :+
|
|
dec zaStage ; may be the stage being sought but step once more
|
|
|
|
:
|
|
lda zWorldPtr ; move the ptr backwards through the data
|
|
sec ; to the previous data triplet
|
|
sbc #3
|
|
sta zWorldPtr
|
|
bcs :+
|
|
dec zWorldPtr + 1
|
|
|
|
:
|
|
jsr gameUpdateDangerBar ; update the danger meter
|
|
lda zaStage ; see if this is the stage being sought
|
|
beq done ; and exit if it is
|
|
bne lloop
|
|
|
|
done:
|
|
clc
|
|
rts
|
|
|
|
.endproc
|
|
|
|
;-----------------------------------------------------------------------------
|
|
; Use the 4 user font characters (at $80+) to draw a dotted line danger bar
|
|
.proc gameUpdateDangerBar
|
|
|
|
dec dangerTickCount ; count down to adding a dot
|
|
beq tick ; time to add a dot?
|
|
rts
|
|
|
|
tick:
|
|
lda #DANGER_TICKS ; reset the countdown
|
|
sta dangerTickCount
|
|
ldx dangerTickIdx ; get the index for how far into the count
|
|
lda textDangerBar, x ; get the character
|
|
cmp #' ' ; 1st time - this is a space so init
|
|
beq dot
|
|
cmp #$84 ; 4 dots last character so move to next column?
|
|
beq :+
|
|
inc textDangerBar, x ; add a dot by advancing the font
|
|
bne hud ; JuMP to hud update
|
|
:
|
|
inx ; move to next character in the display
|
|
stx dangerTickIdx ; save the index
|
|
dot:
|
|
lda #$80 ; 1 dot - load the single dot font character
|
|
sta textDangerBar, x ; put it in the display
|
|
|
|
hud:
|
|
lda #2 ; update the hud twice so both buffers
|
|
sta updateHUD ; get updated
|
|
clc
|
|
rts
|
|
|
|
.endproc
|
|
|
|
;-----------------------------------------------------------------------------
|
|
; Process the flags in the world data stream. carry must be clear when called
|
|
; radar is %01, missile is %10
|
|
.proc gameWorldFlag
|
|
|
|
bit Bit2Mask ; radar?
|
|
beq missile ; no, see if it's a missile
|
|
lda gameMode ; In edit mode, don't draw nuke's for radars
|
|
bit Bit2Mask ; GAME_MODE_EDIT is $02 so if bit set then skip nuke check
|
|
bne :+
|
|
lda stage
|
|
cmp #(NUM_STAGES - 1) ; stages are 0 based - this is the last stage
|
|
clc
|
|
beq nuke ; in stage 4 the radars become nuke's
|
|
|
|
:
|
|
lda worldBBuffer, x ; load the height of the world below this enemy
|
|
sbc #0 ; carry is clear so this is -1
|
|
tay ; save the height in y
|
|
sta enemyHgtBuffer, x ; set the height for the 1st radar strip
|
|
lda #SPR_RADAR0 ; put the strips into the enemy buffer
|
|
sta enemyBuffer, x
|
|
|
|
inx ; go to the next strip
|
|
tya ; get the height from y
|
|
sta enemyHgtBuffer, x ; and set up this strip and the remaining strips
|
|
lda #SPR_RADAR1
|
|
sta enemyBuffer, x
|
|
|
|
inx
|
|
tya
|
|
sta enemyHgtBuffer, x
|
|
lda #SPR_RADAR2
|
|
sta enemyBuffer, x
|
|
|
|
inx
|
|
tya
|
|
sta enemyHgtBuffer, x
|
|
lda #SPR_RADAR3
|
|
sta enemyBuffer, x
|
|
|
|
clc
|
|
rts
|
|
|
|
nuke:
|
|
lda worldBBuffer, x ; put the nuke strips in
|
|
sbc #0
|
|
tay
|
|
sta enemyHgtBuffer, x
|
|
lda #SPR_NUKE0
|
|
sta enemyBuffer, x
|
|
|
|
inx
|
|
tya
|
|
sta enemyHgtBuffer, x
|
|
lda #SPR_NUKE1
|
|
sta enemyBuffer, x
|
|
|
|
clc
|
|
rts
|
|
|
|
missile:
|
|
bit Bit1Mask ; is it a missile
|
|
beq done ; no, move on
|
|
|
|
ldy stage ; see if in this stage some missiles become monsters
|
|
lda makeMonster, y
|
|
beq :+ ; 0 means no, not in this stage
|
|
dec monsterCnt
|
|
beq monster ; when this is 0, make a missile a monster
|
|
|
|
:
|
|
lda worldBBuffer, x ; get the height
|
|
sbc #0 ; -1 (carry clear) to put it above ground
|
|
tay
|
|
sta enemyHgtBuffer, x ; mark as the height
|
|
lda #SPR_MISSILE0
|
|
sta enemyBuffer, x
|
|
|
|
inx
|
|
tya
|
|
sta enemyHgtBuffer, x
|
|
lda #SPR_MISSILE1
|
|
sta enemyBuffer, x
|
|
|
|
clc
|
|
rts
|
|
|
|
monster:
|
|
lda #NUM_SKIP_MONST
|
|
sta monsterCnt
|
|
|
|
lda worldBBuffer, x
|
|
sbc #0
|
|
tay
|
|
sta enemyHgtBuffer, x ; set the height for the 4 radar strips
|
|
lda #SPR_MONSTER0 ; put the strips into the enemy buffer
|
|
sta enemyBuffer, x
|
|
|
|
inx
|
|
tya
|
|
sta enemyHgtBuffer, x
|
|
lda #SPR_MONSTER1
|
|
sta enemyBuffer, x
|
|
|
|
inx
|
|
tya
|
|
sta enemyHgtBuffer, x
|
|
lda #SPR_MONSTER2
|
|
sta enemyBuffer, x
|
|
|
|
clc
|
|
rts
|
|
|
|
done:
|
|
rts
|
|
|
|
.endproc
|
|
|
|
;-----------------------------------------------------------------------------
|
|
.proc gamePlayerCollision
|
|
|
|
lda #0
|
|
sta zCollision ; reset the collision flag
|
|
|
|
lda playerDead ; already marked as dead?
|
|
beq :+
|
|
rts
|
|
|
|
:
|
|
lda #$20 ; mark the death and hold for
|
|
sta playerDead ; some frames
|
|
|
|
lda #2 ; force a HUD redraw to show
|
|
sta updateHUD ; reduced lives
|
|
|
|
lda #1 ; stop the world moving
|
|
sta stopScrolling
|
|
|
|
lda gameMode ; get the game mode
|
|
bne done ; and only take a life if in gameplay (= 0)
|
|
|
|
sed ; take away a life
|
|
lda lives ; lives are done in decimal
|
|
sec ; for easier printing
|
|
sbc #1
|
|
sta lives
|
|
cld
|
|
clc
|
|
|
|
done:
|
|
rts
|
|
|
|
.endproc
|
|
|
|
;-----------------------------------------------------------------------------
|
|
.proc gameNextPlayer
|
|
|
|
lda lives
|
|
bne stillOk
|
|
|
|
print textGameOver, #(0), #(8*05) ; show the game over text
|
|
jsr drawPresent
|
|
|
|
lda #0
|
|
bit KBDSTRB ; flush the keyboard buffer
|
|
sta lastInput
|
|
:
|
|
jsr inputCheckForInput ; wait for user interaction
|
|
beq :-
|
|
|
|
jsr uiCheckHighScore ; Did this player make it onto the high score table
|
|
|
|
stillOk:
|
|
lda numPlayers ; see if it's a 2-player game
|
|
beq done
|
|
|
|
ldy activePlayer ; save the outgoing players' stats
|
|
jsr gameStatsToPlayer
|
|
|
|
ldy #0 ; assume p1 was active
|
|
lda activePlayer ; get the active player
|
|
eor #1 ; swap 0->1 or 1->0
|
|
tax ; next player in x
|
|
beq :+ ; if 0 is next, y is set up already
|
|
ldy #(rsEnd-rsStart) ; 1 is next, so set y up to point at p1
|
|
:
|
|
lda playerStats, y ; get the lives (1st element is "struct")
|
|
beq done ; if none, then don't switch players
|
|
|
|
ldy activePlayer ; incoming player has lives - save current active in Y
|
|
stx activePlayer ; switch to this (incoming) player
|
|
|
|
ldy activePlayer ; install the players' stats
|
|
jsr gamePlayerToStats
|
|
|
|
done:
|
|
rts
|
|
|
|
.endproc
|
|
|
|
;-----------------------------------------------------------------------------
|
|
; Copy the "live" stats to the backup location for a player
|
|
; y is the index of the player (0 or 1)
|
|
.proc gameStatsToPlayer
|
|
|
|
ldx #(rsEnd-rsStart) - 1 ; assume p0 destination
|
|
dey
|
|
bne :+
|
|
ldx #((rsEnd-rsStart) * 2) - 1 ; actually p1 so adjust copy dest location
|
|
:
|
|
ldy #(rsEnd-rsStart) - 1 ; set y to the end of the runtime stats buffer
|
|
:
|
|
lda rsStart, y ; load runtime stat
|
|
sta playerStats, x ; save as player stat
|
|
dex ; do for all bytes needing to be copied
|
|
dey
|
|
bpl :- ; copy all bytes
|
|
|
|
rts
|
|
|
|
.endproc
|
|
|
|
;-----------------------------------------------------------------------------
|
|
; Copy the backup player stats to the "live" stats location
|
|
; y is the index of the player (0 or 1)
|
|
.proc gamePlayerToStats
|
|
|
|
ldx #(rsEnd-rsStart) - 1 ; assume p0 destination
|
|
dey
|
|
bne :+
|
|
ldx #((rsEnd-rsStart) * 2) - 1 ; actually p1 so adjust destination
|
|
:
|
|
ldy #(rsEnd-rsStart) - 1 ; set y to the end of the runtime stats
|
|
:
|
|
lda playerStats, x ; load a player stat byte
|
|
sta rsStart, y ; copy it to a runtime stat byte
|
|
dex ; next bytes
|
|
dey
|
|
bpl :- ; do for all bytes that need copying
|
|
|
|
rts
|
|
|
|
.endproc
|
|
|
|
;-----------------------------------------------------------------------------
|
|
.proc gameWinSequence
|
|
|
|
iterations = tempBlock + 0 ; how many times to do this loop
|
|
|
|
inc lives ; give the player another life
|
|
lda #1
|
|
sta updateHUD
|
|
jsr uiUpdateGameHud
|
|
jsr drawPresent ; get the score and live visible
|
|
|
|
lda #$07 ; flash the screen iteration times
|
|
sta iterations
|
|
|
|
:
|
|
jsr drawInvertVisibleScreen
|
|
|
|
dec iterations
|
|
bne :-
|
|
|
|
lda direction ; flip the direction
|
|
eor #1
|
|
sta direction
|
|
beq :+ ; if dir zero - going out again from stage 0
|
|
lda #3 ; zero based - second last stage - don't play nuke stage again
|
|
:
|
|
sta stage
|
|
|
|
jsr uiWinScreen ; show the BONUS screen
|
|
|
|
ldx #(NUM_STAGES - 2) ; start at the stage before the last (0 based)
|
|
:
|
|
dex ; step back - but the second last stage was already marked
|
|
bmi :+ ; if at -1 then all stages checked
|
|
lda makeMonster, x ; see if this stage has rockets turn into monsters yet
|
|
bne :- ; if yes, then keep looking for a stage without that
|
|
lda #1 ; mark this stage
|
|
sta makeMonster, x ; as now also having monster-rockets
|
|
:
|
|
|
|
ldy activePlayer ; make sure the stats are backed up
|
|
jmp gameStatsToPlayer
|
|
|
|
.endproc
|
|
|
|
;-----------------------------------------------------------------------------
|
|
; Removes an enemy from screen that overlaps with zEnemyCol
|
|
; zaDrawSpriteWidth says how many columns to check - the bomb could
|
|
; kill in one of 2 columns, otherwise just one
|
|
.proc gameKillEnemy
|
|
|
|
zaDrawSprWidth = tempBlock + 3 ; Parameter
|
|
zaSprColumn = tempBlock + 2 ; Internal segment/column of enemy sprite
|
|
zaEnemyColLocal = tempBlock + 4 ; Internal Backup copy of zEnemyCol but modified
|
|
|
|
lda zEnemyCol
|
|
sta zaEnemyColLocal ; make a backup that's safe to change
|
|
|
|
lda #0 ; reset the collision flag
|
|
sta zCollision
|
|
|
|
ldy #$ff
|
|
sty enemyHitType
|
|
|
|
loop: ; for the width to check
|
|
ldx zaEnemyColLocal
|
|
lda enemyBuffer, x ; see if there's an enemy
|
|
beq next
|
|
sta enemyHit ; save the flags of the enemy that was struck
|
|
|
|
lsr ; see if it's a radar or missile
|
|
bcc other ; lsb clear means not a missile
|
|
lsr ; missile or nuke. 1 more shift to get column in lsb's
|
|
bcc missile
|
|
ldy #1 ; hit a nuke
|
|
sty victory
|
|
ldy #0 ; nuke is 0 in the score table
|
|
beq continue
|
|
|
|
missile:
|
|
ldy #1 ; missile and nuke 2 columns wide
|
|
bne continue
|
|
|
|
other:
|
|
lsr ; check next bit
|
|
bcc monster ; if also clear then a monster
|
|
ldy #3 ; radar is 4 columns wide
|
|
bne continue
|
|
|
|
monster:
|
|
ldy #2 ; monster is 3 columns wide
|
|
|
|
continue:
|
|
sty enemyHitType ; 0 = nuke, 1 = missile, 2 = monster, 3 = radar, $ff - nothing
|
|
bne :+ ; now Y is being used as column (width - 1) of the enemy
|
|
iny ; for the nuke, make y = 1 (it's still 0 here as the type)
|
|
:
|
|
and #%00000011 ; mask column (or strip of the sprite)
|
|
sta zaSprColumn ; save it
|
|
|
|
lda audioFrame
|
|
ora #AUDIO_EXPLOSION ; Add explosion to audio frame
|
|
sta audioFrame
|
|
lda #0 ; start the explosion at frame 0
|
|
sta audioExplFrame
|
|
|
|
|
|
lda zaEnemyColLocal ; see where the sprite starts
|
|
sec ; by subtracting the strip number (index)
|
|
sbc zaSprColumn ; from the current world column
|
|
tax ; and save that
|
|
|
|
lda enemyHgtBuffer, x ; get the height of the enemy that's being killed
|
|
and #%11111000 ; mask off 3 bits for the explosion sprite frame cntr
|
|
sta explosionBuffer, x ; save as the explosion
|
|
|
|
lda #0 ; write 0 to the enemy strip columns
|
|
sta enemyBuffer, x
|
|
sta enemyHgtBuffer, x
|
|
inx
|
|
sta enemyBuffer, x
|
|
sta enemyHgtBuffer, x
|
|
dey
|
|
beq next ; early out on missiles and nukes
|
|
inx
|
|
sta enemyBuffer, x
|
|
sta enemyHgtBuffer, x
|
|
dey
|
|
beq next ; early out on monsters
|
|
inx
|
|
sta enemyBuffer, x
|
|
sta enemyHgtBuffer, x
|
|
|
|
next:
|
|
inc zaEnemyColLocal
|
|
dec zaDrawSprWidth
|
|
bne loop
|
|
|
|
clc
|
|
rts
|
|
|
|
.endproc
|
|
|
|
;-----------------------------------------------------------------------------
|
|
; Add score for the enemy destroyed and mark the HUD as needing an update
|
|
; Acc is 0 = nuke, 1 = missile, 2 = monster, 3 = radar from enemyHitType
|
|
; The scoreTable score is "amended" in that flying objects get 4 added
|
|
; Scores appear on the hud at 10x the score variable value
|
|
.proc gameAddScore
|
|
|
|
asl ; score table is 2 bytes wide
|
|
tax ; save the type
|
|
lda enemyHit ; see if this enemy was flying
|
|
and #%10000000
|
|
bne flying
|
|
|
|
lda #0 ; not flying
|
|
beq :+
|
|
|
|
flying:
|
|
lda #4 ; was flying
|
|
:
|
|
sed ; use decimal mode
|
|
adc scoreTable, x ; add across all 3 score bytes, as needed (3 bytes up to 999999 score)
|
|
adc score
|
|
sta score
|
|
inx
|
|
lda scoreTable, x
|
|
adc score + 1
|
|
sta score + 1
|
|
bcc :+
|
|
lda #0 ; only adc and sbc work right in sed
|
|
adc score + 2
|
|
sta score + 2
|
|
clc
|
|
:
|
|
cld ; turn off decimal mode
|
|
|
|
lda #2
|
|
sta updateHUD
|
|
|
|
rts
|
|
|
|
.endproc
|