; ============================================================================ ; $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