modemwars/disassembly/game/comcenScreens6F00.s
2026-08-23 02:09:40 -05:00

4972 lines
328 KiB
ArmAsm

; ============================================================================
; $6F00-$87FF - comcen missile and drone screens (track 33 sectors 0-16 + track 32 sectors 8-15)
; ============================================================================
; The variant the boot loader installs; $1086 reloads it. Implements the two command centre screens
; reached with the function keys: the missile screen ($7587) and the drone screen ($7FE6), including the
; radar rendering, plus the end-of-game result messages.
.setcpu "6502"
.include "c64.inc"
.include "zeropage.inc"
; ---- references to code/data outside this file ----
switchToRequestedScreen := $06BA
unitGlyphBaseTable := $0799
filmPlaybackMode := $0B9B
playerSide := $0B9F
gameTypeOptions := $0BA3
gameEndReason := $0BA8
checkFirePressed := $0EB1
checkFirePressedInteractive := $0ED5
joystickDirectionTable := $1FA6
diagonalDirectionTable := $1FB2
directionDeltaTable := $20F0
getViewportOriginPointer := $25C7
readMapCell := $25FA
drawMainWindowFrame := $28E1
printOptionWord := $2A1A
useDrawColourFlag := $2A29
drawColourByte := $2A2A
blockFillFlag := $2A2B
blockFillChar := $2A2C
windowOriginRow := $2A2D
windowOriginCol := $2A2E
setBitmapDrawMode := $2A38
setColourDrawMode := $2A47
resetDrawModes := $2A56
fillScreenColourRect := $2A64
calcCellPointersXY := $2AA3
drawCharAt := $2AC3
drawCharBlock := $2B18
drawWindowInnerCorners := $2B75
drawTallGlyph := $2BB7
xorColourBlock2x2 := $2BCE
drawWindowFrame := $2C7F
drawFrameBox := $2CA1
setTerrainTilePtr := $2DBD
magnifyWindow2x := $2E0F
clearSpriteShape := $2FB2
drawLineInPathSprite := $306C
plotPathSpritePixel := $30A1
enableSprites := $31A3
drawCircleInSprite := $3239
loadDirectionShapeSprite := $3340
showComcenCrossMarker := $3383
gridCellToSpriteCoords := $33B1
getUnitSpriteCentreOnGrid := $340B
updateExplosionMarker := $341F
drawComcenRingSprite := $3456
squaresTable := $3860
applyHitToUnit := $3B22
animateRingBurst := $3CF2
haltStunnedUnit := $3FAC
runGameFrames := $4094
updateGameFrame := $40B2
updateComcenCloakLock := $5151
queueCmdBytePending := $55EC
queueCmd3 := $560F
drawScreen1Cell := $5D7F
unpackRleTo0200 := $67CB
fetchRleByte := $681A
D_681B := $681B
D_681C := $681C
sprite0Shape := $8A00
sprite2Shape := $8A80
sprite3Shape := $8AC0
vicBorderShadow := $9003
vicBg0Shadow := $9004
spriteColourShadow := $9008
sprite2ColourShadow := $900A
sprite3ColourShadow := $900B
sprite5ColourShadow := $900D
spriteXShadow := $9010
sprite1XShadow := $9011
sprite2XShadow := $9012
sprite3XShadow := $9013
sprite5XShadow := $9015
spriteYShadow := $9018
sprite1YShadow := $9019
sprite2YShadow := $901A
sprite3YShadow := $901B
sprite5YShadow := $901D
spriteEnableShadow := $9022
drawCellSaveCol := $90F6
drawCellSaveRow := $90F7
currentScreen := $90FB
joyRepeatMask := $91C9
joyHoldCounter := $91CA
activeSpriteIndex := $91CC
spritePlotMode := $91F3
droneHeading := $920B
droneControlFlags := $920D
exchangeAppliedFlag := $920E
radarGhostCountdown := $920F
radarNoiseMasks := $9210
droneFuel := $9220
frameWidth := $9221
frameHeight := $9222
droneRadarOwnColour := $9224
droneRadarEnemyColour := $9225
cellBackgroundColour := $9226
comcenStunStatus := $922A
radarHalfToneFlag := $922D
radarMissilesLeft := $9232
radarDronesLeft := $9233
viewRedrawTimer := $9234
comcenUnit := $9236
radarOwnUnitColour := $9237
radarEnemyUnitColour := $9238
radarTerrainColour := $9239
attackerType := $923D
comcenBattleStatus := $9242
comcenRadarStatus := $9243
comcenDroneStatus := $9244
comcenCloakedFlag := $9245
rleMarkerByte := $9246
rleUnpackFlag := $9247
recyclerMode := $92A6
terrainPointsBySide := $92AC
dronesLeft := $92AE
opponentDronesLeft := $92AF
missilesLeft := $92B0
opponentMissilesLeft := $92B1
comcenSpeedClass := $92B4
opponentComcenSpeedClass := $92B5
savedLineState := $92CA
droneViewOriginCol := $92F6
droneViewOriginRow := $92F7
missileFlightTick := $92F8
droneArrivalFlags := $92F9
radarSweepTick := $92FB
bitmapRowLoTableFrom3 := $9303
bitmapRowLoTableFrom6 := $9306
bitmapRowHiTableFrom3 := $931C
bitmapRowHiTableFrom6 := $931F
bitmapColLoTableFrom3 := $9335
bitmapColLoTableFrom13 := $933F
bitmapColHiTableFrom3 := $935D
bitmapColHiTableFrom13 := $9367
screenRowLoTable := $9382
screenRowLoTableFrom3 := $9385
screenRowLoTableFrom6 := $9388
screenRowHiTable := $939B
screenRowHiTableFrom3 := $939E
screenRowHiTableFrom6 := $93A1
updateCursorPointers := $C099
setFullScreenWindow := $C0D7
bootReceiveSectorData := $C0EF
printString := $C133
printDecimal := $C17D
splitDecimalDigits := $C428
decimalHundreds := $C44E
decimalTens := $C44F
decimalOnes := $C450
queueMessage := $C566
redrawStatusMessage := $C5D7
playSound := $C89A
unitColTable := $F640
unitRowTable := $F6A4
unitFlagsTable := $F708
unitTypeTable := $F76C
unitPathColTable := $F898
unitStunTable := $F960
unitEnergyTable := $F9C4
unitTerrainUnderTable := $FAF0
initLineStepper := $FBB8
stepLine := $FBFA
plotFatPixel := $FC58
nextGameRandom := $FD5D
droneViewfinderSprite := $FF93
; Contents
; --------
; $6F00 drawDroneHeadingPanel Draws the compass / heading dial in the top-left corner of the DRONE screen
; (game screen 2): frame window $0D (columns 1-9, rows 3-11), the 7x7 character picture at $97D1 at row 4 column 2, the
; 5x5 picture at $97B6 at row 5 column 3, and a 3x3 screen-RAM colour fill of $6E (blue %01 pixels over light-blue %10
; pixels) at row 6 column 4.
; $6F36 drawDroneFuelPanel Draws the fuel gauge panel in the bottom-left corner of the DRONE screen: frame
; window $0E (columns 1-9, rows 12-20), the same 7x7 picture as the compass ($97D1) at row 13 column 2 but forced to
; colour $BC (dark grey over medium grey), then the three caption characters $57,$58,$59 at row 17 columns 4-6 in colour
; $0C (black over medium grey).
; $6F89 drawDroneRadarFrame Draws the double frame around the drone camera window and clears the window:
; builds a fresh set of radar noise masks, draws window $0F (left 11, top 4, 18x16) and window $10 (left 12, top 5,
; 16x14), then falls through into clearRadarWindowBlack which blanks the 14x12 camera area and re-centres the view on
; the player's own comcen.
; $6F99 drawDroneFuelDigits Prints droneFuel ($9220) as three tall (2-row) digits at rows 15-16, columns
; 4-6 of the fuel panel.
; $6FF5 drawDroneCountPanel Draws the 'DRONE' counter panel at the top right of the DRONE screen: resets
; the text window to the full screen, draws window $11 (left 29, top 3, 11x7) with the four inner corner marks, window
; $12 (left 32, top 4, 5x4), fills the 3x2 colour box for the digit at row 5 column 33 with $30, and prints the caption
; 'DRONE' ($868C) at row 7 column 32 in colour $CF with the bitmap draw mode set to OR.
; $7036 drawDroneCountDigit Draws the number of drones still available (radarDronesLeft $9233) as a single
; tall digit at rows 5-6, column 34, in the fixed colour $30.
; $7061 blinkDroneCountBox Alternates the background of the drones-left box (3x2 colour cells at row 5,
; column 33) between $30 and its nibble-swapped twin $03 according to bit 0 of the frame counter, which swaps the
; foreground and background of the tall digit sitting in it and makes the counter flash.
; $708D drawLaunchStrikeButtons Draws the two-button panel down the right edge of the DRONE screen: frame
; window $13 (left 30, top 10, 10 wide x 11 rows) with inner corner marks, the 2x2 button icon from $9804 (characters
; $45-$48) at row 12 column 34 painted colour $05, the caption 'LAUNCH' ($8691) at row 14 column 32, the same icon again
; at row 16 column 34, and the caption 'STRIKE' ($8697) at row 18 column 32.
; $70F5 buildDroneScreen Paints the whole DRONE screen (game screen 2) from scratch: clears the glyph
; plotter's parameter block, sets border and background shadows to $0C (medium grey), sets the radar cell background
; nibble to $0A (light red) and marks the screen as built, then draws every panel in turn and finishes by redrawing the
; status line through the $C000 overlay.
; $7135 clearRadarWindowGrey Blanks the 14x12 drone camera window (row 6, column 13) to colour $BB (dark
; grey on dark grey, i.e.
; $713C clearRadarWindowBlack Same as clearRadarWindowGrey but with colour 0: blanks the drone camera window
; to black and re-centres the view on the own comcen.
; $7163 playDroneLostAnimation The 'drone is gone' effect, used when the drone is shot down, detonated by its
; own strike or runs out of fuel.
; $7204 flashRadarAndWipe Paints the whole 14x12 drone camera window white for two game frames, then
; clears radarWipeColour and falls into drawRadarIrisWipe, so the iris closes over the white flash with no pauses.
; $7221 drawRadarIrisWipe Closes (or opens) an iris over the drone camera window: it first draws window
; $16 (the camera area itself, left 13, top 6, 14x12) and then repeatedly draws a rectangular frame out of the all-$03
; solid tile set at $9848, starting 12x12 cells at row 6 column 14 and shrinking two cells and moving in one cell each
; pass until the height drops below 3.
; $727A buildRadarNoiseMasks Fills the 16-byte table radarNoiseMasks ($9210-$921F) with $FF and then knocks
; four randomly chosen bits out of each byte.
; $72A1 applyDroneBlast Resolves a drone detonation: damages every unit standing in the 2x2 block of
; map cells whose top-left corner is (cmdParam1, cmdParam2) - (x,y), (x+1,y), (x+1,y+1) and (x,y+1) - by calling
; applyDroneBlastToCell four times.
; $72B3 applyDroneBlastToCell Applies the drone blast to the single map cell (cmdParam1, cmdParam2).
; $731E steerDrone Advances the locally flown drone by one map cell.
; $7396 drawRadarGlyph Plots one 8x8 map glyph into the bitmap cell pointed at by bitmapPtr
; (zp_B4/zp_B5) and writes its colour through screenPtr (zp_B6/zp_B7) at offset Y.
; $741C drawDroneRadarWindow Redraws the whole 14x12 drone camera window (character rows 6-17, columns
; 13-26, one character per map cell) from the map around the view origin zp_A5/zp_A6.
; $74C3 getRadarCellGlyph Turns one map cell byte into the glyph index and colour that drawRadarGlyph
; needs.
; $7552 playStrikeZoomAnimation Drone strike effect on the DRONE screen: magnifies the middle of the
; $7587 runMissileScreen Entry of game screen 1, the comcen RADAR / MISSILE screen (function key F3).
; $759C missileScreenResume Main loop of the missile screen, and the re-entry point used after the
; $7688 refreshMissileScreen Redraw hook of screen 1, run once when the screen is built and again after
; $76B2 moveRadarCursorByDirection Turns a joystick direction code 0-7 into a (dCol,dRow) pair and hands
; $76EC offsetRadarCursor Moves the missile target cursor by (X,Y) grid cells, refusing the move if it
; $7713 animateMissileFlight Flies the launched missile across the radar window: builds a short slanted
; $7847 plotMissileTrailSegment Draws one 4-pixel line segment into the shape of sprite 3, from
; $785B stepAndPlotTrailPixel Advances the line stepper one cell and sets the resulting pixel in sprite
; $7861 saveLineStepperState Copies the 12-byte line stepper working set $8C-$97 (both error
; $786D restoreLineStepperState Copies savedLineState ($92CA-$92D5) back over the 12-byte line stepper
; $7879 rebasePairToMin Subtracts the smaller of A and Y from both, i.e.
; $7894 updateRadarSweep Per-frame driver of the radar sweep on screen 1.
; $78CB restartRadarSweep Starts a new sweep at grid row 0 and immediately: clears the step counter, the
; $78E7 paintRadarSweepRow Rewrites the 21 video-matrix bytes of one radar grid row (screen row A+3,
; $7926 redrawEnemyUnitsOnRadar Repaints all 50 units of the opposing side on the radar window and gives
; $7959 scrollRadarOnComcenMove Entered by JMP from drawScreen1Cell ($5D8A) whenever the cell being
; $7987 isRadarCursorOnComcen Tests whether the missile target cursor is on the comcen's own cell, which
; $7992 radarCursorToMapCoords Converts the radar grid position of the target cursor into absolute map
; $79A1 checkStrikeHitsDrone Run straight after a missile has been launched: if a drone is airborne and
; $79D8 showDroneKillBurst Plays the 'drone shot down' effect on the radar screen once our $92 command
; $7A13 mapCellToRadarGrid Converts absolute map coordinates into radar grid coordinates relative to the
; $7A2B isRadarGridCellInRange Range check for a radar grid cell: the window is 21 columns by 17 rows,
; $7A32 initRadarScreen Builds the whole RADAR / MISSILE screen (game screen 1): resets the glyph
; $7A6B redrawRadarMapWindow Draws the double frame around the radar window and then the map inside it.
; $7A78 drawMissilePanel Draws the MISSILES panel in the bottom right corner: an outer frame with inner
; $7ABA drawMissileCount Shows the number of missiles left as two tall (two-character-high) digits in
; $7AEC blinkMissileCount Swaps the two colour nibbles of the missile counter box, which exchanges the
; $7B05 drawFireButton Draws the FIRE button panel on the right: a framed box with inner corner marks,
; $7B44 drawDroneAlertPanel Draws the three nested frames of the DRONE ALERT panel in the top right
; $7B59 resetDroneAlertPanel Prints the two-line 'DRONE' / 'ALERT' label into the panel and paints its
; $7B86 flashDroneAlertPanel Toggles the DRONE ALERT panel between its two colour states and repaints the
; $7B91 fillDroneAlertPanel Shared tail of resetDroneAlertPanel and flashDroneAlertPanel: fills the 7 x 4
; $7BA2 drawRadarMap Redraws the whole 21 x 17 cell strategic map of screen 1 (the RADAR/MISSILE
; screen)
; $7C5C getComcenCentredViewOrigin Returns the map origin that puts the player's own comcen in the middle
; $7FE6 enterDroneScreen Entry point of game screen 2, the DRONE screen (F5).
; $7FF6 resetDroneScreen (Re)initialises the drone screen's sprites and read-outs and drops into the
; idle
; $803D droneScreenIdleLoop Main loop of the DRONE screen while nothing is flying.
; $808D launchDrone Sends a drone on its way: latches the joystick heading, queues command $97
; (drone
; $8124 droneFlightLoop Per-frame loop while our drone is in the air.
; $8182 strikeWithDrone Fire pressed while the drone is flying: stop steering it, mark a detonation as
; $81D0 endDroneFlight Common tail of every drone flight: explosion sound, empty the tank, clear the
; $81FE getDroneMapPosition Converts the camera origin into the drone's own map cell: the 14 x 12 window
; $820F showDroneAlert Notification that the opponent has launched a drone: plays the alert sound and,
; in
; $821F fillDroneViewWindow Fills the drone camera window (14 columns x 12 rows starting at character row
; $8230 releaseDroneControl Gives an airborne drone back to the engine: clears the 'airborne' and 'locally
; $823F drawDroneHeadingIndicator Redraws the compass pointer of the drone screen's left panel.
; $8279 advanceDroneCamera One drone step on the DRONE screen, run once per lock-step command exchange.
; $82A4 readDroneHeadingFromJoystick Latches the joystick direction as the drone's new steer direction and
; $82BF drawShotLineInViewport Draws the tracer of one shot inside the 7 x 5 cell zoomed battlefield
; $840F rebaseLineCoordsToMin Subtracts the smallest of the three column values (window left $82B5,
; $84B2 scaleLineCoordsToFatPixels Converts the two line endpoints from map cells to fat pixels: row*8+4
; $84F1 plotDiamondMarker Plots the four colour-1 fat pixels at (X-1,Y-1), (X+1,Y-1), (X-1,Y+1) and
; $8518 drawStatsTab Fills in the numbers of the console's STATS panel.
; $8601 printNumberRightAligned prints the unsigned byte in A as decimal, right-aligned in a three-character
; field that starts at text column X of the current text row.
; $862D setTextColumn moves the text cursor to column A of the current row and recomputes the bitmap
; and screen-RAM pointers for that character cell by tail-calling updateCursorPointers ($C099), which leaves X and Y
; untouched.
; $8632 drawComcenSchematic unpacks the RLE-compressed comcen picture at comcenSchematicRle ($7C6E)
; straight into the hires bitmap for the console REPAIR tab: a 20x10 character (160x80 pixel) side view of the tracked
; comcen vehicle with its dish and antenna, drawn at character row 10, column 13.
.org $6F00
; ----------------------------------------------------------------------
; drawDroneHeadingPanel - Draws the compass / heading dial in the top-left corner of the DRONE screen
; (game screen 2): frame window $0D (columns 1-9, rows 3-11), the 7x7 character picture at $97D1 at
; row 4 column 2, the 5x5 picture at $97B6 at row 5 column 3, and a 3x3 screen-RAM colour fill of $6E
; (blue %01 pixels over light-blue %10 pixels) at row 6 column 4. Sprites 3 and 0 (the heading line
; plus its white shadow) and sprite 5 (the red aircraft silhouette) are parked over this panel by the
; screen-2 entry code, so the panel is only the dial they sit in.
; In: nothing; expects the glyph-plotter parameter block $2A29-$2A2C to be all zero (buildDroneScreen
; clears it just before)
; Out: frame, two character pictures and 6 colour cells drawn; D_2A2D = 6 and D_2A2E = 4 left pointing
; at the colour patch; frameWidth/frameHeight ($9221/$9222) left at 5x5 by the last drawCharBlock
; Called from: buildDroneScreen ($7122) - the only caller in this overlay variant
; ----------------------------------------------------------------------
drawDroneHeadingPanel:
ldx #$0D ; 6F00 window $0D = left 1, top 3, 9x9 - the compass panel outline
jsr drawWindowFrame ; 6F02 draw the panel frame from that window's 9-tile set
lda #$04 ; 6F05 character-block origin row 4 (one row inside the frame top)
sta windowOriginRow ; 6F07 D_2A2D is the glyph plotters' window row
lda #$02 ; 6F0A character-block origin column 2 (one column inside the frame left)
sta windowOriginCol ; 6F0C D_2A2E is the glyph plotters' window column
lda #$D1 ; 6F0F low byte of the picture block at $97D1 (header 7,7 then 49 character codes)
ldy #$97 ; 6F11 high byte -> $97D1: the 7x7 outer dial face
jsr drawCharBlock ; 6F13 stamp it over rows 4-10, columns 2-8
lda #$05 ; 6F16 next block starts one row lower, row 5
sta windowOriginRow ; 6F18 set the window row for the inner picture
lda #$03 ; 6F1B and one column further right, column 3
sta windowOriginCol ; 6F1D set the window column
lda #$B6 ; 6F20 low byte of the picture block at $97B6 (header 5,5 then 25 character codes)
ldy #$97 ; 6F22 high byte -> $97B6: the 5x5 inner dial face
jsr drawCharBlock ; 6F24 stamp it over rows 5-9, columns 3-7
inc windowOriginRow ; 6F27 step the fill origin down to row 6
L_6F2A:
inc windowOriginCol ; 6F2A and right to column 4 - the 3x3 centre of the dial (label L_6F2A is a variant-A branch target only)
lda #$6E ; 6F2D screen-RAM colour byte $6E: %01 pixels blue, %10 pixels light blue
ldx #$03 ; 6F2F 3 columns wide
ldy #$03 ; 6F31 3 rows high
jmp fillScreenColourRect ; 6F33 paint rows 6-8, columns 4-6 and return through fillScreenColourRect
; ----------------------------------------------------------------------
; drawDroneFuelPanel - Draws the fuel gauge panel in the bottom-left corner of the DRONE screen: frame
; window $0E (columns 1-9, rows 12-20), the same 7x7 picture as the compass ($97D1) at row 13 column 2
; but forced to colour $BC (dark grey over medium grey), then the three caption characters $57,$58,$59
; at row 17 columns 4-6 in colour $0C (black over medium grey). drawDroneFuelDigits writes the three
; tall digits into rows 15-16 of the same panel.
; In: nothing
; Out: frame, picture and caption drawn; the fixed-colour override textDrawParams ($2A29) and D_2A2A
; are cleared again on exit; D_2A2B = 0 (block-fill off); zp_18 = 17, zp_19 = 6
; Called from: buildDroneScreen ($7125) only
; ----------------------------------------------------------------------
drawDroneFuelPanel:
ldx #$0E ; 6F36 window $0E = left 1, top 12, 9x9 - the fuel panel outline
jsr drawWindowFrame ; 6F38 draw its frame
lda #$FF ; 6F3B non-zero = 'use the fixed colour in D_2A2A' instead of the per-character colour table $93B4
sta useDrawColourFlag ; 6F3D textDrawParams ($2A29) is the plotter's useFixedColour flag
lda #$BC ; 6F40 screen-RAM colour $BC: %01 pixels dark grey, %10 pixels medium grey
sta drawColourByte ; 6F42 D_2A2A is the fixed colour byte used by every glyph plotted from now on
lda #$0D ; 6F45 character-block origin row 13
sta windowOriginRow ; 6F47 set the window row
lda #$02 ; 6F4A character-block origin column 2
sta windowOriginCol ; 6F4C set the window column
lda #$D1 ; 6F4F low byte of the 7x7 picture at $97D1
ldy #$97 ; 6F51 high byte -> $97D1, the same dial art reused as the gauge body
jsr drawCharBlock ; 6F53 stamp it over rows 13-19, columns 2-8
lda #$00 ; 6F56 0 = do not replace the block characters with a fill character
sta blockFillFlag ; 6F58 D_2A2B is drawCharBlock's block-fill flag
lda #$FF ; 6F5B keep the fixed-colour override on for the caption too
sta useDrawColourFlag ; 6F5D textDrawParams = $FF
lda #$0C ; 6F60 screen-RAM colour $0C: %01 pixels black, %10 pixels medium grey
sta drawColourByte ; 6F62 fixed colour for the caption characters
lda #$11 ; 6F65 caption row 17 (two rows below the digits at rows 15-16)
sta scratch18 ; 6F67 zp_18 is drawCharAt's cell row
lda #$04 ; 6F69 caption starts at column 4, under the leftmost digit
sta scratch19 ; 6F6B zp_19 is drawCharAt's cell column
lda #$57 ; 6F6D caption glyph $57 (first third of the 3-character 'fuel' legend)
jsr drawCharAt ; 6F6F plot it at row 17, column 4
inc scratch19 ; 6F72 advance to column 5
lda #$58 ; 6F74 caption glyph $58
jsr drawCharAt ; 6F76 plot it at row 17, column 5
inc scratch19 ; 6F79 advance to column 6
lda #$59 ; 6F7B caption glyph $59
jsr drawCharAt ; 6F7D plot it at row 17, column 6
lda #$00 ; 6F80 release the colour override again...
sta useDrawColourFlag ; 6F82 ...so later text uses the per-character colour table
sta drawColourByte ; 6F85 and clear the fixed colour byte
rts ; 6F88 done
; ----------------------------------------------------------------------
; drawDroneRadarFrame - Draws the double frame around the drone camera window and clears the window:
; builds a fresh set of radar noise masks, draws window $0F (left 11, top 4, 18x16) and window $10
; (left 12, top 5, 16x14), then falls through into clearRadarWindowBlack which blanks the 14x12 camera
; area and re-centres the view on the player's own comcen.
; In: comcenUnit ($9236), unit column/row arrays
; Out: two frames drawn; radarNoiseMasks ($9210-$921F) re-randomised; camera area filled with colour
; 0; radarGhostCountdown ($920F) = 0; viewOriginCol/Row (zp_A5/zp_A6) = comcen - (6,5);
; radarWipeColour ($7134) = 0
; Called from: buildDroneScreen ($712B) only
; ----------------------------------------------------------------------
drawDroneRadarFrame:
jsr buildRadarNoiseMasks ; 6F89 re-roll the 16 noise masks used for unspotted enemy blips
ldx #$0F ; 6F8C window $0F = left 11, top 4, 18 wide x 16 rows - outer camera bezel
L_6F8E:
jsr drawWindowFrame ; 6F8E draw it (label L_6F8E is a variant-A branch target only)
; ----------------------------------------------------------------------
; The label at $6F91 is a variant-A entry point (serviceLinkWhileGenerating) that XREF merges into
; this file; in overlay variant B it is just the next instruction of drawDroneRadarFrame.
; ----------------------------------------------------------------------
drawRadarInnerBezel:
ldx #$10 ; 6F91 window $10 = left 12, top 5, 16 wide x 14 rows - inner camera bezel
jsr drawWindowFrame ; 6F93 draw it
jmp clearRadarWindowBlack ; 6F96 blank the camera area and centre the view on the own comcen; returns from there
; ----------------------------------------------------------------------
; drawDroneFuelDigits - Prints droneFuel ($9220) as three tall (2-row) digits at rows 15-16, columns
; 4-6 of the fuel panel. splitDecimalDigits splits the value into hundreds ($C44E), tens ($C44F) and
; ones ($C450) and drawTallGlyph draws each digit as a pair of characters from the glyph-pair table at
; $9810. The colour is a fuel warning: $30 (cyan over black) above 20, $B7 (dark grey over yellow) for
; 11-20, $21 (red over white) for 10 or less; below 21 the two nibbles are swapped on even fuel
; values, so the read-out flashes as it counts down towards empty.
; In: droneFuel ($9220), 0-100
; Out: six characters drawn; $C44E-$C450 = the decimal digits; textDrawParams/D_2A2A cleared on exit;
; zp_18 = 15, zp_19 = 6, zp_1A used as the nibble-rotate carry buffer
; Called from: buildDroneScreen ($7128), playDroneLostAnimation ($71FC, fuel forced to 0), launchDrone
; ($80B5, fuel set to 100), endDroneFlight ($81DA) and advanceDroneCamera ($82A1, once per
; exchange)
; ----------------------------------------------------------------------
drawDroneFuelDigits:
lda droneFuel ; 6F99 current fuel, counted down one per command exchange
jsr splitDecimalDigits ; 6F9C split it into hundreds/tens/ones at $C44E/$C44F/$C450
lda #$0F ; 6F9F digits go on row 15 (drawTallGlyph also uses row 16)
sta scratch18 ; 6FA1 zp_18 = cell row
lda #$04 ; 6FA3 leftmost digit column 4
sta scratch19 ; 6FA5 zp_19 = cell column
lda #$FF ; 6FA7 force the fixed draw colour instead of the per-character table
sta useDrawColourFlag ; 6FA9 textDrawParams = $FF
ldx #$30 ; 6FAC default colour $30: %01 cyan, %10 black - plenty of fuel
lda droneFuel ; 6FAE reload the fuel level for the range tests
cmp #$15 ; 6FB1 21 or more?
bcs L_6FD3 ; 6FB3 yes - keep $30 and skip the flashing logic entirely
ldx #$B7 ; 6FB5 warning colour $B7: %01 dark grey, %10 yellow
cmp #$0B ; 6FB7 11 or more?
bcs L_6FBD ; 6FB9 yes - keep $B7
ldx #$21 ; 6FBB critical colour $21: %01 red, %10 white
L_6FBD:
and #$01 ; 6FBD test the fuel level's parity (A still holds the fuel)
bne L_6FD3 ; 6FBF odd exchange - draw in the plain colour
sta scratch1A ; 6FC1 even exchange: A is 0 here, so this clears the rotate buffer
txa ; 6FC3 take the colour byte into A for the nibble swap
L_6FC4:
asl a ; 6FC4 shift the colour left one bit...
rol scratch1A ; 6FC5 ...catching the bit that falls out in zp_1A (rotate left through 8 bits)
asl a ; 6FC7 bit 2 of 4
rol scratch1A ; 6FC8 collect it
asl a ; 6FCA bit 3 of 4
L_6FCB:
rol scratch1A ; 6FCB collect it
asl a ; 6FCD bit 4 of 4 - the byte has now rotated left by four
rol scratch1A ; 6FCE collect the last carried bit
ora scratch1A ; 6FD0 merge the two halves: the nibbles are now swapped
L_6FD2:
tax ; 6FD2 back into X as the colour byte
L_6FD3:
stx drawColourByte ; 6FD3 install the chosen colour as the plotter's fixed colour
lda decimalHundreds ; 6FD6 hundreds digit (1 only when fuel is 100)
jsr drawTallGlyph ; 6FD9 draw it as a 2-row glyph pair at row 15/16, column 4
inc scratch19 ; 6FDC move to column 5
L_6FDE:
lda decimalTens ; 6FDE tens digit
jsr drawTallGlyph ; 6FE1 draw it at column 5
inc scratch19 ; 6FE4 move to column 6
lda decimalOnes ; 6FE6 ones digit
jsr drawTallGlyph ; 6FE9 draw it at column 6
lda #$00 ; 6FEC restore the normal colour handling...
sta useDrawColourFlag ; 6FEE ...clear the fixed-colour flag...
sta drawColourByte ; 6FF1 ...and the fixed colour byte
rts ; 6FF4 done
; ----------------------------------------------------------------------
; drawDroneCountPanel - Draws the 'DRONE' counter panel at the top right of the DRONE screen: resets
; the text window to the full screen, draws window $11 (left 29, top 3, 11x7) with the four inner
; corner marks, window $12 (left 32, top 4, 5x4), fills the 3x2 colour box for the digit at row 5
; column 33 with $30, and prints the caption 'DRONE' ($868C) at row 7 column 32 in colour $CF with the
; bitmap draw mode set to OR.
; In: nothing
; Out: frames, colour box and caption drawn; textColour zp_3D = $CF, textCursorCol zp_3F = 32,
; textCursorRow zp_40 = 7; both draw modes reset to replace on exit
; Called from: buildDroneScreen ($7119) only
; ----------------------------------------------------------------------
drawDroneCountPanel:
jsr setFullScreenWindow ; 6FF5 text window = whole 40x25 screen so the absolute column below is not shifted
ldx #$11 ; 6FF8 window $11 = left 29, top 3, 11 wide x 7 rows - outer counter panel
jsr drawWindowFrame ; 6FFA draw its frame
jsr drawWindowInnerCorners ; 6FFD add character $38 just inside the four corners of that frame
ldx #$12 ; 7000 window $12 = left 32, top 4, 5 wide x 4 rows - the box around the digit
jsr drawWindowFrame ; 7002 draw its frame
lda #$05 ; 7005 colour fill origin row 5
sta windowOriginRow ; 7007 D_2A2D = window row
lda #$21 ; 700A colour fill origin column 33
sta windowOriginCol ; 700C D_2A2E = window column
lda #$30 ; 700F screen-RAM colour $30: %01 cyan, %10 black - the digit's background
ldx #$03 ; 7011 3 columns
ldy #$02 ; 7013 2 rows (the tall digit is 2 cells high)
jsr fillScreenColourRect ; 7015 paint rows 5-6, columns 33-35
lda #$CF ; 7018 colour RAM value for the caption ($F = light grey for the %11 pixels)
sta textColour ; 701A zp_3D is the colour the $C000 text plotter writes per cell
printDroneCaption:
ldy #$01 ; 701C bitmap draw mode 1 = OR the glyph into the existing bitmap (label is a variant-A entry point only)
jsr setBitmapDrawMode ; 701E patch the plotter's read instruction accordingly
lda #$20 ; 7021 caption column 32
sta textCursorCol ; 7023 zp_3F = cursor column, relative to the window left edge (0 here)
lda #$07 ; 7025 caption row 7 (the bottom frame row of window $12)
L_7027:
sta textCursorRow ; 7027 zp_40 = cursor row
jsr updateCursorPointers ; 7029 recompute the bitmap and colour-RAM pointers for that cell
lda #$8C ; 702C low byte of the string 'DRONE' at $868C
ldy #$86 ; 702E high byte -> $868C
jsr printString ; 7030 print it (strings end on a byte with bit 7 set)
jmp resetDrawModes ; 7033 put both draw modes back to plain replace and return
; ----------------------------------------------------------------------
; drawDroneCountDigit - Draws the number of drones still available (radarDronesLeft $9233) as a single
; tall digit at rows 5-6, column 34, in the fixed colour $30. When no drones are left the colour byte
; is forced to 0, which makes the digit the same colour as its background, i.e. invisible.
; In: radarDronesLeft ($9233), 0-9
; Out: one glyph pair drawn; textDrawParams/D_2A2A cleared on exit; zp_18 = 5, zp_19 = 34
; Called from: buildDroneScreen ($711C) and launchDrone ($80BB) after the counter is decremented
; ----------------------------------------------------------------------
drawDroneCountDigit:
lda #$05 ; 7036 digit row 5
sta scratch18 ; 7038 zp_18 = cell row
lda #$22 ; 703A digit column 34 (centre of the 33-35 colour box)
sta scratch19 ; 703C zp_19 = cell column
lda #$FF ; 703E use the fixed colour in D_2A2A...
sta useDrawColourFlag ; 7040 ...rather than the per-character colour table
ldx #$30 ; 7043 normal digit colour $30 (%01 cyan, %10 black)
lda radarDronesLeft ; 7045 drones left
cmp #$01 ; 7048 at least one left?
bcs L_704D ; 704A yes - keep colour $30
tax ; 704C none left: colour byte 0, so the glyph vanishes into the background
L_704D:
stx drawColourByte ; 704D install the colour
lda radarDronesLeft ; 7050 reload the count - it is also the glyph-pair index for drawTallGlyph
bne L_7055 ; 7053 no-op branch: both paths continue at $7055 (probably a leftover leading-zero test)
L_7055:
jsr drawTallGlyph ; 7055 draw the digit as a 2-row glyph pair at rows 5-6, column 34
lda #$00 ; 7058 restore normal colour handling...
sta useDrawColourFlag ; 705A ...clear the fixed-colour flag...
sta drawColourByte ; 705D ...and the fixed colour
rts ; 7060 done
; ----------------------------------------------------------------------
; blinkDroneCountBox - Alternates the background of the drones-left box (3x2 colour cells at row 5,
; column 33) between $30 and its nibble-swapped twin $03 according to bit 0 of the frame counter,
; which swaps the foreground and background of the tall digit sitting in it and makes the counter
; flash.
; In: frameCounter (zp_45) bit 0
; Out: 6 screen-RAM colour cells rewritten; D_2A2D = 5, D_2A2E = 33, D_2A2A = the colour used; zp_1A
; used as the rotate buffer
; Called from: droneScreenIdleLoop ($8067, every 11 frames) and droneFlightLoop ($813E, every 40
; frames), in both cases only while fewer than two drones remain
; ----------------------------------------------------------------------
blinkDroneCountBox:
lda #$05 ; 7061 colour fill origin row 5
sta windowOriginRow ; 7063 D_2A2D = window row
lda #$21 ; 7066 colour fill origin column 33
sta windowOriginCol ; 7068 D_2A2E = window column
L_706B:
ldx #$30 ; 706B base colour $30 (%01 cyan, %10 black)
lda frameCounter ; 706D free-running frame counter maintained by the raster IRQ
and #$01 ; 706F look at its parity
bne L_7085 ; 7071 odd frame - use $30 unchanged
sta scratch1A ; 7073 even frame: A is 0, so this clears the rotate buffer
txa ; 7075 colour byte into A for the nibble swap
asl a ; 7076 rotate the byte left by four bits, one shift...
rol scratch1A ; 7077 ...catching the bit that leaves the byte
asl a ; 7079 shift 2
rol scratch1A ; 707A catch
asl a ; 707C shift 3
rol scratch1A ; 707D catch
asl a ; 707F shift 4
rol scratch1A ; 7080 catch
ora scratch1A ; 7082 merge - $30 has become $03 (%01 black, %10 cyan)
tax ; 7084 keep it in X
L_7085:
txa ; 7085 colour byte back into A for the fill call
ldx #$03 ; 7086 3 columns (33-35)
L_7088:
ldy #$02 ; 7088 2 rows (5-6)
jmp fillScreenColourRect ; 708A repaint the box and return through fillScreenColourRect
; ----------------------------------------------------------------------
; drawLaunchStrikeButtons - Draws the two-button panel down the right edge of the DRONE screen: frame
; window $13 (left 30, top 10, 10 wide x 11 rows) with inner corner marks, the 2x2 button icon from
; $9804 (characters $45-$48) at row 12 column 34 painted colour $05, the caption 'LAUNCH' ($8691) at
; row 14 column 32, the same icon again at row 16 column 34, and the caption 'STRIKE' ($8697) at row
; 18 column 32. The screen-2 loops afterwards recolour these two 2x2 icon blocks: colour $05 when
; LAUNCH is pressed, colour $02 when STRIKE is pressed and an XOR of $08 for the idle blink.
; In: nothing
; Out: frame, two icons and two captions drawn; zp_3D/zp_3F/zp_40 left at $C7/32/18; both draw modes
; reset to replace
; Called from: buildDroneScreen ($711F) only
; Note: the survey's window geometry was wrong - windowLeftTable[$13] = 30 and windowWidthTable[$13] =
; 10, not left 29 / width 14.
; ----------------------------------------------------------------------
drawLaunchStrikeButtons:
ldx #$13 ; 708D window $13 = left 30, top 10, 10 wide x 11 rows - the button panel
jsr drawWindowFrame ; 708F draw its frame
jsr drawWindowInnerCorners ; 7092 add the four inner corner marks (character $38)
lda #$0C ; 7095 LAUNCH icon row 12
setLaunchIconOrigin:
sta windowOriginRow ; 7097 D_2A2D = window row (label is a variant-A entry point only)
lda #$22 ; 709A LAUNCH icon column 34
sta windowOriginCol ; 709C D_2A2E = window column
L_709F:
lda #$04 ; 709F low byte of the 2x2 icon block at $9804 (header 2,2 then characters $45,$46,$47,$48)
ldy #$98 ; 70A1 high byte -> $9804
jsr drawCharBlock ; 70A3 stamp the icon at rows 12-13, columns 34-35
lda #$05 ; 70A6 screen-RAM colour $05: %01 black, %10 green - the unpressed button
ldx #$02 ; 70A8 2 columns
ldy #$02 ; 70AA 2 rows
L_70AC:
jsr fillScreenColourRect ; 70AC paint the icon's 4 colour cells
lda #$CD ; 70AF colour RAM value for the caption ($D = light green for the %11 pixels)
sta textColour ; 70B1 zp_3D = text colour
jsr setFullScreenWindow ; 70B3 text window = whole screen so column 32 is absolute
lda #$20 ; 70B6 caption column 32
sta textCursorCol ; 70B8 zp_3F = cursor column
lda #$0E ; 70BA caption row 14, just under the icon
sta textCursorRow ; 70BC zp_40 = cursor row
jsr updateCursorPointers ; 70BE recompute the cell pointers
lda #$91 ; 70C1 low byte of 'LAUNCH' at $8691
ldy #$86 ; 70C3 high byte -> $8691
jsr printString ; 70C5 print it
lda #$10 ; 70C8 STRIKE icon row 16
sta windowOriginRow ; 70CA D_2A2D = window row
lda #$22 ; 70CD STRIKE icon column 34
sta windowOriginCol ; 70CF D_2A2E = window column
lda #$04 ; 70D2 the same 2x2 icon block at $9804...
ldy #$98 ; 70D4 ...high byte
jsr drawCharBlock ; 70D6 stamp it at rows 16-17, columns 34-35 (its colour cells are left as the frame drew them until a strike is armed)
printStrikeCaption:
lda #$C7 ; 70D9 colour RAM value for the caption (7 = yellow for the %11 pixels) (label is a variant-A entry point only)
sta textColour ; 70DB zp_3D = text colour
jsr setFullScreenWindow ; 70DD text window = whole screen
lda #$20 ; 70E0 caption column 32
sta textCursorCol ; 70E2 zp_3F = cursor column
lda #$12 ; 70E4 caption row 18
sta textCursorRow ; 70E6 zp_40 = cursor row
jsr updateCursorPointers ; 70E8 recompute the cell pointers
lda #$97 ; 70EB low byte of 'STRIKE' at $8697
ldy #$86 ; 70ED high byte -> $8697
jsr printString ; 70EF print it
jmp resetDrawModes ; 70F2 restore both draw modes and return
; ----------------------------------------------------------------------
; buildDroneScreen - Paints the whole DRONE screen (game screen 2) from scratch: clears the glyph
; plotter's parameter block, sets border and background shadows to $0C (medium grey), sets the radar
; cell background nibble to $0A (light red) and marks the screen as built, then draws every panel in
; turn and finishes by redrawing the status line through the $C000 overlay.
; In: droneFuel ($9220), radarDronesLeft ($9233), comcenUnit ($9236)
; Out: complete screen 2; vicBorderShadow/vicBg0Shadow ($9003/$9004) = $0C, cellBackgroundColour
; ($9226) = $0A, screenBuiltFlag (zp_41) = 1, $2A29-$2A2C = 0; jumps away into redrawStatusMessage
; Called from: resetDroneScreen ($7FF3), which is entered from the screen-2 entry $7FE6 and from the
; comcen knock-out sequence at $076A
; ----------------------------------------------------------------------
buildDroneScreen:
lda #$00 ; 70F5 clear the glyph plotter parameter block...
sta useDrawColourFlag ; 70F7 ...useFixedColour flag off
sta drawColourByte ; 70FA ...fixed colour byte 0
sta blockFillFlag ; 70FD ...block-fill flag off
sta blockFillChar ; 7100 ...block-fill character 0
lda #$0C ; 7103 $0C = medium grey
sta vicBg0Shadow ; 7105 background colour 0 shadow (written to $D021 by the raster IRQ)
sta vicBorderShadow ; 7108 border colour shadow ($D020)
lda #$0A ; 710B $0A = light red
sta cellBackgroundColour ; 710D cellBackgroundColour supplies the low nibble (%10 pixels) of every radar cell on this screen
lda #$01 ; 7110 every screen builder stores 1 here
L_7112:
sta screenBuiltFlag ; 7112 zp_41 screenBuiltFlag; nothing ever reads it
lda #$00 ; 7114 redundant - D_2A2B was already cleared at $70FD
sta blockFillFlag ; 7116 block-fill flag off (again)
jsr drawDroneCountPanel ; 7119 top-right 'DRONE' counter panel
jsr drawDroneCountDigit ; 711C the drones-left digit inside it
jsr drawLaunchStrikeButtons ; 711F the LAUNCH / STRIKE button panel
L_7122:
jsr drawDroneHeadingPanel ; 7122 top-left compass dial
jsr drawDroneFuelPanel ; 7125 bottom-left fuel gauge panel
jsr drawDroneFuelDigits ; 7128 the three fuel digits
jsr drawDroneRadarFrame ; 712B camera bezels; also blanks the camera and centres the view on the comcen
jsr drawMainWindowFrame ; 712E the outer main window / status bar frame (window 4 while a screen id is active)
jmp redrawStatusMessage ; 7131 re-show whatever the status line was displaying, and return from there
; Radar/camera wipe state byte, shared by clearRadarWindowGrey, clearRadarWindowBlack,
; flashRadarAndWipe and drawRadarIrisWipe. It doubles as (a) the colour the camera window is blanked
; with and (b) a flag: non-zero makes the iris wipe pause 3 game frames per ring, zero makes it close
; instantly.
radarWipeColour:
.byte $00 ; 7134 . $BB = animated grey wipe (after a launch), $00 = instant black wipe
; ----------------------------------------------------------------------
; clearRadarWindowGrey - Blanks the 14x12 drone camera window (row 6, column 13) to colour $BB (dark
; grey on dark grey, i.e. static) and re-centres the view origin on the player's own comcen, leaving
; radarWipeColour ($7134) non-zero so the iris wipe that follows animates with 3-frame pauses.
; In: comcenUnit ($9236), unitColTable/unitRowTable
; Out: radarWipeColour = $BB; camera window filled with $BB; radarGhostCountdown ($920F) = 0;
; viewOriginCol/Row (zp_A5/zp_A6) = comcen column - 6, comcen row - 5
; Called from: launchDrone ($8116) only
; ----------------------------------------------------------------------
clearRadarWindowGrey:
lda #$BB ; 7135 $BB = dark grey in both multicolour nibbles - looks like blank static
sta radarWipeColour ; 7137 remember it as both the fill colour and the 'animate the wipe' flag
bne L_7141 ; 713A always taken ($BB is non-zero); joins the shared tail
; ----------------------------------------------------------------------
; clearRadarWindowBlack - Same as clearRadarWindowGrey but with colour 0: blanks the drone camera
; window to black and re-centres the view on the own comcen. radarWipeColour is left 0, so a following
; iris wipe runs with no pauses.
; In: comcenUnit ($9236), unitColTable/unitRowTable
; Out: radarWipeColour = 0; camera window black; radarGhostCountdown ($920F) = 0; viewOriginCol/Row =
; comcen - (6,5)
; Called from: drawDroneRadarFrame ($6F96, screen build) and endDroneFlight ($81F8)
; ----------------------------------------------------------------------
clearRadarWindowBlack:
lda #$00 ; 713C colour 0 = all pixels black
sta radarWipeColour ; 713E 0 also means 'no pauses in the iris wipe'
; ----------------------------------------------------------------------
; Shared tail of the two window clears: paint the camera area, forget any pending noise ghost and park
; the map view so that the own comcen sits in the middle of the 14x12 window (the drone starts its
; flight over the comcen).
; ----------------------------------------------------------------------
L_7141:
jsr fillDroneViewWindow ; 7141 fill the 14 column x 12 row camera window at row 6, column 13 with the colour in A
lda #$00 ; 7144 clear the countdown that decides when the next unspotted enemy is drawn as a ghost blip
sta radarGhostCountdown ; 7146 radarGhostCountdown ($920F)
ldy comcenUnit ; 7149 the player's own comcen (unit 49 for side 0, 99 for side 1)
L_714C:
lda unitColTable,y ; 714C its map column (unit arrays are 100-byte arrays $64 apart from $F640)
sec ; 714F prepare the subtraction
sbc #$06 ; 7150 the window is 14 cells wide, so back off 6 to put the comcen at cell 6
sta viewOriginCol ; 7152 zp_A5 = map column of the window's left edge
lda unitRowTable,y ; 7154 the comcen's map row ($F6A4 = $F640 + $64)
sec ; 7157 prepare the subtraction
sbc #$05 ; 7158 the window is 12 cells high, so back off 5 to put the comcen at cell 5
sta viewOriginRow ; 715A zp_A6 = map row of the window's top edge
rts ; 715C done
; Six bytes with no reference anywhere in overlay variant B ($21,$A1,$21,$81,$A1,$11). They are code
; or data of overlay variant A, which shares this address range; the label L_7161 is a branch target
; only in variant A. Dead in this variant.
unusedBytes715D:
.byte $21,$A1,$21,$81 ; 715D !.!. unreferenced filler in variant B (variant A code/data)
L_7161:
.byte $A1 ; 7161 continuation of the same unreferenced filler
.byte $11 ; 7162 .
; ----------------------------------------------------------------------
; playDroneLostAnimation - The 'drone is gone' effect, used when the drone is shot down, detonated by
; its own strike or runs out of fuel. It switches sprite 4 (the comcen range rings) off, flashes the
; camera window white and irises it shut, then rebuilds sprite 2 (the drone reticle) as a solid disc
; of radius 3 and lets it shrink and fade (white -> light grey -> medium grey -> dark grey) by erasing
; one ring at a time, disables sprite 2, zeroes the fuel read-out and waits 30 game frames.
; In: nothing meaningful (droneHeading is copied to a byte nobody reads)
; Out: spriteEnableShadow ($9022) bits 4 and 2 cleared; sprite 2 shape and colour changed;
; spritePlotMode ($91F3) = 0; droneFuel ($9220) = 0 and redrawn; textDrawParams/D_2A2A = 0;
; roughly 50 game frames elapse (the battle keeps running inside runGameFrames)
; Called from: playStrikeZoomAnimation ($7582, after a strike) and droneFlightLoop ($8177, drone lost)
; Dead code: 'sta zp_30' at $716A (only magnifyWindow2x uses zp_30, and it initialises it itself) and
; the store to savedDroneHeading at $717C (never read back).
; ----------------------------------------------------------------------
playDroneLostAnimation:
lda #$FF ; 7163 force the fixed draw colour for everything drawn from here
sta useDrawColourFlag ; 7165 textDrawParams = $FF
lda #$05 ; 7168 dead store: 5 into magnifySrcRow...
sta magnifySrcRow ; 716A ...zp_30, which magnifyWindow2x always initialises itself
lda spriteEnableShadow ; 716C current sprite enable mask shadow
and #$EF ; 716F clear bit 4 = sprite 4, the comcen range rings
sta spriteEnableShadow ; 7171 the raster IRQ copies this to $D015
lda #$00 ; 7174 fixed colour 0 (black) for the wipe frames
L_7176:
sta drawColourByte ; 7176 D_2A2A = fixed colour byte
lda droneHeading ; 7179 current drone heading 0-7
sta savedDroneHeading ; 717C dead store - savedDroneHeading ($7271) is never read
jsr flashRadarAndWipe ; 717F flash the camera window white and iris it shut
lda #$02 ; 7182 sprite 2 = the drone reticle
jsr clearSpriteShape ; 7184 zero its 64 shape bytes at $8A80
lda #$40 ; 7187 sprite plot mode $40 = OR pixels in (draw)
sta spritePlotMode ; 7189 spritePlotMode ($91F3)
lda #$02 ; 718C sprite 2
ldx #$03 ; 718E radius 3
jsr drawCircleInSprite ; 7190 draw a radius-3 circle centred at (12,10) in the sprite
lda #$02 ; 7193 sprite 2 again
tax ; 7195 X = 2 (A was 2) - radius 2
jsr drawCircleInSprite ; 7196 fill in the radius-2 ring, making the shape a solid disc
lda #$02 ; 7199 sprite 2 again
ldx #$01 ; 719B radius 1 = the 3x3 centre dot
jsr drawCircleInSprite ; 719D complete the disc
lda #$04 ; 71A0 bit 2 = sprite 2
jsr enableSprites ; 71A2 switch it on (unless sprites are suppressed by $922A bit 6)
lda #$01 ; 71A5 colour 1 = white
sta sprite2ColourShadow ; 71A7 sprite 2 colour shadow ($900A -> $D029)
L_71AA:
lda #$03 ; 71AA hold the white disc for 3 frames...
jsr runGameFrames ; 71AC ...while the battle keeps updating
lda #$00 ; 71AF sprite plot mode 0 = clear pixels (erase)
sta spritePlotMode ; 71B1 spritePlotMode
lda #$02 ; 71B4 sprite 2
ldx #$03 ; 71B6 radius 3
jsr drawCircleInSprite ; 71B8 erase the outer ring - the disc shrinks
lda #$0F ; 71BB colour $0F = light grey
sta sprite2ColourShadow ; 71BD fade step 1
lda #$03 ; 71C0 3 more frames
jsr runGameFrames ; 71C2 run them
lda #$02 ; 71C5 sprite 2
ldx #$02 ; 71C7 radius 2
jsr drawCircleInSprite ; 71C9 erase the next ring
lda #$0C ; 71CC colour $0C = medium grey
sta sprite2ColourShadow ; 71CE fade step 2
lda #$05 ; 71D1 5 frames
jsr runGameFrames ; 71D3 run them
lda #$02 ; 71D6 sprite 2
ldx #$01 ; 71D8 radius 1
jsr drawCircleInSprite ; 71DA erase the last dot - the sprite is now empty
lda #$0B ; 71DD colour $0B = dark grey
sta sprite2ColourShadow ; 71DF fade step 3
lda #$06 ; 71E2 6 frames
jsr runGameFrames ; 71E4 run them
lda spriteEnableShadow ; 71E7 sprite enable shadow
and #$FB ; 71EA clear bit 2 = sprite 2 off
sta spriteEnableShadow ; 71EC store it back
lda #$00 ; 71EF restore normal colour handling...
sta useDrawColourFlag ; 71F1 ...fixed-colour flag off...
sta drawColourByte ; 71F4 ...and fixed colour 0
lda #$00 ; 71F7 the drone is gone, so the tank reads empty
sta droneFuel ; 71F9 droneFuel ($9220) = 0
jsr drawDroneFuelDigits ; 71FC redraw '000' in the critical colour
lda #$1E ; 71FF 30 frames = about half a second
jmp runGameFrames ; 7201 wait them out and return from runGameFrames
; ----------------------------------------------------------------------
; flashRadarAndWipe - Paints the whole 14x12 drone camera window white for two game frames, then
; clears radarWipeColour and falls into drawRadarIrisWipe, so the iris closes over the white flash
; with no pauses.
; In: nothing
; Out: camera window colour 1; radarWipeColour ($7134) = 0; then see drawRadarIrisWipe
; Called from: playDroneLostAnimation ($717F) only
; ----------------------------------------------------------------------
flashRadarAndWipe:
lda #$06 ; 7204 fill origin row 6 = top of the camera window
sta windowOriginRow ; 7206 D_2A2D = window row
lda #$0D ; 7209 fill origin column 13 = left edge of the camera window
sta windowOriginCol ; 720B D_2A2E = window column
ldx #$0E ; 720E 14 columns
ldy #$0C ; 7210 12 rows
lda #$01 ; 7212 colour byte $01: %01 pixels black, %10 pixels white - a bright flash
jsr fillScreenColourRect ; 7214 paint it
lda #$02 ; 7217 hold for 2 frames
jsr runGameFrames ; 7219 while the battle keeps running
L_721C:
lda #$00 ; 721C 0 = close the iris with no pauses
sta radarWipeColour ; 721E radarWipeColour; falls straight into drawRadarIrisWipe
; ----------------------------------------------------------------------
; drawRadarIrisWipe - Closes (or opens) an iris over the drone camera window: it first draws window
; $16 (the camera area itself, left 13, top 6, 14x12) and then repeatedly draws a rectangular frame
; out of the all-$03 solid tile set at $9848, starting 12x12 cells at row 6 column 14 and shrinking
; two cells and moving in one cell each pass until the height drops below 3. The centre is finished
; off with a 2x2 block of character $05 at row 11, column 19. Each pass waits for raster line $B0 so
; the frame appears in one go, and when radarWipeColour is non-zero it also runs 3 game frames per
; pass, turning the wipe into a visible animation.
; In: radarWipeColour ($7134); the current fixed colour in $2A29/$2A2A ($AA light red after a launch,
; 0 during the loss animation)
; Out: the camera area is covered by concentric frames; D_2A2D = 11, D_2A2E = 19;
; frameWidth/frameHeight ($9221/$9222) clobbered
; Called from: flashRadarAndWipe ($721E, fall-through) and launchDrone ($8121, as the iris-open
; effect)
; ----------------------------------------------------------------------
drawRadarIrisWipe:
ldx #$16 ; 7221 window $16 = left 13, top 6, 14x12 - the camera area drawn as a plain frame
jsr drawWindowFrame ; 7223 draw it
lda #$0C ; 7226 the first iris ring is 12x12 cells
sta frameWidth ; 7228 frameWidth ($9221)
sta frameHeight ; 722B frameHeight ($9222)
lda #$06 ; 722E first ring's top row 6
sta windowOriginRow ; 7230 D_2A2D = frame row
lda #$0E ; 7233 first ring's left column 14
sta windowOriginCol ; 7235 D_2A2E = frame column
; ----------------------------------------------------------------------
; Iris loop: draw one concentric frame, then shrink it by two cells and move its origin in by one
; cell, so the ring closes in on the centre of the camera window. drawFrameBox itself decrements width
; and height once and this loop decrements them again, hence two cells per pass.
; ----------------------------------------------------------------------
L_7238:
lda frameHeight ; 7238 current ring height
cmp #$03 ; 723B smaller than 3 cells?
bcc L_7265 ; 723D yes - the iris is shut, go and plug the centre
L_723F:
lda VIC_RASTER ; 723F poll the raster...
cmp #$B0 ; 7242 ...for line $B0, below the picture, so the ring is drawn between frames
bne L_723F ; 7244 keep polling until it matches
lda #$48 ; 7246 low byte of the 9-byte frame tile set at $9848 (all nine tiles are character $03 = solid)
ldy #$98 ; 7248 high byte -> $9848
jsr drawFrameBox ; 724A draw the ring; this also decrements frameWidth/frameHeight by one
dec frameWidth ; 724D second decrement: the ring loses one cell on each side
dec frameHeight ; 7250 same for the height
lda radarWipeColour ; 7253 animated wipe?
beq L_725D ; 7256 no - draw the next ring immediately
lda #$03 ; 7258 3 game frames of pause...
jsr runGameFrames ; 725A ...so the iris is visible as an animation
L_725D:
inc windowOriginRow ; 725D move the origin one row down...
inc windowOriginCol ; 7260 ...and one column right, keeping the ring centred
bne L_7238 ; 7263 always taken - next ring
L_7265:
lda #$13 ; 7265 centre column 19
sta windowOriginCol ; 7267 D_2A2E; D_2A2D is already 11 after the last pass
lda #$0A ; 726A low byte of the 2x2 block at $980A (header 2,2 then four copies of character $05)
ldy #$98 ; 726C high byte -> $980A
jmp drawCharBlock ; 726E plug the 2x2 hole at rows 11-12, columns 19-20 and return from drawCharBlock
; savedDroneHeading: byteTable, 1 bytes. Dead copy of droneHeading written by playDroneLostAnimation
savedDroneHeading:
.byte $00 ; 7271 . written by playDroneLostAnimation ($717C) and never read - a dead copy of droneHeading
; Eight AND masks that clear a single bit: index n clears bit n. buildRadarNoiseMasks picks the index
; with a random number 0-7.
clearBitMaskTable:
.byte $FE,$FD,$FB,$F7,$EF,$DF,$BF,$7F; 7272 ........ $FE,$FD,$FB,$F7,$EF,$DF,$BF,$7F - clear bit 0..7 respectively
; ----------------------------------------------------------------------
; buildRadarNoiseMasks - Fills the 16-byte table radarNoiseMasks ($9210-$921F) with $FF and then
; knocks four randomly chosen bits out of each byte. drawRadarGlyph ANDs the eight rows of a glyph
; with eight consecutive entries of this table (starting at a random offset 0-7, which is why the
; table is 16 and not 8 bytes long) to turn an unspotted enemy blip into a speckled 'ghost'.
; In: the game RNG state $57-$59
; Out: radarNoiseMasks ($9210-$921F); zp_1A = 0; Y = $FF; A/X clobbered
; Called from: drawDroneRadarFrame ($6F89), drawDroneRadarWindow ($742D), redrawEnemyUnitsOnRadar
; ($7926) and drawRadarMap ($7BA2) - i.e. the noise pattern is re-rolled on every radar redraw
; ----------------------------------------------------------------------
buildRadarNoiseMasks:
ldy #$0F ; 727A 16 masks, index 15 down to 0
lda #$FF ; 727C start from 'all bits set' = no noise
; ----------------------------------------------------------------------
; Clear the table to $FF.
; ----------------------------------------------------------------------
L_727E:
sta radarNoiseMasks,y ; 727E radarNoiseMasks[Y] = $FF
dey ; 7281 next entry down
bpl L_727E ; 7282 until index 0 has been written
ldy #$0F ; 7284 second pass, again from index 15
; ----------------------------------------------------------------------
; For every mask byte, punch out four random bits (they may collide, so between one and four bits
; actually end up clear).
; ----------------------------------------------------------------------
L_7286:
lda #$04 ; 7286 four bits to clear per mask byte
sta scratch1A ; 7288 zp_1A = per-byte bit counter
L_728A:
jsr nextGameRandom ; 728A step the shared 24-bit game RNG (both machines run the same sequence)
and #$07 ; 728D keep a bit number 0-7
L_728F:
tax ; 728F use it as the index into clearBitMaskTable
lda clearBitMaskTable,x ; 7290 AND mask that clears just that bit
and radarNoiseMasks,y ; 7293 combine with the mask built so far
sta radarNoiseMasks,y ; 7296 store it back
dec scratch1A ; 7299 one of the four bits done
bne L_728A ; 729B repeat until four have been cleared
dey ; 729D next mask byte
bpl L_7286 ; 729E until all 16 are done
rts ; 72A0 done
; ----------------------------------------------------------------------
; applyDroneBlast - Resolves a drone detonation: damages every unit standing in the 2x2 block of map
; cells whose top-left corner is (cmdParam1, cmdParam2) - (x,y), (x+1,y), (x+1,y+1) and (x,y+1) - by
; calling applyDroneBlastToCell four times. The blast strength arrives in A and is written into the
; LDA # operand at $7310+1, so each cell's damage call ends with 'LDA #strength / JSR applyHitToUnit'.
; In: A = blast strength (cmdDroneDetonate at $536C passes $19 = 25), cmdParam1/cmdParam2
; (zp_74/zp_75) = the top-left map cell of the blast
; Out: up to four units damaged and stunned; the operand at $7311 is patched; cmdParam1 is restored to
; x but cmdParam2 is left at y+1, which is what the caller then uses to place the explosion marker
; sprite
; Called from: cmdDroneDetonate in the main program ($536C) only
; ----------------------------------------------------------------------
applyDroneBlast:
sta L_7310+1 ; 72A1 self-modifying code: store the blast strength into the operand byte of the 'lda #$FF' at $7310
jsr applyDroneBlastToCell ; 72A4 cell (x, y) - the top-left of the 2x2 blast
inc cmdParam1 ; 72A7 step to column x+1
jsr applyDroneBlastToCell ; 72A9 cell (x+1, y)
inc cmdParam2 ; 72AC step to row y+1
jsr applyDroneBlastToCell ; 72AE cell (x+1, y+1)
L_72B1:
dec cmdParam1 ; 72B1 back to column x; falls straight into the fourth call for cell (x, y+1)
; ----------------------------------------------------------------------
; applyDroneBlastToCell - Applies the drone blast to the single map cell (cmdParam1, cmdParam2). It
; returns at once if the cell is outside the 40x40 map or holds no unit. Otherwise, if the blast
; strength is at least 15 and the game type is not THE BOMB (type 2), the unit's stun counter is set
; to 15 and haltStunnedUnit stops it where it stands. If the unit is the player's own comcen, the
; cloak lock is refreshed and - with the DAMAGE option on ($0BA3 bit 4) and not in a solo/film game -
; all three comcen systems are marked damaged and comcenStunStatus is set to $FF, which triggers the
; knock-out cut-scene at $0709. Without DAMAGE the 'COMCEN STUNNED!' message (id $0C) is queued
; instead. Finally the unit takes the blast damage.
; In: cmdParam1/cmdParam2 = map cell, blast strength in the operand at $7311, gameTypeOptions
; ($0BA3), filmPlaybackMode ($0B9B), comcenUnit ($9236)
; Out: unit stun/halt, possibly the comcen knock-out flags, and applyHitToUnit with A = strength, Y =
; the unit index
; Called from: applyDroneBlast ($72A4/$72A9/$72AE plus the fall-through at $72B1)
; Note: the labels L_72B1, L_72D3, L_72D5, L_72E1, L_72E5, L_72F2, L_72F5, sub_72F6 and L_730D are
; XREF artefacts of overlay variant A sharing these addresses; only sub_72F6 ($72F6) is also a real
; branch target here.
; ----------------------------------------------------------------------
applyDroneBlastToCell:
ldx cmdParam1 ; 72B3 map column of the cell
cpx #$28 ; 72B5 the map is 40 cells wide
bcs L_7315 ; 72B7 off the map - nothing to damage
ldy cmdParam2 ; 72B9 map row of the cell
cpy #$28 ; 72BB the map is 40 cells high
bcs L_7315 ; 72BD off the map - nothing to damage
jsr readMapCell ; 72BF read map[$F000 + row*40 + col]
bpl L_7315 ; 72C2 bit 7 clear = terrain only, no unit standing here
and #$7F ; 72C4 strip the 'unit here' bit...
tay ; 72C6 ...leaving the unit index 0-99 in Y
lda L_7310+1 ; 72C7 the blast strength patched in by applyDroneBlast
cmp #$0F ; 72CA weaker than 15?
bcc L_7310 ; 72CC yes - just apply damage, no stun
lda gameTypeOptions ; 72CE game options byte
and #$07 ; 72D1 bits 0-2 = game type
L_72D3:
cmp #$02 ; 72D3 type 2 = THE BOMB
L_72D5:
beq L_7310 ; 72D5 in THE BOMB nothing gets stunned - just apply damage
lda unitStunTable,y ; 72D7 the unit's stun / dug-in counter
ora #$0F ; 72DA set the low nibble to 15 = fully stunned
sta unitStunTable,y ; 72DC store it back
jsr haltStunnedUnit ; 72DF cancel the unit's orders so it stops where it is
cpy comcenUnit ; 72E2 is this the local player's own comcen?
L_72E5:
bne L_7310 ; 72E5 no - just apply damage
jsr updateComcenCloakLock ; 72E7 refresh comcenCloakedFlag ($9245) from the comcen's cloak bit
lda gameTypeOptions ; 72EA game options byte
and #$10 ; 72ED bit 4 = the DAMAGE option
beq queueComcenStunnedMessage; 72EF DAMAGE off - only announce the stun
lda filmPlaybackMode ; 72F1 solo trainer / film playback flag
bpl L_7300 ; 72F4 bit 7 clear = real linked game, so knock the comcen out
; ----------------------------------------------------------------------
; Soft outcome: announce the stun on the status line and skip the knock-out.
; ----------------------------------------------------------------------
queueComcenStunnedMessage:
lda #$0C ; 72F6 message id $0C = 'COMCEN STUNNED!'
jsr queueMessage ; 72F8 queue it in the $C000 overlay's sorted message ring
ldy comcenUnit ; 72FB reload the unit index for applyHitToUnit
bne L_7310 ; 72FE always taken: comcenUnit is 49 or 99, never 0 - so the knock-out block below is skipped
; ----------------------------------------------------------------------
; Hard outcome: the comcen is knocked out. All three of its subsystems are marked damaged and
; comcenStunStatus is set, which the main loop turns into the black-out cut-scene at $0709.
; ----------------------------------------------------------------------
L_7300:
lda #$80 ; 7300 $80 = system damaged
sta comcenBattleStatus ; 7302 comcenSystemStatus[1]: the overview map drawing
sta comcenRadarStatus ; 7305 comcenSystemStatus[2]: screen 1, the radar/missile scanner
sta comcenDroneStatus ; 7308 comcenSystemStatus[3]: the drone system
lda #$FF ; 730B $FF = comcen stunned
L_730D:
sta comcenStunStatus ; 730D comcenStunStatus ($922A); the knock-out sequence polls this
L_7310:
lda #$FF ; 7310 operand $7311 is patched by applyDroneBlast with the blast strength (25 for a drone)
jsr applyHitToUnit ; 7312 apply the damage to unit Y (halved and score-adjusted inside applyHitToUnit)
L_7315:
rts ; 7315 done
; Maps a clockwise compass heading 0-7 (0 N, 1 NE, 2 E, 3 SE, 4 S, 5 SW, 6 W, 7 NW) to the engine's
; direction code 0-7 (0 N, 1 S, 2 E, 3 W, 4 NW, 5 NE, 6 SE, 7 SW), which is what directionDeltaTable
; ($20F0) and diagonalDirectionTable ($1FB2) are indexed with. joyDirToHeadingTable ($7FCE) is the
; inverse mapping.
headingToDirectionTable:
.byte $00,$05,$02,$06 ; 7316 .... headings N,NE,E,SE -> direction codes 0,5,2,6
L_731A:
.byte $01 ; 731A headings S,SW,W,NW -> direction codes 1,7,3,4
.byte $07,$03,$04 ; 731B ...
; ----------------------------------------------------------------------
; steerDrone - Advances the locally flown drone by one map cell. Called once per command exchange from
; sendOutgoingPacket ($5639) while droneControlFlags ($920D) bit 6 says the drone is under local
; control, just before the $8D heading,col,row report is queued. It turns droneHeading ($920B) at most
; one 45-degree step towards the heading the joystick is asking for, taking the shorter way round and
; playing sound $10 on each turn, then converts the heading to a direction code and adds that
; direction's dx/dy to droneViewOriginCol/Row ($92F6/$92F7).
; In: droneJoyDirection (zp_5E) = direction code of the last joystick reading, droneHeading ($920B),
; droneViewOriginCol/Row ($92F6/$92F7)
; Out: droneHeading turned by at most one step and kept in 0-7; the drone's map position advanced by
; one cell; sound $10 when it turned; A/X/Y clobbered
; Called from: sendOutgoingPacket ($5639) in the main program
; ----------------------------------------------------------------------
steerDrone:
ldy droneJoyDirection ; 731E the direction code the joystick last produced (0 N, 1 S, 2 E, 3 W, 4 NW, 5 NE, 6 SE, 7 SW)
lda joyDirToHeadingTable,y ; 7320 convert it to a clockwise heading 0-7 through joyDirToHeadingTable ($7FCE)
sec ; 7323 prepare the subtraction
sbc droneHeading ; 7324 how far is that from the heading we are flying?
beq L_734C ; 7327 already on heading - no turn
bcc L_7335 ; 7329 wanted heading is numerically smaller - handle the negative case
ldx #$FF ; 732B assume a turn of -1 (anticlockwise)
cmp #$04 ; 732D is the difference 4 or more?
bcs L_733D ; 732F yes - going anticlockwise is the short way round
ldx #$01 ; 7331 difference 1-3: turn +1, clockwise, is shorter
bne L_733D ; 7333 always taken
L_7335:
ldx #$01 ; 7335 negative difference: assume +1 (clockwise)
cmp #$FC ; 7337 difference of -4..-1 (i.e. $FC..$FF)?
bcc L_733D ; 7339 no - the gap is bigger than 4, so clockwise is shorter
ldx #$FF ; 733B -1..-4: turn anticlockwise
L_733D:
txa ; 733D the turn step (+1 or -1) into A
L_733E:
clc ; 733E prepare the addition
adc droneHeading ; 733F apply it to the current heading
and #$07 ; 7342 wrap into the range 0-7
sta droneHeading ; 7344 the drone has swung 45 degrees
lda #$10 ; 7347 sound effect $10 = the turn
jsr playSound ; 7349 play it
; ----------------------------------------------------------------------
; Translate the (possibly just updated) heading into a map step and move the drone.
; ----------------------------------------------------------------------
L_734C:
ldy droneHeading ; 734C current heading 0-7
lda headingToDirectionTable,y; 734F -> direction code 0-7
ldy #$00 ; 7352 Y will hold the row delta, default 0
ldx #$00 ; 7354 X will hold the column delta, default 0
cmp #$04 ; 7356 codes 4-7 are the diagonals
bcs L_736E ; 7358 handle a diagonal
cmp #$02 ; 735A codes 2-3 are east/west
bcs L_7366 ; 735C handle a pure column move
tay ; 735E codes 0-1 (N/S): index directionDeltaTable with the code
lda directionDeltaTable,y ; 735F $20F0[0] = -1 for north, [1] = +1 for south
L_7362:
tay ; 7362 that is the row delta
jmp L_7384 ; 7363 go and apply the step
L_7366:
tax ; 7366 codes 2-3 (E/W): index directionDeltaTable with the code
lda directionDeltaTable,x ; 7367 $20F0[2] = +1 for east, [3] = -1 for west
tax ; 736A that is the column delta
L_736B:
jmp L_7384 ; 736B go and apply the step
L_736E:
tay ; 736E codes 4-7 (NW/NE/SE/SW): use the packed table
lda diagonalDirectionTable,y; 736F $1FB2[4..7] = $30,$20,$21,$31 - high nibble = column index, low nibble = row index
pha ; 7372 keep the packed byte
and #$0F ; 7373 low nibble = index into directionDeltaTable for the row
tay ; 7375 into Y
lda directionDeltaTable,y ; 7376 fetch the row delta
tay ; 7379 Y = row delta
pla ; 737A packed byte back
lsr a ; 737B shift the high nibble down, 1 of 4
lsr a ; 737C 2 of 4
lsr a ; 737D 3 of 4
lsr a ; 737E 4 of 4 - now the column index
tax ; 737F into X
lda directionDeltaTable,x ; 7380 fetch the column delta
tax ; 7383 X = column delta
L_7384:
txa ; 7384 column delta into A
clc ; 7385 prepare the addition
adc droneViewOriginCol ; 7386 the drone's current map column ($92F6, the camera window's centre)
sta droneViewOriginCol ; 7389 move it one cell
tya ; 738C row delta into A
clc ; 738D prepare the addition
adc droneViewOriginRow ; 738E the drone's current map row ($92F7)
sta droneViewOriginRow ; 7391 move it one cell
L_7394:
rts ; 7394 done - the caller now queues the $8D heading,col,row report
; radarGlyphSavedY: byteTable, 1 bytes. Saved Y register of drawRadarGlyph
radarGlyphSavedY:
.byte $00 ; 7395 . scratch byte holding drawRadarGlyph's entry Y (the colour-cell offset), restored on exit
; ----------------------------------------------------------------------
; drawRadarGlyph - Plots one 8x8 map glyph into the bitmap cell pointed at by bitmapPtr (zp_B4/zp_B5)
; and writes its colour through screenPtr (zp_B6/zp_B7) at offset Y. The glyph source is the
; 8-bytes-per-tile set at $9C6E, selected by setTerrainTilePtr from A & $7F. Two effects can be
; applied: if radarHalfToneFlag ($922D) is set (a cloaked unit) the even bitmap rows are blanked and
; only the odd rows copied, so the unit shows up as a half-tone; if bit 7 of A is set (a noise
; 'ghost') every row is ANDed with eight consecutive entries of radarNoiseMasks starting at a random
; offset 0-7. The colour byte in tileColour (zp_32) is written raw on screen 1 - where the caller has
; biased screenPtr into colour RAM at $D800 - and as (tileColour << 4) | cellBackgroundColour on
; screen 2, where screenPtr points into screen RAM at $8C00 and supplies both multicolour nibbles.
; In: A = glyph index (bit 7 = draw with noise), Y = colour-cell offset, X free, tileColour (zp_32),
; bitmapPtr, screenPtr, radarHalfToneFlag ($922D), currentScreen ($90FB), cellBackgroundColour
; ($9226), radarNoiseMasks ($9210)
; Out: 8 bitmap bytes and 1 colour byte written; bitmapPtr advanced 8 bytes to the next cell to the
; right; radarHalfToneFlag cleared; A = glyph & $7F, X and Y restored via self-modified operands;
; radarGlyphSavedY = entry Y
; Called from: drawDroneRadarWindow ($7474), drawRadarMap ($7C0E) and the main program's
; drawScreen1Cell ($5DF5)
; Dead code: the 'lda textDrawParams / beq' test at $73F9 can never take the non-zero path, because
; setTerrainTilePtr at $73A4 always clears textDrawParams first - so the forced colour 8 at $73FE is
; unreachable.
; ----------------------------------------------------------------------
drawRadarGlyph:
sta L_73B6+1 ; 7396 self-modifying: save the raw glyph byte (bit 7 = noise flag) into the 'lda #' operand at $73B7
and #$7F ; 7399 strip the noise flag
sta L_7414+1 ; 739B save the plain glyph index into the 'lda #' operand at $7415 so it can be returned in A
sty radarGlyphSavedY ; 739E save the caller's Y (the colour-cell offset within the row)
stx L_7419+1 ; 73A1 save the caller's X into the 'ldx #' operand at $741A
jsr setTerrainTilePtr ; 73A4 glyphPtr (zp_B2/B3) = $9C6E + glyph*8; also clears textDrawParams
lda radarHalfToneFlag ; 73A7 half-tone flag, set by getRadarCellGlyph for a cloaked unit
bpl L_73B6 ; 73AA not cloaked - leave all eight rows to the copy loop
; ----------------------------------------------------------------------
; Cloaked unit: blank the four even bitmap rows first; the copy loop below will then only write the
; odd rows, so the unit appears on alternate scanlines.
; ----------------------------------------------------------------------
L_73AC:
ldy #$06 ; 73AC start at bitmap row 6
lda #$00 ; 73AE blank byte
L_73B0:
sta (bitmapPtr),y ; 73B0 clear this row of the cell
dey ; 73B2 step two rows...
dey ; 73B3 ...i.e. rows 6, 4, 2, 0
bpl L_73B0 ; 73B4 until row 0 has been cleared
L_73B6:
lda #$FF ; 73B6 operand patched at $7396 with the glyph byte including its bit 7
bmi L_73CB ; 73B8 bit 7 set = draw this blip as noise
; ----------------------------------------------------------------------
; Plain copy of the glyph into the bitmap cell, bottom row upwards. When the half-tone flag is set the
; loop steps two rows at a time (odd rows only).
; ----------------------------------------------------------------------
ldy #$07 ; 73BA bitmap rows 7 down to 0
L_73BC:
lda (glyphPtr),y ; 73BC glyph row from the $9C6E tile set
sta (bitmapPtr),y ; 73BE into the bitmap cell
lda radarHalfToneFlag ; 73C0 cloaked?
bpl L_73C6 ; 73C3 no - step one row
dey ; 73C5 cloaked: skip a row so only odd rows are written
L_73C6:
dey ; 73C6 next row up
bpl L_73BC ; 73C7 until row 0 is done
bmi L_73E4 ; 73C9 always taken - go to the colour write
; ----------------------------------------------------------------------
; Noise variant: the glyph is ANDed row by row with eight consecutive noise masks, starting at a
; random offset 0-7 within the 16-byte table. That is why the table holds 16 masks: the walk of eight
; never runs off the end.
; ----------------------------------------------------------------------
L_73CB:
jsr nextGameRandom ; 73CB random number from the shared game RNG
and #$07 ; 73CE start offset 0-7 into radarNoiseMasks
tax ; 73D0 X = mask index
ldy #$07 ; 73D1 bitmap rows 7 down to 0
L_73D3:
lda (glyphPtr),y ; 73D3 glyph row
and radarNoiseMasks,x ; 73D5 knock out four random bits of it
sta (bitmapPtr),y ; 73D8 store the speckled row
inx ; 73DA next mask for the next row
lda radarHalfToneFlag ; 73DB cloaked as well?
bpl L_73E1 ; 73DE no - step one row
dey ; 73E0 cloaked: skip a row
L_73E1:
dey ; 73E1 next row up
bpl L_73D3 ; 73E2 until row 0 is done
; ----------------------------------------------------------------------
; Common tail: clear the one-shot half-tone flag, step the bitmap pointer to the next cell and write
; the cell's colour.
; ----------------------------------------------------------------------
L_73E4:
ldy #$00 ; 73E4 Y = 0 for the flag clear
sty radarHalfToneFlag ; 73E6 radarHalfToneFlag is per cell - clear it for the next one
clc ; 73E9 prepare the 16-bit add
lda bitmapPtr ; 73EA bitmap pointer low byte
adc #$08 ; 73EC one character cell = 8 bytes to the right
storeGlyphBitmapPtr:
sta bitmapPtr ; 73EE store it back (label sub_73EE is a variant-A entry point only)
lda bitmapPtrHi ; 73F0 bitmap pointer high byte
adc #$00 ; 73F2 propagate the carry
sta bitmapPtrHi ; 73F4 bitmapPtr now addresses the next cell of the row
ldy radarGlyphSavedY ; 73F6 restore the colour-cell offset
lda useDrawColourFlag ; 73F9 dead test: setTerrainTilePtr cleared textDrawParams at $73A4, so this is always 0
beq L_7402 ; 73FC always taken
lda #$08 ; 73FE unreachable: forced colour 8
sta tileColour ; 7400 unreachable
L_7402:
lda tileColour ; 7402 the colour getRadarCellGlyph chose for this cell
ldx currentScreen ; 7404 which screen are we drawing?
cpx #$01 ; 7407 screen 1 = the RADAR / MISSILE screen
beq L_7412 ; 7409 yes - screenPtr already points into colour RAM, store the raw nibble
asl a ; 740B screen 2: move the colour into the high nibble, shift 1...
asl a ; 740C ...2...
asl a ; 740D ...3...
asl a ; 740E ...4 - it is now the %01 pixel colour
ora cellBackgroundColour ; 740F the %10 pixel colour comes from cellBackgroundColour ($0A on the drone screen)
L_7412:
sta (screenPtr),y ; 7412 write the cell's colour byte
L_7414:
lda #$FF ; 7414 operand patched at $739B - return the glyph index in A
ldy radarGlyphSavedY ; 7416 restore the caller's Y
L_7419:
ldx #$FF ; 7419 operand patched at $73A1 - restore the caller's X
rts ; 741B done
; ----------------------------------------------------------------------
; drawDroneRadarWindow - Redraws the whole 14x12 drone camera window (character rows 6-17, columns
; 13-26, one character per map cell) from the map around the view origin zp_A5/zp_A6. It sets the
; three radar colours, re-rolls the noise masks, walks the map row by row and calls getRadarCellGlyph
; followed by drawRadarGlyph for every cell. The bitmap pointer is built from the $9300 address tables
; biased to row 6 / column 13 (D_9306/D_931F and D_933F/D_9367) and the screen RAM pointer from the
; screen row table biased to row 6 (D_9388/D_93A1) plus column 13. It waits for raster line $90 before
; starting so the redraw is not torn.
; In: viewOriginCol/Row (zp_A5/zp_A6), the 40x40 map at $F000, the unit arrays, droneRadarOwnColour
; ($9224) and droneRadarEnemyColour ($9225)
; Out: the camera window redrawn; radarTerrainColour ($9239) = 8, radarOwnUnitColour ($9237) and
; radarEnemyUnitColour ($9238) set; noise masks re-rolled; drawCellSaveCol/Row ($90F6/$90F7) hold
; the saved view origin, which is restored on exit; zp_48/49, zp_B4-zp_B7 clobbered
; Called from: advanceDroneCamera ($8281), i.e. once per lock-step command exchange while the drone
; flies
; ----------------------------------------------------------------------
drawDroneRadarWindow:
lda #$08 ; 741C terrain colour 8 = orange for the %01 pixels of every terrain cell
sta radarTerrainColour ; 741E radarTerrainColour ($9239)
lda droneRadarOwnColour ; 7421 own-side unit colour for the drone camera ($0B dark grey, set by overlay A at $7904)
sta radarOwnUnitColour ; 7424 radarOwnUnitColour ($9237)
lda droneRadarEnemyColour ; 7427 enemy unit colour for the drone camera ($09 brown, set by overlay A at $78FB)
L_742A:
sta radarEnemyUnitColour ; 742A radarEnemyUnitColour ($9238)
jsr buildRadarNoiseMasks ; 742D re-roll the noise masks so ghost blips shimmer between frames
jsr getViewportOriginPointer; 7430 zp_48/49 = map address of the cell at (zp_A5, zp_A6), handling negative origins
lda viewOriginCol ; 7433 the view origin column...
sta drawCellSaveCol ; 7435 ...is saved so the per-cell walk can trample zp_A5
lda viewOriginRow ; 7438 the view origin row...
sta drawCellSaveRow ; 743A ...likewise
clc ; 743D prepare the 16-bit add
lda bitmapRowLoTableFrom6 ; 743E bitmap row table entry for row 6 ($9300 + 6)
adc bitmapColLoTableFrom13 ; 7441 plus the column offset for column 13 ($9332 + 13 = col*8)
storeRadarBitmapPtr:
sta bitmapPtr ; 7444 bitmapPtr low (label sub_7444 is a variant-A entry point only)
lda bitmapRowHiTableFrom6 ; 7446 high byte of the row address
adc bitmapColHiTableFrom13 ; 7449 high byte of the column offset
sta bitmapPtrHi ; 744C bitmapPtr now addresses bitmap cell (6,13)
clc ; 744E prepare the 16-bit add
lda screenRowLoTableFrom6 ; 744F screen RAM row table entry for row 6 ($9382 + 6, base $8C00)
adc #$0D ; 7452 plus column 13
sta screenPtr ; 7454 screenPtr low
lda screenRowHiTableFrom6 ; 7456 high byte of the screen row address
adc #$00 ; 7459 propagate the carry
sta screenPtrHi ; 745B screenPtr addresses screen RAM cell (6,13)
; ----------------------------------------------------------------------
; Wait until the raster is below the visible picture (line $90) so the whole window is redrawn between
; two displayed frames.
; ----------------------------------------------------------------------
L_745D:
lda VIC_RASTER ; 745D read the raster line counter
L_7460:
cmp #$90 ; 7460 line $90
bne L_745D ; 7462 keep polling
ldx #$00 ; 7464 X = camera row 0
; ----------------------------------------------------------------------
; Outer loop over the 12 camera rows; inner loop over the 14 cells of a row. zp_A5/zp_A6 are walked
; cell by cell so getRadarCellGlyph can tell whether the cell is off the 40x40 map.
; ----------------------------------------------------------------------
L_7466:
ldy #$00 ; 7466 Y = camera column 0
tya ; 7468 column index into A
clc ; 7469 prepare the add
adc drawCellSaveCol ; 746A plus the saved origin column
sta viewOriginCol ; 746D zp_A5 = map column of this cell
L_746F:
lda (mapCellPtr),y ; 746F the map byte for this cell
jsr getRadarCellGlyph ; 7471 classify it into a glyph index (A) and a colour (zp_32)
jsr drawRadarGlyph ; 7474 plot it; this also advances bitmapPtr to the next cell
iny ; 7477 next camera column
inc viewOriginCol ; 7478 and the matching map column
tya ; 747A column counter into A
L_747B:
cmp #$0E ; 747B the window is 14 cells wide
bcc L_746F ; 747D more cells on this row
inc viewOriginRow ; 747F row finished - step the map row
L_7481:
lda mapCellPtr ; 7481 map pointer low
clc ; 7483 prepare the add
adc #$28 ; 7484 one map row = 40 bytes
sta mapCellPtr ; 7486 store it
lda mapCellPtrHi ; 7488 map pointer high
adc #$00 ; 748A propagate the carry
sta mapCellPtrHi ; 748C zp_48/49 now addresses the next map row
clc ; 748E prepare the add
lda screenPtr ; 748F screen RAM pointer low
adc #$28 ; 7491 one screen row = 40 bytes
sta screenPtr ; 7493 store it
lda screenPtrHi ; 7495 screen RAM pointer high
adc #$00 ; 7497 propagate the carry
sta screenPtrHi ; 7499 zp_B6/B7 now addresses the next screen row
inx ; 749B next camera row
ldy #$00 ; 749C back to column 0 for the bitmap address
L_749E:
clc ; 749E prepare the add
lda bitmapRowLoTableFrom6,x ; 749F bitmap row address for row 6+X
adc bitmapColLoTableFrom13,y; 74A2 plus the column offset for column 13+0
sta bitmapPtr ; 74A5 bitmapPtr low
lda bitmapRowHiTableFrom6,x ; 74A7 high byte of the row address
adc bitmapColHiTableFrom13,y; 74AA high byte of the column offset
sta bitmapPtrHi ; 74AD bitmapPtr = first cell of the next camera row
cpx #$0C ; 74AF 12 rows in the window
bcc L_7466 ; 74B1 more rows to draw
lda #$00 ; 74B3 already 0 - setTerrainTilePtr clears it on every glyph
sta useDrawColourFlag ; 74B5 redundant clear of textDrawParams
lda drawCellSaveCol ; 74B8 restore the view origin column...
sta viewOriginCol ; 74BB ...into zp_A5
L_74BD:
lda drawCellSaveRow ; 74BD restore the view origin row...
sta viewOriginRow ; 74C0 ...into zp_A6
rts ; 74C2 done
; ----------------------------------------------------------------------
; getRadarCellGlyph - Turns one map cell byte into the glyph index and colour that drawRadarGlyph
; needs. X and Y are preserved through self-modified operands, so it can be called from inside a
; rendering loop. Cells outside the 40x40 map become one of four hatch glyphs ($28-$2B, chosen by
; (row+column) & 3) in the terrain colour. Terrain cells become glyph (cell - $40) in
; radarTerrainColour ($9239). A cell holding a unit ($80 | index) becomes unitGlyphBaseTable[type] +
; facing: in radarOwnUnitColour ($9237) for the local player's units, and for the opponent's units in
; radarEnemyUnitColour ($9238) only while the unit is spotted (unitEnergyTable bit 7). An unspotted
; enemy normally shows the terrain saved under it, except that roughly every 1-8th one (countdown
; radarGhostCountdown $920F, reseeded from the RNG) is drawn as a speckled 'ghost' blip (glyph | $80)
; in the terrain colour. A dead unit (unitTypeTable bit 7) also shows the terrain hidden under it. A
; cloaked unit (unitPathColTable bit 7) sets radarHalfToneFlag so it is drawn on alternate scanlines.
; In: A = the map cell byte, zp_A5/zp_A6 = that cell's map coordinates,
; radarOwnUnitColour/radarEnemyUnitColour/radarTerrainColour, radarGhostCountdown, unit arrays
; $F708/$F76C/$F898/$F9C4/$FAF0, unitGlyphBaseTable ($0799)
; Out: A = glyph index (bit 7 set = draw with noise), tileColour (zp_32) = colour byte,
; radarHalfToneFlag ($922D) = $FF for a cloaked unit, radarGhostCountdown updated; X and Y
; restored
; Called from: drawDroneRadarWindow ($7471), drawRadarMap ($7C0B) and the main program's
; drawScreen1Cell ($5DF2)
; Self-modifying: $750A is NOT a constant BCS. loadOverlayB in the main program copies the
; side-dependent opcode from $206B into it right after loading this overlay ($10A5): $B0 (BCS) when
; the local player is side 0 so units 50-99 are the enemy, $90 (BCC) when the local player is side 1
; so units 0-49 are. Treat $750A as a variable. Quirk: a dead unit's terrain is emitted with
; tileColour still set to radarOwnUnitColour (set at $74F6) rather than the terrain colour; on screen
; 1 both are $0B so it is invisible, on the drone camera it tints dead units' ground dark grey instead
; of orange. If the saved terrain byte happens to be 0 the 'bne' at $7503 falls through and the dead
; unit is drawn as a live GRUNT - terrain 0 does not occur on a real map ($40 is blank).
; ----------------------------------------------------------------------
getRadarCellGlyph:
stx L_754C+1 ; 74C3 self-modifying: save the caller's X into the 'ldx #' operand at $754D
sty L_754E+1 ; 74C6 and the caller's Y into the 'ldy #' operand at $754F
L_74C9:
tay ; 74C9 keep the map cell byte in Y while the coordinates are range-checked
lda viewOriginCol ; 74CA this cell's map column
cmp #$28 ; 74CC the map is 40 columns wide
bcs L_74D6 ; 74CE off the map - draw hatching
lda viewOriginRow ; 74D0 this cell's map row
L_74D2:
cmp #$28 ; 74D2 the map is 40 rows high
bcc L_74E1 ; 74D4 on the map - use the real cell byte
; ----------------------------------------------------------------------
; Off-map cell: pick one of the four hatch patterns so the border round the battlefield gets a woven
; look instead of a flat block.
; ----------------------------------------------------------------------
L_74D6:
lda viewOriginRow ; 74D6 row...
clc ; 74D8 prepare the add
adc viewOriginCol ; 74D9 ...plus column
and #$03 ; 74DB pick one of four patterns
L_74DD:
clc ; 74DD prepare the add
adc #$68 ; 74DE map codes $68-$6B are the four hatch tiles
L_74E0:
tay ; 74E0 pretend the map held that code
; ----------------------------------------------------------------------
; Default: the cell is plain terrain, drawn in the terrain colour.
; ----------------------------------------------------------------------
L_74E1:
ldx radarTerrainColour ; 74E1 terrain colour ($9239): 8 on the drone camera, $0B on screen 1
stx tileColour ; 74E4 zp_32 is the colour drawRadarGlyph will write
tya ; 74E6 the cell byte back into A
bmi L_74F3 ; 74E7 bit 7 set = a unit stands on this cell
L_74E9:
sec ; 74E9 terrain codes start at $40...
sbc #$40 ; 74EA ...so the glyph index in the $9C6E tile set is cell - $40
L_74EC:
bcs L_754C ; 74EC normal terrain - return the glyph
glyphFromLowCellCode:
adc #$20 ; 74EE cell below $40 (should not happen): wrap to cell - $20
jmp L_754C ; 74F0 return the glyph
; ----------------------------------------------------------------------
; A unit stands here. Work out its glyph from type and facing, and its colour from the side, whether
; it is spotted, dead or cloaked.
; ----------------------------------------------------------------------
L_74F3:
and #$7F ; 74F3 strip the 'unit here' bit...
tay ; 74F5 ...leaving the unit index 0-99 in Y
unitCellToGlyph:
lda radarOwnUnitColour ; 74F6 assume it is one of ours
sta tileColour ; 74F9 zp_32 = own unit colour
lda unitTypeTable,y ; 74FB the unit's type byte (bit 7 = destroyed)
testUnitAliveForRadar:
bpl L_7505 ; 74FE alive - carry on (label sub_74FE is a variant-A entry point only)
lda unitTerrainUnderTable,y ; 7500 dead: show the terrain that was hidden under it
bne L_74E9 ; 7503 and render it as terrain (note: the colour is still the own-unit colour)
L_7505:
and #$07 ; 7505 type 0 GRUNT, 1 RIDER, 2 BOOMER, 3 SPY, 4 COMCEN
tax ; 7507 X = unit type, the index into unitGlyphBaseTable
cpy #$32 ; 7508 units 0-49 are side 0, 50-99 are side 1
radarSideBranchOpcode:
bcs L_7518 ; 750A SIDE-DEPENDENT OPCODE: patched by loadOverlayB ($10A5) from $206B - $B0 (BCS) for side 0, $90 (BCC) for side 1; taken when this unit belongs to the opponent
L_750C:
lda unitFlagsTable,y ; 750C the unit's flags byte
and #$03 ; 750F bits 0-1 = facing
clc ; 7511 prepare the add
adc unitGlyphBaseTable,x ; 7512 base glyph per type ($2C GRUNT, $30 RIDER, $34 BOOMER, $38 SPY, $3C COMCEN) plus the facing
jmp L_7542 ; 7515 go and check the cloak flag
; ----------------------------------------------------------------------
; The unit belongs to the opponent. It is only drawn if we can currently see it; otherwise the terrain
; under it is shown, with an occasional random 'ghost' blip to keep the radar noisy.
; ----------------------------------------------------------------------
L_7518:
lda radarEnemyUnitColour ; 7518 enemy colour (1 = white on screen 1, so the sweep can find these cells; $09 brown on the camera)
sta tileColour ; 751B zp_32 = enemy colour
lda unitEnergyTable,y ; 751D the unit's energy byte; bit 7 = currently visible/spotted
bmi L_750C ; 7520 spotted - draw it normally in the enemy colour
hiddenEnemyCell:
lda unitTerrainUnderTable,y ; 7522 not spotted: fall back to the terrain hidden under it
dec radarGhostCountdown ; 7525 count down towards the next ghost blip
bpl L_74E0 ; 7528 not due yet - just show the terrain
jsr nextGameRandom ; 752A due: reseed the countdown from the shared RNG...
and #$07 ; 752D ...with 0-7, so about one unspotted enemy in five is ghosted
sta radarGhostCountdown ; 752F radarGhostCountdown ($920F)
lda unitFlagsTable,y ; 7532 the unit's flags byte
and #$03 ; 7535 bits 0-1 = facing
clc ; 7537 prepare the add
adc unitGlyphBaseTable,x ; 7538 the unit's real glyph
ora #$80 ; 753B bit 7 = tell drawRadarGlyph to speckle it with the noise masks
ldx radarTerrainColour ; 753D and draw the ghost in the terrain colour, not the enemy colour
stx tileColour ; 7540 zp_32 = terrain colour
; ----------------------------------------------------------------------
; Cloak test, reached for every unit glyph: a cloaked unit is drawn on alternate scanlines only.
; ----------------------------------------------------------------------
L_7542:
ldx unitPathColTable,y ; 7542 the unit's waypoint-column byte, whose bit 7 is the CLOAK flag
bpl L_754C ; 7545 not cloaked - done
ldx #$FF ; 7547 $FF = half-tone
stx radarHalfToneFlag ; 7549 radarHalfToneFlag ($922D); drawRadarGlyph clears it again after the cell
L_754C:
ldx #$FF ; 754C operand patched at $74C3 - restore the caller's X
L_754E:
ldy #$FF ; 754E operand patched at $74C6 - restore the caller's Y
rts ; 7550 done; A = glyph index, zp_32 = colour
; Work byte of playStrikeZoomAnimation (see below). In overlay A the same address holds unrelated
; map-generator data, so the label is unit-scoped.
zoomStepCounter:
.byte $01 ; 7551 . zoom step counter: loaded with 3 and counted down to $FF (four magnification passes)
; ----------------------------------------------------------------------
; playStrikeZoomAnimation - Drone strike effect on the DRONE screen: magnifies the middle of the 14x12
; drone camera window four times in a row, each pass blowing the centre 7x6 cells up to 2x2 characters
; per cell so the picture appears to dive into the target, then hands over to playDroneLostAnimation
; which flashes and wipes the window and shrinks the drone icon away.
; In: nothing; the camera window must already show the drone's surroundings. Uses zoomStepCounter
; ($7551) as the pass counter.
; Out: the camera window (rows 6-17, columns 13-26) is left magnified; zoomStepCounter = $FF; never
; returns - it jumps into playDroneLostAnimation ($7163).
; Called from: strikeWithDrone's wait loop ($81CD), right after sound $11 (the detonation).
; Four magnification passes: 12, 10, 8 and 6 game frames are run between them.
; ----------------------------------------------------------------------
playStrikeZoomAnimation:
lda #$03 ; 7552 four zoom passes: the counter runs 3,2,1,0
sta zoomStepCounter ; 7554 pass counter for the loop below
; ----------------------------------------------------------------------
; One zoom pass. magnifyWindow2x eats windowWidth/windowHeight, so both are reloaded every time.
; Source = the 7x6 cell block at row 9, column 16; the routine centres it, so the result covers rows
; 6-17 and columns 13-26 - exactly the drone camera window.
; ----------------------------------------------------------------------
L_7557:
lda #$09 ; 7557 fillRow = character row 9
sta windowOriginRow ; 7559 row of the source block magnifyWindow2x reads
lda #$10 ; 755C fillColumn = character column 16
sta windowOriginCol ; 755E column of the source block
lda #$07 ; 7561 source block is 7 cells wide
sta frameWidth ; 7563 windowWidth for magnifyWindow2x
lda #$06 ; 7566 source block is 6 cells high
sta frameHeight ; 7568 windowHeight for magnifyWindow2x
jsr magnifyWindow2x ; 756B blow those 7x6 cells up 2x2 in place, filling the whole 14x12 camera window
lda zoomStepCounter ; 756E pass number 3..0
clc ; 7571 clear carry before the add
adc #$03 ; 7572 pass+3 = 6,5,4,3
asl a ; 7574 doubled: 12,10,8,6 game frames of pause after this pass
jsr runGameFrames ; 7575 keep the battle running while the picture is held
dec zoomStepCounter ; 7578 one pass done
bpl L_7557 ; 757B branch while zoom passes remain (counter 3..0; it leaves the loop at $FF)
lda #$06 ; 757D hold the final, fully magnified picture
jsr runGameFrames ; 757F for 6 more game frames
jmp playDroneLostAnimation ; 7582 continue with the drone-destroyed animation (white flash, iris wipe, shrinking icon)
; Two state bytes of the missile screen.
missileScreenLocked:
.byte $00 ; 7585 . bit 7 set while the missile screen is inoperative (SCRIMAGE game type, or our comcen is cloaked)
; cursorMovedFlag: byteTable, 1 bytes. 0 until the missile cursor was moved, then $7F
cursorMovedFlag:
.byte $00 ; 7586 . 0 until the target cursor has been moved once, then $7F; firing is refused while it is 0
; ----------------------------------------------------------------------
; runMissileScreen - Entry of game screen 1, the comcen RADAR / MISSILE screen (function key F3).
; Resets the 'cursor moved' interlock, takes a display copy of the local missile count, unpacks the
; eight direction sprite shapes used for the enemy drone marker and builds the whole screen; then
; falls into missileScreenResume, which never returns.
; In: missilesLeft ($92B0, own side), comcenUnit ($9236), the $F000 map and the unit arrays.
; Out: cursorMovedFlag = 0, radarMissilesLeft ($9232) = missilesLeft, $0200 = unpacked sprite shapes,
; screen 1 drawn. Never returns.
; Called from: the screen dispatcher at $0703 (game/scratchAndMessages0400) when currentScreen = 1.
; ----------------------------------------------------------------------
runMissileScreen:
lda #$00 ; 7587 clear the interlock
sta cursorMovedFlag ; 7589 a fire press does nothing until the cursor has been moved
lda missilesLeft ; 758C missiles the local player still has
sta radarMissilesLeft ; 758F display copy shown in the MISSILES box and decremented as they are launched
lda #$70 ; 7592 low byte of the packed sprite shapes at $FD70
ldy #$FD ; 7594 high byte: $FD70 in the resident high-memory block
jsr unpackRleTo0200 ; 7596 unpack the eight direction/aircraft silhouettes to $0200 for loadDirectionShapeSprite
jsr initRadarScreen ; 7599 draw the frames, the MISSILES/FIRE/DRONE ALERT panels and the 21x17 radar map
; ----------------------------------------------------------------------
; missileScreenResume - Main loop of the missile screen, and the re-entry point used after the 'COMCEN
; STUNNED!' blackout cut-scene. Decides first whether the screen works at all (it is dead in a
; SCRIMAGE game, which has no comcens, and while our own comcen is cloaked); if it works the radar
; picture, the sweep and the target cross are (re)built. The loop then runs one game frame at a time,
; blinks the FIRE button and - on the last missile - the count box, redraws after every command
; exchange, advances the radar sweep, and turns a fire press into a $90 (missile strike) command or a
; joystick deflection into a cursor move with auto-repeat.
; In: gameTypeOptions ($0BA3 bits 0-2), comcenCloakedFlag ($9245), exchangeAppliedFlag ($920E),
; comcenRadarStatus ($9243), radarMissilesLeft ($9232), droneArrivalFlags ($92F9),
; joyDirection (zp_5D), joyRepeatTimer (zp_6C), markerBlinkTimer (zp_6F), joyRepeatMask/
; joyHoldCounter ($91C9/$91CA), joystickDirectionTable ($1FA6).
; Out: missileScreenLocked, cursorMovedFlag, radarMissilesLeft, joyRepeatMask = $7F, sprites 1-3,
; $90 commands queued. Never returns.
; Called from: fallen into from runMissileScreen; JMP from $0764 (end of runComcenStunnedBlackout).
; Is the screen usable? Game type 0 (SCRIMAGE) is played without command centres, and a cloaked
; comcen runs its radar silent. Either sets bit 7 of missileScreenLocked.
; ----------------------------------------------------------------------
missileScreenResume:
lda gameTypeOptions ; 759C game option byte
and #$07 ; 759F bits 0-2 = game type (0 SCRIMAGE .. 6 DEFENDER)
bne L_75A5 ; 75A1 branch unless the type is 0 (SCRIMAGE), which is played without comcens
lda #$80 ; 75A3 no comcen: mark the missile screen inoperative
L_75A5:
ora comcenCloakedFlag ; 75A5 bit 7 of $9245 is set while our own comcen is cloaked (radar off the air)
sta missileScreenLocked ; 75A8 bit 7 = screen locked
bmi L_75B9 ; 75AB branch if locked: keep ticking the game but draw nothing
jsr refreshMissileScreen ; 75AD enemy blips, missile count and the DRONE ALERT panel
jsr drawRadarMap ; 75B0 the 21x17 cell radar map centred on our comcen
jsr restartRadarSweep ; 75B3 start a fresh sweep from the top row
jsr showComcenCrossMarker ; 75B6 sprite 2 = the target cross, parked on the comcen at grid cell (10,8)
; ----------------------------------------------------------------------
; Main loop. One pass per game frame. Everything below the 'locked' test is skipped while the screen
; is inoperative.
; ----------------------------------------------------------------------
L_75B9:
jsr updateGameFrame ; 75B9 one game tick: IRQ counters, unit updates, link traffic
lda droneArrivalFlags ; 75BC drone status flags
and #$01 ; 75BF bit 0 = our own $92 'drone destroyed' command is queued but not yet acknowledged
bne L_75C6 ; 75C1 branch if it is still pending - do not leave the screen before it is confirmed
jsr switchToRequestedScreen ; 75C3 F1/F3/F5/F7 may switch to another screen (never returns if it does)
L_75C6:
lda markerBlinkTimer ; 75C6 IRQ frame countdown for the button blink
bne L_75E7 ; 75C8 branch until the 9-frame blink interval has expired
lda #$09 ; 75CA next blink in 9 frames
sta markerBlinkTimer ; 75CC restart the countdown
lda #$0A ; 75CE character row 10
sta windowOriginRow ; 75D0 row of the 2x2 FIRE button icon
lda #$20 ; 75D3 character column 32
sta windowOriginCol ; 75D5 column of the FIRE button icon
lda #$08 ; 75D8 EOR mask 8: flips bit 3 of the %10 colour nibble, so green 5 <-> light green $D
jsr xorColourBlock2x2 ; 75DA toggle the 4 video-matrix cells of the button, making it pulse
lda radarMissilesLeft ; 75DD missiles left on the display
cmp #$02 ; 75E0 compare with 2
bcs L_75E7 ; 75E2 branch while at least 2 missiles remain
jsr blinkMissileCount ; 75E4 down to the last missile: flash the count box as well
L_75E7:
lda missileScreenLocked ; 75E7 screen lock flag
bmi L_75B9 ; 75EA branch if locked - nothing but the frame tick and the blink runs
; ----------------------------------------------------------------------
; A command exchange has been applied ($920E = $80 from $41F8): the opponent's units have moved, so
; the enemy blips and the drone panel are redrawn and a pending drone kill is shown.
; ----------------------------------------------------------------------
L_75EC:
lda exchangeAppliedFlag ; 75EC $80 after processCommandExchange has applied a turn's commands
beq L_75F7 ; 75EF branch unless a new exchange has arrived
jsr refreshMissileScreen ; 75F1 redraw the enemy blips, the count and the DRONE ALERT panel
jsr showDroneKillBurst ; 75F4 if our missile killed the drone, play the burst now
L_75F7:
jsr updateRadarSweep ; 75F7 advance the sweep band by one step if its 2-frame slot is due
; ----------------------------------------------------------------------
; Fire button. A press only counts once the cursor has been moved, and a comcen whose fire control is
; damaged loses roughly half of the presses.
; ----------------------------------------------------------------------
jsr checkFirePressedInteractive; 75FA returns A = 0 (Z set) only for a fresh, accepted press
bne L_760E ; 75FD no press this frame - go and read the joystick instead
lda cursorMovedFlag ; 75FF has the cursor ever been moved?
beq L_760E ; 7602 no - ignore the press (guards against firing at the comcen's own cell)
lda comcenRadarStatus ; 7604 non-zero once the comcen radar/fire control has been knocked out
beq L_763F ; 7607 undamaged: the shot always goes off
jsr nextGameRandom ; 7609 damaged: draw a random bit
bmi L_763F ; 760C about half the presses still get through
; ----------------------------------------------------------------------
; Joystick: move the target cursor over the radar grid, with a 10-frame delay before the first repeat
; and 4 frames between the following ones.
; ----------------------------------------------------------------------
L_760E:
lda joyRepeatTimer ; 760E IRQ auto-repeat countdown
L_7610:
bne L_75B9 ; 7610 branch back while the repeat delay is still running
ldy joyDirection ; 7612 active-low joystick nibble latched by the IRQ ($0F = centred)
lda joystickDirectionTable,y; 7614 -> direction code 0-7 (N,S,E,W,NW,NE,SE,SW), $FF when centred
bmi L_75B9 ; 7617 stick centred - nothing to do this frame
ldy #$0A ; 7619 10 frames before the first auto-repeat
pha ; 761B stash the direction code across the timing code
; ----------------------------------------------------------------------
; Auto-repeat pacing. joyRepeatMask is cleared by the IRQ ($1220) whenever the stick returns to
; centre, so the first deflection sees mask 0 (counter stays 0 -> long delay) and every later frame
; sees mask $7F (counter free-runs -> short delay). (The label itself is only a branch target in the
; overlay A variant of this address.)
; ----------------------------------------------------------------------
radarCursorRepeatStep:
inc joyHoldCounter ; 761C count the frames the stick has been held
lda joyHoldCounter ; 761F reload it
and joyRepeatMask ; 7622 mask = 0 on the very first deflection, $7F while the stick stays deflected
sta joyHoldCounter ; 7625 store the masked counter back
cmp #$01 ; 7628 still zero?
bcc L_762E ; 762A yes - first step, keep the long 10-frame delay
radarCursorRepeatFast:
ldy #$04 ; 762C held down: repeat every 4 frames
L_762E:
sty joyRepeatTimer ; 762E arm the IRQ countdown
lda #$7F ; 7630 mask $7F
sta joyRepeatMask ; 7632 from now on the hold counter free-runs, so the fast repeat sticks
sta cursorMovedFlag ; 7635 $7F also marks 'the cursor has been moved', which unlocks firing
pla ; 7638 recover the direction code
jsr moveRadarCursorByDirection; 7639 step the cursor one grid cell that way
jmp L_75B9 ; 763C back to the top of the loop
; ----------------------------------------------------------------------
; Fire accepted: spend a missile, light the button, queue command $90 (missile strike at col,row) and
; play the flight and impact animation.
; ----------------------------------------------------------------------
L_763F:
lda radarMissilesLeft ; 763F missiles left
checkMissilesRemain:
beq L_760E ; 7642 none left - the press does nothing
dec radarMissilesLeft ; 7644 spend one
jsr drawMissileCount ; 7647 redraw the two-digit count
; ----------------------------------------------------------------------
; Light the FIRE button while the missile is away.
; ----------------------------------------------------------------------
launchMissileEffect:
lda #$0A ; 764A character row 10
sta windowOriginRow ; 764C row of the button icon
lda #$20 ; 764F character column 32
L_7651:
sta windowOriginCol ; 7651 column of the button icon
lda #$05 ; 7654 $05 = %01 pixels black, %10 pixels green: button lit
ldx #$02 ; 7656 2 cells wide
ldy #$02 ; 7658 2 cells high
jsr fillScreenColourRect ; 765A write the 4 video-matrix cells
lda #$0F ; 765D sound $0F = missile launch
jsr playSound ; 765F play it
jsr radarCursorToMapCoords ; 7662 cmdParam1/cmdParam2 = the map cell under the target cursor
lda #$90 ; 7665 command $90 = missile strike at col,row
jsr queueCmd3 ; 7667 queue it for this turn's packet (and for the game film)
jsr animateMissileFlight ; 766A fly sprite 3 from the comcen to the target cell
; ----------------------------------------------------------------------
; Impact: put the ring-burst sprite 1 exactly where the target cross (sprite 2) is.
; ----------------------------------------------------------------------
L_766D:
lda sprite2XShadow ; 766D target cross X
sta sprite1XShadow ; 7670 -> ring burst X
lda sprite2YShadow ; 7673 target cross Y
sta sprite1YShadow ; 7676 -> ring burst Y
lda #$03 ; 7679 3 radius steps
jsr animateRingBurst ; 767B small expanding ring at the impact point
jsr checkStrikeHitsDrone ; 767E did the shot hit an airborne drone?
lda #$0A ; 7681 10 frames
sta fireDebounceTimer ; 7683 swallow further fire presses for that long
jmp L_75B9 ; 7685 back to the top of the loop
; ----------------------------------------------------------------------
; refreshMissileScreen - Redraw hook of screen 1, run once when the screen is built and again after
; every applied command exchange: acknowledges the refresh request, repaints every enemy unit blip
; with a fresh noise pattern and puts the DRONE ALERT panel into its flashing or its dark state
; depending on whether any drone is airborne. With no drone up it also blanks and disables sprite 0
; (the enemy drone marker) and sprite 1 (the ring burst).
; In: droneControlFlags ($920D bit 0, set by cmdDroneMove while a drone is flying), gameEndReason
; ($0BA8), the unit arrays.
; Out: exchangeAppliedFlag = 0, blips redrawn, radarNoiseMasks re-randomised, sprites 0/1 off or
; left alone, DRONE ALERT panel repainted. Tail-jumps into the panel routines.
; Called from: missileScreenResume ($75AD screen build, $75F1 after an exchange).
; (survey name sub_7688)
; ----------------------------------------------------------------------
refreshMissileScreen:
lda #$00 ; 7688 clear the request
sta exchangeAppliedFlag ; 768A the refresh has been served
jsr redrawEnemyUnitsOnRadar ; 768D repaint all 50 enemy units with a new random dropout pattern
lda gameEndReason ; 7690 0 while a game is running; 1 = aborted, $08 = time up, $A0/$C0 = a comcen was knocked out
beq L_7698 ; 7693 branch while a game is running - so the count is only refreshed once the game is already over,
; ----------------------------------------------------------------------
; which looks like an inverted test (the count is redrawn from the fire handler at $7647 anyway, so
; during play this branch simply never fires).
; ----------------------------------------------------------------------
L_7695:
jsr drawMissileCount ; 7695 redraw the two-digit missile count
; ----------------------------------------------------------------------
; DRONE ALERT panel: flashing while any drone is up, dark otherwise.
; ----------------------------------------------------------------------
L_7698:
lda droneControlFlags ; 7698 drone control flags
and #$01 ; 769B bit 0 is set by cmdDroneMove ($52E9) while a drone - ours or theirs - is in the air
bne L_76AF ; 769D a drone is up: flash the panel
lda #$01 ; 769F sprite 1 = the ring burst
jsr clearSpriteShape ; 76A1 blank its shape
lda spriteEnableShadow ; 76A4 sprite enable shadow
and #$FC ; 76A7 clear bits 0 and 1: drone marker (sprite 0) and ring burst (sprite 1) off
L_76A9:
sta spriteEnableShadow ; 76A9 store it back
jmp resetDroneAlertPanel ; 76AC print DRONE / ALERT dark and return through it
L_76AF:
jmp flashDroneAlertPanel ; 76AF toggle the panel colour and return through it
; ----------------------------------------------------------------------
; moveRadarCursorByDirection - Turns a joystick direction code 0-7 into a (dCol,dRow) pair and hands
; it to offsetRadarCursor. Codes 0/1 are N/S, 2/3 are E/W and 4-7 are the diagonals, whose deltas are
; packed one per nibble in diagonalDirectionTable; a diagonal step also stretches the auto-repeat
; delay by 1.5 so diagonal travel is not faster than straight travel.
; In: A = direction code 0-7 (from joystickDirectionTable), joyRepeatTimer (zp_6C).
; Out: X = column delta, Y = row delta, then tail-calls offsetRadarCursor; zp_6C stretched for
; diagonals.
; Called from: missileScreenResume ($7639).
; (survey name sub_76B2)
; ----------------------------------------------------------------------
moveRadarCursorByDirection:
ldy #$00 ; 76B2 row delta defaults to 0
ldx #$00 ; 76B4 column delta defaults to 0
cmp #$04 ; 76B6 codes 0-3 are orthogonal, 4-7 diagonal
L_76B8:
bcs L_76CE ; 76B8 branch for the four diagonals
cmp #$02 ; 76BA codes 0/1 = N/S, 2/3 = E/W
bcs L_76C6 ; 76BC branch for the two horizontal directions
L_76BE:
tay ; 76BE N/S: use the code as the table index
lda directionDeltaTable,y ; 76BF $20F0: index 0 -> $FF (up), 1 -> $01 (down)
tay ; 76C2 that is the row delta
jmp offsetRadarCursor ; 76C3 apply it (column delta stays 0)
L_76C6:
tax ; 76C6 E/W: use the code as the table index
lda directionDeltaTable,x ; 76C7 $20F0: index 2 -> $01 (right), 3 -> $FF (left)
tax ; 76CA that is the column delta
jmp offsetRadarCursor ; 76CB apply it (row delta stays 0)
L_76CE:
tay ; 76CE diagonal: index the packed table with the code
lda diagonalDirectionTable,y; 76CF $1FB2+4..7 = $30,$20,$21,$31 = (column index << 4) | row index
pha ; 76D2 keep the packed byte
and #$0F ; 76D3 low nibble = row index into directionDeltaTable
tay ; 76D5 as index
lda directionDeltaTable,y ; 76D6 -1 or +1
tay ; 76D9 row delta
pla ; 76DA packed byte back
lsr a ; 76DB shift the
lsr a ; 76DC high nibble
lsr a ; 76DD down into
lsr a ; 76DE bits 0-3
tax ; 76DF column index into directionDeltaTable
lda directionDeltaTable,x ; 76E0 -1 or +1
tax ; 76E3 column delta
lda joyRepeatTimer ; 76E4 current auto-repeat delay
lsr a ; 76E6 half of it
clc ; 76E7 clear carry before the add
adc joyRepeatTimer ; 76E8 delay + delay/2
sta joyRepeatTimer ; 76EA a diagonal step costs 1.5 repeat delays, so diagonal travel is not faster
; ----------------------------------------------------------------------
; offsetRadarCursor - Moves the missile target cursor by (X,Y) grid cells, refusing the move if it
; would leave the 21x17 radar grid, and repositions the cross-hair sprite 2 on the new cell.
; In: X = column delta, Y = row delta, gridCursorCol/gridCursorRow (zp_67/zp_68).
; Out: zp_67/zp_68 and sprite2XShadow/sprite2YShadow updated, or nothing at all when the target cell
; is off the grid.
; Called from: moveRadarCursorByDirection ($76C3, $76CB) and scrollRadarOnComcenMove ($797B).
; Self-modifying: the candidate column and row are parked in the LDA # operands at setCursorColImm+1
; ($7702) and setCursorRowImm+1 ($7706) while the range test runs. (survey name sub_76EC)
; ----------------------------------------------------------------------
offsetRadarCursor:
txa ; 76EC column delta
clc ; 76ED clear carry before the add
adc gridCursorCol ; 76EE current cursor column 0-20
tax ; 76F0 candidate column in X for the range test
stx setCursorColImm+1 ; 76F1 park it in the LDA # operand at $7701 (self-modifying code)
tya ; 76F4 row delta
clc ; 76F5 clear carry before the add
adc gridCursorRow ; 76F6 current cursor row 0-16
tay ; 76F8 candidate row in Y for the range test
sty setCursorRowImm+1 ; 76F9 park it in the LDA # operand at $7705 (self-modifying code)
jsr isRadarGridCellInRange ; 76FC C = 0 only when the cell lies inside the 21x17 grid
bcs L_7712 ; 76FF off the grid - leave the cursor where it was
setCursorColImm:
lda #$FF ; 7701 operand patched at $76F1: the accepted column
sta gridCursorCol ; 7703 new cursor column
setCursorRowImm:
lda #$FF ; 7705 operand patched at $76F9: the accepted row
sta gridCursorRow ; 7707 new cursor row
jsr gridCellToSpriteCoords ; 7709 grid cell -> sprite coordinates (column*8+$27, row*8+$43)
stx sprite2XShadow ; 770C move the target cross horizontally
sty sprite2YShadow ; 770F and vertically
L_7712:
rts ; 7712 done
; ----------------------------------------------------------------------
; animateMissileFlight - Flies the launched missile across the radar window: builds a short slanted
; dash in sprite 3 pointing along the line from the comcen cell (grid 10,8) to the target cursor, then
; walks that line pixel by pixel, moving the sprite every 6 frames and taking more pixel steps per
; move as the flight goes on (X = squaresTable[move/4]) so the missile accelerates. The battle keeps
; running underneath, so the 12-byte line stepper state is saved and restored around every game frame.
; The animation stops when the line is exhausted or the game ends.
; In: gridCursorCol/gridCursorRow (zp_67/zp_68) = the target cell, missileFlightTick ($92F8) which
; the IRQ increments once per frame, gameEndReason ($0BA8).
; Out: sprite 3 shape/position/colour, sprite 3 enabled then disabled again, line stepper state
; ($8C-$97) consumed, savedLineState ($92CA) used as scratch.
; Called from: missileScreenResume ($766A), right after the $90 command has been queued.
; Self-modifying: the sprite origin bytes at missileSpriteXOrigin+1/$77E0 and missileSpriteYOrigin+1/
; $77E8, the frame threshold at missileTickThresholdCmp+1/$7808 and the move counter at
; missileStepIndexLoad+1/$782B are all written by this routine.
; (survey name sub_7713) Rebase both endpoints of the flight to the top-left corner of their bounding
; box, so the line stepper works with small positive numbers; scratch20/scratch21 keep that corner in
; grid cells.
; ----------------------------------------------------------------------
animateMissileFlight:
lda #$0A ; 7713 the comcen is always drawn at grid column 10
ldy gridCursorCol ; 7715 target column 0-20
; ----------------------------------------------------------------------
; $7717 is only a fall-through point here (in the overlay A variant the same address is the map
; generator's enterMapName, which is why the survey gave it a label).
; ----------------------------------------------------------------------
missileLineFromComcen:
jsr rebasePairToMin ; 7717 X = min of the two, A and Y = both values minus that min
stx scratch20 ; 771A left column of the bounding box, in grid cells
sta lineCurCol ; 771C line start column, relative to the box
sta scratch1C ; 771E keep a copy: the line is walked twice
sty lineEndCol ; 7720 line end column, relative to the box
sty scratch1E ; 7722 copy
lda #$08 ; 7724 the comcen is always drawn at grid row 8
ldy gridCursorRow ; 7726 target row 0-16
jsr rebasePairToMin ; 7728 same rebasing for the rows
stx scratch21 ; 772B top row of the bounding box
sta lineCurRow ; 772D line start row
sta scratch1D ; 772F copy
sty lineEndRow ; 7731 line end row
sty scratch1F ; 7733 copy
tya ; 7735 end row
ora scratch1E ; 7736 OR in the end column
ora scratch1D ; 7738 OR in the start row
L_773A:
ora scratch1C ; 773A OR in the start column
bne L_7741 ; 773C branch if any of the four is non-zero
jmp L_7846 ; 773E all four zero: the cursor sits on the comcen itself, nothing to fly
L_7741:
cmp #$04 ; 7741 is the longest side at least 4 cells?
bcs L_7755 ; 7743 yes - the coordinates already have enough resolution
; ----------------------------------------------------------------------
; Very short shot: scale the four coordinates by 4 so that the three-step probe below still finds the
; true direction of the line.
; ----------------------------------------------------------------------
asl lineCurCol ; 7745 start column x2
asl lineCurCol ; 7747 x4
asl lineCurRow ; 7749 start row x2
asl lineCurRow ; 774B x4
asl lineEndCol ; 774D end column x2
asl lineEndCol ; 774F x4
L_7751:
asl lineEndRow ; 7751 end row x2
asl lineEndRow ; 7753 x4
; ----------------------------------------------------------------------
; Build the missile shape: walk three units along the line, then draw that little segment twice (one
; pixel apart) into sprite 3, giving a 4-pixel long, 2-pixel thick dash aimed at the target.
; ----------------------------------------------------------------------
L_7755:
lda lineCurCol ; 7755 line start column
sta scratch1A ; 7757 remember it
lda lineCurRow ; 7759 line start row
sta scratch1B ; 775B remember it
L_775D:
jsr initLineStepper ; 775D set up the Bresenham walk from (start) to (end)
jsr stepLine ; 7760 step 1
jsr stepLine ; 7763 step 2
jsr stepLine ; 7766 step 3 - far enough to know the direction
lda scratch1A ; 7769 segment start column
ldy lineCurCol ; 776B segment end column after 3 steps
jsr rebasePairToMin ; 776D rebase the short segment to its own origin
sta scratch1A ; 7770 segment start column, 0-based
sty lineEndCol ; 7772 segment end column
lda scratch1B ; 7774 segment start row
ldy lineCurRow ; 7776 segment end row after 3 steps
jsr rebasePairToMin ; 7778 rebase the rows the same way
sta scratch1B ; 777B segment start row, 0-based
sty lineEndRow ; 777D segment end row
lda #$03 ; 777F sprite 3 is the missile
jsr clearSpriteShape ; 7781 blank its 64-byte shape
jsr plotMissileTrailSegment ; 7784 plot the 4-pixel dash
inc scratch1A ; 7787 shift the start one pixel right
inc lineEndCol ; 7789 and the end with it
jsr plotMissileTrailSegment ; 778B plot it again: the dash is now 2 pixels thick
; ----------------------------------------------------------------------
; Now walk the real flight path. The four saved cell coordinates are turned into pixels (8 pixels per
; grid cell) and stepped one pixel at a time.
; ----------------------------------------------------------------------
ldy #$03 ; 778E index of the last of the four saved coordinates ($1C..$1F)
L_7790:
lda a:scratch1C,y ; 7790 one saved coordinate (absolute addressing forced so it is a 3-byte instruction)
asl a ; 7793 x2
asl a ; 7794 x4
L_7795:
asl a ; 7795 x8 - one grid cell is 8 pixels
sta a:scratch1C,y ; 7796 write it back
dey ; 7799 next coordinate
bpl L_7790 ; 779A loop over start column, start row, end column, end row
lda scratch1C ; 779C start column in pixels
sta lineCurCol ; 779E into the line stepper
lda scratch1D ; 77A0 start row in pixels
sta lineCurRow ; 77A2 into the line stepper
lda scratch1E ; 77A4 end column in pixels
sta lineEndCol ; 77A6 into the line stepper
lda scratch1F ; 77A8 end row in pixels
sta lineEndRow ; 77AA into the line stepper
jsr initLineStepper ; 77AC set the walk up again, now measured in pixels
jsr stepLine ; 77AF skip the first pixel
jsr stepLine ; 77B2 and the second, so the missile starts clear of the comcen
ldx scratch20 ; 77B5 bounding box origin column (grid cells)
ldy scratch21 ; 77B7 bounding box origin row
jsr isRadarGridCellInRange ; 77B9 the returned carry is never tested - a leftover range check
jsr gridCellToSpriteCoords ; 77BC -> sprite coordinates of that corner cell
txa ; 77BF sprite X of the corner
clc ; 77C0 clear carry before the add
adc #$0B ; 77C1 +11 pixels centres the dash inside the cell
sta missileSpriteXOrigin+1 ; 77C3 patch the ADC # operand at $77DF (self-modifying code)
tya ; 77C6 sprite Y of the corner
clc ; 77C7 clear carry before the add
adc #$09 ; 77C8 +9 pixels centres the dash vertically
sta missileSpriteYOrigin+1 ; 77CA patch the ADC # operand at $77E7 (self-modifying code)
lda #$00 ; 77CD restart the frame counter
sta missileFlightTick ; 77CF $92F8 is incremented once per frame by the IRQ ($129C)
lda #$06 ; 77D2 first move after 6 frames
sta missileTickThresholdCmp+1; 77D4 patch the CMP # operand at $7807 (self-modifying code)
lda #$00 ; 77D7 no moves made yet
sta missileStepIndexLoad+1 ; 77D9 patch the LDA # operand at $782A (self-modifying code)
; ----------------------------------------------------------------------
; Per-move: place sprite 3 at the current pixel, then wait 6 frames while the game keeps running.
; ----------------------------------------------------------------------
L_77DC:
lda lineCurCol ; 77DC current pixel column along the line
clc ; 77DE clear carry before the add
missileSpriteXOrigin:
adc #$FF ; 77DF operand patched at $77C3: sprite X of the bounding box corner + 11
sta sprite3XShadow ; 77E1 sprite 3 X position
lda lineCurRow ; 77E4 current pixel row along the line
clc ; 77E6 clear carry before the add
missileSpriteYOrigin:
adc #$FF ; 77E7 operand patched at $77CA: sprite Y of the bounding box corner + 9
sta sprite3YShadow ; 77E9 sprite 3 Y position
lda #$01 ; 77EC colour 1 = white
sta sprite3ColourShadow ; 77EE sprite 3 colour
lda #$08 ; 77F1 sprite 3 enable bit
jsr enableSprites ; 77F3 switch it on
jsr saveLineStepperState ; 77F6 the game frame below reuses the line stepper ($8C-$97), so save it
L_77F9:
jsr updateRadarSweep ; 77F9 keep the radar sweep running during the flight
jsr updateGameFrame ; 77FC one game tick: units keep moving, the link keeps talking
L_77FF:
lda gameEndReason ; 77FF 0 while a game is running
bne L_783E ; 7802 the game ended under us - drop the animation
L_7804:
lda missileFlightTick ; 7804 free-running frame counter
missileTickThresholdCmp:
cmp #$FF ; 7807 operand patched at $77D4/$781F: the frame at which the next move is due
bcc L_77F9 ; 7809 not yet - run another frame
lda missileTickThresholdCmp+1; 780B current threshold
; ----------------------------------------------------------------------
; Both counters are folded back into 0-$7F before the threshold could pass $FF, so the unsigned
; compare above stays meaningful for arbitrarily long flights.
; ----------------------------------------------------------------------
L_780E:
bpl L_781C ; 780E still below $80 - no wrap needed
and #$7F ; 7810 fold the threshold back into 0-$7F
L_7812:
tax ; 7812 keep it
lda missileFlightTick ; 7813 frame counter
and #$7F ; 7816 fold it back the same way
sta missileFlightTick ; 7818 store it
txa ; 781B threshold back into A
L_781C:
clc ; 781C clear carry before the add
adc #$06 ; 781D the next move is due 6 frames later
sta missileTickThresholdCmp+1; 781F patch the CMP # operand at $7807
jsr restoreLineStepperState ; 7822 put the line stepper state back the way the game frame found it
inc missileStepIndexLoad+1 ; 7825 count this move (patches the LDA # operand at $782A)
L_7828:
ldx #$00 ; 7828 at least one pixel step per move
missileStepIndexLoad:
lda #$FF ; 782A operand patched at $77D9/$7825: the move number
bmi L_7834 ; 782C never taken in practice - the flight ends long before 128 moves
lsr a ; 782E move/2
lsr a ; 782F move/4
tay ; 7830 as table index
ldx squaresTable,y ; 7831 n*n extra steps: the missile accelerates as it travels
L_7834:
jsr stepLine ; 7834 advance one pixel along the line
beq L_783E ; 7837 returns 0 when the line is exhausted - the missile has arrived
dex ; 7839 one of the extra steps used
bpl L_7834 ; 783A keep stepping while X >= 0
bmi L_77DC ; 783C always taken: redraw the sprite at its new position
; ----------------------------------------------------------------------
; Common exit: switch the missile sprite off again.
; ----------------------------------------------------------------------
L_783E:
lda spriteEnableShadow ; 783E sprite enable shadow
; ----------------------------------------------------------------------
; $7841 is only a fall-through point in this overlay (the same address is printMapName in the
; overlay A variant).
; ----------------------------------------------------------------------
hideMissileSprite:
and #$F7 ; 7841 clear bit 3 = sprite 3 off
sta spriteEnableShadow ; 7843 store it back
L_7846:
rts ; 7846 done (also the early exit for a zero-length shot)
; ----------------------------------------------------------------------
; plotMissileTrailSegment - Draws one 4-pixel line segment into the shape of sprite 3, from
; (scratch1B,scratch1A) to (lineEndRow,lineEndCol): the start pixel plus three stepped pixels.
; Called twice by animateMissileFlight, one pixel apart, to make the missile dash 2 pixels thick.
; In: scratch1A/scratch1B = segment start column/row, lineEndCol/lineEndRow = segment end.
; Out: 4 bits set in sprite 3's shape at $8AC0; the line stepper state is consumed.
; Called from: animateMissileFlight ($7784, $778B).
; (survey name sub_7847)
; ----------------------------------------------------------------------
plotMissileTrailSegment:
lda scratch1A ; 7847 segment start column
sta lineCurCol ; 7849 into the line stepper
lda scratch1B ; 784B segment start row
sta lineCurRow ; 784D into the line stepper
jsr initLineStepper ; 784F set the Bresenham walk up
jsr plotPathSpritePixel ; 7852 plot pixel 1 (the start)
jsr stepAndPlotTrailPixel ; 7855 step and plot pixel 2
L_7858:
jsr stepAndPlotTrailPixel ; 7858 step and plot pixel 3
; ----------------------------------------------------------------------
; stepAndPlotTrailPixel - Advances the line stepper one cell and sets the resulting pixel in sprite
; 3's shape. plotMissileTrailSegment both calls it and falls through into it, so the segment ends up
; 4 pixels long.
; In: the active line stepper state ($8C-$97).
; Out: one bit set in $8AC0-$8AFF; returns through plotPathSpritePixel.
; Called from: plotMissileTrailSegment ($7855, $7858) and by falling through from it.
; (survey name sub_785B)
; ----------------------------------------------------------------------
stepAndPlotTrailPixel:
jsr stepLine ; 785B advance one pixel
jmp plotPathSpritePixel ; 785E set that pixel and return to the caller
; ----------------------------------------------------------------------
; saveLineStepperState - Copies the 12-byte line stepper working set $8C-$97 (both error accumulators,
; the deltas, the step signs, the current and the end point) into savedLineState ($92CA-$92D5) so that
; a game frame, which uses the same variables for unit movement, can run in the middle of the missile
; flight.
; In: $8C-$97.
; Out: $92CA-$92D5.
; Called from: animateMissileFlight ($77F6).
; (survey name sub_7861)
; ----------------------------------------------------------------------
saveLineStepperState:
ldy #$0B ; 7861 12 bytes: lineErrRow $8C .. lineStepCol $97
L_7863:
lda a:lineErrRow,y ; 7863 read one byte of the live stepper state (absolute addressing so Y indexes past $FF safely)
sta savedLineState,y ; 7866 park it in the $92CA save area
dey ; 7869 next byte
bpl L_7863 ; 786A loop over all 12
rts ; 786C done
; ----------------------------------------------------------------------
; restoreLineStepperState - Copies savedLineState ($92CA-$92D5) back over the 12-byte line stepper
; working set $8C-$97, undoing whatever the game frame did to it.
; In: $92CA-$92D5.
; Out: $8C-$97.
; Called from: animateMissileFlight ($7822), before every stepLine burst.
; (survey name sub_786D)
; ----------------------------------------------------------------------
restoreLineStepperState:
ldy #$0B ; 786D 12 bytes
L_786F:
lda savedLineState,y ; 786F read the saved byte
; ----------------------------------------------------------------------
; $7872 is only a fall-through point here (the same address is randomTileVariant in the overlay A
; variant).
; ----------------------------------------------------------------------
restoreLineStateStore:
sta a:lineErrRow,y ; 7872 write it back into the live stepper state
dey ; 7875 next byte
bpl L_786F ; 7876 loop over all 12
rts ; 7878 done
; ----------------------------------------------------------------------
; rebasePairToMin - Subtracts the smaller of A and Y from both, i.e. shifts a pair of coordinates so
; that the smaller one becomes 0, and returns that offset. Used by animateMissileFlight to move the
; flight path into a small positive coordinate box before the x4 / x8 scaling can overflow a byte.
; In: A = first value, Y = second value.
; Out: X = min(A,Y), A = A - X, Y = Y - X (one of A and Y is always 0); scratch18 clobbered.
; Called from: animateMissileFlight ($7717, $7728, $776D, $7778).
; (survey name sub_7879)
; ----------------------------------------------------------------------
rebasePairToMin:
sty scratch18 ; 7879 keep the second value
cmp scratch18 ; 787B which of the two is smaller?
L_787D:
bcs L_788A ; 787D branch if A >= Y
sta scratch18 ; 787F A is the smaller one - it becomes the offset
tya ; 7881 the larger value
sec ; 7882 set carry for the subtract
sbc scratch18 ; 7883 Y - A
tay ; 7885 back into Y
lda #$00 ; 7886 and A itself becomes 0
beq L_788E ; 7888 always taken
L_788A:
sbc scratch18 ; 788A A - Y (carry is already set by the CMP at $787B)
ldy #$00 ; 788C and Y becomes 0
L_788E:
ldx scratch18 ; 788E return the offset that was subtracted
; ----------------------------------------------------------------------
; $7890 carries a label only because the overlay A variant writes to this byte; here it is just the
; RTS of rebasePairToMin.
; ----------------------------------------------------------------------
rebasePairToMinRts:
rts ; 7890 done
; Three state bytes of the radar sweep animation, all zero on disk. (The survey called the block
; radarSweepState; the three bytes are used independently, so they are named separately here.)
radarSweepStep:
.byte $00 ; 7891 . sweep step 0-30; steps 0-16 light grid row = step, steps 14-30 re-hide row = step-14
radarSweepRow:
.byte $00 ; 7892 . grid row paintRadarSweepRow is currently working on
radarSweepDarkFlag:
.byte $00 ; 7893 . $FF = this whole sweep stays dark (a damaged radar drops roughly every other sweep)
; ----------------------------------------------------------------------
; updateRadarSweep - Per-frame driver of the radar sweep on screen 1. Every two frames it performs
; one sweep step: step s lights grid row s and re-hides grid row s-14, so a 14-row bright band wipes
; down the 21x17 radar window in 31 steps (about 62 frames); after the last step the sweep is
; restarted. The pacing uses the free-running frame counter $92FB against a threshold kept in the CMP
; operand at radarSweepThresholdCmp+1 ($7898).
; In: radarSweepTick ($92FB, bumped by the IRQ at $129F), radarSweepStep ($7891), the threshold byte
; at $7898.
; Out: radarSweepStep incremented or reset, radarSweepRow = the row painted, $7898 += 2, video matrix
; cells of one or two radar rows rewritten; A/X/Y clobbered.
; Called from: missileScreenResume ($75F7) and animateMissileFlight ($77F9).
; Self-modifying: the immediate operand of the CMP at $7897 is the next-step frame number; it is
; advanced here and cleared by restartRadarSweep.
; ----------------------------------------------------------------------
updateRadarSweep:
lda radarSweepTick ; 7894 free-running frame counter, incremented once per frame by the IRQ
radarSweepThresholdCmp:
cmp #$FF ; 7897 operand patched at $78A1/$78D6: the frame number of the next sweep step
bcc L_78CA ; 7899 not yet time for the next step
lda radarSweepThresholdCmp+1; 789B current threshold
clc ; 789E clear carry before the add
adc #$02 ; 789F one sweep step every 2 frames
sta radarSweepThresholdCmp+1; 78A1 patch the CMP # operand at $7897
lda radarSweepStep ; 78A4 current step number
cmp #$1F ; 78A7 31 steps make one full sweep
bcs restartRadarSweep ; 78A9 sweep finished - start the next one (and return through it)
sta radarSweepRow ; 78AB the row to light is the step number itself
cmp #$11 ; 78AE the grid has 17 rows
bcs L_78B9 ; 78B0 steps 17-30 are past the bottom, so there is no leading row left to light
jsr paintRadarSweepRow ; 78B2 light grid row = step
lda radarSweepStep ; 78B5 step number again
; ----------------------------------------------------------------------
; Reached with carry already set from the BCS at $78B0; the SEC only matters on the fall-through.
; ----------------------------------------------------------------------
D_78B8:
sec ; 78B8 set carry for the subtract
L_78B9:
sbc #$0E ; 78B9 trailing edge of the band = step - 14 (a 14-row bright band)
sta radarSweepRow ; 78BB row to re-hide
bcc L_78C7 ; 78BE still negative - the band has not fully entered the window yet
cmp #$11 ; 78C0 17 grid rows
bcs L_78C7 ; 78C2 past the bottom row - nothing to re-hide
jsr paintRadarSweepRow ; 78C4 darken that row again
L_78C7:
inc radarSweepStep ; 78C7 next step
L_78CA:
rts ; 78CA done
; ----------------------------------------------------------------------
; restartRadarSweep - Starts a new sweep at grid row 0 and immediately: clears the step counter, the
; dark flag, the frame counter and the step threshold. If the comcen's radar has been knocked out, a
; coin toss makes the entire coming sweep dark, so a damaged radar shows nothing on roughly half of
; its sweeps.
; In: comcenRadarStatus ($9243, non-zero once the radar system is destroyed), the game RNG $57-$59.
; Out: radarSweepStep = 0, radarSweepDarkFlag = 0 or $FF, radarSweepTick = 0, threshold at $7898 = 0;
; X clobbered.
; Called from: missileScreenResume ($75B3) and updateRadarSweep ($78A9, end of every sweep).
; ----------------------------------------------------------------------
restartRadarSweep:
lda #$00 ; 78CB zero
sta radarSweepStep ; 78CD sweep starts at grid row 0
sta radarSweepDarkFlag ; 78D0 assume a normal, visible sweep
sta radarSweepTick ; 78D3 restart the frame counter
sta radarSweepThresholdCmp+1; 78D6 threshold 0 = the first step happens on the next call
ldx comcenRadarStatus ; 78D9 non-zero once the comcen radar has been knocked out
beq L_78E6 ; 78DC radar intact - always sweep normally
jsr nextGameRandom ; 78DE damaged: draw a random byte
bmi L_78E6 ; 78E1 about half the draws still give a normal sweep
dec radarSweepDarkFlag ; 78E3 0 -> $FF: this whole sweep will show nothing
L_78E6:
rts ; 78E6 done
; ----------------------------------------------------------------------
; paintRadarSweepRow - Rewrites the 21 video-matrix bytes of one radar grid row (screen row A+3,
; columns 3-23). In the multicolour bitmap the video matrix byte gives the colours of the %01 and %10
; pixel pairs, so writing $F0 (light grey / black) makes the cell's map glyph visible and $00 makes it
; black. A cell is lit when its colour RAM nibble is 1 - the colour drawRadarMap gives a spotted
; enemy unit - and the radar is intact, or while the bright sweep band is passing over it.
; In: A = grid row 0-16; radarSweepStep/radarSweepRow/radarSweepDarkFlag ($7891-$7893),
; comcenRadarStatus ($9243), colour RAM ($D800) of that row.
; Out: 21 bytes at $8C00 + (row+3)*40 + 3 set to $F0 or $00; blockPtr (zp_B8) = video matrix pointer,
; screenPtr (zp_B6) = colour RAM pointer; A/X/Y clobbered.
; Called from: updateRadarSweep ($78B2 leading edge, $78C4 trailing edge).
; ----------------------------------------------------------------------
paintRadarSweepRow:
tay ; 78E7 grid row 0-16 as table index
clc ; 78E8 clear carry before the add
lda screenRowLoTableFrom3,y ; 78E9 $9382+3: low byte of the screen address of row+3
adc #$03 ; 78EC +3 characters: the radar window starts at column 3
sta blockPtr ; 78EE low byte of the video matrix pointer
sta screenPtr ; 78F0 and of the colour RAM pointer
lda screenRowHiTableFrom3,y ; 78F2 $939B+3: high byte of the screen address of row+3
adc #$00 ; 78F5 propagate the carry from the +3
sta blockPtrHi ; 78F7 blockPtr now points into the video matrix at $8C00
clc ; 78F9 clear carry before the add
adc #$4C ; 78FA +$4C pages: $8C00 + $4C00 = colour RAM $D800
sta screenPtrHi ; 78FC screenPtr now points at the matching colour RAM cell
ldy #$00 ; 78FE start at the left edge of the row
; ----------------------------------------------------------------------
; One cell per pass; 21 cells make up the width of the radar window.
; ----------------------------------------------------------------------
L_7900:
lda (screenPtr),y ; 7900 colour RAM byte = the colour of this cell's %11 pixels
and #$0F ; 7902 only the low nibble is real
cmp #$01 ; 7904 colour 1 (white) is what drawRadarMap gives a spotted enemy unit
bne L_790D ; 7906 not an enemy blip - it only shows while the band passes
lda comcenRadarStatus ; 7908 is the comcen radar still working?
beq L_791C ; 790B yes - a spotted enemy stays lit permanently
L_790D:
lda #$00 ; 790D default: both nibbles black, the cell is hidden
ldx radarSweepDarkFlag ; 790F is this sweep a dead one?
bmi L_791E ; 7912 yes - leave everything dark
ldx radarSweepRow ; 7914 the row being painted
cpx radarSweepStep ; 7917 is it the leading (bright) edge of the band?
bne L_791E ; 791A no - this call is the trailing edge, so hide the row
L_791C:
lda #$F0 ; 791C $F0 = %01 pixels light grey, %10 pixels black: the cell shows
L_791E:
sta (blockPtr),y ; 791E write the video matrix byte
iny ; 7920 next cell
cpy #$15 ; 7921 21 cells across
bcc L_7900 ; 7923 loop
rts ; 7925 done
; ----------------------------------------------------------------------
; redrawEnemyUnitsOnRadar - Repaints all 50 units of the opposing side on the radar window and gives
; them a fresh random dropout pattern, so the blips move and shimmer once per turn. Walks downwards
; from the enemy comcen index (comcenUnit EOR $52 turns 49 into 99 and 99 into 49) through 50 units,
; handing each one's map cell value and position to the main program's drawScreen1Cell.
; In: comcenUnit ($9236); unit arrays unitColTable ($F640), unitRowTable ($F6A4), unitTypeTable
; ($F76C) and unitTerrainUnderTable ($FAF0), all 100 bytes, $64 apart.
; Out: radarNoiseMasks ($9210-$921F) re-randomised, cells redrawn; scratch18 = unit index,
; scratch19 = countdown, savedRegA/X/Y ($54-$56) = cell value/column/row.
; Called from: refreshMissileScreen ($768D).
; ----------------------------------------------------------------------
redrawEnemyUnitsOnRadar:
jsr buildRadarNoiseMasks ; 7926 new random dropout mask set for the unit glyphs
lda comcenUnit ; 7929 49 for player 0, 99 for player 1
eor #$52 ; 792C $31 EOR $52 = $63 and $63 EOR $52 = $31: the opposing side's comcen index
sta scratch18 ; 792E unit index, walked downwards from there
lda #$32 ; 7930 50 units per side
sta scratch19 ; 7932 loop counter
; ----------------------------------------------------------------------
; One enemy unit per pass, from the enemy comcen down to that side's first unit.
; ----------------------------------------------------------------------
L_7934:
ldy scratch18 ; 7934 current unit index
lda unitColTable,y ; 7936 unitColTable[unit] = map column
sta savedRegX ; 7939 drawScreen1Cell takes the column in $55
L_793B:
lda unitRowTable,y ; 793B unitRowTable[unit] = map row (the next 100-byte array, $64 further on)
sta savedRegY ; 793E drawScreen1Cell takes the row in $56
tya ; 7940 unit index
ora #$80 ; 7941 map cell code $80|index = 'this unit stands on this cell'
ldx unitTypeTable,y ; 7943 unitTypeTable[unit]; bit 7 set = the unit is dead
bpl L_794D ; 7946 alive - draw the unit itself
lda unitTerrainUnderTable,y ; 7948 dead: draw the terrain the corpse was standing on instead
bmi L_7952 ; 794B $FF = the unit is not on the map at all, skip it
L_794D:
sta savedRegA ; 794D drawScreen1Cell takes the cell value in $54
jsr drawScreen1Cell ; 794F plot that one cell of the radar window
L_7952:
dec scratch18 ; 7952 previous unit index
dec scratch19 ; 7954 one unit done
bne L_7934 ; 7956 loop over all 50 units of the enemy side
rts ; 7958 done
; ----------------------------------------------------------------------
; scrollRadarOnComcenMove - Entered by JMP from drawScreen1Cell ($5D8A) whenever the cell being
; redrawn is the local player's comcen. The radar window is always centred on the comcen, so if the
; comcen has moved the whole window has to scroll: the target cursor is shifted by the same amount
; (unless it is parked on the comcen cell itself, where it simply rides along), the map is redrawn and
; the sweep restarts at the top row.
; In: comcenUnit ($9236) and the unit position arrays, viewOriginCol/Row (zp_A5/zp_A6),
; gridCursorCol/Row (zp_67/zp_68).
; Out: nothing at all when the comcen has not moved; otherwise zp_A5/zp_A6 = the new origin (set by
; drawRadarMap), cursor and sprite 2 shifted, window redrawn, radarSweepStep = 0.
; Called from: drawScreen1Cell ($5D8A, JMP).
; Self-modifying: the new origin is written into the SBC # operands at subNewOriginCol+1 ($7970) and
; subNewOriginRow+1 ($7979) to compute the shift.
; ----------------------------------------------------------------------
scrollRadarOnComcenMove:
jsr getComcenCentredViewOrigin; 7959 X = comcenCol - 10, Y = comcenRow - 8: the map cell shown at the top-left of the window
cpx viewOriginCol ; 795C same column as the window currently shows?
bne L_7964 ; 795E no - the comcen has moved sideways
cpy viewOriginRow ; 7960 same row?
L_7962:
beq L_7986 ; 7962 origin unchanged - the comcen just repainted itself, nothing to do
L_7964:
jsr isRadarCursorOnComcen ; 7964 is the target cursor sitting on the comcen cell (10,8)?
beq L_797E ; 7967 yes - leave it there so it rides along with the comcen
stx subNewOriginCol+1 ; 7969 park the new origin column in the SBC # operand at $796F
lda viewOriginCol ; 796C old origin column
sec ; 796E set carry for the subtract
subNewOriginCol:
sbc #$FF ; 796F operand patched at $7969: the new origin column
tax ; 7971 column shift = old - new
sty subNewOriginRow+1 ; 7972 park the new origin row in the SBC # operand at $7978
lda viewOriginRow ; 7975 old origin row
sec ; 7977 set carry for the subtract
subNewOriginRow:
sbc #$FF ; 7978 operand patched at $7972: the new origin row
tay ; 797A row shift = old - new
jsr offsetRadarCursor ; 797B shift the cursor so it keeps pointing at the same map cell (refused if it would leave the grid)
L_797E:
jsr drawRadarMap ; 797E redraw the whole 21x17 window and set zp_A5/zp_A6 to the new origin
lda #$00 ; 7981 zero
sta radarSweepStep ; 7983 restart the sweep at the top row of the new picture
L_7986:
rts ; 7986 done
; ----------------------------------------------------------------------
; isRadarCursorOnComcen - Tests whether the missile target cursor is on the comcen's own cell, which
; is always the centre cell (10,8) of the 21x17 radar grid.
; In: gridCursorCol (zp_67), gridCursorRow (zp_68).
; Out: Z = 1 when the cursor is on the comcen cell; A clobbered.
; Called from: scrollRadarOnComcenMove ($7964).
; ----------------------------------------------------------------------
isRadarCursorOnComcen:
lda gridCursorCol ; 7987 cursor column 0-20
cmp #$0A ; 7989 the comcen is always drawn at grid column 10
bne L_7991 ; 798B different column - return with Z clear
lda gridCursorRow ; 798D cursor row 0-16
cmp #$08 ; 798F the comcen is always drawn at grid row 8; Z set = cursor is on it
L_7991:
rts ; 7991 done
; ----------------------------------------------------------------------
; radarCursorToMapCoords - Converts the radar grid position of the target cursor into absolute map
; coordinates for an outgoing command, by adding the map coordinates of the window's top-left cell.
; In: gridCursorCol/Row (zp_67/zp_68), viewOriginCol/Row (zp_A5/zp_A6).
; Out: cmdParam1 (zp_74) = map column, cmdParam2 (zp_75) = map row; A clobbered.
; Called from: missileScreenResume ($7662, before queueing the $90 strike) and checkStrikeHitsDrone
; ($79AB).
; ----------------------------------------------------------------------
radarCursorToMapCoords:
lda gridCursorCol ; 7992 cursor column inside the grid
clc ; 7994 clear carry before the add
adc viewOriginCol ; 7995 map column of the window's left edge
sta cmdParam1 ; 7997 command argument 1 = map column of the target cell
lda gridCursorRow ; 7999 cursor row inside the grid
clc ; 799B clear carry before the add
adc viewOriginRow ; 799C map row of the window's top edge
sta cmdParam2 ; 799E command argument 2 = map row of the target cell
rts ; 79A0 done
; ----------------------------------------------------------------------
; checkStrikeHitsDrone - Run straight after a missile has been launched: if a drone is airborne and
; has not been dealt with yet, works out whether the targeted map cell is one of the four cells the
; drone occupies (the drone covers a 2x2 block whose top-left corner is its own cell), and if so
; queues the 1-byte command $92 that tells both machines the drone is finished.
; In: droneControlFlags ($920D), droneArrivalFlags ($92F9), droneViewOriginCol/Row ($92F6/$92F7 =
; the drone's map cell), the cursor and view origin used by radarCursorToMapCoords.
; Out: cmdParam1/2 = the target cell; on a hit, command $92 queued and droneArrivalFlags bit 0 set
; (which also blocks screen switching until the command has gone out).
; Called from: missileScreenResume ($767E).
; ----------------------------------------------------------------------
checkStrikeHitsDrone:
lda droneControlFlags ; 79A1 drone flags: 0 = no drone in the air
beq L_79D7 ; 79A4 no drone - nothing to hit
lda droneArrivalFlags ; 79A6 non-zero once the drone has been hit, detonated or has landed
bne L_79D7 ; 79A9 the drone is already out of the game
jsr radarCursorToMapCoords ; 79AB cmdParam1/2 = map cell the missile was aimed at
lda droneViewOriginCol ; 79AE the drone's own map column
sec ; 79B1 set carry for the subtract
sbc cmdParam1 ; 79B2 difference to the targeted column
cmp #$FF ; 79B4 compare with -1
bcs L_79BC ; 79B6 difference is -1: the target is the drone's right-hand cell, still a hit
cmp #$01 ; 79B8 compare with +1
bcs L_79D7 ; 79BA difference is not 0 or -1 - the shot missed
L_79BC:
lda droneViewOriginRow ; 79BC the drone's own map row
L_79BF:
sec ; 79BF set carry for the subtract
sbc cmdParam2 ; 79C0 difference to the targeted row
L_79C2:
cmp #$FF ; 79C2 compare with -1
bcs L_79CA ; 79C4 difference is -1: the drone's lower cell, still a hit
cmp #$01 ; 79C6 compare with +1
bcs L_79D7 ; 79C8 difference is not 0 or -1 - the shot missed
L_79CA:
lda #$92 ; 79CA command $92 = the drone is destroyed / its flight is over
jsr queueCmdBytePending ; 79CC queue the single command byte for this turn's packet
lda droneArrivalFlags ; 79CF drone flags
ora #$01 ; 79D2 bit 0 = our own $92 is queued and not yet acknowledged
sta droneArrivalFlags ; 79D4 store it back
L_79D7:
rts ; 79D7 done
; ----------------------------------------------------------------------
; showDroneKillBurst - Plays the 'drone shot down' effect on the radar screen once our $92 command has
; actually been executed by the engine (cmdDroneArrived sets droneArrivalFlags bit 4 for an own
; command). Bit 4 is moved to bit 5 so the burst is played only once. Suppressed during game film
; playback.
; In: filmPlaybackMode ($0B9B bit 7 = a film is being replayed), droneArrivalFlags ($92F9),
; droneViewOriginCol/Row ($92F6/$92F7) = the drone's map cell.
; Out: droneArrivalFlags bit 4 -> bit 5, droneControlFlags = 0, sounds $17 and $09, sprite 1
; positioned and animated as an 11-step expanding ring.
; Called from: missileScreenResume ($75F4), whenever an exchange has just been applied.
; ----------------------------------------------------------------------
showDroneKillBurst:
lda filmPlaybackMode ; 79D8 bit 7 = a recorded game film is being played back
bmi L_7A11 ; 79DB during playback the effect is skipped
lda droneArrivalFlags ; 79DD drone flags
and #$10 ; 79E0 bit 4 = cmdDroneArrived executed our own $92
beq L_7A11 ; 79E2 not our kill - nothing to show
lda droneArrivalFlags ; 79E4 drone flags again
eor #$10 ; 79E7 clear bit 4
ora #$20 ; 79E9 set bit 5 instead, so the burst plays exactly once
sta droneArrivalFlags ; 79EB store it back
lda #$00 ; 79EE zero
sta droneControlFlags ; 79F0 there is no drone under control any more
lda #$17 ; 79F3 sound $17 = explosion
jsr playSound ; 79F5 play it
lda #$09 ; 79F8 sound $09 = debris
jsr playSound ; 79FA play it
ldx droneViewOriginCol ; 79FD the drone's map column
ldy droneViewOriginRow ; 7A00 and map row
jsr getUnitSpriteCentreOnGrid; 7A03 -> sprite coordinates of that cell's centre (C = 1 if off the window; not tested here)
stx sprite1XShadow ; 7A06 ring burst sprite 1 X
sty sprite1YShadow ; 7A09 ring burst sprite 1 Y
lda #$0B ; 7A0C 11 radius steps = a big burst
jsr animateRingBurst ; 7A0E play the expanding/contracting ring
L_7A11:
rts ; 7A11 done
; Single unreferenced $00 byte between the two routines (alignment filler).
padding7A12:
.byte $00 ; 7A12 . filler
; ----------------------------------------------------------------------
; mapCellToRadarGrid - Converts absolute map coordinates into radar grid coordinates relative to the
; local comcen, which is always drawn at the centre cell (10,8) of the 21x17 window, and falls into
; the range test.
; In: X = map column, Y = map row, comcenUnit ($9236), unitColTable/unitRowTable.
; Out: X = grid column, Y = grid row, C = 0 when the cell is inside the window, C = 1 outside.
; Called from: the main program's getUnitSpriteCentreOnGrid ($340B) and the opponent modules
; (trainer $EE7C, drone AI $EEE1) when they place sprites on screen 1.
; ----------------------------------------------------------------------
mapCellToRadarGrid:
tya ; 7A13 map row
clc ; 7A14 clear carry before the add
adc #$08 ; 7A15 the comcen is drawn at grid row 8
sec ; 7A17 set carry for the subtract
ldy comcenUnit ; 7A18 the local comcen's unit index (49 or 99)
sbc unitRowTable,y ; 7A1B minus the comcen's map row
L_7A1E:
tay ; 7A1E Y = grid row
txa ; 7A1F map column
clc ; 7A20 clear carry before the add
adc #$0A ; 7A21 the comcen is drawn at grid column 10
sec ; 7A23 set carry for the subtract
L_7A24:
ldx comcenUnit ; 7A24 the local comcen's unit index again
sbc unitColTable,x ; 7A27 minus the comcen's map column
tax ; 7A2A X = grid column; fall into the range test
; ----------------------------------------------------------------------
; isRadarGridCellInRange - Range check for a radar grid cell: the window is 21 columns by 17 rows, and
; the comparisons are unsigned so a negative coordinate (which wraps to >= $80) counts as outside.
; In: X = grid column, Y = grid row.
; Out: C = 0 when Y < 17 and X < 21, C = 1 otherwise; registers untouched.
; Called from: mapCellToRadarGrid (fall-through), offsetRadarCursor ($76FC), animateMissileFlight
; ($77B9, result unused) and the main program at $33A0.
; ----------------------------------------------------------------------
isRadarGridCellInRange:
cpy #$11 ; 7A2B 17 grid rows
bcs L_7A31 ; 7A2D off the bottom, or negative (which wraps above $80)
cpx #$15 ; 7A2F 21 grid columns; C = 0 only if the column is inside too
L_7A31:
rts ; 7A31 done
; ----------------------------------------------------------------------
; initRadarScreen - Builds the whole RADAR / MISSILE screen (game screen 1): resets the glyph
; plotter's parameter block, sets border and background to grey, then draws the MISSILES panel and its
; count, the FIRE button, the DRONE ALERT panel, the framed 21x17 radar map and the bottom message
; frame, and finally reprints whatever message the status line was showing.
; In: radarMissilesLeft ($9232), comcen position, the $F000 map.
; Out: whole screen drawn; vicBorderShadow/vicBg0Shadow ($9003/$9004) = $0C, cellBackgroundColour
; ($9226) = 0, screenBuiltFlag (zp_41) = 1, textDrawParams block ($2A29-$2A2C) cleared.
; Called from: runMissileScreen ($7599).
; ----------------------------------------------------------------------
initRadarScreen:
lda #$00 ; 7A32 zero
sta useDrawColourFlag ; 7A34 useFixedColourFlag = 0: glyphs take their colour from fontColorTable
sta drawColourByte ; 7A37 fixed draw colour byte = 0
sta blockFillFlag ; 7A3A block fill flag = 0
sta blockFillChar ; 7A3D block fill character = 0
lda #$0C ; 7A40 colour $0C = medium grey
sta vicBorderShadow ; 7A42 border colour shadow (the IRQ copies it to $D020)
sta vicBg0Shadow ; 7A45 background colour shadow ($D021)
lda #$00 ; 7A48 zero
sta cellBackgroundColour ; 7A4A screen 1 writes glyph colours straight to colour RAM, so no background nibble is ORed in
lda #$01 ; 7A4D 1
sta screenBuiltFlag ; 7A4F every screen builder sets this flag; nothing reads it
lda #$00 ; 7A51 zero
sta blockFillFlag ; 7A53 redundant - D_2A2B was already cleared at $7A3A
L_7A56:
jsr drawMissilePanel ; 7A56 MISSILES panel, bottom right
jsr drawMissileCount ; 7A59 the two-digit count inside it
jsr drawFireButton ; 7A5C FIRE button panel
jsr drawDroneAlertPanel ; 7A5F DRONE ALERT panel, top right
jsr redrawRadarMapWindow ; 7A62 the two frames plus the 21x17 radar map itself
jsr drawMainWindowFrame ; 7A65 the bottom status/message frame shared by all screens
jmp redrawStatusMessage ; 7A68 reprint the current status-line message and return through it
; ----------------------------------------------------------------------
; redrawRadarMapWindow - Draws the double frame around the radar window and then the map inside it.
; In: the comcen position and the $F000 map (used by drawRadarMap).
; Out: window 5 (25x21 at column 1, row 1) and window 6 (23x19 at column 2, row 2) drawn, then the
; 21x17 cell map.
; Called from: initRadarScreen ($7A62) and clearCurrentScreenWindows ($2DFA, JMP) while screen 1 is
; the active screen.
; ----------------------------------------------------------------------
redrawRadarMapWindow:
ldx #$05 ; 7A6B window 5 = the outer frame, 25x21 cells at (column 1, row 1)
jsr drawWindowFrame ; 7A6D draw it
ldx #$06 ; 7A70 window 6 = the inner frame, 23x19 cells at (column 2, row 2)
jsr drawWindowFrame ; 7A72 draw it
jmp drawRadarMap ; 7A75 fill it with the 21x17 cell radar map and return through it
; ----------------------------------------------------------------------
; drawMissilePanel - Draws the MISSILES panel in the bottom right corner: an outer frame with inner
; corner marks, a small inner frame around the two-digit counter, the coloured background of the
; counter box and the caption 'MISSILES'.
; In: nothing.
; Out: window 7 (12x7 at (27,15)) and window 8 (4x4 at (31,16)) drawn, the 2x2 counter box at row 17
; column 32 filled with $30, the caption printed at column 29 row 19; textColour (zp_3D), the
; text cursor and the draw modes are left reset.
; Called from: initRadarScreen ($7A56).
; ----------------------------------------------------------------------
drawMissilePanel:
jsr setFullScreenWindow ; 7A78 text window = the whole screen, so the coordinates below are absolute
ldx #$07 ; 7A7B window 7 = 12x7 cells at column 27, row 15
jsr drawWindowFrame ; 7A7D draw the frame
jsr drawWindowInnerCorners ; 7A80 put the four inner corner marks (character $38) inside it
L_7A83:
ldx #$08 ; 7A83 window 8 = 4x4 cells at column 31, row 16
jsr drawWindowFrame ; 7A85 draw the inner frame around the counter
lda #$11 ; 7A88 character row 17
sta windowOriginRow ; 7A8A row of the counter box
lda #$20 ; 7A8D character column 32
sta windowOriginCol ; 7A8F column of the counter box
lda #$30 ; 7A92 $30 = %01 pixels cyan, %10 pixels black
ldx #$02 ; 7A94 2 cells wide
ldy #$02 ; 7A96 2 cells high
jsr fillScreenColourRect ; 7A98 paint the counter box background
lda #$CF ; 7A9B colour byte for the caption glyphs
sta textColour ; 7A9D text colour
ldy #$01 ; 7A9F bitmap draw mode 1
jsr setBitmapDrawMode ; 7AA1 OR the glyphs into the bitmap so the frame keeps showing through
lda #$1D ; 7AA4 column 29
sta textCursorCol ; 7AA6 text cursor column
lda #$13 ; 7AA8 row 19
sta textCursorRow ; 7AAA text cursor row
jsr updateCursorPointers ; 7AAC recompute the bitmap/screen pointers from the cursor
lda #$9E ; 7AAF low byte of the string address
ldy #$CC ; 7AB1 $CC9E = 'MISSILES' in the $C000 text overlay
jsr printString ; 7AB3 print it
jmp resetDrawModes ; 7AB6 back to replace mode and return through it
; Colour byte the missile counter box is currently painted with.
missileCountColour:
.byte $00 ; 7AB9 . $30 (cyan digits on black) or $03 (black digits on cyan) while it flashes
; ----------------------------------------------------------------------
; drawMissileCount - Shows the number of missiles left as two tall (two-character-high) digits in the
; counter box at row 17, columns 32 and 33, in the fixed colour $30.
; In: radarMissilesLeft ($9232).
; Out: the two digits drawn, missileCountColour ($7AB9) = $30, the glyph plotter's fixed-colour mode
; switched on and off again, scratch18/scratch19 = the row/column drawTallGlyph used.
; Called from: initRadarScreen ($7A59), the fire handler after a launch ($7647) and
; refreshMissileScreen ($7695).
; ----------------------------------------------------------------------
drawMissileCount:
lda radarMissilesLeft ; 7ABA missiles left
jsr splitDecimalDigits ; 7ABD split into $C44E hundreds, $C44F tens, $C450 ones
lda #$11 ; 7AC0 character row 17
sta scratch18 ; 7AC2 drawTallGlyph's row
lda #$20 ; 7AC4 character column 32
sta scratch19 ; 7AC6 drawTallGlyph's column
lda #$FF ; 7AC8 non-zero
sta useDrawColourFlag ; 7ACA useFixedColourFlag: take the colour from $2A2A, not from the font colour table
L_7ACD:
ldx #$30 ; 7ACD $30 = %01 pixels cyan, %10 pixels black
stx drawColourByte ; 7ACF the fixed draw colour
stx missileCountColour ; 7AD2 remember it so blinkMissileCount can toggle it
lda decimalTens ; 7AD5 tens digit
jsr drawTallGlyph ; 7AD8 draw it as a two-character-high glyph pair
inc scratch19 ; 7ADB next column (33)
lda decimalOnes ; 7ADD ones digit
jsr drawTallGlyph ; 7AE0 draw it
lda #$00 ; 7AE3 zero
sta useDrawColourFlag ; 7AE5 back to per-character colours
sta drawColourByte ; 7AE8 and clear the fixed colour byte
rts ; 7AEB done
; ----------------------------------------------------------------------
; blinkMissileCount - Swaps the two colour nibbles of the missile counter box, which exchanges the
; digit and background colours and makes the counter flash.
; In: missileCountColour ($7AB9).
; Out: $7AB9 toggled between $30 and $03, the 2x2 video matrix cells at row 17 column 32 rewritten.
; Called from: missileScreenResume ($75E4), every 9 frames while fewer than two missiles are left.
; ----------------------------------------------------------------------
blinkMissileCount:
lda missileCountColour ; 7AEC current box colour
eor #$33 ; 7AEF EOR $33 swaps the two nibbles' colours: $30 <-> $03
sta missileCountColour ; 7AF1 keep the new one
ldx #$11 ; 7AF4 character row 17
stx windowOriginRow ; 7AF6 row of the counter box
ldx #$20 ; 7AF9 character column 32
stx windowOriginCol ; 7AFB column of the counter box
ldx #$02 ; 7AFE 2 cells wide
ldy #$02 ; 7B00 2 cells high
jmp fillScreenColourRect ; 7B02 repaint them (the draw mode is 'replace' here) and return through it
; ----------------------------------------------------------------------
; drawFireButton - Draws the FIRE button panel on the right: a framed box with inner corner marks, the
; 2x2 character button icon from $9804 in green, and the caption 'FIRE' underneath.
; In: nothing.
; Out: window 9 (8x7 at (29,8)) drawn, the icon at row 10 column 32 painted with colour byte $05,
; the caption at column 31 row 12; the text window and the draw modes are left reset.
; Called from: initRadarScreen ($7A5C). The main loop makes the button pulse by EORing 8 into the
; same four cells every 9 frames ($75DA).
; ----------------------------------------------------------------------
drawFireButton:
ldx #$09 ; 7B05 window 9 = 8x7 cells at column 29, row 8
jsr drawWindowFrame ; 7B07 draw the frame
jsr drawWindowInnerCorners ; 7B0A add the four inner corner marks
lda #$0A ; 7B0D character row 10
sta windowOriginRow ; 7B0F row of the button icon
lda #$20 ; 7B12 character column 32
sta windowOriginCol ; 7B14 column of the button icon
lda #$04 ; 7B17 low byte of the block address
ldy #$98 ; 7B19 $9804 = a 2x2 character block (width, height, then glyphs $45-$48)
jsr drawCharBlock ; 7B1B stamp the icon into the bitmap
lda #$05 ; 7B1E $05 = %01 pixels black, %10 pixels green
ldx #$02 ; 7B20 2 cells wide
ldy #$02 ; 7B22 2 cells high
jsr fillScreenColourRect ; 7B24 paint the icon's colour cells
lda #$CD ; 7B27 colour byte for the caption glyphs
sta textColour ; 7B29 text colour
jsr setFullScreenWindow ; 7B2B text window = the whole screen again
lda #$1F ; 7B2E column 31
sta textCursorCol ; 7B30 text cursor column
lda #$0C ; 7B32 row 12
sta textCursorRow ; 7B34 text cursor row
jsr updateCursorPointers ; 7B36 recompute the bitmap/screen pointers
L_7B39:
lda #$9D ; 7B39 low byte of the string address
ldy #$86 ; 7B3B $869D = 'FIRE'
jsr printString ; 7B3D print it
jmp resetDrawModes ; 7B40 back to replace mode and return through it
; Flash colour of the DRONE ALERT panel.
droneAlertColour:
.byte $00 ; 7B43 . $02 (red text area) or $20 (inverted), toggled by flashDroneAlertPanel
; ----------------------------------------------------------------------
; drawDroneAlertPanel - Draws the three nested frames of the DRONE ALERT panel in the top right corner
; and falls into resetDroneAlertPanel, which prints the text and leaves the panel dark.
; In: nothing.
; Out: windows $0A (11x8 at (28,0)), $0B (9x6 at (29,1)) and $0C (7x4 at (30,2)) drawn, then the
; text and the inactive colour.
; Called from: initRadarScreen ($7A5F).
; ----------------------------------------------------------------------
drawDroneAlertPanel:
jsr setFullScreenWindow ; 7B44 text window = the whole screen
ldx #$0A ; 7B47 window $0A = the outer frame, 11x8 cells at column 28, row 0
jsr drawWindowFrame ; 7B49 draw it
jsr drawWindowInnerCorners ; 7B4C add the four inner corner marks
ldx #$0B ; 7B4F window $0B = the middle frame, 9x6 cells at column 29, row 1
jsr drawWindowFrame ; 7B51 draw it
ldx #$0C ; 7B54 window $0C = the inner frame, 7x4 cells at column 30, row 2
L_7B56:
jsr drawWindowFrame ; 7B56 draw it and fall into the text/colour part
; ----------------------------------------------------------------------
; resetDroneAlertPanel - Prints the two-line 'DRONE' / 'ALERT' label into the panel and paints its
; interior in the dark, inactive colour, also resetting the flash state so the next alert starts on
; the red phase.
; In: nothing.
; Out: the label printed at column 31, row 3; droneAlertColour ($7B43) = $02; textWindowLeft (zp_3B)
; = 31, textColour (zp_3D) = $0B, the 7x4 interior filled with $0B via fillDroneAlertPanel.
; Called from: drawDroneAlertPanel (fall-through) and refreshMissileScreen ($76AC, JMP) whenever no
; drone is airborne.
; ----------------------------------------------------------------------
resetDroneAlertPanel:
ldy #$00 ; 7B59 colour draw mode 0
jsr setColourDrawMode ; 7B5B replace the colour cells instead of ORing/EORing into them
lda #$0B ; 7B5E colour byte $0B for the label glyphs
sta textColour ; 7B60 text colour
lda #$02 ; 7B62 $02 = the first flash phase
L_7B64:
sta droneAlertColour ; 7B64 reset the flash state so the next alert starts red
lda #$1F ; 7B67 column 31
sta textWindowLeft ; 7B69 make it the left edge of the text window, so the label wraps inside the panel
lda #$00 ; 7B6B column 0 relative to that
sta textCursorCol ; 7B6D text cursor column
lda #$03 ; 7B6F row 3
sta textCursorRow ; 7B71 text cursor row
jsr updateCursorPointers ; 7B73 recompute the bitmap/screen pointers
lda #$A1 ; 7B76 low byte of the string address
ldy #$86 ; 7B78 $86A1 = 'DRONE' + CR + 'ALERT'
jsr printString ; 7B7A print both lines
lda #$0B ; 7B7D 11 frames
sta keyRepeatTimer ; 7B7F delays the keyboard auto-repeat countdown; nothing here depends on it (leftover)
lda #$0B ; 7B81 $0B = %01 pixels black, %10 pixels dark grey: the panel is dark/inactive
jmp fillDroneAlertPanel ; 7B83 fill the 7x4 interior with it and return through it
; ----------------------------------------------------------------------
; flashDroneAlertPanel - Toggles the DRONE ALERT panel between its two colour states and repaints the
; 7x4 colour cells behind the two-line 'DRONE / ALERT' text, so the panel blinks red while an opponent
; drone is airborne. droneAlertColour ($7B43) alternates between $02 (red as the %10 bit pair) and
; $20 (red as the %01 bit pair), which swaps the text and its background.
; In: droneAlertColour $7B43
; Out: $7B43 toggled, 28 screen RAM colour cells at row 2 column 30 rewritten, draw modes reset,
; D_2A2A = the new colour
; Called from: JMP at $76AF in the screen-1 refresh routine sub_7688, taken when droneControlFlags
; ($920D) bit 0 says an enemy drone is in the air (the other path JMPs to resetDroneAlertPanel)
; ----------------------------------------------------------------------
flashDroneAlertPanel:
jsr resetDrawModes ; 7B86 put the bitmap and colour draw modes back to plain replace
lda droneAlertColour ; 7B89 current state of the alert panel ($02 or $20)
eor #$22 ; 7B8C EOR $22 swaps the two red nibbles: $02 <-> $20
sta droneAlertColour ; 7B8E remember the new state for the next refresh
; ----------------------------------------------------------------------
; fillDroneAlertPanel - Shared tail of resetDroneAlertPanel and flashDroneAlertPanel: fills the 7 x 4
; character interior of DRONE ALERT window $0C (top-left corner row 2, column 30) with the colour byte
; in A.
; In: A = screen RAM colour byte
; Out: 28 colour cells written, D_2A2D = 2, D_2A2E = $1E, D_2A2A = A
; Called from: JMP at $7B83 (resetDroneAlertPanel) and by falling through from flashDroneAlertPanel
; ----------------------------------------------------------------------
fillDroneAlertPanel:
ldx #$02 ; 7B91 colour rectangle starts at character row 2
stx windowOriginRow ; 7B93 D_2A2D = fill origin row
ldx #$1E ; 7B96 character column 30
stx windowOriginCol ; 7B98 D_2A2E = fill origin column
ldx #$07 ; 7B9B 7 character columns wide
ldy #$04 ; 7B9D 4 character rows high
jmp fillScreenColourRect ; 7B9F paint them and return straight to the panel routine's caller
; ----------------------------------------------------------------------
; drawRadarMap - Redraws the whole 21 x 17 cell strategic map of screen 1 (the RADAR/MISSILE screen)
; at character rows 3-19, columns 3-23, one 8x8 character per map cell, centred so that the player's
; own comcen sits at grid cell (10,8). Terrain and own units are dark grey, a spotted enemy is white;
; the actual per-cell decision is made by getRadarCellGlyph and the plotting by drawRadarGlyph. The
; colour bytes go to colour RAM $D800 (the %11 bit pair); paintRadarSweepRow later writes $F0/$00 into
; screen RAM $8C00 to reveal or hide each row as the sweep passes.
; In: comcenUnit $9236, the 40x40 map at $F000, the unit arrays at $F640+
; Out: 357 bitmap cells and colour cells written; zp_A5/zp_A6 restored; $90F6/$90F7 = the saved view
; origin; $9237/$9239 = $0B, $9238 = 1; zp_48/49, zp_B4-zp_B7 clobbered; textDrawParams = 0
; Called from: $75B0 (screen-1 build), $797E (comcen moved, via scrollRadarOnComcenMove) and by the
; JMP at $7A75 in redrawRadarMapWindow
; ----------------------------------------------------------------------
drawRadarMap:
jsr buildRadarNoiseMasks ; 7BA2 new random noise masks so unspotted enemy 'ghost' blips shimmer on every redraw
lda #$0B ; 7BA5 $0B = dark grey
sta radarOwnUnitColour ; 7BA7 $9237: colour used for our own units
sta radarTerrainColour ; 7BAA $9239: and for plain terrain
lda #$01 ; 7BAD 1 = white
sta radarEnemyUnitColour ; 7BAF $9238: a spotted enemy blip - the sweep only lights up colour-1 cells
; ----------------------------------------------------------------------
; Blank the window, then work out where the map has to be scrolled to keep the comcen centred.
; ----------------------------------------------------------------------
drawRadarMapBody:
lda #$03 ; 7BB2 colour fill origin: character row 3 (top edge of the radar window)
sta windowOriginRow ; 7BB4 D_2A2D = fill origin row
lda #$03 ; 7BB7 character column 3 (left edge)
sta windowOriginCol ; 7BB9 D_2A2E = fill origin column
L_7BBC:
ldx #$15 ; 7BBC 21 cells across
ldy #$11 ; 7BBE 17 cells down
lda #$00 ; 7BC0 colour 0 = both multicolour nibbles black
jsr fillScreenColourRect ; 7BC2 clear the screen RAM colour cells of the whole window first
jsr getComcenCentredViewOrigin; 7BC5 X/Y = map origin that puts the comcen at grid cell (10,8)
stx viewOriginCol ; 7BC8 zp_A5 = map column of the left edge
sty viewOriginRow ; 7BCA zp_A6 = map row of the top edge
jsr getViewportOriginPointer; 7BCC zp_48/zp_49 = address in the $F000 map of that top-left cell (handles negative origins)
; ----------------------------------------------------------------------
; Save the origin - the drawing loop walks zp_A5/zp_A6 cell by cell so that getRadarCellGlyph can test
; for off-map coordinates - and build the two output pointers for character cell (row 3, column 3).
; ----------------------------------------------------------------------
L_7BCF:
lda viewOriginCol ; 7BCF keep the origin column ...
sta drawCellSaveCol ; 7BD1 ... in $90F6
lda viewOriginRow ; 7BD4 keep the origin row ...
sta drawCellSaveRow ; 7BD6 ... in $90F7
clc ; 7BD9 no carry into the address addition
lda bitmapRowLoTableFrom3 ; 7BDA bitmap row table (real base $9300) entry for character row 3, low byte
adc bitmapColLoTableFrom3 ; 7BDD plus 8*column table (real base $9332) entry for character column 3
sta bitmapPtr ; 7BE0 zp_B4 = low byte of the first bitmap cell
lda bitmapRowHiTableFrom3 ; 7BE2 same tables, high bytes ($9319+3 and $935A+3)
adc bitmapColHiTableFrom3 ; 7BE5 add with the carry from the low half
sta bitmapPtrHi ; 7BE8 zp_B5 = high byte; zp_B4/B5 now point into the $A000 bitmap
clc ; 7BEA clear carry for the screen RAM address
lda screenRowLoTableFrom3 ; 7BEB screen RAM row table (real base $9382) entry for character row 3, low byte
adc #$03 ; 7BEE +3 characters for column 3
sta screenPtr ; 7BF0 zp_B6 = low byte
lda screenRowHiTableFrom3 ; 7BF2 row 3 high byte from the $939B table (points into screen RAM $8C00)
adc #$00 ; 7BF5 propagate the carry of the column addition
sta screenPtrHi ; 7BF7 dead store: zp_B7 is overwritten three instructions later with the colour RAM value
radarMapColourRamPtr:
clc ; 7BF9 clear carry for the +$4C00
adc #$4C ; 7BFA $8C00 + $4C00 = $D800: turn the screen RAM address into the colour RAM address
sta screenPtrHi ; 7BFC zp_B6/B7 now address colour RAM; on screen 1 drawRadarGlyph stores the raw colour there
; ----------------------------------------------------------------------
; Outer loop over the 17 radar rows (X), inner loop over the 21 columns (Y). Y is also the offset
; added to zp_B6/B7, while zp_B4/B5 is advanced 8 bytes per cell by drawRadarGlyph itself.
; ----------------------------------------------------------------------
ldx #$00 ; 7BFE X = radar row 0..16 (character row 3+X)
L_7C00:
ldy #$00 ; 7C00 Y = radar column 0..20 (character column 3+Y)
tya ; 7C02 A = 0
L_7C03:
clc ; 7C03 clear carry for the column reset
adc drawCellSaveCol ; 7C04 restart the running map column at the left edge of the window
sta viewOriginCol ; 7C07 zp_A5 = map column of the cell about to be drawn
L_7C09:
lda (mapCellPtr),y ; 7C09 map byte of this cell: $80|unitIndex, or a terrain tile
jsr getRadarCellGlyph ; 7C0B classify it: A = glyph index (bit 7 = draw through the noise masks), zp_32 = colour
jsr drawRadarGlyph ; 7C0E plot the 8x8 glyph, store zp_32 at (zp_B6),Y and step zp_B4/B5 on by one character
iny ; 7C11 next character column of the window
L_7C12:
inc viewOriginCol ; 7C12 and the matching map column, for the off-map test in getRadarCellGlyph
cpy #$15 ; 7C14 21 columns done?
bcc L_7C09 ; 7C16 no - draw the next cell of this row
; ----------------------------------------------------------------------
; End of a row: advance the map pointer, the colour RAM pointer and the bitmap pointer by one row.
; ----------------------------------------------------------------------
inc viewOriginRow ; 7C18 the next window row shows the next map row
lda mapCellPtr ; 7C1A map pointer low byte
radarMapNextMapRow:
clc ; 7C1C clear carry
adc #$28 ; 7C1D one map row = 40 bytes
sta mapCellPtr ; 7C1F zp_48 updated
lda mapCellPtrHi ; 7C21 map pointer high byte
adc #$00 ; 7C23 add the carry
sta mapCellPtrHi ; 7C25 zp_49 updated
clc ; 7C27 clear carry
lda screenPtr ; 7C28 colour RAM pointer low byte
adc #$28 ; 7C2A one character row = 40 bytes
sta screenPtr ; 7C2C zp_B6 updated
radarMapScreenPtrCarry:
lda screenPtrHi ; 7C2E colour RAM pointer high byte
adc #$00 ; 7C30 add the carry
sta screenPtrHi ; 7C32 zp_B7 updated
inx ; 7C34 next radar row
ldy #$00 ; 7C35 column index 0 = character column 3 again
clc ; 7C37 clear carry
lda bitmapRowLoTableFrom3,x ; 7C38 bitmap row table entry for character row 3+X, low byte
adc bitmapColLoTableFrom3,y ; 7C3B plus the column-3 offset
L_7C3E:
sta bitmapPtr ; 7C3E zp_B4 = start of the new row
lda bitmapRowHiTableFrom3,x ; 7C40 row 3+X high byte
adc bitmapColHiTableFrom3,y ; 7C43 plus the column-3 high byte
sta bitmapPtrHi ; 7C46 zp_B5 = start of the new row
cpx #$11 ; 7C48 17 rows done?
bcc L_7C00 ; 7C4A no - draw the next row
; ----------------------------------------------------------------------
; Finished: drop any fixed-colour override and put the view origin back the way the caller left it.
; ----------------------------------------------------------------------
lda #$00 ; 7C4C 0 = use each glyph's own colour again
sta useDrawColourFlag ; 7C4E clear the fixed-colour flag $2A29
lda drawCellSaveCol ; 7C51 reload the saved origin column
L_7C54:
sta viewOriginCol ; 7C54 zp_A5 restored
lda drawCellSaveRow ; 7C56 reload the saved origin row
sta viewOriginRow ; 7C59 zp_A6 restored
rts ; 7C5B back to the caller
; ----------------------------------------------------------------------
; getComcenCentredViewOrigin - Returns the map origin that puts the player's own comcen in the middle
; of the 21 x 17 cell strategic window of screen 1: column = unitCol[comcenUnit] - 10, row =
; unitRow[comcenUnit] - 8. Either value may be negative when the comcen is near an edge of the 40x40
; map; getViewportOriginPointer copes with that.
; In: comcenUnit $9236 (49 for side 0, 99 for side 1), unitColTable $F640, unitRowTable $F6A4
; Out: X = origin column, Y = origin row; A clobbered
; Called from: $7959 (scrollRadarOnComcenMove) and $7BC5 (drawRadarMap)
; ----------------------------------------------------------------------
getComcenCentredViewOrigin:
ldy comcenUnit ; 7C5C $9236 = index of this side's comcen (49 or 99)
lda unitColTable,y ; 7C5F its map column ($F640 + unit)
sec ; 7C62 no borrow
sbc #$0A ; 7C63 the comcen is drawn at grid column 10 of the 21-wide window
tax ; 7C65 return the origin column in X
lda unitRowTable,y ; 7C66 its map row ($F6A4 + unit; the arrays are $64 apart)
sec ; 7C69 no borrow
sbc #$08 ; 7C6A and at grid row 8 of the 17-high window
tay ; 7C6C return the origin row in Y
rts ; 7C6D done
; comcenSchematicRle - 864 bytes of RLE-compressed hires picture (20 x 10 characters = 160 x 80
; pixels, 1600 bytes when unpacked): the side view of the tracked comcen vehicle shown on the REPAIR
; tab. Stream format as used by unpackRleTo0200: byte 0 is the escape marker ($13), afterwards any
; byte other than the marker is a literal and 'marker, count, value' emits value count times (count 0
; means 256). The stream ends at $7FCB with $13,$13,$00 = 256 zero bytes. drawComcenSchematic
; ($8632) is the only reader; it unpacks straight into the bitmap at character row 10, column 13 with
; a 320-byte row stride. Every sub_/L_/D_ label inside this block is an artefact of XREF merging the
; two $6F00 overlay variants: those addresses are code only in the map generator overlay.
comcenSchematicRle:
.byte $13,$13,$1C,$00,$13,$04,$04,$13; 7C6E ........
.byte $06,$00,$01,$41,$13,$07,$00,$10; 7C76 ...A....
.byte $13,$14,$00,$01,$03,$06,$44,$13; 7C7E ......D.
D_7C86:
.byte $04,$00,$80,$13,$6B,$00,$04,$04; 7C86 ....k...
.byte $04,$07,$13,$04,$04,$41,$41,$E1; 7C8E .....AA.
.byte $FF,$E1,$41,$41,$41,$14,$14,$14; 7C96 ..AAA...
.byte $FF,$14 ; 7C9E ..
L_7CA0:
.byte $14,$14 ; 7CA0 ..
sub_7CA2:
.byte $10,$00,$00,$40,$C0,$40,$13,$04; 7CA2 ...@.@..
.byte $00,$01,$13 ; 7CAA ...
L_7CAD:
.byte $06 ; 7CAD .
sub_7CAE:
.byte $00,$AC,$FF,$AC,$E4,$A6,$A3,$A1; 7CAE ........
.byte $A0,$00,$80,$13 ; 7CB6 ....
L_7CBA:
.byte $04,$00,$80,$13,$58 ; 7CBA ....X
sub_7CBF:
.byte $00 ; 7CBF .
D_7CC0:
.byte $03,$13,$07,$00,$FF,$13,$07,$00; 7CC0 ........
.byte $FF,$04,$04,$04,$00,$00,$00; 7CC8 .......
L_7CCF:
.byte $01,$FF ; 7CCF ..
L_7CD1:
.byte $41,$13,$04,$40,$E0,$F0,$FF,$13; 7CD1 A..@....
.byte $07,$00,$FF,$00,$00,$00,$1F,$20; 7CD9 .......
.byte $40,$80,$00,$01,$02,$07,$FF,$13; 7CE1 @.......
.byte $04,$00,$F0,$08,$FC,$FF,$13,$07; 7CE9 ........
.byte $00,$FF,$00 ; 7CF1 ...
L_7CF4:
.byte $0F,$04,$04,$00,$00,$00,$80,$80; 7CF4 ........
.byte $13,$07,$00,$18,$14,$12,$11,$13; 7CFC ........
L_7D04:
.byte $40,$00,$0C,$10,$20,$40,$40,$40; 7D04 @... @@@
.byte $4F,$48,$13,$06,$00,$FF,$13,$07; 7D0C OH......
.byte $00,$F0,$10,$13,$30,$00,$02,$02; 7D14 ....0...
.byte $01,$01,$13,$08,$00,$80,$80,$40; 7D1C .......@
.byte $40,$10,$10,$20,$2F,$20,$1F,$00; 7D24 @.. / ..
.byte $00,$80,$7F,$00,$00 ; 7D2C .....
L_7D31:
.byte $38,$D7,$44,$44 ; 7D31 8.DD
L_7D35:
.byte $00,$FF,$00,$7F,$00,$FF,$00,$00; 7D35 ........
.byte $00,$FF,$00,$00,$38,$D7,$44,$44; 7D3D ....8.DD
.byte $00,$F0,$0F,$00,$0F,$F0,$13,$05; 7D45 ........
.byte $00 ; 7D4D .
L_7D4E:
.byte $E0,$13,$1C,$00,$4A,$48,$48,$49; 7D4E ....JHHI
.byte $49,$48,$49,$49,$00,$00,$FF,$00; 7D56 IHII....
.byte $00,$81,$00,$00,$50,$10,$10,$90; 7D5E ....P...
.byte $90,$10,$90,$90,$13,$06,$00,$1F; 7D66 ........
.byte $60,$13 ; 7D6E `.
sub_7D70:
.byte $06,$00 ; 7D70 ..
L_7D72:
.byte $FF,$13,$07,$00,$FF,$13,$07,$00; 7D72 ........
.byte $FF,$13,$07 ; 7D7A ...
sub_7D7D:
.byte $00,$FF,$13,$07 ; 7D7D ....
L_7D81:
.byte $00,$FF,$13,$07,$00,$FF,$00,$20; 7D81 .......
.byte $20,$1F ; 7D89 .
sub_7D8B:
.byte $20,$40,$80,$FF,$00,$00,$01,$FF; 7D8B @......
.byte $00,$00,$00,$FF,$00 ; 7D93 .....
sub_7D98:
.byte $88,$10 ; 7D98 ..
L_7D9A:
.byte $FF,$00,$00,$00,$FF,$00,$00,$01; 7D9A ........
.byte $FF,$00,$00,$00,$FF ; 7DA2 .....
sub_7DA7:
.byte $00,$88,$10,$FF,$00,$00,$00,$FF; 7DA7 ........
.byte $00,$00,$00,$80,$40,$40,$40,$F0; 7DAF ....@@@.
L_7DB7:
.byte $0C ; 7DB7 .
L_7DB8:
.byte $13,$07,$00,$01,$13,$05,$00,$60; 7DB8 .......`
.byte $C0,$8E,$13,$10,$00,$48,$49,$49; 7DC0 .....HII
L_7DC8:
.byte $48,$49,$49,$48,$49,$81,$00,$00; 7DC8 HIIHI...
.byte $81,$00,$00,$81,$00,$90,$91,$91; 7DD0 ........
.byte $12,$92,$92,$12,$92,$80,$13,$5F; 7DD8 ......._
.byte $00,$03,$13 ; 7DE0 ...
L_7DE3:
.byte $07,$00,$03,$C6,$3C,$0C,$03,$00; 7DE3 ....<...
.byte $00,$00,$16,$23,$45,$88,$11,$E2; 7DEB ...#E...
.byte $34 ; 7DF3 4
L_7DF4:
.byte $0C,$00,$80,$80,$E0,$60,$38,$58; 7DF4 .....`8X
.byte $88,$13,$08,$00,$49,$48,$49,$49; 7DFC ....IHII
.byte $48,$48,$4A,$48,$00,$81,$00,$00; 7E04 HHJH....
.byte $FF,$00,$00 ; 7E0C ...
D_7E0F:
.byte $00,$92,$12,$92,$92,$12,$12,$52; 7E0F .......R
.byte $12,$13,$70,$00,$03,$13,$07,$00; 7E17 ..p.....
.byte $10,$E0,$30,$0C,$03 ; 7E1F ..0..
L_7E24:
.byte $13,$08,$00,$C0,$30,$0C ; 7E24 ....0.
L_7E2A:
.byte $4F ; 7E2A O
L_7E2B:
.byte $7F,$13,$05,$80,$61,$FF,$FF,$00; 7E2B ....a...
.byte $00,$00,$3F,$C0,$00,$F2 ; 7E33 ..?...
L_7E39:
.byte $FF,$00,$00,$00,$FF,$00,$00,$00; 7E39 ........
.byte $FF ; 7E41 .
L_7E42:
.byte $00,$00,$00,$FF,$00,$00,$00,$FF; 7E42 ........
.byte $00,$00,$00,$FF,$00 ; 7E4A .....
sub_7E4F:
.byte $00,$00,$FF,$00,$00,$00,$FF,$00; 7E4F ........
.byte $00,$00,$FF,$00,$00,$00,$FF,$00; 7E57 ........
.byte $00,$00,$FF,$00 ; 7E5F ....
L_7E63:
.byte $00,$00,$FF,$00,$00,$00,$FF,$00; 7E63 ........
.byte $00,$00 ; 7E6B ..
L_7E6D:
.byte $F8,$07,$00 ; 7E6D ...
L_7E70:
.byte $00 ; 7E70 .
L_7E71:
.byte $FF,$13,$05 ; 7E71 ...
sub_7E74:
.byte $00,$C0,$00,$FF,$13,$07,$00,$FF; 7E74 ........
.byte $13,$04,$00,$3F,$C0,$00,$FF,$13; 7E7C ...?....
.byte $04,$00,$FF,$00,$00,$FF,$13; 7E84 .......
L_7E8B:
.byte $04,$00,$FF,$00,$00,$FF,$13,$04; 7E8B ........
.byte $00,$FF,$00,$00,$FF,$13,$04,$00; 7E93 ........
.byte $FF,$00 ; 7E9B ..
L_7E9D:
.byte $00,$FF,$13 ; 7E9D ...
sub_7EA0:
.byte $04,$00,$FF,$00,$00,$FF,$13,$04; 7EA0 ........
.byte $00,$FC,$03,$00,$FF,$13,$06,$00; 7EA8 ........
L_7EB0:
.byte $02,$FF,$13,$06,$01,$1D,$12,$0A; 7EB0 ........
.byte $0B,$06,$02,$02,$03,$00,$00,$FF; 7EB8 ........
.byte $00,$1F,$26,$26,$E0,$00,$00,$FF; 7EC0 ..&&....
.byte $00,$FF,$41 ; 7EC8 ..A
L_7ECB:
.byte $41,$40,$00,$00,$FF,$00,$FF,$68; 7ECB A@.....h
.byte $08,$F0,$00,$00 ; 7ED3 ....
L_7ED7:
.byte $FF,$00,$FF,$5A,$42,$3C,$00,$00; 7ED7 ...ZB<..
.byte $FF,$00,$FF,$16,$10,$0F,$00,$00; 7EDF ........
.byte $FF,$00,$FF,$85,$84,$03,$00,$00; 7EE7 ........
.byte $FF,$00,$FF,$A0,$20,$C0,$00,$00; 7EEF .... ...
.byte $FF,$00,$FE,$99,$99,$81,$2F,$10; 7EF7 ....../.
.byte $D0,$30,$10,$10,$1F,$F0,$FD,$02; 7EFF .0......
.byte $02,$03,$02,$02,$FE,$03,$00,$00; 7F07 ........
.byte $FF,$00,$1F,$26,$26,$E0,$00,$00; 7F0F ...&&...
.byte $FF,$00,$FF,$41,$41 ; 7F17 ...AA
L_7F1C:
.byte $40,$00,$00,$FF,$00,$FF,$68,$08; 7F1C @.....h.
.byte $F0,$00,$00,$FF,$00,$FF,$5A,$42; 7F24 ......ZB
.byte $3C,$00,$00,$FF,$00,$FF,$16,$10; 7F2C <.......
.byte $0F,$00,$00,$FF,$00,$FF,$82,$82; 7F34 ........
.byte $02,$00,$00 ; 7F3C ...
sub_7F3F:
.byte $FF,$00,$F8,$64,$64,$07,$BF,$40; 7F3F ...dd..@
.byte $40,$C3,$7C,$40,$40,$C0,$FE,$06; 7F47 @.|@@...
.byte $38,$C0,$13,$0C,$00,$10,$0F,$06; 7F4F 8.......
L_7F57:
.byte $03,$01,$00,$00,$00,$9E,$21,$40; 7F57 ......!@
.byte $CC,$CC,$C0,$61,$1F,$07,$08,$90; 7F5F ...a....
.byte $93,$F3,$90,$08,$FF,$81,$42,$24; 7F67 ......B$
.byte $24,$3C,$24,$42,$FF,$E0,$10,$09; 7F6F $<$B....
L_7F77:
.byte $C9,$CF,$09,$10,$FF,$78,$84,$02; 7F77 .....x..
.byte $32,$33,$02,$FC,$FF,$1E,$21,$40; 7F7F 23....!@
.byte $4C,$CC,$40,$21,$FE,$42,$3C,$98; 7F87 L.@!.B<.
.byte $B0,$E0,$C0,$80,$13,$11,$00,$10; 7F8F ........
.byte $0F,$06,$03,$01,$00,$00,$00; 7F97 .......
L_7F9E:
.byte $9E,$21,$40 ; 7F9E .!@
D_7FA1:
.byte $4C ; 7FA1 L
D_7FA2:
.byte $CC ; 7FA2 .
D_7FA3:
.byte $C0,$61,$1F ; 7FA3 .a.
sub_7FA6:
.byte $07,$08,$90,$93,$F3,$90,$08,$FF; 7FA6 ........
.byte $81,$42,$24,$24,$3C,$24,$42,$FF; 7FAE .B$$<$B.
.byte $E0,$10,$09,$C9 ; 7FB6 ....
L_7FBA:
.byte $CF,$09,$10,$FF,$79,$84,$02,$32; 7FBA ....y..2
.byte $33,$03,$86,$F8,$08,$F0,$60,$C0; 7FC2 3.....`.
L_7FCA:
.byte $80 ; 7FCA .
sub_7FCB:
.byte $13,$13,$00 ; 7FCB ...
; Three 8-byte lookup tables for the drone heading indicator, indexed 0-7.
joyDirToHeadingTable:
.byte $00,$04,$02,$06,$07,$01,$03,$05; 7FCE ........ joyDirToHeadingTable: joystick direction code (0 N, 1 S, 2 E, 3 W, 4 NW, 5 NE, 6 SE, 7 SW) -> clockwise heading (0 N, 1 NE, 2 E, 3 SE, 4 S, 5 SW, 6 W, 7 NW); also read by steerDrone at $7320 and by launchDrone at $8092
; headingLineEndColTable: byteTable, 8 bytes. 0C 13 16 13 0C 05 03 05: end column inside the 24x21
; sprite of the heading pointer line for headings 0-7 (centre column 12).
headingLineEndColTable:
.byte $0C,$13,$16,$13 ; 7FD6 .... headingLineEndColTable[0..3]: end column of the pointer line inside the 24x21 sprite for headings N, NE, E, SE (the line starts at column 12)
L_7FDA:
.byte $0C ; 7FDA headingLineEndColTable[4..6]: headings S, SW, W
.byte $05,$03 ; 7FDB ..
sub_7FDD:
.byte $05 ; 7FDD headingLineEndColTable[7]: heading NW
; headingLineEndRowTable: byteTable, 8 bytes. 00 03 0A 11 14 11 0A 03: end row of the heading pointer
; line for headings 0-7 (centre row 10).
headingLineEndRowTable:
.byte $00,$03,$0A,$11,$14,$11,$0A,$03; 7FDE ........ headingLineEndRowTable[0..7]: end row of the pointer line for headings N..NW (the line starts at row 10)
; ----------------------------------------------------------------------
; enterDroneScreen - Entry point of game screen 2, the DRONE screen (F5). Copies this side's drone
; stock into the display counter, unpacks the eight aircraft silhouettes into the $0200 sprite shape
; buffer, builds the whole screen and falls into resetDroneScreen.
; In: dronesLeft $92AE
; Out: radarDronesLeft $9233; $0200-$03FF = 8 sprite shapes of 64 bytes; screen 2 drawn; never returns
; (the idle loop is entered by falling through)
; Called from: JMP at $06FC in switchToRequestedScreen when the requested screen becomes 2
; ----------------------------------------------------------------------
enterDroneScreen:
lda dronesLeft ; 7FE6 $92AE = drones this side still owns
sta radarDronesLeft ; 7FE9 $9233 = the digit painted in the count box at row 5 column 34
L_7FEC:
lda #$24 ; 7FEC low byte of the compressed shape set
ldy #$FE ; 7FEE $FE24 in the high-memory block = eight aircraft silhouettes, one per heading
jsr unpackRleTo0200 ; 7FF0 decompress them into $0200-$03FF (64 bytes per heading)
jsr buildDroneScreen ; 7FF3 draw the frames, panels, buttons, fuel read-out and the empty camera window
; ----------------------------------------------------------------------
; resetDroneScreen - (Re)initialises the drone screen's sprites and read-outs and drops into the idle
; loop that waits for a launch. Parks sprite 0 (white heading line) at ($37,$64), sprite 3 (the same
; line one pixel down-right, used as its shadow) at ($38,$65) and sprite 5 (aircraft silhouette) at
; ($38,$64), all over the compass panel, and shows a full tank if a drone is still available.
; In: dronesLeft $92AE
; Out: $9233, droneFuel $9220 (100 or 0), zp_5E = 0, sprite shadows $9008-$901D, spriteEnableShadow
; $9022 = $09, activeSpriteIndex $91CC = 5; falls into droneScreenIdleLoop
; Called from: fall-through from enterDroneScreen, JMP at $81FB (endDroneFlight) and JMP at $076A
; (the comcen knock-out sequence returning to this screen)
; ----------------------------------------------------------------------
resetDroneScreen:
lda dronesLeft ; 7FF6 reload the drone stock ...
sta radarDronesLeft ; 7FF9 ... into the on-screen counter
; ----------------------------------------------------------------------
; Park the three compass-panel sprites. Sprite 0 is white and sits one pixel up and to the left of
; sprite 3, so the dark line of sprite 3 reads as its shadow.
; ----------------------------------------------------------------------
ldx #$37 ; 7FFC sprite X = $37 (left panel, low byte only - MSB stays clear)
stx spriteXShadow ; 7FFE $9010 = sprite 0 X shadow
inx ; 8001 $38 = one pixel further right
stx sprite5XShadow ; 8002 $9015 = sprite 5 (aircraft silhouette) X
stx sprite3XShadow ; 8005 $9013 = sprite 3 (heading line) X
ldy #$64 ; 8008 sprite Y = $64
sty spriteYShadow ; 800A $9018 = sprite 0 Y
sty sprite5YShadow ; 800D $901D = sprite 5 Y
L_8010:
iny ; 8010 $65 = one pixel further down
sty sprite3YShadow ; 8011 $901B = sprite 3 Y, so sprite 3 trails sprite 0 by (1,1)
lda #$01 ; 8014 colour 1 = white
sta spriteColourShadow ; 8016 $9008 = sprite 0 colour (the highlight of the pointer line)
lda #$02 ; 8019 colour 2 = red
sta sprite5ColourShadow ; 801B $900D = sprite 5 colour (the aircraft)
lda #$00 ; 801E direction code 0 = due north
sta droneJoyDirection ; 8020 zp_5E = the steer direction the pointer shows
jsr drawDroneHeadingIndicator; 8022 build the pointer line into sprites 3 and 0
lda #$05 ; 8025 5 = sprite 5 is the shape target
sta activeSpriteIndex ; 8027 $91CC selects sprite 5 inside loadDirectionShapeSprite
jsr loadDirectionShapeSprite; 802A probable bug: A is still 5, so silhouette 5 (SW) is loaded although the pointer shows north
lda #$09 ; 802D bit 0 + bit 3 = sprites 0 and 3
sta spriteEnableShadow ; 802F assigned, not ORed - this also switches sprite 5 back off, so the idle panel shows only the pointer
ldx #$64 ; 8032 a full tank is 100 exchanges of fuel
L_8034:
lda radarDronesLeft ; 8034 any drone left to launch?
bne L_803A ; 8037 yes - keep 100
tax ; 8039 no - A is 0, so show an empty tank
L_803A:
stx droneFuel ; 803A $9220 = fuel value printed by drawDroneFuelDigits
; ----------------------------------------------------------------------
; droneScreenIdleLoop - Main loop of the DRONE screen while nothing is flying. Each frame it runs the
; game engine and honours a screen change; every 11 frames it flashes the LAUNCH button and, with
; fewer than two drones left, the count box. A fresh fire press launches, unless the own comcen is
; cloaked (never) or the drone bay is damaged (then only one press in four gets through).
; In: zp_6C blink countdown, radarDronesLeft $9233, comcenDroneStatus $9244, comcenCloakedFlag $9245,
; the fire button
; Out: never returns - either jumps to launchDrone or leaves through switchToRequestedScreen /
; updateGameFrame
; Called from: fall-through from resetDroneScreen; looped back to from $805E, $8071, $8082 and $808A
; ----------------------------------------------------------------------
droneScreenIdleLoop:
jsr updateGameFrame ; 803D one game frame: engine step, message line, sound, timers
L_8040:
jsr switchToRequestedScreen ; 8040 act on an F1/F3/F5/F7 request - does not return if the screen changes
jsr checkFirePressedInteractive; 8043 Z=1 only for a fresh press, and only in a live (non-solo, non-film) game
bne L_805C ; 8046 no press - fall through to the blinking
lda comcenCloakedFlag ; 8048 $9245 bit 7 = our comcen is cloaked
bmi L_805C ; 804B a cloaked comcen cannot launch drones
lda comcenDroneStatus ; 804D $9244 != 0 = the drone bay has been knocked out
beq L_8059 ; 8050 undamaged - accept the launch
jsr nextGameRandom ; 8052 damaged: roll the game RNG
and #$03 ; 8055 keep two bits
bne L_805C ; 8057 three presses out of four are swallowed
L_8059:
jmp L_8085 ; 8059 go and check that a drone is actually in stock
; ----------------------------------------------------------------------
; Blinking, once every 11 frames.
; ----------------------------------------------------------------------
L_805C:
lda joyRepeatTimer ; 805C zp_6C is decremented once per frame by the IRQ
L_805E:
bne droneScreenIdleLoop ; 805E not expired yet - keep polling
lda radarDronesLeft ; 8060 how many drones are left?
cmp #$02 ; 8063 fewer than two?
bcs L_806A ; 8065 no - leave the count box alone
jsr blinkDroneCountBox ; 8067 yes - pulse the count box as a warning
L_806A:
lda #$0B ; 806A 11 frames ...
sta joyRepeatTimer ; 806C ... until the next blink
lda radarDronesLeft ; 806E drones left?
beq droneScreenIdleLoop ; 8071 none - leave the LAUNCH button dark
lda #$0C ; 8073 character row 12 ...
sta windowOriginRow ; 8075 ... into the colour-fill origin row
lda #$22 ; 8078 character column 34 = the LAUNCH button ...
sta windowOriginCol ; 807A ... into the colour-fill origin column
lda #$08 ; 807D colour 8 (orange)
jsr xorColourBlock2x2 ; 807F XOR it into the 2x2 button cells so the button flashes
jmp droneScreenIdleLoop ; 8082 round again
L_8085:
lda radarDronesLeft ; 8085 drones in stock?
L_8088:
bne launchDrone ; 8088 yes - go
jmp droneScreenIdleLoop ; 808A no - ignore the press
; ----------------------------------------------------------------------
; launchDrone - Sends a drone on its way: latches the joystick heading, queues command $97 (drone
; launch) for the engine and the opponent, lights the LAUNCH button, fills the tank with 100, drops
; the stock by one and puts the 14 x 12 camera window over our own comcen, where the drone starts.
; Then it loads the targeting reticle into sprite 2, the aircraft silhouette into sprite 5, plays the
; launch sound and opens the camera with a grey iris wipe.
; In: zp_5D joystick, comcenUnit $9236, unitColTable/unitRowTable, the reticle shape at $FF93
; Out: droneHeading $920B, droneControlFlags $920D |= $C0, droneFuel $9220 = 100, $9233 decremented,
; droneViewOriginCol/Row $92F6/$92F7 and zp_A5/zp_A6 = comcen - (6,5), sprites 2/4/5, command $97
; queued; falls into droneFlightLoop
; Called from: branch at $8088 in droneScreenIdleLoop
; ----------------------------------------------------------------------
launchDrone:
jsr readDroneHeadingFromJoystick; 808D sample the stick that was held while fire was pressed
ldy droneJoyDirection ; 8090 zp_5E = direction code 0-7
lda joyDirToHeadingTable,y ; 8092 translate it into a clockwise heading 0-7 starting at north
sta droneHeading ; 8095 $920B: the heading steerDrone turns towards and $8D reports every exchange
L_8098:
lda #$97 ; 8098 command $97 = drone launched (one byte, no arguments)
jsr queueCmdBytePending ; 809A queue it so the engine and the opponent both see it
lda #$0C ; 809D character row 12 ...
sta windowOriginRow ; 809F ... fill origin row
lda #$22 ; 80A2 character column 34 = the LAUNCH button ...
sta windowOriginCol ; 80A4 ... fill origin column
L_80A7:
lda #$05 ; 80A7 colour 5 (green)
ldx #$02 ; 80A9 2 character columns
ldy #$02 ; 80AB 2 character rows
jsr fillScreenColourRect ; 80AD light the button up solid
lda #$64 ; 80B0 100 = a full tank (one unit per lock-step exchange)
sta droneFuel ; 80B2 $9220
jsr drawDroneFuelDigits ; 80B5 print it as three tall digits at row 15
dec radarDronesLeft ; 80B8 one drone fewer in stock
jsr drawDroneCountDigit ; 80BB reprint the count digit
lda droneControlFlags ; 80BE $920D drone state flags
ora #$C0 ; 80C1 bit 7 = one of our drones is airborne, bit 6 = we are steering it
sta droneControlFlags ; 80C3 store back (bit 0, the enemy-drone alert, is left alone)
; ----------------------------------------------------------------------
; Put the camera window over our comcen: the window is 14 x 12 cells and keeps the drone in the
; middle, so its origin is the drone cell minus (6,5).
; ----------------------------------------------------------------------
ldy comcenUnit ; 80C6 own comcen index
lda unitColTable,y ; 80C9 its map column
sec ; 80CC no borrow
sbc #$06 ; 80CD 6 columns to the left of the drone
sta viewOriginCol ; 80CF zp_A5 = view origin column
sta droneViewOriginCol ; 80D1 $92F6 = the drone's own camera origin, stepped by steerDrone
lda unitRowTable,y ; 80D4 comcen map row
sec ; 80D7 no borrow
sbc #$05 ; 80D8 5 rows above the drone
sta viewOriginRow ; 80DA zp_A6 = view origin row
sta droneViewOriginRow ; 80DC $92F7
ldx #$00 ; 80DF leftover: drawComcenRingSprite reads nothing from X
L_80E1:
jsr drawComcenRingSprite ; 80E1 sprite 4 = the two concentric rings marking our comcen under the drone
; ----------------------------------------------------------------------
; Load the targeting reticle into sprite 2 and park it in the centre of the camera window.
; ----------------------------------------------------------------------
L_80E4:
lda #$00 ; 80E4 colour 0 = black
sta sprite2ColourShadow ; 80E6 $900A = sprite 2 colour
ldy #$3F ; 80E9 64 shape bytes, copied backwards
L_80EB:
lda droneViewfinderSprite,y ; 80EB $FF93 in the high-memory block holds the reticle shape (the last byte read, $FFD2, is one past the loaded block but is the unused 64th sprite byte)
sta sprite2Shape,y ; 80EE sprite 2 data at $8A80
dey ; 80F1 next byte down
bpl L_80EB ; 80F2 until byte 0 has been copied
lda #$AC ; 80F4 X = $AC, the middle of the camera window
sta sprite2XShadow ; 80F6 $9012 = sprite 2 X
lda #$88 ; 80F9 Y = $88
L_80FB:
sta sprite2YShadow ; 80FB $901A = sprite 2 Y
lda #$04 ; 80FE bit 2 = sprite 2
jsr enableSprites ; 8100 OR it into the sprite enable shadow
L_8103:
lda #$05 ; 8103 5 = sprite 5
sta activeSpriteIndex ; 8105 $91CC selects sprite 5 for the shape load
lda droneHeading ; 8108 silhouette index = the launch heading
jsr loadDirectionShapeSprite; 810B copy that shape to $8B40 and enable sprite 5
jsr resetDrawModes ; 810E back to plain replace draw modes
; ----------------------------------------------------------------------
; Launch effect: sound, grey static in the camera window, then the iris opens.
; ----------------------------------------------------------------------
lda #$0F ; 8111 sound $0F = launch
jsr playSound ; 8113 play it
jsr clearRadarWindowGrey ; 8116 fill the camera window with $BB (dark grey) and set radarWipeColour so the iris animates slowly
lda #$AA ; 8119 $AA = light red in both multicolour nibbles
sta useDrawColourFlag ; 811B $2A29 != 0 forces every following draw to use one fixed colour ...
sta drawColourByte ; 811E ... and D_2A2A holds it
jsr drawRadarIrisWipe ; 8121 shrinking concentric frames over the camera window
; ----------------------------------------------------------------------
; droneFlightLoop - Per-frame loop while our drone is in the air. A fire press jumps to
; strikeWithDrone; every 40 frames the STRIKE button (and, below two drones, the count box) flashes;
; when the joystick lockout has expired the heading is re-read. The camera only advances when
; exchangeAppliedFlag ($920E) bit 7 says a lock-step command exchange has been applied, which keeps
; both machines in step. The flight ends when the tank runs dry, when the game ends, or when the
; command handlers clear droneControlFlags bit 7 (drone arrived / detonated by the other side).
; In: zp_6C, zp_6E, exchangeAppliedFlag $920E, droneControlFlags $920D, droneFuel $9220,
; gameEndReason $0BA8, the fire button
; Out: camera redrawn one step per exchange; falls into endDroneFlight
; Called from: fall-through from launchDrone; looped back to from the JMP at $8171
; ----------------------------------------------------------------------
droneFlightLoop:
jsr updateGameFrame ; 8124 run one game frame
jsr checkFirePressed ; 8127 Z=1 on a fresh fire press (this variant also fires during solo/film)
bne L_812F ; 812A no press - carry on flying
jmp strikeWithDrone ; 812C fire during flight = detonate here
L_812F:
lda joyRepeatTimer ; 812F blink countdown
bne L_8150 ; 8131 not expired - skip the blinking
lda #$28 ; 8133 40 frames ...
sta joyRepeatTimer ; 8135 ... until the next blink (slower than the idle loop's 11)
lda radarDronesLeft ; 8137 drones still in stock?
cmp #$02 ; 813A fewer than two?
L_813C:
bcs L_8141 ; 813C no - leave the count box
jsr blinkDroneCountBox ; 813E pulse the count box
L_8141:
lda #$10 ; 8141 character row 16 ...
flightStrikeBoxOrigin:
sta windowOriginRow ; 8143 ... fill origin row
lda #$22 ; 8146 character column 34 = the STRIKE button ...
sta windowOriginCol ; 8148 ... fill origin column
lda #$08 ; 814B colour 8 (orange)
L_814D:
jsr xorColourBlock2x2 ; 814D XOR it into the 2x2 button cells
L_8150:
lda menuBlinkTimer ; 8150 zp_6E = the 20-frame lockout between joystick reads
bne L_8157 ; 8152 still locked out - do not re-read the stick
jsr readDroneHeadingFromJoystick; 8154 latch a new steer direction and redraw the pointer
; ----------------------------------------------------------------------
; The camera moves exactly one cell per lock-step command exchange, never per frame.
; ----------------------------------------------------------------------
L_8157:
lda exchangeAppliedFlag ; 8157 $920E bit 7 = an exchange was applied this frame
L_815A:
bmi L_815F ; 815A yes - step the camera
jmp L_8167 ; 815C no - a plain BPL $8167 would have done; the JMP is two bytes of slack
L_815F:
jsr advanceDroneCamera ; 815F redraw the camera window and burn one unit of fuel
lda droneFuel ; 8162 tank empty?
beq L_8174 ; 8165 yes - hand the drone back to the engine
; ----------------------------------------------------------------------
; Decide whether the flight goes on for another frame.
; ----------------------------------------------------------------------
L_8167:
lda droneControlFlags ; 8167 $920D drone state
bpl L_8177 ; 816A bit 7 cleared by cmdDroneArrived ($92) or a remote detonation - end without sending $8F
lda gameEndReason ; 816C $0BA8 != 0 = the game has ended
bne L_8174 ; 816F then release the drone as well
jmp droneFlightLoop ; 8171 otherwise keep flying
L_8174:
jsr releaseDroneControl ; 8174 clear bits 7-6 and queue command $8F so the opponent stops tracking the drone
L_8177:
jsr playDroneLostAnimation ; 8177 white flash, iris close and the shrinking disc
lda #$1C ; 817A sound $1C = explosion ...
jsr playSound ; 817C ... which endDroneFlight immediately plays a second time
jmp endDroneFlight ; 817F common tail
; ----------------------------------------------------------------------
; strikeWithDrone - Fire pressed while the drone is flying: stop steering it, mark a detonation as
; pending (droneControlFlags bit 5), light the STRIKE button and queue command $8E col,row with the
; drone's current map cell. Then it waits, still flying the camera one step per exchange, until the
; command handler executes our own $8E and clears bit 5; the strike then gets its sound and the
; zoom-in animation.
; In: droneControlFlags $920D, exchangeAppliedFlag $920E, droneViewOriginCol/Row $92F6/$92F7
; Out: command $8E queued with cmdParam1/2, $920D bit 6 cleared and bit 5 set, sound and animation;
; falls into endDroneFlight
; Called from: JMP at $812C in droneFlightLoop
; ----------------------------------------------------------------------
strikeWithDrone:
lda droneControlFlags ; 8182 $920D drone state
and #$BF ; 8185 clear bit 6 - we stop steering from now on
ora #$20 ; 8187 set bit 5 - our own detonate command is queued but not yet executed
sta droneControlFlags ; 8189 store back
lda #$10 ; 818C character row 16 ...
sta windowOriginRow ; 818E ... fill origin row
lda #$22 ; 8191 character column 34 = the STRIKE button ...
sta windowOriginCol ; 8193 ... fill origin column
lda #$02 ; 8196 colour 2 (red)
ldx #$02 ; 8198 2 character columns
ldy #$02 ; 819A 2 character rows
jsr fillScreenColourRect ; 819C light the STRIKE button solid
jsr getDroneMapPosition ; 819F zp_74/zp_75 = the drone's own map cell
lda #$8E ; 81A2 command $8E = detonate a drone at col,row
jsr queueCmd3 ; 81A4 queue the 3-byte command
; ----------------------------------------------------------------------
; Wait for our own $8E to come back out of the command stream; keep the camera moving (with a rumble)
; on every exchange until then.
; ----------------------------------------------------------------------
L_81A7:
jsr updateGameFrame ; 81A7 one game frame
lda droneControlFlags ; 81AA $920D drone state
bpl endDroneFlight ; 81AD bit 7 gone - the drone was destroyed before the command landed
and #$20 ; 81AF test the 'detonation pending' bit
beq L_81C3 ; 81B1 cleared = cmdDroneDetonate has run our command
lda exchangeAppliedFlag ; 81B3 $920E bit 7 = an exchange was applied
strikeWaitForExchange:
bpl L_81A7 ; 81B6 not yet - keep waiting
jsr advanceDroneCamera ; 81B8 another camera step
lda #$1C ; 81BB sound $1C ...
jsr playSound ; 81BD ... on every step, so the drone rumbles towards the target
jmp L_81A7 ; 81C0 keep waiting
L_81C3:
lda droneControlFlags ; 81C3 dead test: bit 7 was already proved set at $81AD and nothing here clears it
bpl endDroneFlight ; 81C6 never taken
D_81C8:
lda #$11 ; 81C8 sound $11 = the strike itself
jsr playSound ; 81CA play it
jsr playStrikeZoomAnimation ; 81CD four 2x zooms into the camera centre, then the drone-lost animation
; ----------------------------------------------------------------------
; endDroneFlight - Common tail of every drone flight: explosion sound, empty the tank, clear the
; 'airborne' and 'we steer it' bits, put the camera window back over our own comcen, blank it without
; static and restart the idle screen.
; In: comcenUnit $9236, unitColTable/unitRowTable, droneControlFlags $920D
; Out: droneFuel $9220 = 0 and reprinted, $920D &= $3F, zp_A5/zp_A6 = comcen - (6,5), camera window
; black; jumps to resetDroneScreen
; Called from: JMP at $817F and branches at $81AD / $81C6, plus fall-through from strikeWithDrone
; ----------------------------------------------------------------------
endDroneFlight:
lda #$1C ; 81D0 sound $1C = explosion
jsr playSound ; 81D2 play it
lda #$00 ; 81D5 no fuel left
sta droneFuel ; 81D7 $9220
jsr drawDroneFuelDigits ; 81DA read-out back to 000
L_81DD:
lda droneControlFlags ; 81DD $920D drone state
and #$3F ; 81E0 clear bit 7 (airborne) and bit 6 (locally steered); bit 5 and bit 0 survive
sta droneControlFlags ; 81E2 store back
ldy comcenUnit ; 81E5 own comcen index
lda unitColTable,y ; 81E8 its map column
sec ; 81EB no borrow
sbc #$06 ; 81EC the 14-wide window is centred 6 columns to the right of its origin
sta viewOriginCol ; 81EE zp_A5
lda unitRowTable,y ; 81F0 comcen map row
sec ; 81F3 no borrow
endFlightRowOffset:
sbc #$05 ; 81F4 the 12-high window is centred 5 rows below its origin
sta viewOriginRow ; 81F6 zp_A6
jsr clearRadarWindowBlack ; 81F8 blank the camera window to colour 0 (no grey static this time)
jmp resetDroneScreen ; 81FB back to the idle screen, ready for the next launch
; ----------------------------------------------------------------------
; getDroneMapPosition - Converts the camera origin into the drone's own map cell: the 14 x 12 window
; is kept centred on the drone, so the drone stands at origin + (6,5).
; In: droneViewOriginCol $92F6, droneViewOriginRow $92F7
; Out: cmdParam1 zp_74 = drone map column, cmdParam2 zp_75 = drone map row
; Called from: $819F (strikeWithDrone, for the $8E detonate command) and $563C in sendOutgoingPacket
; (for the $8D heading,col,row move report sent every exchange)
; ----------------------------------------------------------------------
getDroneMapPosition:
lda droneViewOriginCol ; 81FE $92F6 = map column of the window's left edge
clc ; 8201 no carry in
L_8202:
adc #$06 ; 8202 the drone sits 6 columns into the 14-wide window
sta cmdParam1 ; 8204 zp_74 = command argument 1
lda droneViewOriginRow ; 8206 $92F7 = map row of the window's top edge
clc ; 8209 no carry in
L_820A:
adc #$05 ; 820A and 5 rows into the 12-high window
sta cmdParam2 ; 820C zp_75 = command argument 2
rts ; 820E done
; ----------------------------------------------------------------------
; showDroneAlert - Notification that the opponent has launched a drone: plays the alert sound and, in
; a real two-player game, queues the 'DRONE ALERT!' status message. In a solo or film game the
; message is suppressed because there is nobody to warn.
; In: filmPlaybackMode $0B9B bit 7 (set = solo trainer or film playback)
; Out: sound 7 played, message $20 queued
; Called from: $53AF, the cmdDroneLaunch handler of command $97 in the main program
; ----------------------------------------------------------------------
showDroneAlert:
lda #$07 ; 820F sound 7 = alert
jsr playSound ; 8211 play it
lda filmPlaybackMode ; 8214 $0B9B bit 7 = solo trainer or film playback
bmi L_821E ; 8217 then say nothing
lda #$20 ; 8219 message id $20 = 'DRONE ALERT!'
jsr queueMessage ; 821B insert it into the status message queue
L_821E:
rts ; 821E done (also the RTS shared by releaseDroneControl)
; ----------------------------------------------------------------------
; fillDroneViewWindow - Fills the drone camera window (14 columns x 12 rows starting at character row
; 6, column 13) with one screen RAM colour byte.
; In: A = colour byte
; Out: 168 colour cells written, D_2A2D = 6, D_2A2E = $0D, D_2A2A = A
; Called from: $7141 (the shared radar-clear tail), $8290 (static when the drone bay is damaged) and
; $2E07 in the main program
; ----------------------------------------------------------------------
fillDroneViewWindow:
ldy #$06 ; 821F camera window starts at character row 6
sty windowOriginRow ; 8221 D_2A2D = fill origin row
ldy #$0D ; 8224 ... and character column 13
sty windowOriginCol ; 8226 D_2A2E = fill origin column
ldx #$0E ; 8229 14 character columns = 14 map cells
ldy #$0C ; 822B 12 character rows = 12 map cells
jmp fillScreenColourRect ; 822D fill and return to the caller
; ----------------------------------------------------------------------
; releaseDroneControl - Gives an airborne drone back to the engine: clears the 'airborne' and 'locally
; steered' bits and queues the single-byte command $8F so the opponent stops tracking it. Does
; nothing when no drone of ours is up.
; In: droneControlFlags $920D
; Out: $920D &= $3F, command $8F queued
; Called from: $8174 (fuel out or game over) and $0709 (the comcen knock-out sequence)
; ----------------------------------------------------------------------
releaseDroneControl:
lda droneControlFlags ; 8230 $920D drone state
bpl L_821E ; 8233 bit 7 clear - nothing of ours is airborne, so nothing to release
and #$3F ; 8235 clear bit 7 (airborne) and bit 6 (steered by us)
sta droneControlFlags ; 8237 store back
lda #$8F ; 823A command $8F = drone released
jmp queueCmdBytePending ; 823C queue it and return through the queue routine
; ----------------------------------------------------------------------
; drawDroneHeadingIndicator - Redraws the compass pointer of the drone screen's left panel. It clears
; sprite 3, draws a line inside it from the sprite centre (column 12, row 10) out to the end point
; looked up for the current heading, colours it, then copies the identical shape into sprite 0.
; Because sprite 0 is white and sits one pixel up and left of sprite 3, the pointer appears as a white
; line with a dark shadow.
; In: droneJoyDirection zp_5E, joyDirToHeadingTable $7FCE, headingLineEndColTable $7FD6,
; headingLineEndRowTable $7FDE
; Out: sprite 3 shape $8AC0 and sprite 0 shape $8A00 redrawn, sprite3ColourShadow $900B, sprites 0 and
; 3 enabled; zp_92-zp_95 and the line stepper state clobbered
; Called from: $8022 (resetDroneScreen) and $82B1 (readDroneHeadingFromJoystick)
; ----------------------------------------------------------------------
drawDroneHeadingIndicator:
lda #$03 ; 823F sprite 3 = the path/route sprite at $8AC0
jsr clearSpriteShape ; 8241 zero its 64 shape bytes
lda #$0A ; 8244 sprite row 10 ...
sta lineCurRow ; 8246 ... zp_92 = line start row (centre of the 24x21 sprite)
lda #$0C ; 8248 sprite column 12 ...
sta lineCurCol ; 824A ... zp_93 = line start column
lda #$0B ; 824C default line colour $0B (dark grey)
L_824E:
ldy droneJoyDirection ; 824E current steer direction code
cpy #$02 ; 8250 codes 0 and 1 are due north and due south
bcs L_8256 ; 8252 any other direction keeps dark grey
lda #$06 ; 8254 N/S get colour 6 (blue) instead
L_8256:
sta sprite3ColourShadow ; 8256 $900B = sprite 3 colour
ldx joyDirToHeadingTable,y ; 8259 direction code -> clockwise heading 0-7
lda headingLineEndRowTable,x; 825C end row of the pointer for that heading
sta lineEndRow ; 825F zp_94 = line end row
lda headingLineEndColTable,x; 8261 end column of the pointer
L_8264:
sta lineEndCol ; 8264 zp_95 = line end column
jsr drawLineInPathSprite ; 8266 A still holds the end column ($03-$16), so bit 7 is clear and the line is drawn solid
; ----------------------------------------------------------------------
; Sprite 0 carries an identical copy of the line, one pixel up and left and in white.
; ----------------------------------------------------------------------
ldy #$3F ; 8269 64 shape bytes, copied backwards
L_826B:
lda sprite3Shape,y ; 826B read sprite 3's shape
sta sprite0Shape,y ; 826E write sprite 0's shape at $8A00
dey ; 8271 next byte down
bpl L_826B ; 8272 until byte 0
lda #$09 ; 8274 bit 0 + bit 3 = sprites 0 and 3
L_8276:
jmp enableSprites ; 8276 OR them into the enable shadow and return
; ----------------------------------------------------------------------
; advanceDroneCamera - One drone step on the DRONE screen, run once per lock-step command exchange. It
; consumes the exchange flag, redraws the 14 x 12 camera window from the map around the drone,
; replaces half of the pictures with static when the drone bay is damaged, burns one unit of fuel and
; reloads the silhouette for the drone's current heading. The drone itself is moved by steerDrone,
; which sendOutgoingPacket calls just before the $8D report goes out.
; In: exchangeAppliedFlag $920E, droneHeading $920B, droneFuel $9220, comcenDroneStatus $9244,
; droneViewOriginCol/Row $92F6/$92F7
; Out: $920E = 0, $9220 decremented, camera window redrawn, sprite 5 reloaded, fuel read-out reprinted
; Called from: $815F (droneFlightLoop) and $81B8 (strikeWithDrone waiting for its $8E)
; ----------------------------------------------------------------------
advanceDroneCamera:
lda #$00 ; 8279 clear the flag ...
sta exchangeAppliedFlag ; 827B ... so only one camera step happens per exchange
jsr updateExplosionMarker ; 827E in this build this only switches sprite 4 (the comcen ring) off again
jsr drawDroneRadarWindow ; 8281 redraw the 14x12 camera window from the map around zp_A5/zp_A6
lda comcenDroneStatus ; 8284 $9244 != 0 = the drone bay has been knocked out
beq L_8293 ; 8287 undamaged - show a clean picture
jsr nextGameRandom ; 8289 roll the game RNG
bmi L_8293 ; 828C bit 7 set - this step still shows a clean picture
lda #$AA ; 828E $AA = light red in both nibbles
jsr fillDroneViewWindow ; 8290 otherwise bury the window in static
L_8293:
dec droneFuel ; 8293 one unit of fuel per exchange, so 100 exchanges of flight
lda #$05 ; 8296 5 = sprite 5
sta activeSpriteIndex ; 8298 $91CC selects sprite 5
lda droneHeading ; 829B current heading, which steerDrone turns one step per exchange
D_829E:
jsr loadDirectionShapeSprite; 829E reload the matching silhouette and enable sprite 5
jmp drawDroneFuelDigits ; 82A1 reprint the fuel digits and return through them
; ----------------------------------------------------------------------
; readDroneHeadingFromJoystick - Latches the joystick direction as the drone's new steer direction and
; redraws the compass pointer. A centred stick ($FF from joystickDirectionTable) leaves the current
; heading alone; after a successful read the stick is ignored for 20 frames so the pointer cannot
; spin.
; In: joyDirection zp_5D (the IRQ-latched joystick 2 nibble), joystickDirectionTable $1FA6
; Out: droneJoyDirection zp_5E, menuBlinkTimer zp_6E = 20, sprites 0 and 3 redrawn
; Called from: $808D (launchDrone) and $8154 (droneFlightLoop, once the lockout has expired)
; ----------------------------------------------------------------------
readDroneHeadingFromJoystick:
ldy joyDirection ; 82A4 zp_5D = joystick 2 direction bits latched by the IRQ
lda joystickDirectionTable,y; 82A6 $1FA6 turns them into a direction code 0-7, or $FF when centred
bmi L_82B4 ; 82A9 stick centred - keep steering the way we were
ldy #$14 ; 82AB 20 frames ...
sty menuBlinkTimer ; 82AD ... of lockout before the stick is read again
sta droneJoyDirection ; 82AF zp_5E = the new steer direction
jsr drawDroneHeadingIndicator; 82B1 redraw the compass pointer to match
L_82B4:
rts ; 82B4 done
; shotLineWorkVars - the eight work bytes of drawShotLineInViewport, zero in the loaded image.
shotLineWorkVars:
.byte $00 ; 82B5 . viewLeft: map column of the viewport's left edge; later rebased to 0 and multiplied by 8 (fat pixels)
D_82B6:
.byte $00 ; 82B6 column bound used by the endpoint test = viewLeft + 7 (the window is 7 cells wide)
D_82B7:
.byte $00 ; 82B7 viewTop: map row of the viewport's top edge; later rebased and multiplied by 8
D_82B8:
.byte $00 ; 82B8 row bound used by the endpoint test = viewTop + 5 (the window is 5 cells high)
D_82B9:
.byte $00 ; 82B9 endpoint visibility: $FF = only the shooter is in view, $02 = only the target, $01 = both, 0 = neither
D_82BA:
.byte $00 ; 82BA shift added to every column when the view origin is negative (= -viewOriginCol)
D_82BB:
.byte $00 ; 82BB shift added to every row when the view origin is negative (= -viewOriginRow)
D_82BC:
.byte $00 ; 82BC countdown of the muzzle gap: 4 down to 1 = skip, 0 = draw the diamond, negative = draw the line
; unusedBytes82BD - $00,$60: not referenced from anywhere in this overlay variant, padding in front of
; drawShotLineInViewport.
unusedBytes82BD:
.byte $00,$60 ; 82BD .` unreferenced filler
; ----------------------------------------------------------------------
; drawShotLineInViewport - Draws the tracer of one shot inside the 7 x 5 cell zoomed battlefield
; viewport (2 x 2 characters per cell at character row 1, column 25; 8 'fat' multicolour pixels per
; map cell, fat-pixel origin X=100, Y=4). It first forces the %01 colour of all 14 x 10 viewport
; cells to white, then decides whether the shooter and/or the target cell is inside the window,
; rebases and scales the coordinates, walks the line with the high-memory stepper and plots colour-1
; fat pixels. The first four steps out of the muzzle are left blank, the fifth carries a small
; diamond, and a BOOMER shot ($923D = 2) advances three cells per plotted pixel, which makes it
; dashed. Finally viewRedrawTimer is armed so the view is redrawn - erasing the line - 15 frames on.
; In: zp_92/zp_93 = shooter row/column, zp_94/zp_95 = target row/column, zp_A5/zp_A6 = view origin,
; attackerType $923D, the screen row tables at $9382/$939B
; Out: bitmap pixels and viewport colour nibbles changed, viewRedrawTimer $9234 = $8F, $82B5-$82BC
; used, zp_92-zp_95 and the line stepper state clobbered, zp_B6/zp_B7 clobbered
; Called from: $3A30, resolveAttack in the main program. The only user of plotFatPixel ($FC58).
; Force the %01 bit pair of every character cell of the viewport (rows 1-10, columns 25-38) to white
; so the line about to be plotted is visible whatever the terrain colours were.
; ----------------------------------------------------------------------
drawShotLineInViewport:
ldy #$01 ; 82BF screen row table index 1 = character row 1, the top of the viewport
lda screenRowLoTable,y ; 82C1 screen RAM ($8C00) row address, low byte
sta screenPtr ; 82C4 zp_B6
lda screenRowHiTable,y ; 82C6 row address high byte
sta screenPtrHi ; 82C9 zp_B7 - the loop writes screen RAM, not colour RAM
ldx #$0A ; 82CB 10 character rows = 5 map cells
L_82CD:
ldy #$26 ; 82CD start at the rightmost character column, 38
L_82CF:
lda (screenPtr),y ; 82CF current colour byte of the cell
and #$0F ; 82D1 keep the low nibble = the %10 colour
L_82D3:
ora #$10 ; 82D3 force the high nibble to 1 = white for the %01 bit pair
sta (screenPtr),y ; 82D5 store it back
dey ; 82D7 next column to the left
cpy #$19 ; 82D8 leftmost viewport column is 25
bcs L_82CF ; 82DA keep going while Y >= 25
dex ; 82DC one row done
beq L_82EC ; 82DD all ten rows done
clc ; 82DF clear carry
lda screenPtr ; 82E0 screen pointer low byte
adc #$28 ; 82E2 one character row = 40 bytes
sta screenPtr ; 82E4 zp_B6
bcc L_82CD ; 82E6 no carry - next row
inc screenPtrHi ; 82E8 carry into the high byte
bne L_82CD ; 82EA always taken - next row
; ----------------------------------------------------------------------
; The 7x5 window may hang off the left or top edge of the map, in which case zp_A5/zp_A6 are negative.
; Shift the origin and both endpoints right/down by -origin so all the following arithmetic stays
; unsigned, and work out the window bounds.
; ----------------------------------------------------------------------
L_82EC:
lda #$00 ; 82EC no endpoint seen yet
sta D_82B9 ; 82EE $82B9 = endpoint visibility flags
lda viewOriginCol ; 82F1 view origin column
bpl L_8313 ; 82F3 non-negative - use it as it is
eor #$FF ; 82F5 negate it: EOR $FF ...
L_82F7:
clc ; 82F7 ... clear carry ...
adc #$01 ; 82F8 ... +1 gives -viewOriginCol
sta D_82BA ; 82FA $82BA = the column shift
clc ; 82FD clear carry
lda lineCurCol ; 82FE shooter column
adc D_82BA ; 8300 shift it
sta lineCurCol ; 8303 zp_93 updated
clc ; 8305 clear carry
lda lineEndCol ; 8306 target column
adc D_82BA ; 8308 shift it
sta lineEndCol ; 830B zp_95 updated
clc ; 830D clear carry
lda viewOriginCol ; 830E the origin itself
adc D_82BA ; 8310 shifted origin = 0
L_8313:
sta shotLineWorkVars ; 8313 $82B5 = left edge of the window (now never negative)
clc ; 8316 clear carry
adc #$07 ; 8317 the window is 7 cells wide
D_8319:
sta D_82B6 ; 8319 $82B6 = right bound used below (note it is left+7, while the value tested against it is already relative to left - too permissive for a non-zero origin; the fat-pixel clip in the plot loop catches the difference)
lda viewOriginRow ; 831C view origin row
bpl L_833E ; 831E non-negative - use it as it is
eor #$FF ; 8320 negate it ...
L_8322:
clc ; 8322 ... clear carry ...
adc #$01 ; 8323 ... +1 gives -viewOriginRow
sta D_82BB ; 8325 $82BB = the row shift
clc ; 8328 clear carry
L_8329:
lda lineCurRow ; 8329 shooter row
L_832B:
adc D_82BB ; 832B shift it
sta lineCurRow ; 832E zp_92 updated
clc ; 8330 clear carry
lda lineEndRow ; 8331 target row
adc D_82BB ; 8333 shift it
sta lineEndRow ; 8336 zp_94 updated
L_8338:
clc ; 8338 clear carry
L_8339:
lda viewOriginRow ; 8339 the origin itself
adc D_82BB ; 833B shifted origin = 0
L_833E:
sta D_82B7 ; 833E $82B7 = top edge of the window
clc ; 8341 clear carry
adc #$05 ; 8342 the window is 5 cells high
sta D_82B8 ; 8344 $82B8 = bottom bound (same +top skew as $82B6)
; ----------------------------------------------------------------------
; Is the shooter's cell inside the 7x5 window?
; ----------------------------------------------------------------------
sec ; 8347 no borrow
lda lineCurCol ; 8348 shooter column
sbc shotLineWorkVars ; 834A make it relative to the left edge
bcc testTargetEndpointInView; 834D left of the window - not visible
cmp D_82B6 ; 834F compare against the right bound
bcs testTargetEndpointInView; 8352 right of the window - not visible
sec ; 8354 no borrow
lda lineCurRow ; 8355 shooter row
sbc D_82B7 ; 8357 make it relative to the top edge
bcc testTargetEndpointInView; 835A above the window - not visible
cmp D_82B8 ; 835C compare against the bottom bound
bcs testTargetEndpointInView; 835F below the window - not visible
dec D_82B9 ; 8361 flags = $FF: the shooter is in view
; ----------------------------------------------------------------------
; ... and is the target's cell inside it?
; ----------------------------------------------------------------------
testTargetEndpointInView:
sec ; 8364 no borrow
lda lineEndCol ; 8365 target column
sbc shotLineWorkVars ; 8367 relative to the left edge
bcc L_8384 ; 836A outside
cmp D_82B6 ; 836C right bound
bcs L_8384 ; 836F outside
sec ; 8371 no borrow
lda lineEndRow ; 8372 target row
sbc D_82B7 ; 8374 relative to the top edge
bcc L_8384 ; 8377 outside
cmp D_82B8 ; 8379 bottom bound
bcs L_8384 ; 837C outside
inc D_82B9 ; 837E flags += 1 ...
inc D_82B9 ; 8381 ... twice, so $02 = only the target, $01 = both endpoints in view
L_8384:
lda D_82B9 ; 8384 any endpoint in view?
bne L_838C ; 8387 yes - draw it
jmp L_840E ; 8389 neither - nothing to draw, and no redraw to schedule
; ----------------------------------------------------------------------
; Rebase the window origin and both endpoints so the smallest value is 0, then scale everything from
; map cells to fat pixels (8 per cell). Rebasing is what stops the x8 from overflowing a byte.
; ----------------------------------------------------------------------
L_838C:
jsr rebaseLineCoordsToMin ; 838C subtract the minimum column and the minimum row from all three values
lda shotLineWorkVars ; 838F window left edge, in cells
asl a ; 8392 x2 ...
asl a ; 8393 x4 ...
asl a ; 8394 x8 = fat pixels per map cell
sta shotLineWorkVars ; 8395 $82B5 now in fat pixels
L_8398:
lda D_82B7 ; 8398 window top edge, in cells
asl a ; 839B x2 ...
asl a ; 839C x4 ...
asl a ; 839D x8
sta D_82B7 ; 839E $82B7 now in fat pixels
jsr scaleLineCoordsToFatPixels; 83A1 scale zp_92-zp_95 the same way and add the half-cell offsets (+4 rows, +3 columns)
jsr initLineStepper ; 83A4 set up the Bresenham walk from (zp_93,zp_92) to (zp_95,zp_94)
lda #$04 ; 83A7 4 steps of muzzle gap ...
sta D_82BC ; 83A9 ... in $82BC
; ----------------------------------------------------------------------
; Walk the line one fat pixel step at a time. A point is plotted only when it lies strictly inside
; the window - relative coordinate 0 is skipped, so the left column and top row are never drawn.
; ----------------------------------------------------------------------
L_83AC:
sec ; 83AC no borrow
lda lineCurCol ; 83AD current column of the walk, in fat pixels
sbc shotLineWorkVars ; 83AF relative to the window's left edge
beq L_83E9 ; 83B2 exactly on the left edge - skip it
bcc L_83E9 ; 83B4 left of the window - skip
cmp #$38 ; 83B6 7 cells x 8 fat pixels = 56
bcs L_83E9 ; 83B8 right of the window - skip
clc ; 83BA clear carry
adc #$64 ; 83BB character column 25 = fat pixel column 100
tax ; 83BD X = fat pixel column on screen
sec ; 83BE no borrow
lda lineCurRow ; 83BF current row of the walk
sbc D_82B7 ; 83C1 relative to the window's top edge
beq L_83E9 ; 83C4 exactly on the top edge - skip
bcc L_83E9 ; 83C6 above the window - skip
cmp #$28 ; 83C8 5 cells x 8 fat pixels = 40
bcs L_83E9 ; 83CA below the window - skip
clc ; 83CC clear carry
adc #$04 ; 83CD character row 1 = fat pixel row 4
tay ; 83CF Y = fat pixel row on screen
lda D_82B9 ; 83D0 endpoint visibility flags
cmp #$02 ; 83D3 only the target is in view?
beq L_83E4 ; 83D5 then the shooter is off screen and the whole visible line is solid
lda D_82BC ; 83D7 muzzle gap countdown
bmi L_83E4 ; 83DA already negative - past the gap, plot the line
bne L_83E9 ; 83DC still positive - inside the 4-step gap, plot nothing
jsr plotDiamondMarker ; 83DE exactly 0 - this is the muzzle, draw the little diamond
jmp L_83E9 ; 83E1 and step on
L_83E4:
lda #$01 ; 83E4 colour 1 = white
jsr plotFatPixel ; 83E6 plot one fat multicolour pixel at (X,Y)
; ----------------------------------------------------------------------
; Advance the walk. A BOOMER's shot ($923D = 2) takes three steps for every plotted pixel, so its
; tracer comes out dashed.
; ----------------------------------------------------------------------
L_83E9:
dec D_82BC ; 83E9 one step of the gap countdown used up
lda lineStepsLeft ; 83EC steps left on the line
beq L_8409 ; 83EE line finished
jsr stepLine ; 83F0 advance zp_92/zp_93 by one cell along the line
lda attackerType ; 83F3 $923D = type of the shooting unit
cmp #$02 ; 83F6 2 = BOOMER
bne L_8406 ; 83F8 anything else plots every step
dec D_82BC ; 83FA BOOMER: burn a second gap count ...
jsr stepLine ; 83FD ... and a second step ...
dec D_82BC ; 8400 ... and a third gap count ...
jsr stepLine ; 8403 ... and a third step, leaving gaps in the tracer
L_8406:
jmp L_83AC ; 8406 back for the next point
L_8409:
lda #$8F ; 8409 $8F = redraw pending, 15 frames of countdown (the IRQ stops at $80)
sta viewRedrawTimer ; 840B $9234: the view is repainted 15 frames from now, erasing the line
L_840E:
rts ; 840E done (also the early exit when neither endpoint is visible)
; ----------------------------------------------------------------------
; rebaseLineCoordsToMin - Subtracts the smallest of the three column values (window left $82B5,
; shooter zp_93, target zp_95) from all three, and likewise the smallest of the three row values
; ($82B7, zp_92, zp_94), so the smallest becomes 0. That is what keeps the following multiply by 8
; inside a byte.
; In: $82B5, $82B7, zp_92-zp_95
; Out: the same six values, rebased
; Called from: $838C (drawShotLineInViewport)
; ----------------------------------------------------------------------
rebaseLineCoordsToMin:
lda shotLineWorkVars ; 840F window left edge
cmp lineCurCol ; 8412 compare with the shooter column
bcs L_841A ; 8414 left >= shooter, so the minimum is one of the two endpoints
cmp lineEndCol ; 8416 left < shooter: compare with the target column
bcc L_844C ; 8418 left is smaller than both - it is the minimum
L_841A:
lda lineCurCol ; 841A shooter column
cmp lineEndCol ; 841C compare with the target column
bcc L_8436 ; 841E shooter is the minimum
sec ; 8420 no borrow
lda shotLineWorkVars ; 8421 target column is the minimum: rebase the window edge ...
sbc lineEndCol ; 8424 ... by subtracting it ...
sta shotLineWorkVars ; 8426 ... and store it back
sec ; 8429 no borrow
lda lineCurCol ; 842A rebase the shooter column ...
sbc lineEndCol ; 842C ... the same way ...
sta lineCurCol ; 842E ... and store it
lda #$00 ; 8430 the minimum itself becomes 0
L_8432:
sta lineEndCol ; 8432 zp_95 = 0
beq L_8461 ; 8434 always taken - now do the rows
L_8436:
sec ; 8436 no borrow
lda shotLineWorkVars ; 8437 shooter column is the minimum: rebase the window edge
sbc lineCurCol ; 843A subtract it
sta shotLineWorkVars ; 843C store it back
sec ; 843F no borrow
lda lineEndCol ; 8440 rebase the target column
sbc lineCurCol ; 8442 subtract it
sta lineEndCol ; 8444 store it back
lda #$00 ; 8446 the minimum becomes 0
sta lineCurCol ; 8448 zp_93 = 0
beq L_8461 ; 844A always taken - now do the rows
L_844C:
sec ; 844C no borrow
lda lineCurCol ; 844D window edge is the minimum: rebase the shooter column
D_844F:
sbc shotLineWorkVars ; 844F subtract it
sta lineCurCol ; 8452 store it back
sec ; 8454 no borrow
lda lineEndCol ; 8455 rebase the target column
sbc shotLineWorkVars ; 8457 subtract it
sta lineEndCol ; 845A store it back
L_845C:
lda #$00 ; 845C the minimum becomes 0
sta shotLineWorkVars ; 845E $82B5 = 0
; ----------------------------------------------------------------------
; Exactly the same three-way minimum for the rows.
; ----------------------------------------------------------------------
L_8461:
lda D_82B7 ; 8461 window top edge
cmp lineCurRow ; 8464 compare with the shooter row
bcs L_846C ; 8466 top >= shooter, so an endpoint holds the minimum
cmp lineEndRow ; 8468 compare with the target row
D_846A:
bcc L_849C ; 846A the top edge is smaller than both
L_846C:
lda lineCurRow ; 846C shooter row
cmp lineEndRow ; 846E compare with the target row
bcc L_8487 ; 8470 shooter row is the minimum
sec ; 8472 no borrow
lda D_82B7 ; 8473 target row is the minimum: rebase the top edge
sbc lineEndRow ; 8476 subtract it
sta D_82B7 ; 8478 store it back
sec ; 847B no borrow
lda lineCurRow ; 847C rebase the shooter row
L_847E:
sbc lineEndRow ; 847E subtract it
sta lineCurRow ; 8480 store it back
lda #$00 ; 8482 the minimum becomes 0
sta lineEndRow ; 8484 zp_94 = 0
rts ; 8486 done
L_8487:
sec ; 8487 no borrow
lda D_82B7 ; 8488 shooter row is the minimum: rebase the top edge
sbc lineCurRow ; 848B subtract it
sta D_82B7 ; 848D store it back
sec ; 8490 no borrow
lda lineEndRow ; 8491 rebase the target row
D_8493:
sbc lineCurRow ; 8493 subtract it
sta lineEndRow ; 8495 store it back
lda #$00 ; 8497 the minimum becomes 0
sta lineCurRow ; 8499 zp_92 = 0
rts ; 849B done
L_849C:
sec ; 849C no borrow
lda lineCurRow ; 849D top edge is the minimum: rebase the shooter row
sbc D_82B7 ; 849F subtract it
sta lineCurRow ; 84A2 store it back
L_84A4:
sec ; 84A4 no borrow
lda lineEndRow ; 84A5 rebase the target row
sbc D_82B7 ; 84A7 subtract it
L_84AA:
sta lineEndRow ; 84AA store it back
lda #$00 ; 84AC the minimum becomes 0
sta D_82B7 ; 84AE $82B7 = 0
rts ; 84B1 done
; ----------------------------------------------------------------------
; scaleLineCoordsToFatPixels - Converts the two line endpoints from map cells to fat pixels: row*8+4
; and column*8+3, i.e. the middle of the cell in the 8x8-fat-pixel grid the viewport uses. Three of
; the four cases saturate to $FF on overflow; the zp_92 (shooter row) case is missing its LDA #$FF, so
; it keeps the wrapped value instead - harmless because rebaseLineCoordsToMin has already brought all
; four values below 32 cells in practice.
; In: zp_92-zp_95 in map cells
; Out: zp_92-zp_95 in fat pixels
; Called from: $83A1 (drawShotLineInViewport)
; ----------------------------------------------------------------------
scaleLineCoordsToFatPixels:
lda lineEndRow ; 84B2 target row, in cells
asl a ; 84B4 x2 ...
asl a ; 84B5 x4 ...
asl a ; 84B6 x8 fat pixels per cell
bcc L_84BD ; 84B7 no overflow - add the half-cell offset
lda #$FF ; 84B9 overflow - clamp to the last fat pixel row
bmi L_84C0 ; 84BB always taken ($FF is negative)
L_84BD:
clc ; 84BD clear carry
adc #$04 ; 84BE +4 = the vertical centre of the cell
L_84C0:
sta lineEndRow ; 84C0 zp_94
lda lineEndCol ; 84C2 target column, in cells
asl a ; 84C4 x2 ...
asl a ; 84C5 x4 ...
asl a ; 84C6 x8
L_84C7:
bcc L_84CD ; 84C7 no overflow
lda #$FF ; 84C9 clamp
bmi L_84D0 ; 84CB always taken
L_84CD:
clc ; 84CD clear carry
adc #$03 ; 84CE +3 = the horizontal centre of the cell
L_84D0:
sta lineEndCol ; 84D0 zp_95
lda lineCurRow ; 84D2 shooter row, in cells
asl a ; 84D4 x2 ...
asl a ; 84D5 x4 ...
asl a ; 84D6 x8
bcc L_84DB ; 84D7 no overflow - add the offset
bmi L_84DE ; 84D9 overflowed: the LDA #$FF of the other three cases is missing here, so the wrapped value is kept
L_84DB:
clc ; 84DB clear carry
adc #$04 ; 84DC +4 = the vertical centre of the cell
L_84DE:
sta lineCurRow ; 84DE zp_92
lda lineCurCol ; 84E0 shooter column, in cells
asl a ; 84E2 x2 ...
asl a ; 84E3 x4 ...
asl a ; 84E4 x8
bcc L_84EB ; 84E5 no overflow
lda #$FF ; 84E7 clamp
bmi L_84EE ; 84E9 always taken
L_84EB:
clc ; 84EB clear carry
scaleColAddHalfCell:
adc #$03 ; 84EC +3 = the horizontal centre of the cell
L_84EE:
sta lineCurCol ; 84EE zp_93
rts ; 84F0 done
; ----------------------------------------------------------------------
; plotDiamondMarker - Plots the four colour-1 fat pixels at (X-1,Y-1), (X+1,Y-1), (X-1,Y+1) and
; (X+1,Y+1): the small diamond drawn at the muzzle end of a shot tracer. X and Y are left exactly as
; they were on entry.
; In: X = fat pixel column, Y = fat pixel row
; Out: four bitmap pixels set; X and Y preserved
; Called from: $83DE (drawShotLineInViewport, on the fifth step out of the muzzle)
; ----------------------------------------------------------------------
plotDiamondMarker:
dey ; 84F1 one fat pixel up
dex ; 84F2 one fat pixel left
lda #$01 ; 84F3 colour 1 = white
jsr plotFatPixel ; 84F5 top-left corner of the diamond
inx ; 84F8 two to the right ...
inx ; 84F9 ... = one right of the centre
lda #$01 ; 84FA colour 1
jsr plotFatPixel ; 84FC top-right corner
dex ; 84FF two to the left ...
dex ; 8500 ... back to one left of the centre
iny ; 8501 two down ...
iny ; 8502 ... = one below the centre
lda #$01 ; 8503 colour 1
jsr plotFatPixel ; 8505 bottom-left corner
inx ; 8508 two to the right ...
inx ; 8509 ... = one right of the centre
lda #$01 ; 850A colour 1
jsr plotFatPixel ; 850C bottom-right corner
dex ; 850F back to the entry column ...
dey ; 8510 ... and the entry row
plotDiamondDone:
rts ; 8511 done, X and Y unchanged
; comSpeedDisplayTable - the COMSPEED figure shown on the STATS tab, indexed by the comcen speed class
; 0-5 held in $92B4 (us) and $92B5 (them). Class 4 is the immobile comcen and prints 0.
comSpeedDisplayTable:
.byte $01,$02,$02,$02,$00,$03 ; 8512 ...... class 0 -> 1, class 1-3 -> 2, class 4 -> 0 (immobile), class 5 -> 3
; ----------------------------------------------------------------------
; drawStatsTab - Fills in the numbers of the console's STATS panel. With the text window left edge at
; column 10 it prints two columns - US at column 9 (relative), THEM at column 14 - of: living GRUNTS,
; RIDERS, BOOMERS and SPIES (rows 9-12, counted by scanning the 50 unit slots downwards from each
; side's comcen), DRONES (row 14), MISSILES (row 15), COMSPEED (row 17), TER PTS (row 18), RECYCLER
; (row 19) and the five-character map name (row 20). Clearing exchangeAppliedFlag $920E marks the
; panel as refreshed for this turn.
; In: comcenUnit $9236, unitTypeTable $F76C, $92AE/$92AF drones, $92B0/$92B1 missiles, $92B4/$92B5
; comcen speed, $92AC terrain points, $92A6 recycler mode, playerSide $0B9F, map name $0BA9
; Out: the STATS text drawn; $920E = 0; zp_18, zp_1A, zp_20, zp_21, zp_3B, zp_3D, zp_3F, zp_40 used
; Called from: $1460 and $14D3 in the main program (console tab drawing and refresh)
; ----------------------------------------------------------------------
drawStatsTab:
lda #$0A ; 8518 text window starts at screen column 10
sta textWindowLeft ; 851A zp_3B, added to every text column below
lda #$00 ; 851C 0 = refreshed
sta exchangeAppliedFlag ; 851E $920E: consume the 'a turn was exchanged, redraw the stats' request
lda #$61 ; 8521 colour byte $61: %01 pixels blue, %10 pixels white
sta textColour ; 8523 zp_3D = colour written with every printed character
lda #$09 ; 8525 first data row
sta textCursorRow ; 8527 zp_40 = text cursor row 9 (GRUNTS)
lda #$00 ; 8529 unit type 0 = GRUNT
sta scratch20 ; 852B zp_20 = the type being counted
; ----------------------------------------------------------------------
; Outer loop over the four fighting unit types (0 GRUNT, 1 RIDER, 2 BOOMER, 3 SPY); one text row each.
; Inner pass counts our side first (column 9) and then the opponent's (column 14).
; ----------------------------------------------------------------------
L_852D:
ldx #$01 ; 852D one more column to print after this one
stx scratch21 ; 852F zp_21 = columns still to do
lda #$09 ; 8531 column 9 = the US column
ldy comcenUnit ; 8533 start the scan at our own comcen (49 or 99)
L_8536:
jsr setTextColumn ; 8536 position the text cursor at that column
lda #$00 ; 8539 count = 0
sta scratch18 ; 853B zp_18 = the running count
lda #$32 ; 853D 50 unit slots per side
sta scratch1A ; 853F zp_1A = slots still to scan
L_8541:
lda unitTypeTable,y ; 8541 type/flags byte of this unit ($F76C + unit)
bmi L_854E ; 8544 bit 7 set = the unit is dead, skip it
and #$07 ; 8546 type is in bits 0-2 (0 GRUNT, 1 RIDER, 2 BOOMER, 3 SPY, 4 COMCEN)
cmp scratch20 ; 8548 is it the type being counted?
bne L_854E ; 854A no - next slot
inc scratch18 ; 854C yes - one more of this type alive
L_854E:
dey ; 854E next unit index downwards (comcen is the highest of its side)
dec scratch1A ; 854F one slot fewer
bne L_8541 ; 8551 50 slots not done yet
ldx textCursorCol ; 8553 X = the column just set
lda scratch18 ; 8555 the count
jsr printNumberRightAligned ; 8557 print it right-aligned in a 3-character field
dec scratch21 ; 855A one column done
L_855C:
bmi L_8568 ; 855C both columns done - next unit type
lda comcenUnit ; 855E own comcen index
eor #$52 ; 8561 EOR $52 swaps 49 <-> 99, i.e. the other side's comcen
tay ; 8563 scan starts there
lda #$0E ; 8564 column 14 = the THEM column
bne L_8536 ; 8566 always taken - print the second column
L_8568:
inc textCursorRow ; 8568 next text row
inc scratch20 ; 856A next unit type
lda scratch20 ; 856C which type is next?
cmp #$04 ; 856E types 0-3 only; type 4 (COMCEN) is not listed
bcc L_852D ; 8570 more types to do
; ----------------------------------------------------------------------
; Fixed rows below the unit table: DRONES (14), MISSILES (15), COMSPEED (17), TER PTS (18), RECYCLER
; (19) and the map name (20).
; ----------------------------------------------------------------------
inc textCursorRow ; 8572 skip a row - DRONES is on row 14
ldx #$09 ; 8574 US column
lda dronesLeft ; 8576 $92AE = our drones left
jsr printNumberRightAligned ; 8579 print it
ldx #$0E ; 857C THEM column
lda opponentDronesLeft ; 857E $92AF = the opponent's drones left
jsr printNumberRightAligned ; 8581 print it
inc textCursorRow ; 8584 row 15 = MISSILES
ldx #$09 ; 8586 US column
lda missilesLeft ; 8588 $92B0 = our missiles left
jsr printNumberRightAligned ; 858B print it
ldx #$0E ; 858E THEM column
lda opponentMissilesLeft ; 8590 $92B1 = the opponent's missiles left
jsr printNumberRightAligned ; 8593 print it
inc textCursorRow ; 8596 row 16 ...
inc textCursorRow ; 8598 ... row 17 = COMSPEED
ldx #$09 ; 859A US column
ldy comcenSpeedClass ; 859C $92B4 = our comcen speed class 0-5
lda comSpeedDisplayTable,y ; 859F translate it into the printed figure
jsr printNumberRightAligned ; 85A2 print it
ldx #$0E ; 85A5 THEM column
ldy opponentComcenSpeedClass; 85A7 $92B5 = the opponent's speed class
lda comSpeedDisplayTable,y ; 85AA translate it
L_85AD:
jsr printNumberRightAligned ; 85AD print it
inc textCursorRow ; 85B0 row 18 = TER PTS
ldx #$09 ; 85B2 US column
lda playerSide ; 85B4 $0B9F = which side we are
eor #$01 ; 85B7 the OTHER side's index - note this is the opposite indexing to the RECYCLER row below, so one of the two rows is probably swapped
L_85B9:
tay ; 85B9 index the per-side table
lda terrainPointsBySide,y ; 85BA $92AC[side EOR 1]
jsr printNumberRightAligned ; 85BD print it
ldx #$0E ; 85C0 THEM column
ldy playerSide ; 85C2 our own side index
lda terrainPointsBySide,y ; 85C5 $92AC[side]
jsr printNumberRightAligned ; 85C8 print it
inc textCursorRow ; 85CB row 19 = RECYCLER
lda #$09 ; 85CD US column
jsr setTextColumn ; 85CF position the cursor there
ldy playerSide ; 85D2 our side
lda recyclerMode,y ; 85D5 $92A6[side] = FULL / HALF / NONE
jsr printOptionWord ; 85D8 print the 4-character option word
statsPrintThemRecycler:
lda #$0E ; 85DB THEM column
jsr setTextColumn ; 85DD position the cursor there
lda playerSide ; 85E0 our side ...
eor #$01 ; 85E3 ... EOR 1 = the opponent
tay ; 85E5 index the table
lda recyclerMode,y ; 85E6 $92A6[side EOR 1]
jsr printOptionWord ; 85E9 print the option word
D_85EC:
inc textCursorRow ; 85EC row 20 = the map name
lda #$09 ; 85EE column 9 for side 0 ...
ldy playerSide ; 85F0 which side are we?
beq L_85F7 ; 85F3 side 0 - use column 9
lda #$0E ; 85F5 side 1 - the name is printed under the THEM column instead
L_85F7:
jsr setTextColumn ; 85F7 position the cursor
lda #$A9 ; 85FA $0BA9 = the five-character map name, terminated at $0BAE
ldy #$0B ; 85FC high byte of the string address
jmp printString ; 85FE print it and return through the text engine
; ----------------------------------------------------------------------
; printNumberRightAligned - prints the unsigned byte in A as decimal, right-aligned in a
; three-character field that starts at text column X of the current text row. It works out how many
; blank glyphs have to go in front (2 for 0-9, 1 for 10-99, none for 100-255), patches that count and
; the value into three immediate operands further down ($8622, $8627, $8629), positions the text
; cursor and then prints padding + number through the resident $C000 text engine. Only the low byte is
; ever printed: the LDY #$00 at the entry wipes whatever the caller left in Y, which makes the CPY
; #$00 at $8607 and the whole $8626/$8627 patch vestiges of an earlier 16-bit version of this routine
; (see the notes on those lines).
; In: A = value 0-255, X = column relative to textWindowLeft (zp_3B). Also uses zp_40 (text row),
; zp_3D (colour), zp_3E (fill glyph, 0 = blank) and needs decimalValueHigh zp_7E to be 0 - it
; always is, because convertToDecimalDigits clears it at $C1DD after every conversion.
; Out: up to 3 cells printed and the cursor left just past the number; zp_3F = X; the three immediate
; operands at $8622/$8627/$8629 patched; A, X, Y and zp_18-zp_1F clobbered (printDecimal uses
; them).
; Called from: drawStatsTab only - $8557 (the GRUNTS/RIDERS/BOOMERS/SPIES counts, both columns),
; $8579/$8581 (DRONES), $858B/$8593 (MISSILES), $85A2/$85AD (COMSPEED), $85BD/$85C8 (TER PTS).
; XREF also lists the same call sites under game/mapGenerator6F00; that is a label-merge artifact
; - variant A has completely different code at $8557 and at $8601 (bytes 'a9 fe 8d f8 90' there).
; Number printer for the console STATS tab. Right alignment is done the cheap way: count the digits
; first, print that many blanks, then let printDecimal ($C17D) emit the digits with no leading zeros.
; The value and the pad count are carried to the print calls in self-modified immediate operands
; rather than on the stack, because updateCursorPointers ($C099) preserves only X and Y, not A.
; ----------------------------------------------------------------------
printNumberRightAligned:
ldy #$00 ; 8601 high byte of the number to print: always 0 here - the STATS fields are all 8-bit counts
stx textCursorCol ; 8603 X held the target column (relative to textWindowLeft zp_3B); make it the text cursor column zp_3F
ldx #$00 ; 8605 X now counts the blank glyphs to print in front of the number; start by assuming none
cpy #$00 ; 8607 dead test: would leave the pad count at 0 when a 16-bit caller passed a non-zero high byte in Y, but $8601 has just forced Y = 0
bne emitPaddedNumber ; 8609 branch never taken (Y is always 0) - leftover of the 16-bit version of this routine
; ----------------------------------------------------------------------
; Pad count: how many blanks are needed so that the digits end at the right edge of the
; three-character field. X = 3 - number of decimal digits. In variant B this is just an internal
; label; the JSRs to $860B in XREF come from variant A, where a different routine (its OFF/ON option
; editor) starts on this byte.
; ----------------------------------------------------------------------
countNumberPadChars:
cmp #$64 ; 860B is the value 100 or more, i.e. does it already fill all three columns?
bcs emitPaddedNumber ; 860D 100-255: three digits, no padding at all
inx ; 860F under 100: at least one blank in front
cmp #$0A ; 8610 is the value 10 or more?
bcs emitPaddedNumber ; 8612 10-99: two digits, so the one blank counted above is enough
inx ; 8614 0-9: a single digit, so two blanks in front
; ----------------------------------------------------------------------
; Self-modifying code. The three immediates below are written here on every call:
; $8622 (operand of the LDA at $8621) = number of blanks to print
; $8627 (operand of the LDY at $8626) = middle byte handed to printDecimal - always 0
; $8629 (operand of the LDA at $8628) = the value to print
; Nothing else in either overlay writes these three bytes.
; ----------------------------------------------------------------------
emitPaddedNumber:
stx padCountPatch+1 ; 8615 patch the blank count into the LDA #imm at $8621 that feeds printFillChars
sty valueMidPatch+1 ; 8618 patch the (always zero) middle byte into the LDY #imm at $8626 that feeds printDecimal
sta valueLowPatch+1 ; 861B patch the value itself into the LDA #imm at $8628
jsr updateCursorPointers ; 861E $C099: absolute column = zp_3F + textWindowLeft zp_3B, row = zp_40; sets the bitmap pointer zp_B4/B5 and the screen pointer zp_B6/B7. It preserves X and Y but destroys A - which is why the value had to be patched in above
padCountPatch:
lda #$FF ; 8621 operand $8622 patched at $8615: 0, 1 or 2 leading blanks
jsr bootReceiveSectorData ; 8623 printFillChars at $C0EF of the resident $C000 text engine (the listing shows the boot loader's name for the same address): prints the fill glyph zp_3E - normally 0, the blank - A times, advancing the cursor one cell each time (also textEngineC000:printFillChars)
valueMidPatch:
ldy #$FF ; 8626 operand $8627 patched at $8618: middle byte of printDecimal's 24-bit value, always 0
valueLowPatch:
lda #$FF ; 8628 operand $8629 patched at $861B: low byte = the number to print
jmp printDecimal ; 862A tail-call printDecimal $C17D (A = low, Y = middle, zp_7E = high): converts by repeated division by 10 and prints the digits without leading zeros, then returns to drawStatsTab
; ----------------------------------------------------------------------
; setTextColumn - moves the text cursor to column A of the current row and recomputes the bitmap and
; screen-RAM pointers for that character cell by tail-calling updateCursorPointers ($C099), which
; leaves X and Y untouched.
; In: A = column relative to textWindowLeft (zp_3B; 10 while the STATS tab is being drawn), zp_40 =
; text row.
; Out: zp_3F = A, zp_B4/B5 = bitmap address of the cell, zp_B6/B7 = screen RAM address; A clobbered, X
; and Y preserved.
; Called from: drawStatsTab only - $8536 (the US/THEM column of each unit-count row), $85CF and $85DD
; (the two RECYCLER words) and $85F7 (the MAP ID column). The game/mapGenerator6F00 callers in
; XREF are label-merge artifacts; variant A holds unrelated code at $862D.
; Two-instruction helper used wherever the STATS tab jumps to a fixed column without printing a
; number: column 9 is the US column, column 14 the THEM column (both relative to textWindowLeft 10).
; ----------------------------------------------------------------------
setTextColumn:
sta textCursorCol ; 862D new cursor column, counted from the left edge of the text window (zp_3B)
jmp updateCursorPointers ; 862F $C099 recomputes zp_B4/B5 and zp_B6/B7 for cell (zp_3B + zp_3F, zp_40) and returns straight to the caller
; ----------------------------------------------------------------------
; drawComcenSchematic - unpacks the RLE-compressed comcen picture at comcenSchematicRle ($7C6E)
; straight into the hires bitmap for the console REPAIR tab: a 20x10 character (160x80 pixel) side
; view of the tracked comcen vehicle with its dish and antenna, drawn at character row 10, column 13.
; Stream format (the same one unpackRleTo0200 $67CB uses): byte 0 is the escape marker ($13 here);
; afterwards any byte other than the marker is a literal, and 'marker, count, value' emits value count
; times, with count 0 meaning 256. Runs are allowed to cross the 160-byte row boundary, which is why
; the run value is stacked over the pointer advance at $8678/$8686.
; In: the compressed stream at $7C6E-$7FCD. No registers.
; Out: 10 x 160 bytes written into the bitmap from $A000 + 10*320 + 13*8 = $ACE8 with a 320-byte row
; stride; rleMarkerByte $9246 = $13, rleUnpackFlag $9247 = 0, fetchRleByte's self-modified operand
; $681B/$681C left just past the end of the stream; zp_1A (row counter), zp_1B (run count),
; zp_B4/B5 (bitmap pointer) and zp_B6/B7 (set but unused) clobbered.
; Called from: drawRepairTab ($176B) in the main program, its only caller. Since the routine lives in
; overlay B, the console REPAIR tab implicitly requires variant B to be resident.
; Set up the decoder: escape marker, source pointer and destination cell. Note that this routine does
; not use the general unpacker at $67CB - that one always writes 512 bytes to $0200 - but it does
; share its byte fetcher (fetchRleByte $681A) and its marker byte ($9246).
; ----------------------------------------------------------------------
drawComcenSchematic:
ldy #$00 ; 8632 index 0 of the stream, and the zero about to be stored in $9247
sty rleUnpackFlag ; 8634 $9247 = 0, exactly as unpackRleTo0200 does at $67D3 - but no code in any unit ever reads this byte, so it is a dead leftover
lda comcenSchematicRle,y ; 8637 byte 0 of the compressed stream is the escape marker ($13 for this picture)
sta rleMarkerByte ; 863A keep the marker in $9246 for the compare at $865E (shared with the main program's unpacker)
ldy #$7C ; 863D high byte of the source address...
sty D_681C ; 863F ...poked into the high half of fetchRleByte's self-modified 'lda $XXXX' operand at $681B/$681C
lda #$6F ; 8642 low byte: decoding starts at $7C6F, one past the marker byte
sta D_681B ; 8644 complete the patched source pointer = $7C6F
ldx #$0D ; 8647 left edge of the picture: character column 13, just right of the 22-column map panel
ldy #$0A ; 8649 top edge: character row 10, under the REPAIR tab heading
jsr calcCellPointersXY ; 864B $2AA3: zp_B4/B5 = $A000 + row*320 + col*8 (bitmap cell), zp_B6/B7 = $8C00 + row*40 + col (screen RAM cell, not used here)
lda #$0A ; 864E the picture is 10 character rows tall
sta scratch1A ; 8650 zp_1A counts the rows still to emit
ldy #$00 ; 8652 Y = byte offset inside the current character row, 0..159
; ----------------------------------------------------------------------
; Decoder loop. X is the number of copies of A still to store: it is decremented on entry to each
; pass, and only when it reaches 0 is a new item pulled from the stream. Y walks the 160 bytes of one
; picture row (20 cells x 8 scanlines, stored cell by cell), zp_1A counts the rows. primeRleRunCounter
; is entered once, by falling through from $8652; the per-row restart at $8689 jumps back to
; rleEmitLoop with X and A intact. It is a branch target only in variant A.
; ----------------------------------------------------------------------
primeRleRunCounter:
ldx #$01 ; 8654 prime the run counter with 1 so that the very first pass fetches from the stream
rleEmitLoop:
dex ; 8656 one byte of the current run consumed
bne rleStoreByte ; 8657 run not exhausted: store the same value again without touching the stream
jsr fetchRleByte ; 8659 $681A: fetch the next stream byte into A (and X) and bump the source pointer
ldx #$01 ; 865C assume a literal byte: exactly one store
cmp rleMarkerByte ; 865E is this byte the escape marker that introduces a run?
bne rleStoreByte ; 8661 not the marker: plain literal, store it once
jsr fetchRleByte ; 8663 marker seen - the next byte is the repeat count (0 counts as 256)
rleSaveRunCount:
sta scratch1B ; 8666 park the repeat count while the value byte is fetched (this label is a branch target only in variant A)
jsr fetchRleByte ; 8668 third byte of the run: the value to repeat
ldx scratch1B ; 866B X = repeat count; this pass stores the value once and X-1 further passes repeat it
rleStoreByte:
sta (bitmapPtr),y ; 866D write one bitmap byte; eight consecutive bytes are the eight scanlines of one character cell
iny ; 866F next byte of the picture row
cpy #$A0 ; 8670 160 bytes = 20 character cells x 8 scanlines = one full picture row
bcc rleEmitLoop ; 8672 row not finished yet, keep decoding
; ----------------------------------------------------------------------
; End of a picture row: step the bitmap pointer down one character row. A (the current run value) and
; X (the remaining repeat count) are carried across untouched, so a run may span rows.
; ----------------------------------------------------------------------
dec scratch1A ; 8674 one of the 10 picture rows done
beq schematicDone ; 8676 all 10 rows drawn - the picture is complete
pha ; 8678 save the current run value across the pointer arithmetic
clc ; 8679 clear carry for the 16-bit add below
lda bitmapPtr ; 867A advance the bitmap pointer...
adc #$40 ; 867C ...by $0140 = 320 bytes = one character row of the 40-column bitmap ($40 into the low byte...
sta bitmapPtr ; 867E ...store the new low byte
lda bitmapPtrHi ; 8680 ...carry into the high byte...
adc #$01 ; 8682 ...plus 1, completing the +$140
sta bitmapPtrHi ; 8684 zp_B4/B5 now points at the leftmost cell of the next picture row (still column 13)
pla ; 8686 restore the run value so a run that crossed the row boundary keeps storing the same byte
ldy #$00 ; 8687 back to the left edge of the picture row
beq rleEmitLoop ; 8689 always taken (Y was just loaded with 0): continue decoding into the new row
schematicDone:
rts ; 868B return to drawRepairTab, which then paints the colour cells over the picture
; ================================================================== From here to the end of the
; overlay ($868C-$87FE) variant B contains no code at all: five user- interface captions, the 79-byte
; STATS tab label blob, a 12-byte word table for the film status line and 16 bit-7-terminated message
; strings, then six padding zeroes and the variant flag. Every routine-looking label inside this area
; belongs to variant A, which keeps its custom- scenario dialog code at $868C-$879E - XREF merges both
; variants into one namespace, so names such as setDestroyModeMessage ($86AA),
; showCustomSummaryMessage ($86C3), setPersistentMessage ($8722), appendOnOffWord ($872E),
; showScenarioBannerAndWaitForButton ($8756) and printCentredTableEntry ($8795), as well as the D_/L_
; labels sprinkled through the strings, mean nothing here and only mark where variant A branches or
; stores. The only two interior addresses that matter with variant B resident are $8726 and $8730,
; both patched by the film replay key handler at $40B2. A byte compare of extracted/runtimeImage_A.bin
; and _B.bin shows $868C-$879E differing,
; $879F-$87FE identical in both variants and $87FF differing ($00 in A, $FF in B).
; ==================================================================
;
; strDrone - 'DRONE', bit 7 on the final E. Caption of the drones-left box on the DRONE screen,
; printed by overlay B code at $7018-$7033 with textColour $CF at column 32, row 7.
strDrone:
.byte "D" ; 868C 'D' of DRONE
D_868D:
.byte $52 ; 868D 'R' - label from variant A, which stores here at $8213/$86AC
L_868E:
.byte $4F ; 868E 'ONE' with bit 7 on the E as the string terminator
.byte $4E,$C5 ; 868F N.
; strLaunch - 'LAUNCH', caption under the 2x2 LAUNCH button icon of the DRONE screen; printed at
; $70B6-$70C5 at column 32, row 14.
strLaunch:
.byte "LAUNC",'H'|$80 ; 8691 6 bytes, terminated by 'H' | $80
; strStrike - 'STRIKE', caption under the 2x2 STRIKE button icon of the DRONE screen; printed by the
; code at $70D9-$70EF with textColour $C7 at column 32, row 18.
strStrike:
.byte "STR" ; 8697 'STR'
D_869A:
.byte $49 ; 869A 'I' - variant A writes here ($8221/$86BA); no meaning in variant B
D_869B:
.byte $4B ; 869B 'KE' with bit 7 on the E
.byte $C5 ; 869C .
; strFire - 'FIRE', caption of the missile FIRE button on the main game screen; printed at
; $7B2E-$7B3D at column 31, row 12.
strFire:
.byte "FIR",'E'|$80 ; 869D 4 bytes, terminated by 'E' | $80
; strDroneAlertTwoLine - 'DRONE' + CR + 'ALERT': the two-line label of the top-right DRONE ALERT
; panel, printed at $7B67-$7B7A with textWindowLeft 31, column 0, row 3. The $0D is the text engine's
; newline, so the two words land on consecutive rows. What flashes is the panel colour
; (droneAlertColour $7B43, $02 or $20), not this text.
strDroneAlertTwoLine:
.byte "DRONE",$0D,"ALE" ; 86A1 'DRONE', carriage return, 'ALE'
strDroneAlertEnd:
.byte $52 ; 86AA 'RT' with bit 7 on the T (terminator)
.byte $D4 ; 86AB .
; statsTabLabels - the entire left-hand label column of the console STATS tab as ONE bit-7- terminated
; string with embedded carriage returns ($0D), printed by a single printString call at
; $161D-$1621 in the main program (textWindowLeft 10, column 3, right after ownerNameString $0B7F).
; Each $0D drops one row, so the leading $0D lands the first label on row 9:
; row 9 GRUNTS, 10 RIDERS, 11 BOOMERS, 12 SPIES, (13 blank), 14 DRONES, 15 MISSILES,
; (16 blank), 17 COMSPEED, 18 TER PTS, 19 RECYCLER, 20 MAP ID.
; The matching numbers are printed separately by drawStatsTab ($8518) into column 9 (US) and column 14
; (THEM). 79 bytes, $86AC-$86FA.
statsTabLabels:
.byte $0D,"GRUNTS",$0D,"RIDERS",$0D,"BOOMERS",$0D; 86AC CR, 'GRUNTS', CR, 'RIDERS', CR, 'BOOMERS', CR
statsLabelsSpies:
.byte $53 ; 86C3 'SP' - split here only because variant A has a routine entry on this byte
.byte $50 ; 86C4 P
L_86C5:
.byte $49 ; 86C5 'IES' CR CR 'DRO' - end of the SPIES row, a blank row, then DRONES
.byte $45,$53,$0D,$0D,$44,$52,$4F; 86C6 ES..DRO
.byte $4E ; 86CD 'NES' CR 'MISS'
.byte $45,$53,$0D,$4D,$49,$53,$53; 86CE ES.MISS
.byte $49 ; 86D5 'ILES' CR CR 'CO' - end of MISSILES, a blank row, then COMSPEED
.byte $4C,$45,$53,$0D,$0D,$43,$4F; 86D6 LES..CO
.byte $4D ; 86DD 'MS'
.byte $53 ; 86DE S
L_86DF:
.byte $50 ; 86DF 'PEED' CR 'TER'
.byte $45,$45,$44,$0D,$54,$45,$52; 86E0 EED.TER
.byte $20 ; 86E7 ' PTS' CR 'REC'
.byte $50,$54,$53,$0D,$52,$45,$43; 86E8 PTS.REC
.byte $59 ; 86EF 'YCLE'
.byte $43,$4C,$45 ; 86F0 CLE
L_86F3:
.byte $52 ; 86F3 'R' CR 'MAP I' + 'D' | $80: last label, bit 7 ends the whole blob
.byte $0D,$4D,$41,$50,$20,$49,$C4; 86F4 .MAP I.
; filmViewWordTable - three fixed 4-byte words used to complete the 'SHOW ****' status line during
; game-film playback: 'US' (padded with two zeroes), 'THEM', 'BOTH'. The film key handler indexes it
; with (playModeFlags $0B9B & 3) * 4 at $4116-$4120 and copies four bytes over $8726-$8729 ($4123)
; before queueing message 6. Each word carries its own bit-7 terminator, so the two padding zeroes of
; the 'US' entry are copied but never printed. Only indices 0-2 occur: the CLR/ HOME handler wraps
; playModeFlags from $83 back to $80 at $4110-$4116.
filmViewWordTable:
.byte $55,$D3,$00,$00 ; 86FB U... entry 0 'US' | $80 plus two pad bytes, then entry 1 'THEM' | $80
.byte $54,$48,$45,$CD ; 86FF THE.
.byte $42 ; 8703 entry 2 'BOTH' with bit 7 on the H
.byte $4F,$54,$C8 ; 8704 OT.
; Message strings. From here on every block is one entry of the message table at $0500 (low bytes) /
; $0524 (high bytes), with the sound played for it in the parallel table at $0612 ($80 = silent).
; Messages are queued by id through queueMessage ($C566) or queueMessageUnlessFilm ($C560), the latter
; dropping the message during solo-trainer or film playback.
;
; msgEndOfFilm - 'END OF FILM.', message id 2, sound 3; queued at $59FC after setFilmPtrToEnd when a
; recorded game film has been replayed to its last frame.
msgEndOfFilm:
.byte "END OF FILM" ; 8707 'END OF FILM'
D_8712:
.byte $AE ; 8712 '.' with bit 7 set = terminator
; msgUnitRecycled - 'UNIT RECYCLED.', message id 5, sound 4; queued with queueMessageUnlessFilm at
; $3EA3 after initUnitRecord, but only when the recycled unit's owner (zp_1A) is the local player.
msgUnitRecycled:
.byte "U" ; 8713 'U'
L_8714:
.byte $4E ; 8714 'NIT RECY'
.byte $49,$54,$20,$52,$45,$43,$59; 8715 IT RECY
.byte $43 ; 871C 'CLED' + '.' | $80
.byte $4C,$45,$44,$AE ; 871D LED.
; msgShowViewTemplate - 'SHOW ****', message id 6, sound 3: the film playback status line. The four
; '*' at $8726-$8729 are overwritten with a filmViewWordTable entry (US / THEM / BOTH) at
; $4123 before the message is queued at $412E, and that entry supplies the bit-7 terminator.
; As stored on disk the template has NO terminator, so printing it unpatched would run straight on
; through 'SPEED *' and only stop at the '!' of 'SPOTTED RECYCLER!' ($8741).
msgShowViewTemplate:
.byte "S" ; 8721 'S'
msgShowViewBody:
.byte $48 ; 8722 'HOW ' - the space before the patched word
.byte $4F,$57,$20 ; 8723 OW
msgShowViewWord:
.byte $2A ; 8726 four-byte patch field: receives 'US'+$80+2 pad / 'THEM'|$80 / 'BOTH'|$80 from filmViewWordTable, written by the film key handler at $4123
.byte $2A,$2A,$2A ; 8727 ***
; msgSpeedTemplate - 'SPEED *', message id 7, sound 3: shown when a digit key changes the film replay
; speed. Pressing '1'-'9' sets stepIntervalFrames $92E3 = 8 - (key - $B1) and stores the raw key code
; $B1-$B9 into $8730 at $40DE; because those codes already have bit 7 set they double as the string
; terminator. Unterminated as stored on disk, like the SHOW template above.
msgSpeedTemplate:
.byte "SPEE" ; 872A 'SPEE'
msgSpeedBody:
.byte $44 ; 872E 'D' and a space
.byte $20 ; 872F
msgSpeedDigit:
.byte $2A ; 8730 one-byte patch field: the pressed digit's key code $B1-$B9 (screen code with bit 7 = terminator), written at $40DE
; msgSpottedRecycler - 'SPOTTED RECYCLER!', message id 8, sound 4; queued with queueMessageUnlessFilm
; at $3F8C right after drawRecyclerOnMap / drawMapCell, i.e. the moment the recycler cell first
; becomes visible to the local player.
msgSpottedRecycler:
.byte "SPO" ; 8731 'SPO'
L_8734:
.byte $54 ; 8734 'TTED REC'
.byte $54,$45,$44,$20,$52,$45,$43; 8735 TED REC
.byte $59 ; 873C 'YCLE'
.byte $43,$4C,$45 ; 873D CLE
L_8740:
.byte $52 ; 8740 'R' + '!' | $80
.byte $A1 ; 8741 .
; msgComcenStunned - 'COMCEN STUNNED!', message id $0C, sound 4; queued at $0741-$0743 by the
; game-tick code in the $0400 block when $9162 reads $FF. The same text is also printed straight into
; the console status area at column 5 by $17C4-$17D4 for as long as comcenStunnedFlag ($922A) bit 7 is
; set.
msgComcenStunned:
.byte "COMCEN STUNNED",'!'|$80; 8742 15 bytes, terminated by '!' | $80
; msgSpottedEnemyComcen - 'SPOTTED ENEMY COMCEN!', message id $0D, sound 4; queued with
; queueMessageUnlessFilm at $36EF when a newly visible enemy unit's type field ($F76C,y & 7) is 4
; (COMCEN). Types 0-2 (GRUNT, RIDER, BOOMER) are spotted silently.
msgSpottedEnemyComcen:
.byte "SPOT" ; 8751 'SPOT'
L_8755:
.byte $54 ; 8755 'T'
msgSpottedComcenBody:
.byte $45 ; 8756 'ED ENEMY'
.byte $44,$20,$45,$4E,$45,$4D,$59; 8757 D ENEMY
.byte $20 ; 875E ' COMCEN'
.byte $43,$4F,$4D,$43,$45,$4E ; 875F COMCEN
L_8765:
.byte $A1 ; 8765 '!' | $80 terminator
; msgSpyUnderAttack - 'SPY UNDER ATTACK!', message id $0E, sound 4; queued with queueMessageUnlessFilm
; at $3B3B when the unit being hit is type 3 (SPY) and its energy field ($F9C4,y & $3F) is still $32 =
; 50, i.e. full - so it fires once, on the first hit only.
msgSpyUnderAttack:
.byte "SPY UNDER ATTACK",'!'|$80; 8766 17 bytes, terminated by '!' | $80
; msgSpottedEnemySpy - 'SPOTTED ENEMY SPY!', message id $0F, sound 4; queued with
; queueMessageUnlessFilm at $36E7, the sibling of the COMCEN case above in the visibility code.
msgSpottedEnemySpy:
.byte "SPOTTED ENEMY SPY",'!'|$80; 8777 18 bytes, terminated by '!' | $80
; The five end-of-game result messages. computeGameResult ($28F1) picks one of them into
; persistentMessageId and queues it at $29A0.
;
; msgWeLostOnPoints - 'WE LOST ON POINTS!', message id $11, sound $0E: the time-out result when the
; LOSING side ($2963-$296B: X, the side whose accumulated damage score is higher) is the local player.
; If it is the other side the code does INY and picks $12 instead. Variant A stores 22 bytes of its
; own code over $8789-$879E, so this string is only intact while variant B is resident - which is
; fine, because results are only shown during the battle.
msgWeLostOnPoints:
.byte "W" ; 8789 'W'
L_878A:
.byte $45 ; 878A 'E LOST O'
.byte $20,$4C,$4F,$53,$54,$20,$4F; 878B LOST O
.byte $4E ; 8792 'N P'
.byte $20,$50 ; 8793 P
msgWeLostOnPointsEnd:
.byte $4F ; 8795 'OINTS' + '!' | $80
.byte $49,$4E,$54,$53,$A1 ; 8796 INTS.
; msgWeWonOnPoints - 'WE WON ON POINTS!', message id $12, sound $0D: the time-out win (losing side !=
; local player). Its first four bytes 'WE W' ($879B-$879E) are the last of the bytes variant A
; overlays; from $879F on the two overlay variants are byte-identical to the end of the block.
msgWeWonOnPoints:
.byte "WE W" ; 879B 'WE W' - the last four bytes that variant A clobbers
.byte "ON ON POINTS",'!'|$80 ; 879F 'ON ON POINTS' + '!' | $80; identical in both overlay variants
; msgItWasADraw - 'IT WAS A DRAW.', message id $13 and the only result message with no sound ($80 in
; the sound table). Chosen when the 16-bit score difference is exactly zero at time-out ($2917-$2929)
; or when gameOverFlags bits 5-6 are both set, i.e. both comcens were knocked out ($297A); both sides
; are then given 250 points.
msgItWasADraw:
.byte "IT WAS A DRAW",'.'|$80 ; 87AC 14 bytes, terminated by '.' | $80
; msgWeWereKnockedOut - 'WE WERE KNOCKED OUT!', message id $14, sound $0E; selected at $298D-$2995
; when the knocked-out side equals the local player. The loser is given 500 points, the winner 150.
msgWeWereKnockedOut:
.byte "WE WERE KNOCKED OUT",'!'|$80; 87BA 20 bytes, terminated by '!' | $80
; msgWeWonByAKnockout - 'WE WON BY A KNOCKOUT!', message id $15, sound $0D: the enemy comcen was
; destroyed (the INY sibling of message $14). The solo trainer's stores to 'msgWeWonByAKnockout,y' at
; $EB29/$EB53 do NOT corrupt this string: there Y is an absolute unit index of the AI side, 50-99, so
; the writes land at $8800-$8831 - the trainer's per-unit order-timestamp table, which it addresses
; through the base $87CE.
msgWeWonByAKnockout:
.byte "WE WON BY A KNOCKOUT",'!'|$80; 87CE 21 bytes, terminated by '!' | $80
; msgGameOver - 'GAME OVER.', message id $16, sound $19; queued first at $2901-$2903 on the time-out
; path, ahead of the points result message.
msgGameOver:
.byte "GAME OVER",'.'|$80 ; 87E3 10 bytes, terminated by '.' | $80
; msgDroneAlert - 'DRONE ALERT!'. This one has no fixed id: overlay A installs its address into
; run-time message slot $20 at $7BA6-$7BAC (ldx #$20 / lda #$ED / ldy #$87 / jsr setMessageSlot
; $C924) just before jumping into the opponent module's battle-start entry. The pointer survives
; the swap to overlay B because this string sits in the tail that both variants hold identically.
; During the battle showDroneAlert ($820F) plays sound 7 and queues id $20 whenever the opponent
; launches a drone, unless playModeFlags $0B9B bit 7 (solo trainer / film playback) is set.
msgDroneAlert:
.byte "DRONE ALERT",'!'|$80 ; 87ED 12 bytes, terminated by '!' | $80
; overlayTailPadding - six unused $00 bytes between the last string and the variant flag. Nothing
; reads them; they are identical in both overlay variants and exist only to push the flag to the very
; last byte of the block.
overlayTailPadding:
.byte $00,$00,$00,$00,$00,$00 ; 87F9 ...... filler up to $87FE
; overlayVariantFlag - identity byte of whichever $6F00-$87FF overlay is currently resident. The
; overlay loader tests bit 7 at $1036 and $1080 to decide whether the variant it wants is already in
; memory or has to be pulled from disk (variant A = track 31 s0-16 + track 32 s0-7, variant B = track
; 33 s0-16 + track 32 s8-15).
overlayVariantFlag:
.byte $FF ; 87FF . $FF = variant B (comcen missile and drone screens) is resident; variant A (map generator and setup) stores $00 here