; ============================================================================ ; $C000-$CFFF - text/glyph engine, status message system and unit pictures (track 30 sectors 0-15) ; ============================================================================ ; Loaded by $0F07 at start-up over the boot loader. Draws proportional text into the bitmap, manages the ; message queue and the status panel, and holds most of the game's vocabulary (unit names, group names, ; game types, menu text). .setcpu "6502" .include "c64.inc" .include "zeropage.inc" ; ---- references to code/data outside this file ---- contourRuleTable := $0200 messagePtrLoTable := $0500 messagePtrHiTable := $0524 chatMessageLine := $0548 messageSoundTable := $0612 returnToMainMenu := $0AA3 inputLockoutTimer := $0B7D filmPlaybackMode := $0B9B playerSide := $0B9F isSoloTrainer := $0BA5 gameEndReason := $0BA8 checkFirePressed := $0EB1 endGameToMainMenu := $1ADC voicePauseReconnect := $1BF7 drawViewport := $26C1 calcCellPointersXY := $2AA3 clearCurrentScreenWindows := $2DDE enableSprites := $31A3 serviceCommandExchange := $4DA9 queueCmd2 := $561F handleMenuSessionPacket := $56BB drawOverviewMap := $5AD7 sndVoiceSoundId := $67C8 sndVoice2SoundId := $67CA unpackRleTo0200 := $67CB sprite5Shape := $8B40 vicCtrl2Shadow := $9001 sprite5ColourShadow := $900D sprite6ColourShadow := $900E sprite7ColourShadow := $900F sprite5XShadow := $9015 sprite6XShadow := $9016 sprite7XShadow := $9017 sprite5YShadow := $901D sprite6YShadow := $901E sprite7YShadow := $901F spriteXMsbShadow := $9020 spriteEnableShadow := $9022 glyphBuildBuffer := $90B8 newKeyEvent := $90EA textCentreWidth := $90EF currentScreen := $90FB stepDelayTimer := $90FC linkPollDelay := $9163 shotLineTimer := $91C8 gameClock := $91CB lastShownClock := $9208 viewRedrawTimer := $9234 comcenUnit := $9236 lastTypedKey := $9248 messageQueue := $9249 messageQueueHead := $9251 messageQueueTail := $9252 incomingChatLength := $9277 typedLineBuffer := $9278 typedLineScrollSrc := $9279 chatState := $929C rawKeyCode := $92A0 persistentMessageId := $92A1 sideScoreLo := $92A2 sideScoreHi := $92A4 savedTextWindow := $92B6 setupPhaseMode := $92C6 gameEndCountdown := $92C9 decimalDigitBuffer := $92DA soundRequestSlots := $92E7 soundRequestVoice1 := $92E8 soundRequestVoice2 := $92E9 soundRequestMultiVoice := $92EA lastQuarterFlag := $92FA titleColorRow02Cell14 := $985E titleColorRow03Cell06 := $987E unitDisplayFlagsTable := $FB54 nextGameRandom := $FD5D irqVectorHi := $FFFF ; Contents ; -------- ; $C01A printChar the game's single character output routine. ; $C030 restoreRegsAXY reloads A, X and Y from the zp_54/zp_55/zp_56 save slots filled by saveRegsAXY. ; $C03D handleControlChar the only two control codes printChar understands: $0D carriage return (falls ; $C041 printNewline carriage return: cursor column back to 0 and the row down by one (by two when ; $C06A clearTextWindow homes the cursor to the top left of the current window and falls into ; $C075 clearToWindowBottom fills with the blank glyph zp_3E from the cursor cell down to the window ; $C07A clearToEndOfLine the same fill loop with the stop row set to cursor row + 1, i.e. ; $C097 setCursorRow cursor row = A, then recompute the cell pointers (falls into ; $C099 updateCursorPointers recomputes the bitmap and colour pointers of the cursor cell: absolute ; $C0C0 plotCharAndAdvance draws glyph A at the cursor cell, then advances the cursor one column (two ; $C0D7 setFullScreenWindow resets the text window to the whole screen: top row 0, left column 0, ; $C0E8 saveRegsAXY copies A, X and Y into the zp_54/zp_55/zp_56 scratch triple so restoreRegsAXY ; can ; $C0EF printFillChars prints the current blank glyph zp_3E A times through plotCharAndAdvance; a ; count ; $C100 printTableEntry prints entry X of a table of fixed 8-byte, bit-7-terminated strings whose base ; $C121 printStringCentred sets the centring field width to the current window width and tail-calls ; $C12B printNextStringCentred like printStringCentred, but continues with the string that follows the ; $C133 printString sets the string pointer zp_A2/zp_A3 from A/Y and falls into ; printStringAtPointer. ; $C137 printStringAtPointer prints the bit-7-terminated string at zp_A2/zp_A3. ; $C17D printDecimal prints an unsigned 24-bit value in decimal without leading zeros: converts it ; into ; $C183 convertToDecimalDigits converts the 24-bit value in A (low) / Y (middle) / zp_7E (high) to ; $C1E2 printDecimalDigits prints the digit buffer at $92DA from index zp_1E up to and including index ; $C1F0 plotGlyphCell draws glyph A into the bitmap cell at zp_B4/zp_B5 and writes the colour byte ; zp_3D ; $C24B plotGlyphDoubleSize draws the glyph at zp_B2/zp_B3 magnified 2x2 into four character cells ; $C307 plotGlyphLeftHalf quadrant helper of plotGlyphDoubleSize for a left-hand cell: returns at once ; $C315 plotGlyphRightHalf the same for a right-hand cell: returns at once when the mask suppresses ; $C320 expandGlyphNibbles inner loop of the 2x magnifier: takes 4 glyph rows from the work buffer $90B8 ; $C34B colourDoubleSizeCell writes the colour byte zp_3D into the colour cells of the 2x2 block just ; $C367 colourCellPair writes zp_3D into the two horizontally adjacent colour cells at (zp_B6),0 and ; $C37F waitFrames busy-waits A frames by polling the raster register: per frame it waits until ; $C397 waitRasterBelowScreen spins until the raster is at line $E0 (224) or below, i.e. ; $C39F clearMessageLine blanks the status bar at the bottom of the screen: saves the caller's text ; $C3AB setMessageLineWindow defines the text window as the one-row status bar: left column 2, width ; $C3D8 showMessageString prints the bit-7-terminated string A/Y centred in the 36-column status bar and ; $C3E0 printOnMessageLine worker behind showMessageString: remembers the string pointer in the operands ; $C3F9 advanceFillPointer loop helper of the block fill/clear routines: compares the 16-bit pointer ; $C40E restoreTextWindow copies the 12 saved bytes at $92B6 back into zp_39-zp_44 (window geometry, ; $C41B saveTextWindow saves zp_39-zp_44 (the whole text engine state) into the single slot at $92B6 ; and ; $C428 splitDecimalDigits splits the byte in A into three decimal digits by repeated subtraction: ; $C451 checkUiContinue the shared 'may this modal panel keep running' test used by the menu selector, ; $C470 checkSetupUiContinue tail of checkUiContinue, also called on its own by the map main loop during ; $C493 serviceChatDisplay first half of updateStatusMessage: it owns the status bar while the player is ; $C4DA drawTypedChatLine draws the line being typed to the opponent: the bit-7-terminated buffer at ; $C4EF updateStatusMessage per-frame service of the status bar, called from every polling loop. ; $C557 playMessageSound plays the sound effect assigned to message id Y: the sound number comes from ; $C560 queueMessageUnlessFilm queues message id A like queueMessage, but drops it silently while a game ; $C566 queueMessage inserts message id A into the 8-entry ring at $9249, keeping it sorted so that ; $C5C9 decMessageIndex steps the message-queue index in Y back one slot, wrapping from 0 to 7. ; $C5CF incMessageIndex steps the message-queue index in Y on one slot, wrapping from 7 to 0. ; $C5D7 redrawStatusMessage puts the status bar back after a screen has been rebuilt: the message that ; $C5E4 showMessageById looks message id A up in the pointer tables at $0500 (low) / $0524 (high) and ; $C5EE showNextQueuedMessage chooses what the status line should show next and leaves the answer in ; pendingMessageId zp_7A; the actual printing is done by updateStatusMessage on its next pass. ; $C62A clearChatStatusLine empties message slot 0 (the chat line, text at $0548) by storing $9E in its ; first byte. ; $C631 handleMessageReviewKeys per-frame keyboard hook of the status-message service. ; $C6B6 drawStatusScoreLine redraws the bottom status line as ' CLICKS: US: ; THEM:'. ; $C6E5 printPlayerPoints prints one side's 16-bit point total as decimal. ; $C6EF resetMessageQueue throws away every queued status message: the read index is set equal to the ; write index (empty ring), the fallback message and the display timer are cleared, and it falls into ; showNextQueuedMessage so the status line is refreshed immediately. ; $C6FF showStatusMessage prints message id A on the status line immediately, bypassing the queue, and ; plays the sound the per-message table $0612 assigns to it ($80 = silent). ; $C705 showStatusMessageNoSound the tail of showStatusMessage, also used as an entry point of its own when the ; message should be silent: records A as the message now on the status line and prints it. ; $C70A handlePauseKey RUN/STOP pause handling, called once per frame from updateGameFrame. ; $C7A3 sendPauseCommand queues the 2-byte network command $8C (pause/session control) with A as its ; reason byte. ; $C7AA consumeRunStopKey edge test for RUN/STOP. ; $C7C3 showWaitingForOpponent if this is a real modem game and the pause is our own (bit 0 of the flags ; passed in A), makes message $0B 'WAITING FOR OPPONENT...' the persistent status message and queues it. ; $C7D5 handleTextEntryKey the one-key line editor shared by the in-game chat line and the SAVE GAME FILM ; name field. ; $C853 showWorkingMessage puts message 1 'WORKING...' on the status line at once and makes it the ; persistent message, so it stays there until something clears it. ; $C85B promptPressButtonWhenReady shows 'PRESS BUTTON WHEN READY.' centred on the status line and busy-waits for ; a fresh fire-button click, stirring the 24-bit game RNG on every pass. ; $C86D exitIfGameEnded if the end-of-game display has been up long enough this routine never returns: ; it transfers control to the main menu instead. ; $C89A playSound the public sound entry used by about fifty call sites: queues sound id A and ; gives X and Y back to the caller. ; $C8A8 requestSound the core of the sound request queue. ; $C8E9 dequeueSoundRequest takes one pending sound request out of the four slots, scanning from slot 3 ; (the whole-chip slot, highest priority) down to slot 0. ; $C8FA updateComcenMoveSound keeps the engine sound of the local player's command centre in step with its ; movement. ; $C924 setMessageSlot installs a string address into the run-time message pointer tables, so a ; message id can be re-pointed at different text. ; $CD0A showUnitPictureSprites draws the picture of one unit type in the info panel. .org $C000 ; Six small look-up tables that the boot loader kept here; only bitMaskTable_C008 is still used by the ; game. The stale labels bootEntry/bootLoaderStatus/bootLoadSectors belong to boot/fastLoaderC000, ; which occupies the same addresses in the other build of this page. bitPairHighTable: .byte $00,$40,$80 ; C000 .@. bitPairHighTable: a 2-bit value shifted up into bits 7-6 ($00,$40,$80,$C0); unreferenced in this overlay D_C003: .byte $C0 ; C003 fourth entry of bitPairHighTable (the label here is the boot loader's, not the text engine's) ; solidColourByteTable: byteTable, 4 bytes. $00,$55,$AA,$FF - the byte that fills a whole multicolour ; bitmap row with colour 0-3. No reference in the disassembly. solidColourByteTable: .byte $00,$55,$AA,$FF ; C004 .U.. solidColourByteTable: byte that paints a whole multicolour bitmap row in colour 0,1,2 or 3; unreferenced here ; bitMaskTable_C008: byteTable, 8 bytes. $80,$40,$20,$10,$08,$04,$02,$01 - single bit masks, read as ; L_C008,x by the sprite/bit plotting code at $30B9 (ORA, set bit) and $3217 (LDA, test/EOR). bitMaskTable_C008: .byte $80,$40,$20,$10,$08,$04,$02,$01; C008 .@ ..... bitMaskTable_C008: single-bit masks $80..$01 indexed by bit number; read as L_C008,x by the sprite bit plotter at $30B9 (ORA = set) and $3217 (LDA = test/EOR) ; pixelPairMaskTable: byteTable, 4 bytes. $C0,$30,$0C,$03 - the four multicolour pixel-pair masks of a ; bitmap byte. No reference in the disassembly. pixelPairMaskTable: .byte $C0,$30,$0C,$03 ; C010 .0.. pixelPairMaskTable: the four multicolour pixel-pair masks of a bitmap byte; unreferenced here ; pixelPairClearMaskTable: byteTable, 4 bytes. $3F,$CF,$F3,$FC - complements of pixelPairMaskTable. No ; reference in the disassembly. pixelPairClearMaskTable: .byte $3F,$CF,$F3,$FC ; C014 ?... pixelPairClearMaskTable: complements of pixelPairMaskTable (clear one pixel pair); unreferenced here ; unusedC018: unknown, 2 bytes. $A9,$8D - two bytes between the mask tables and printChar; no ; reference, meaning unknown. unusedC018: .byte $A9,$8D ; C018 .. two orphan bytes ($A9,$8D = 'lda #$8D' in the boot loader build); nothing in the overlay reads them ; ---------------------------------------------------------------------- ; printChar - the game's single character output routine. Unless raw-glyph mode is on it strips the ; bit-7 string terminator, sends codes below $20 to handleControlChar and translates $20-$5F through ; the 64-entry ASCII-to-glyph table at $987E, then draws the glyph and advances the cursor. A, X and ; Y are preserved, so callers can print inside their own loops. ; In: A = character ($20-$5F ASCII, $08/$0D control, or a raw glyph index when rawGlyphMode zp_43 ; bit 7 is set); text window zp_39-zp_44; cursor cell pointers zp_B4-zp_B7 ; Out: one glyph drawn into the bitmap plus its colour byte, cursor advanced (zp_3F/zp_40 and ; zp_B2-zp_B7 updated); A/X/Y restored from zp_54-zp_56 ; Called from: printStringAtPointer ($C16E/$C174), printDecimalDigits ($C1E7), drawTypedChatLine ; ($C4E9) and ~15 sites in the main program and the $6F00 overlay ; ---------------------------------------------------------------------- printChar: jsr saveRegsAXY ; C01A stash A/X/Y in zp_54-zp_56 so printing never disturbs the caller's registers bit rawGlyphMode ; C01D raw glyph mode? (zp_43 bit 7 set = A is already a glyph number) bmi L_C02D ; C01F yes - plot A unchanged, no ASCII translation and no control codes and #$7F ; C021 drop bit 7, the marker that ends a string cmp #$20 ; C023 below the first printable character? bcc L_C037 ; C025 $00-$1F is a control code (CR or backspace) sbc #$20 ; C027 make it 0-based; carry is set on this path so exactly $20 is subtracted tay ; C029 use the character as the table index lda titleColorRow03Cell06,y ; C02A asciiToGlyphTable[c-$20] = glyph number 0-$3B (the table only covers $20-$5F) (also graphicsData9300:asciiToGlyphTable) L_C02D: jsr plotCharAndAdvance ; C02D draw the glyph and step the cursor one cell to the right ; ---------------------------------------------------------------------- ; restoreRegsAXY - reloads A, X and Y from the zp_54/zp_55/zp_56 save slots filled by saveRegsAXY. ; Tail of printChar; the viewport renderer borrows the same triple to recover a cell coordinate. ; In: zp_54 (A), zp_55 (X), zp_56 (Y) ; Out: A, X and Y restored; N/Z reflect A ; Called from: printChar ($C03A), map viewport code $5CA1/$5D11/$5D17/$5DE7 ; ---------------------------------------------------------------------- restoreRegsAXY: ldy savedRegY ; C030 the caller's Y ldx savedRegX ; C032 the caller's X sub_C034: lda savedRegA ; C034 A last, so N/Z describe the character just printed rts ; C036 back to the caller L_C037: jsr handleControlChar ; C037 $00-$1F: handle carriage return / backspace jmp restoreRegsAXY ; C03A then restore A/X/Y and return ; ---------------------------------------------------------------------- ; handleControlChar - the only two control codes printChar understands: $0D carriage return (falls ; into printNewline) and $08 backspace (step back one column, draw the blank glyph 0 over that cell, ; step back onto it again). Every other code below $20 is ignored, which is how the $1E marker at the ; head of the chat line prints as nothing. ; In: A = control code ; Out: cursor moved, possibly one cell erased, cell pointers recomputed ; Called from: printChar ($C037) ; ---------------------------------------------------------------------- handleControlChar: cmp #$0D ; C03D carriage return? bne L_C056 ; C03F no - test for backspace ; ---------------------------------------------------------------------- ; printNewline - carriage return: cursor column back to 0 and the row down by one (by two when ; double-size text is selected), then recompute the cell pointers. The window is never scrolled: the ; compare against the window bottom at $C04D-$C051 branches to the next instruction either way. ; In: window/cursor state zp_39-zp_44 ; Out: zp_3F = 0, zp_40 advanced, zp_B4-zp_B7 recomputed ; Called from: handleControlChar (fall through), plotCharAndAdvance ($C0D1) and ~20 sites in the ; main program and the $6F00 overlay ; ---------------------------------------------------------------------- printNewline: lda #$00 ; C041 column 0 = sta textCursorCol ; C043 back to the left edge of the window inc textCursorRow ; C045 down one text row bit doubleSizeText ; C047 double-size characters? (zp_44 bit 7) bpl L_C04D ; C049 no - one row is enough inc textCursorRow ; C04B a double-size glyph is two rows tall, so skip a second row L_C04D: lda textCursorRow ; C04D vestigial: the new cursor row ... cmp textWindowBottom ; C04F ... compared against the window bottom row ... bcc L_C053 ; C051 ... but both outcomes continue at $C053, so a window never scrolls L_C053: jmp updateCursorPointers ; C053 recompute the bitmap/colour pointers of the new cursor cell L_C056: cmp #$08 ; C056 backspace? bne L_C069 ; C058 no - ignore this control code dec textCursorCol ; C05A step the cursor one column back L_C05C: jsr updateCursorPointers ; C05C point the cell pointers at it lda #$00 ; C05F glyph 0 is the blank glyph jsr plotCharAndAdvance ; C061 erase that cell (printing leaves the cursor one column further right again) dec textCursorCol ; C064 so step back onto the erased cell jmp updateCursorPointers ; C066 and recompute its cell pointers L_C069: rts ; C069 unknown control code: do nothing ; ---------------------------------------------------------------------- ; clearTextWindow - homes the cursor to the top left of the current window and falls into ; clearToWindowBottom, so the whole window is filled with the blank glyph zp_3E in the colour zp_3D. ; The standard 'clear this panel' call of the whole game. ; In: window zp_39-zp_3E ; Out: window filled, cursor left at its top left cell ; Called from: main program $1572/$1596/$1768/$261F/$2648/$26B5/$5E11/$6552/$659D ; ---------------------------------------------------------------------- clearTextWindow: lda #$00 ; C06A column 0 = sta textCursorCol ; C06C left edge of the window lda textWindowTop ; C06E first row of the window = sta textCursorRow ; C070 cursor row jsr updateCursorPointers ; C072 compute the cell pointers of that top-left cell ; ---------------------------------------------------------------------- ; clearToWindowBottom - fills with the blank glyph zp_3E from the cursor cell down to the window ; bottom row (exclusive), then puts the cursor back where it started. ; In: cursor zp_3F/zp_40, window zp_3A/zp_3C, blank glyph zp_3E, colour zp_3D ; Out: cells filled; cursor unchanged on exit ; Called from: clearTextWindow (fall through), main program $24B5 ; ---------------------------------------------------------------------- clearToWindowBottom: lda textWindowBottom ; C075 stop when the cursor reaches the row below the window jmp L_C07F ; C077 into the shared fill loop ; ---------------------------------------------------------------------- ; clearToEndOfLine - the same fill loop with the stop row set to cursor row + 1, i.e. it blanks the ; rest of the current line only. Used to pad the status bar after a message and by the overlay ; screens. Both entries patch the stop row into the CMP operand at $C08F+1. ; In: cursor zp_3F/zp_40, window width zp_3C, blank glyph zp_3E, colour zp_3D ; Out: rest of the line filled; cursor restored; the CMP operand at $C090 is rewritten ; Called from: clearMessageLine ($C3A5), printOnMessageLine ($C3F6), drawStatusScoreLine ($C6DF), ; $6F00 overlay $776D/$8049/$811C/$8471 ; ---------------------------------------------------------------------- clearToEndOfLine: lda textCursorRow ; C07A current row ... clc ; C07C clear carry for the +1 adc #$01 ; C07D ... plus one: stop as soon as the fill wraps onto the next line L_C07F: sta L_C08F+1 ; C07F patch the stop row into the CMP at $C08F lda textCursorRow ; C082 remember the cursor row ... pha ; C084 ... on the stack lda textCursorCol ; C085 and the cursor column ... D_C087: pha ; C087 ... too, both are restored when the fill is done L_C088: lda textFillGlyph ; C088 the current blank/fill glyph sub_C08A: jsr plotCharAndAdvance ; C08A print it; the cursor advances and wraps to the next row at the window edge lda textCursorRow ; C08D which row is the cursor on now? L_C08F: cmp #$FF ; C08F patched: stop row (window bottom, or cursor row + 1 for clearToEndOfLine) bcc L_C088 ; C091 keep filling while the cursor is still above the stop row pla ; C093 restore the starting column ... sta textCursorCol ; C094 put the cursor column back pla ; C096 ... and the starting row (falls into setCursorRow, which stores it) ; ---------------------------------------------------------------------- ; setCursorRow - cursor row = A, then recompute the cell pointers (falls into updateCursorPointers). ; The usual 'move to row A' call; the column is left alone. ; In: A = absolute screen row ; Out: zp_40 = A, zp_B4-zp_B7 recomputed ; Called from: clearToWindowBottom (fall through) and ~15 sites in the main program and the $6F00 ; overlay ; ---------------------------------------------------------------------- setCursorRow: sta textCursorRow ; C097 cursor row = A ; ---------------------------------------------------------------------- ; updateCursorPointers - recomputes the bitmap and colour pointers of the cursor cell: absolute column ; = cursor column + window left edge, row = cursor row, through calcCellPointersXY ($2AA3). In ; multicolour mode ($D016 shadow $9001 bit 4 set) it adds $4C to the high byte, turning the ; $8C00 video-matrix pointer into a $D800 colour-RAM pointer. X and Y survive in self-modified ; immediate operands ($C0B9 and $C0BB) instead of on the stack. ; In: zp_3F cursor column, zp_40 cursor row, zp_3B window left, $9001 ; Out: zp_B4/zp_B5 = bitmap cell address, zp_B6/zp_B7 = colour cell address; X/Y preserved ; Called from: everywhere in this file plus ~30 sites in the main program and both $6F00 overlays ; ---------------------------------------------------------------------- updateCursorPointers: stx L_C0B8+1 ; C099 preserve X in the LDX operand at $C0B9 sty L_C0BA+1 ; C09C preserve Y in the LDY operand at $C0BB clc ; C09F clear carry for the column add lda textCursorCol ; C0A0 cursor column is relative to the window ... adc textWindowLeft ; C0A2 ... so add the window's left edge to get the screen column tax ; C0A4 X = screen column ldy textCursorRow ; C0A5 Y = screen row (rows are absolute already) jsr calcCellPointersXY ; C0A7 -> zp_B4/B5 = $A000 + row*320 + col*8, zp_B6/B7 = $8C00 + row*40 + col lda vicCtrl2Shadow ; C0AA shadow of $D016 ... and #$10 ; C0AD ... bit 4 = multicolour bitmap mode? beq L_C0B8 ; C0AF hires: the colour byte belongs in the video matrix at $8C00, leave the pointer alone lda screenPtrHi ; C0B1 multicolour: the colour byte belongs in colour RAM ... clc ; C0B3 clear carry for the add adc #$4C ; C0B4 ... so add $4C00 ($8C00 -> $D800) sta screenPtrHi ; C0B6 zp_B6/zp_B7 now addresses colour RAM L_C0B8: ldx #$FF ; C0B8 patched by $C099: the caller's X L_C0BA: ldy #$FF ; C0BA patched by $C09C: the caller's Y rts ; C0BC done lda #$04 ; C0BD dead: unreachable 'lda #$04 / rts' left over from an earlier build of this page rts ; C0BF dead (see $C0BD) ; ---------------------------------------------------------------------- ; plotCharAndAdvance - draws glyph A at the cursor cell, then advances the cursor one column (two in ; double-size mode), wrapping to the next line when it reaches the window width, and recomputes the ; cell pointers. ; In: A = glyph number, window/cursor state zp_39-zp_44 ; Out: glyph drawn, cursor advanced, zp_B4-zp_B7 updated ; Called from: printChar ($C02D), handleControlChar ($C061), the window fill loop ($C08A), ; printFillChars ($C0F6) ; ---------------------------------------------------------------------- plotCharAndAdvance: jsr plotGlyphCell ; C0C0 draw the glyph into the cell the cursor points at inc textCursorCol ; C0C3 one column to the right bit doubleSizeText ; C0C5 double-size characters? (zp_44 bit 7) bpl L_C0CB ; C0C7 no inc textCursorCol ; C0C9 a double-size glyph is two cells wide L_C0CB: ldy textCursorCol ; C0CB the new cursor column ... cpy textWindowWidth ; C0CD ... has it reached the right edge of the window? bcc L_C0D4 ; C0CF no - stay on this line jsr printNewline ; C0D1 wrap to column 0 of the next line L_C0D4: jmp updateCursorPointers ; C0D4 recompute the cell pointers for the new cursor position ; ---------------------------------------------------------------------- ; setFullScreenWindow - resets the text window to the whole screen: top row 0, left column 0, bottom ; $19 (row 25, exclusive) and width $28 (40 columns). ; In: none ; Out: zp_39 = 0, zp_3A = $19, zp_3B = 0, zp_3C = $28 ; Called from: saveTextWindow ($C425), main program $1647/$2364/$2622/$2645/$2D41, $6F00 overlay ; $6FF5/$70B3/$70DD/$7A78/$7B2B/$7B44 ; ---------------------------------------------------------------------- setFullScreenWindow: lda #$00 ; C0D7 row 0 = sta textWindowTop ; C0D9 top of the window lda #$00 ; C0DB column 0 = sta textWindowLeft ; C0DD left edge of the window lda #$19 ; C0DF row 25 (exclusive) = sta textWindowBottom ; C0E1 bottom of the window, i.e. all 25 rows lda #$28 ; C0E3 40 columns = sta textWindowWidth ; C0E5 full screen width rts ; C0E7 done ; ---------------------------------------------------------------------- ; saveRegsAXY - copies A, X and Y into the zp_54/zp_55/zp_56 scratch triple so restoreRegsAXY can put ; them back. There is only one slot, so the pair must not be nested. ; In: A, X, Y ; Out: zp_54 = A, zp_55 = X, zp_56 = Y ; Called from: printChar ($C01A), map viewport code $5C81 ; ---------------------------------------------------------------------- saveRegsAXY: sta savedRegA ; C0E8 A save slot stx savedRegX ; C0EA X save slot sty savedRegY ; C0EC Y save slot L_C0EE: rts ; C0EE done ; ---------------------------------------------------------------------- ; printFillChars - prints the current blank glyph zp_3E A times through plotCharAndAdvance; a count of ; 0 does nothing. Used to pad fields, to draw the left pad of a centred string and to draw horizontal ; bars. ; In: A = repeat count, zp_3E = fill glyph, zp_3D = colour ; Out: A cells filled, cursor advanced past them ; Called from: printStringAtPointer ($C15B), main program $15BD/$15CE/$15EC/$165E/$166E/$1728/ ; $1733/$19DB, $6F00 overlay $7727/$7F12/$7F1E/$8623 ; ---------------------------------------------------------------------- printFillChars: cmp #$00 ; C0EF count of 0? beq L_C0FF ; C0F1 then there is nothing to draw L_C0F3: pha ; C0F3 keep the remaining count on the stack lda textFillGlyph ; C0F4 the blank/fill glyph L_C0F6: jsr plotCharAndAdvance ; C0F6 draw it and advance the cursor pla ; C0F9 get the count back L_C0FA: sec ; C0FA set carry so the subtract does not borrow sbc #$01 ; C0FB one cell done bne L_C0F3 ; C0FD keep going until the count runs out L_C0FF: rts ; C0FF done ; ---------------------------------------------------------------------- ; printTableEntry - prints entry X of a table of fixed 8-byte, bit-7-terminated strings whose base ; address is passed in A (low) / Y (high): computes base + X*8 with a 16-bit shift and tail-calls ; printString. Used for the unit type names ($C964), group names ($C92C), menu items and the ; STANDARD/CUSTOM words. ; In: A/Y = table base, X = entry index ; Out: the string is printed at the cursor; zp_18/zp_19/zp_1A clobbered ; Called from: main program $15C9/$15D9/$22CD/$23B5/$242C/$2486/$5FCE/$6168/$6352, $6F00 overlay ; $879C ; ---------------------------------------------------------------------- printTableEntry: sta scratch18 ; C100 table base, low byte lda #$00 ; C102 zero ... sta scratch1A ; C104 clear the high byte of the offset being built sty scratch19 ; C106 table base, high byte txa ; C108 A = entry index asl a ; C109 index * 2 ... rol scratch1A ; C10A ... carrying into the offset high byte asl a ; C10C index * 4 ... rol scratch1A ; C10D ... carrying into the offset high byte asl a ; C10F index * 8 - the table entries are 8 bytes each rol scratch1A ; C110 ... and once more clc ; C112 clear carry for the 16-bit add adc scratch18 ; C113 base low + offset low ... sta scratch18 ; C115 = pointer low byte lda scratch1A ; C117 offset high ... adc scratch19 ; C119 ... plus base high tay ; C11B Y = pointer high byte lda scratch18 ; C11C A = pointer low byte jmp printString ; C11E print the bit-7 terminated string found there ; ---------------------------------------------------------------------- ; printStringCentred - sets the centring field width to the current window width and tail-calls ; printString, so the string is padded on the left to sit centred in the window. A survives the store ; across the stack. ; In: A/Y = string pointer, zp_3C = window width ; Out: $90EF = window width (cleared again by printStringAtPointer); string printed centred ; Called from: main program $4C49/$5F5D/$638B/$6452/$64A8/$64B8/$64FA, $6F00 overlay $7714/$7EB0/ ; $8346/$83A4/$83E9/$85ED/$8780 ; ---------------------------------------------------------------------- printStringCentred: pha ; C121 keep the pointer low byte lda textWindowWidth ; C122 the window width ... sta textCentreWidth ; C124 ... is the field the string is centred in pla ; C127 pointer low byte back jmp printString ; C128 print it centred ; ---------------------------------------------------------------------- ; printNextStringCentred - like printStringCentred, but continues with the string that follows the one ; just printed: zp_A2/zp_A3 already points past the previous terminator, so this prints the next line ; of a multi-string block, centred. ; In: zp_A2/zp_A3 = string pointer, zp_3C = window width ; Out: $90EF = window width; the next string is printed centred ; Called from: main program $6503, $6F00 overlay $83EC ; ---------------------------------------------------------------------- printNextStringCentred: lda textWindowWidth ; C12B the window width ... sta textCentreWidth ; C12D ... is the field to centre in jmp printStringAtPointer ; C130 keep printing where the previous string ended ; ---------------------------------------------------------------------- ; printString - sets the string pointer zp_A2/zp_A3 from A/Y and falls into printStringAtPointer. The ; main text output call of the game; strings end with a character that has bit 7 set. ; In: A = pointer low, Y = pointer high ; Out: string printed at the cursor, zp_A2/zp_A3 left just past the terminator ; Called from: ~25 sites in the main program, ~20 in the $6F00 overlays, and $C11E/$C128/$C3F3/ ; $C6C0 in this file ; ---------------------------------------------------------------------- printString: sta stringPtr ; C133 string pointer, low byte sty stringPtrHi ; C135 string pointer, high byte ; ---------------------------------------------------------------------- ; printStringAtPointer - prints the bit-7-terminated string at zp_A2/zp_A3. When the centring width ; $90EF is non-zero it first measures the string and prints (width - length)/2 blanks in the pad ; colour zp_42 (the text colour is saved across the pad), then prints every character through ; printChar, terminator included, and finally clears $90EF so centring is a one-shot setting. ; In: zp_A2/zp_A3 = string, $90EF = field width or 0, zp_42 = pad colour, zp_3E = blank glyph ; Out: text drawn, zp_A2/zp_A3 past the terminator, $90EF = 0; the SBC operand at $C159 is patched ; Called from: printString (fall through), printNextStringCentred ($C130), drawStatusScoreLine ; ($C6CB/$C6D6), main program $1A2C/$1A3D/$1A45/$5EA9, $6F00 overlay ; $8276/$82E5/$8590/$8596/$863C ; ---------------------------------------------------------------------- printStringAtPointer: lda textCentreWidth ; C137 is a centring width set? beq L_C161 ; C13A no - print left aligned at the cursor lda textColour ; C13C save the text colour ... pha ; C13E ... across the padding lda textPadColour ; C13F the pad blanks use their own colour D_C141: sta textColour ; C141 the pad colour is the text colour while the blanks are drawn ; ---------------------------------------------------------------------- ; Measure the string: count characters until one with bit 7 set (the terminator) has been counted. ; ---------------------------------------------------------------------- ldy #$00 ; C143 start at the first character ldx #$00 ; C145 X counts the characters L_C147: lda (stringPtr),y ; C147 next character of the string pha ; C149 keep it for the bit-7 test and #$7F ; C14A strip the terminator bit (the value itself is not needed) iny ; C14C next character position inx ; C14D count this character, terminator included pla ; C14E test the original character ... bpl L_C147 ; C14F ... loop until bit 7 was set stx L_C158+1 ; C151 patch the length into the SBC at $C158 lda textCentreWidth ; C154 field width ... sec ; C157 set carry for the subtract L_C158: sbc #$FF ; C158 patched: string length including the terminator lsr a ; C15A half of the slack: only the left side is padded jsr printFillChars ; C15B print that many blanks in the pad colour pla ; C15E restore the text colour for the string itself sta textColour ; C15F the string itself uses the caller's colour again ; ---------------------------------------------------------------------- ; Print loop: fetch a character, advance the pointer, print it; the character with bit 7 set is ; printed as well and ends the string. ; ---------------------------------------------------------------------- L_C161: ldy #$00 ; C161 always read at offset 0 ... lda (stringPtr),y ; C163 ... the character the pointer points at inc stringPtr ; C165 advance the string pointer ... bne L_C16B ; C167 no carry into the pointer high byte inc stringPtrHi ; C169 ... across a page boundary if need be L_C16B: tay ; C16B test bit 7 without disturbing A bmi L_C174 ; C16C bit 7 set: this is the last character jsr printChar ; C16E print it jmp L_C161 ; C171 and go on with the next L_C174: jsr printChar ; C174 print the terminating character too (printChar masks its bit 7 off) lda #$00 ; C177 centring is a one-shot setting ... sta textCentreWidth ; C179 ... so clear it for the next string rts ; C17C done ; ---------------------------------------------------------------------- ; printDecimal - prints an unsigned 24-bit value in decimal without leading zeros: converts it into ; the digit buffer at $92DA and then prints the digits. ; In: A = value low, Y = value middle, zp_7E = value high ; Out: digits printed; $92DA filled, zp_7E cleared, zp_18-zp_1F clobbered ; Called from: drawStatusScoreLine ($C6C8), printPlayerPoints ($C6EC), main program $1739/$22DA/ ; $23CE/$618D, $6F00 overlay $7EDC/$846E/$862A ; ---------------------------------------------------------------------- printDecimal: jsr convertToDecimalDigits ; C17D value -> ASCII digits in $92DA jmp printDecimalDigits ; C180 print them, most significant first ; ---------------------------------------------------------------------- ; convertToDecimalDigits - converts the 24-bit value in A (low) / Y (middle) / zp_7E (high) to decimal ; by repeated division by 10: a 24-iteration shift/subtract loop leaves the quotient in zp_18-zp_1A ; and the digit in zp_1B. Digits are stored as $B0 + digit (ASCII with bit 7 set) backwards from the ; end of the buffer at $92DA, so zp_1E ends up pointing at the most significant one. A value of 0 ; still produces one digit. ; In: A/Y/zp_7E = 24-bit value ; Out: $92DA+1..$92DA+8 digits, zp_1E = index of the first digit, zp_7E = 0, zp_18-zp_1F clobbered ; Called from: printDecimal ($C17D) ; ---------------------------------------------------------------------- convertToDecimalDigits: sta scratch18 ; C183 dividend, low byte sty scratch19 ; C185 dividend, middle byte lda decimalValueHigh ; C187 the caller's high byte ... sta scratch1A ; C189 ... completes the 24-bit dividend ldy #$08 ; C18B the digit buffer holds 8 digits ($92DA+1..+8) ... sty scratch1E ; C18D ... and is filled from its last slot backwards ; ---------------------------------------------------------------------- ; Outer loop: one division by 10 per pass, lowest digit first, until the quotient is zero. ; ---------------------------------------------------------------------- L_C18F: lda #$00 ; C18F clear the 24-bit remainder ... sta scratch1B ; C191 remainder, low byte sta scratch1C ; C193 remainder, middle byte sta scratch1D ; C195 remainder, high byte L_C197: lda #$18 ; C197 24 bits of dividend to shift through sta scratch1F ; C199 bit counter for this division clc ; C19B the first quotient bit shifts in as 0 ; ---------------------------------------------------------------------- ; Restoring division by 10: shift dividend:remainder left one bit, subtract 10 from the remainder and ; keep the result only if it did not borrow; the carry becomes the next quotient bit. ; ---------------------------------------------------------------------- L_C19C: rol scratch18 ; C19C shift the dividend left, its top bit ... rol scratch19 ; C19E dividend, middle byte rol scratch1A ; C1A0 ... entering the remainder rol scratch1B ; C1A2 remainder, low byte rol scratch1C ; C1A4 remainder, middle byte rol scratch1D ; C1A6 remainder, high byte sec ; C1A8 no borrow into the trial subtraction lda scratch1B ; C1A9 trial subtraction: remainder - 10, low byte sbc #$0A ; C1AB 10 = the divisor tay ; C1AD hold the result in Y ... lda scratch1C ; C1AE middle byte sbc #$00 ; C1B0 only the borrow tax ; C1B2 ... and in X ... L_C1B3: lda scratch1D ; C1B3 high byte D_C1B5: sbc #$00 ; C1B5 only the borrow bcc L_C1BF ; C1B7 it borrowed: this quotient bit is 0, keep the old remainder sty scratch1B ; C1B9 no borrow: the subtraction stands ... stx scratch1C ; C1BB middle byte of the reduced remainder sta scratch1D ; C1BD ... and the carry makes this quotient bit a 1 L_C1BF: dec scratch1F ; C1BF one of the 24 bits done bne L_C19C ; C1C1 24 shifts in all rol scratch18 ; C1C3 shift the last quotient bit in: zp_18-zp_1A = value / 10 rol scratch19 ; C1C5 quotient, middle byte rol scratch1A ; C1C7 quotient, high byte lda scratch1B ; C1C9 remainder 0-9 = this decimal digit clc ; C1CB clear carry for the add adc #$B0 ; C1CC +$B0 = '0'..'9' with bit 7 set (printChar masks it off again) ldy scratch1E ; C1CE the current end of the digit buffer sta decimalDigitBuffer,y ; C1D0 store the digit there dec scratch1E ; C1D3 the next digit goes one slot to the left lda scratch18 ; C1D5 is the quotient ... ora scratch19 ; C1D7 ... zero in all ... ora scratch1A ; C1D9 ... three bytes? bne L_C18F ; C1DB no - divide again for the next digit sta decimalValueHigh ; C1DD A is 0 here, so this also clears the caller's high byte zp_7E inc scratch1E ; C1DF point zp_1E at the most significant digit rts ; C1E1 done ; ---------------------------------------------------------------------- ; printDecimalDigits - prints the digit buffer at $92DA from index zp_1E up to and including index 8, ; i.e. the significant digits produced by convertToDecimalDigits. ; In: zp_1E = index of the first digit, buffer $92DA ; Out: the digits are printed at the cursor ; Called from: printDecimal ($C180) ; ---------------------------------------------------------------------- printDecimalDigits: ldy scratch1E ; C1E2 start at the most significant digit L_C1E4: lda decimalDigitBuffer,y ; C1E4 digit as '0'+d with bit 7 set jsr printChar ; C1E7 print it iny ; C1EA next digit cpy #$09 ; C1EB index 8 is the last (units) digit bcc L_C1E4 ; C1ED loop over the remaining digits rts ; C1EF done ; ---------------------------------------------------------------------- ; plotGlyphCell - draws glyph A into the bitmap cell at zp_B4/zp_B5 and writes the colour byte zp_3D ; through zp_B6/zp_B7. The glyph source is $98BE + A*8 in multicolour mode or $9A96 + A*8 in hires ; mode: two fonts $1D8 bytes apart with the same shapes, one drawn in bit pairs %11 (colour from ; colour RAM) and one in single bits (colour from the video matrix). With double-size text selected ; it hands the glyph to plotGlyphDoubleSize instead. The copy loop at $C227 reads the bitmap byte ; that is already there and the following opcode at $C22B is patched by setBitmapDrawMode ($2A41) to ; LDA/ORA/EOR/CMP (zp),Y, giving replace / OR / EOR / transparent drawing. ; In: A = glyph number, zp_B4-zp_B7 cell pointers, zp_3D colour, zp_44 double-size flag, $9001 ; Out: 8 bitmap bytes and one colour byte written, zp_B2/zp_B3 = glyph pointer; A preserved ; Called from: plotCharAndAdvance ($C0C0) ; ---------------------------------------------------------------------- plotGlyphCell: pha ; C1F0 keep the glyph number - it is handed back to the caller ldy #$00 ; C1F1 zero ... sty glyphPtr ; C1F3 clear the glyph pointer ... sty glyphPtrHi ; C1F5 ... before shifting the offset into it asl a ; C1F7 glyph * 2 ... rol glyphPtrHi ; C1F8 ... carrying into the pointer high byte asl a ; C1FA glyph * 4 ... rol glyphPtrHi ; C1FB ... carrying into the pointer high byte asl a ; C1FD glyph * 8 - a glyph is 8 bytes rol glyphPtrHi ; C1FE ... carrying into the pointer high byte L_C200: clc ; C200 clear carry for the base add adc #$BE ; C201 add the multicolour font base $98BE ... sta glyphPtr ; C203 glyph pointer, low byte D_C205: lda glyphPtrHi ; C205 offset high byte ... adc #$98 ; C207 ... high byte sta glyphPtrHi ; C209 glyph pointer, high byte lda vicCtrl2Shadow ; C20B shadow of $D016 ... and #$10 ; C20E ... bit 4 = multicolour bitmap? bne L_C21F ; C210 multicolour: the $98BE font is the right one lda glyphPtr ; C212 hires: add $01D8 to reach the identical ... clc ; C214 clear carry for the add adc #$D8 ; C215 low byte of $01D8 ... sta glyphPtr ; C217 glyph pointer, low byte lda glyphPtrHi ; C219 ... hires font at $9A96 (59 glyphs further on) adc #$01 ; C21B high byte of $01D8 ... sta glyphPtrHi ; C21D glyph pointer, high byte L_C21F: bit doubleSizeText ; C21F double-size text? (zp_44 bit 7) bpl L_C227 ; C221 no - draw it at normal size pla ; C223 restore the glyph number for the caller jmp plotGlyphDoubleSize ; C224 the 2x2 magnifier draws it instead ; ---------------------------------------------------------------------- ; Copy the 8 rows of the glyph into the cell, bottom row first. ; ---------------------------------------------------------------------- L_C227: ldy #$07 ; C227 8 bitmap rows, counted down L_C229: lda (bitmapPtr),y ; C229 read the bitmap byte that is already there ... D_C22B: lda (glyphPtr),y ; C22B ... patched by setBitmapDrawMode ($2A41) to LDA/ORA/EOR/CMP (zp),Y = replace/OR/EOR/transparent sta (bitmapPtr),y ; C22D store the combined row back into the bitmap dey ; C22F next row up bpl L_C229 ; C230 loop over all 8 rows of the cell iny ; C232 Y = 0: the cell's single colour byte lda (screenPtr),y ; C233 dead load - nothing patches this instruction, so the colour is always replaced lda textColour ; C235 the text colour ... sta (screenPtr),y ; C237 ... into colour RAM (multicolour) or the video matrix (hires) pla ; C239 hand the glyph number back rts ; C23A done ; pixelDoubleTable: 16 bytes, indexed by a nibble; each entry is that nibble with every pixel doubled ; ($1 -> $03, $F -> $FF). Read by expandGlyphNibbles for the 2x magnifier. pixelDoubleTable: .byte $00,$03,$0C,$0F ; C23B .... nibble $0-$3 doubled sub_C23F: .byte $30 ; C23F nibble $4-$B doubled (the label here is the boot loader's) .byte $33,$3C,$3F,$C0,$C3,$CC,$CF; C240 3= $E0 ; Called from: main program $3CFA ; ---------------------------------------------------------------------- waitRasterBelowScreen: lda VIC_RASTER ; C397 current raster line cmp #$E0 ; C39A below the visible screen (line 224)? bcc waitRasterBelowScreen ; C39C not yet - keep spinning rts ; C39E done ; ---------------------------------------------------------------------- ; clearMessageLine - blanks the status bar at the bottom of the screen: saves the caller's text ; window, selects the one-row message window, clears it and restores the window. ; In: $90FB current screen, $9001 display mode ; Out: the status line is filled with the blank glyph; the text window is restored from $92B6 ; Called from: showNextQueuedMessage ($C61B), promptPressButtonWhenReady ($C86A), main program ; $102B/$10D9/$1B96/$2DDB/$576A, $6F00 overlay $81F1/$8792 ; ---------------------------------------------------------------------- clearMessageLine: jsr saveTextWindow ; C39F stash the caller's text window jsr setMessageLineWindow ; C3A2 switch to the one-row status bar jsr clearToEndOfLine ; C3A5 blank it jmp restoreTextWindow ; C3A8 and put the caller's window back ; ---------------------------------------------------------------------- ; setMessageLineWindow - defines the text window as the one-row status bar: left column 2, width ; $24 (36 columns, i.e. columns 2-37), cursor at its left end, row $17 (23) - or $16 (22) on the ; console/main-menu screen, where the bar sits one row higher. Text and pad colour become 0 in ; multicolour mode (colour RAM nibble) and 7 in hires mode (video matrix byte). ; In: $90FB current screen (0 map, 1 missile, 2 drone, 3 console/menu), $9001 display mode ; Out: zp_3A-zp_3D, zp_3F, zp_40 and zp_42 set; cell pointers recomputed ; Called from: clearMessageLine ($C3A2), printOnMessageLine ($C3EC), drawStatusScoreLine ($C6B9) ; ---------------------------------------------------------------------- setMessageLineWindow: lda #$24 ; C3AB 36 columns wide ... sta textWindowWidth ; C3AD = the window width lda #$02 ; C3AF ... starting at column 2, so the bar covers columns 2-37 sta textWindowLeft ; C3B1 = the window's left edge lda #$00 ; C3B3 cursor to the left end of the bar sta textCursorCol ; C3B5 = the cursor column ldx #$17 ; C3B7 row 23 on the battlefield, missile and drone screens ldy currentScreen ; C3B9 which screen is on show? cpy #$03 ; C3BC 3 = the console / main menu screen bcc L_C3C2 ; C3BE no - keep row 23 D_C3C0: ldx #$16 ; C3C0 on the console screen the bar sits one row higher, on row 22 L_C3C2: stx textWindowBottom ; C3C2 the window is that single row ... stx textCursorRow ; C3C4 ... and the cursor starts on it ldy #$07 ; C3C6 colour 7 for the hires screens lda vicCtrl2Shadow ; C3C8 shadow of $D016 ... and #$10 ; C3CB ... bit 4 = multicolour bitmap? beq L_C3D1 ; C3CD hires - keep 7 ldy #$00 ; C3CF multicolour: colour RAM nibble 0 L_C3D1: sty textColour ; C3D1 text colour ... sty textPadColour ; C3D3 ... and the colour of the padding blanks jmp updateCursorPointers ; C3D5 compute the cell pointers of the first cell of the bar ; ---------------------------------------------------------------------- ; showMessageString - prints the bit-7-terminated string A/Y centred in the 36-column status bar and ; restores the caller's text window. The standard 'put this text in the status bar' call, used for ; disk errors, queued messages and the clock line. ; In: A/Y = string pointer ; Out: the status line is rewritten; the text window is restored ; Called from: updateStatusMessage ($C53C), showMessageById ($C5EB), promptPressButtonWhenReady ; ($C85F), main program $0816/$1023 ; ---------------------------------------------------------------------- showMessageString: ldx #$24 ; C3D8 centre across all 36 columns of the bar jsr printOnMessageLine ; C3DA draw the string there jmp restoreTextWindow ; C3DD and put the caller's window back ; ---------------------------------------------------------------------- ; printOnMessageLine - worker behind showMessageString: remembers the string pointer in the operands ; at $C3F0/$C3F2, takes the centring width from X, saves the text window, selects the status bar, ; prints the string and pads the rest of the line. It leaves the window set to the status bar, so the ; caller must restore it. ; In: A/Y = string pointer, X = centring width (0 = left aligned) ; Out: the status line is drawn; the text window is still the message window on return ; Called from: showMessageString ($C3DA), drawTypedChatLine ($C4E0) ; ---------------------------------------------------------------------- printOnMessageLine: sta L_C3EF+1 ; C3E0 patch the pointer low byte into the LDA at $C3EF sty L_C3F1+1 ; C3E3 and the high byte into the LDY at $C3F1 stx textCentreWidth ; C3E6 X = the field width to centre in, 0 = left aligned jsr saveTextWindow ; C3E9 stash the caller's text window jsr setMessageLineWindow ; C3EC switch to the status bar L_C3EF: lda #$FF ; C3EF patched: string pointer low L_C3F1: ldy #$FF ; C3F1 patched: string pointer high jsr printString ; C3F3 print it (centred if a width was given) jmp clearToEndOfLine ; C3F6 pad the rest of the bar with blanks ; ---------------------------------------------------------------------- ; advanceFillPointer - loop helper of the block fill/clear routines: compares the 16-bit pointer ; zp_48/zp_49 with the last address zp_4A/zp_4B (carry set once the pointer has reached it), then ; steps the pointer and a 16-bit counter that nothing ever reads. Callers store a byte and loop while ; the carry is clear. ; In: zp_48/zp_49 = current address, zp_4A/zp_4B = last address ; Out: C = 1 when the pointer had reached the limit; zp_48/zp_49 and zp_4E/zp_4F incremented ; Called from: memory clear $0C44, the setup block at $0333, modem driver $EC16/$EC8B ; ---------------------------------------------------------------------- advanceFillPointer: lda mapCellPtr ; C3F9 16-bit compare of the fill pointer ... cmp fillEndPtr ; C3FB low bytes ... lda mapCellPtrHi ; C3FD ... against the last address to fill ... sbc fillEndPtrHi ; C3FF ... leaving carry set once it has been reached inc mapCellPtr ; C401 step to the next byte ... bne L_C407 ; C403 no carry into the high byte inc mapCellPtrHi ; C405 ... across a page boundary if need be L_C407: inc fillCounter ; C407 count the bytes filled ... bne L_C40D ; C409 no carry into the high byte inc fillCounterHi ; C40B ... in a 16-bit counter nothing ever reads L_C40D: rts ; C40D carry tells the caller whether to stop ; ---------------------------------------------------------------------- ; restoreTextWindow - copies the 12 saved bytes at $92B6 back into zp_39-zp_44 (window geometry, ; colours, cursor position and mode flags) and recomputes the cell pointers. Counterpart of ; saveTextWindow; there is only one save slot, so the pair cannot be nested. ; In: $92B6-$92C1 ; Out: zp_39-zp_44 restored, zp_B4-zp_B7 recomputed ; Called from: clearMessageLine ($C3A8), showMessageString ($C3DD), drawTypedChatLine ($C4EC), ; handlePauseKey ($C761/$C79A), drawStatusScoreLine ($C6E2), main program $173C/$19D3/$19DE ; ---------------------------------------------------------------------- restoreTextWindow: ldx #$0B ; C40E 12 bytes: zp_39 up to zp_44 L_C410: lda savedTextWindow,x ; C410 byte of the saved window state ... sta textWindowTop,x ; C413 ... back into the zero-page text variables dex ; C415 next byte bpl L_C410 ; C416 all 12 of them jmp updateCursorPointers ; C418 recompute the cell pointers for the restored cursor ; ---------------------------------------------------------------------- ; saveTextWindow - saves zp_39-zp_44 (the whole text engine state) into the single slot at $92B6 and ; then hands the caller a full-screen window. Used before drawing the status bar, the repair window ; and anything else that must not disturb the caller's window. ; In: zp_39-zp_44 ; Out: $92B6-$92C1 = the saved state; the window is reset to 0,0,25,40 ; Called from: clearMessageLine ($C39F), printOnMessageLine ($C3E9), drawStatusScoreLine ($C6B6), ; handlePauseKey ($C75B/$C791), main program $16FF/$19E1 ; ---------------------------------------------------------------------- saveTextWindow: ldx #$0B ; C41B 12 bytes: zp_39 up to zp_44 L_C41D: lda textWindowTop,x ; C41D text engine variable ... sta savedTextWindow,x ; C41F ... into the save slot dex ; C422 next byte bpl L_C41D ; C423 all 12 of them jmp setFullScreenWindow ; C425 leave the caller with a full-screen window ; ---------------------------------------------------------------------- ; splitDecimalDigits - splits the byte in A into three decimal digits by repeated subtraction: units ; into decimalOnes, tens into decimalTens, hundreds into decimalHundreds. The digits are used as ; indices for drawTallGlyph by the drone fuel gauge and the missile counter. ; In: A = value 0-255 ; Out: decimalHundreds/decimalTens/decimalOnes ($C44E-$C450); X clobbered ; Called from: $6F00 overlay $6F9C (drone fuel) and $7ABD (missile count) ; ---------------------------------------------------------------------- splitDecimalDigits: ldx #$FF ; C428 X will count the tens sec ; C42A no borrow into the first subtraction L_C42B: inx ; C42B one more ten ... sbc #$0A ; C42C ... subtract it bcs L_C42B ; C42E keep going until it borrows adc #$0A ; C430 put the last ten back: A = value mod 10 sta decimalOnes ; C432 the units digit txa ; C435 A = value / 10 ldx #$FF ; C436 X will count the hundreds sec ; C438 no borrow into the first subtraction L_C439: inx ; C439 one more hundred ... sbc #$0A ; C43A ... subtract ten from the tens value bcs L_C439 ; C43C keep going until it borrows adc #$0A ; C43E put the last ten back: A = tens digit sta decimalTens ; C440 the tens digit txa ; C443 A = value / 100 bne L_C44A ; C444 dead leading-zero test: ... lda #$00 ; C446 ... A is already 0 on this path ... beq L_C44A ; C448 ... and both branches continue at $C44A L_C44A: sta decimalHundreds ; C44A the hundreds digit rts ; C44D done ; The three digits produced by splitDecimalDigits, stored in the code page itself. decimalHundreds: .byte $00 ; C44E hundreds digit; read by the drone fuel display ($6FD6) decimalTens: .byte $00 ; C44F tens digit; read at $6FDE (fuel) and $7AD5 (missiles left) decimalOnes: .byte $00 ; C450 units digit; read at $6FE6 (fuel) and $7ADD (missiles left) ; ---------------------------------------------------------------------- ; checkUiContinue - the shared 'may this modal panel keep running' test used by the menu selector, the ; unit info panel and the group editor. Returns A = 1 to carry on. It returns A = 0 and arms a ; 60-frame input lockout when the end-of-game sequence has started (and we are not watching a film), ; when a function key has asked for another screen, when SPACE was typed, or when the setup-phase test ; at checkSetupUiContinue says stop; a pause or gamePhase bits 6/7 keep the panel alive. ; In: $0B9B film flag, $92C9 end-of-game counter, zp_BE pause state, zp_B1 game phase, ; zp_66 requested screen key, $9248 last typed key ; Out: A = 1 continue / 0 abort; $0B7D = $3C (60 frames of input lockout) on abort ; Called from: main program $21D9/$2367/$4BE4/$6079/$622B/$63CB/$6400/$64BE/$65F7 ; ---------------------------------------------------------------------- checkUiContinue: lda filmPlaybackMode ; C451 watching a game film? bmi L_C45F ; C454 yes - the film drives everything, skip the end/pause tests lda gameEndCountdown ; C456 is the end-of-game sequence running? bne L_C48B ; C459 yes - close the panel lda pauseState ; C45B paused or disconnected? bne L_C488 ; C45D yes - keep the panel on screen L_C45F: lda gamePhase ; C45F game phase ... and #$C0 ; C461 ... bits 6/7 mark the setup and menu phases ... bne L_C488 ; C463 ... where the panel always stays up lda requestedScreenKey ; C465 has a function key asked for another screen? ($FF = none) bpl L_C48B ; C467 yes - close the panel lda lastTypedKey ; C469 last typed key ... cmp #$A0 ; C46C ... SPACE ($20 with bit 7 set)? beq L_C48B ; C46E yes - SPACE closes the panel ; ---------------------------------------------------------------------- ; checkSetupUiContinue - tail of checkUiContinue, also called on its own by the map main loop during ; the setup phase: the panel stays up while the battle is running (phase 0), while this game type has ; no setup phase, or while only side 0 deploys and we are side 0; otherwise it stays up only when both ; handshake bits of gamePhase are set. ; In: zp_B1 game phase, $92C6 setup phase mode (0 none, 1 both sides, $80 side 0 only), $0B9F side ; Out: A = 1 continue / 0 abort; $0B7D = $3C on abort ; Called from: checkUiContinue (fall through), main program $202D ; ---------------------------------------------------------------------- checkSetupUiContinue: lda gamePhase ; C470 game phase: 0 while the battle runs beq L_C488 ; C472 during the battle the panel stays up lda setupPhaseMode ; C474 does this game type have a unit setup phase? beq L_C488 ; C477 no - stay up bpl L_C480 ; C479 positive: both sides deploy their units lda playerSide ; C47B $80 = only side 0 deploys ... beq L_C488 ; C47E ... and we are side 0, so stay up L_C480: lda gamePhase ; C480 game phase ... and #$03 ; C482 ... low two bits are the setup handshake ... cmp #$03 ; C484 ... both sides ready? bne L_C48B ; C486 no - close the panel and wait L_C488: lda #$01 ; C488 A = 1: the caller's panel may keep running rts ; C48A back to the caller's panel loop L_C48B: lda #$3C ; C48B 60 frames ... sta inputLockoutTimer ; C48D ... during which the IRQ ignores keyboard and joystick lda #$00 ; C490 A = 0: the caller must leave its panel rts ; C492 back to the caller's panel loop ; ---------------------------------------------------------------------- ; serviceChatDisplay - first half of updateStatusMessage: it owns the status bar while the player is ; typing to the opponent. A positive non-zero chatState means the line has just been sent, so the ; typed line is drawn one last time and the bar is handed back to the message system. While typing ; (bit 7 set) holding CRSR up/down ($8B) peeks at the line the opponent is typing (message 0) and ; releasing it redraws our own; bit 6 of chatState means 'the bar no longer shows the typed buffer'. ; In both typing cases two bytes are pulled off the stack so that updateStatusMessage returns to its ; own caller and never touches the message queue. ; In: $929C chat state (bit 7 typing, bit 6 bar dirty, bits 0-5 characters typed), $92A0 held key, ; zp_7A pending message id ; Out: $929C updated, status bar redrawn, zp_7A/zp_7B/zp_72 updated; may discard updateStatusMessage's ; return address ; Called from: updateStatusMessage ($C4F0) ; ---------------------------------------------------------------------- serviceChatDisplay: lda chatState ; C493 chat state beq L_C4D9 ; C496 no chat activity - carry on with the ordinary message handling bmi L_C4B1 ; C498 bit 7 set: still typing lda #$00 ; C49A the line has been sent ... sta chatState ; C49C ... so leave typing mode jsr drawTypedChatLine ; C49F show the finished line once more lda pendingMessageId ; C4A2 the pending message id ... and #$7F ; C4A4 ... without its 'consumed' bit ... sta shownMessageId ; C4A6 ... is what the bar now shows lda #$81 ; C4A8 $81 = bit 7 'nothing pending', but not the fully idle value $80 sta pendingMessageId ; C4AA so the message system sees nothing pending lda #$00 ; C4AC expire the display timer ... sta messageTimer ; C4AE ... so the next queued message can take the bar rts ; C4B0 return normally: updateStatusMessage carries on with the queue L_C4B1: ldy rawKeyCode ; C4B1 which key is being held? cpy #$8B ; C4B4 CRSR up/down = 'show me the other side's line' bne L_C4C5 ; C4B6 not held - make sure our own line is on the bar ora #$40 ; C4B8 remember that the bar no longer shows the typed buffer (A is still chatState) sta chatState ; C4BA bit 6 = the bar is not showing the typed line lda #$00 ; C4BD message 0 = the incoming chat line at $0548 jsr showMessageById ; C4BF print it on the bar jmp L_C4D7 ; C4C2 and take the bar for the chat L_C4C5: lda chatState ; C4C5 chat state ... and #$40 ; C4C8 ... bit 6: is the bar showing something else? beq L_C4D7 ; C4CA no - the typed line is already there lda chatState ; C4CC clear the flag ... and #$BF ; C4CF clear bit 6 ... sta chatState ; C4D1 ... the bar shows the typed line again jsr drawTypedChatLine ; C4D4 ... and put the typed line back on the bar L_C4D7: pla ; C4D7 discard updateStatusMessage's return address ... pla ; C4D8 ... so that while typing the bar belongs to the chat alone L_C4D9: rts ; C4D9 nothing to do ; ---------------------------------------------------------------------- ; drawTypedChatLine - draws the line being typed to the opponent: the bit-7-terminated buffer at ; $9278 is printed left aligned on the status bar, then the colour is set to $11 and one blank is ; printed as the typing cursor (on the hires menu screen the video-matrix byte $11 paints that whole ; cell white, so the blank shows up as a solid block). ; In: $9278 typed line buffer (terminated with $9E, which prints nothing) ; Out: the status bar shows the typed line with a cursor; zp_3D = $11; the text window is restored ; Called from: serviceChatDisplay ($C49F, $C4D4) ; ---------------------------------------------------------------------- drawTypedChatLine: lda #$78 ; C4DA the typed line buffer at $9278 ... ldy #$92 ; C4DC $9278, high byte ldx #$00 ; C4DE ... printed left aligned, no centring jsr printOnMessageLine ; C4E0 draw it on the status bar lda #$11 ; C4E3 $11 = white in both nibbles ... sta textColour ; C4E5 ... so the next cell reads as a solid block lda #$A0 ; C4E7 a space (bit 7 set, printChar strips it) jsr printChar ; C4E9 print it as the typing cursor jmp restoreTextWindow ; C4EC put the caller's text window back ; ---------------------------------------------------------------------- ; updateStatusMessage - per-frame service of the status bar, called from every polling loop. It runs ; the chat display and the message-review keys, returns while the current message still has time on ; the clock, and otherwise retires that message (showNextQueuedMessage), takes the pending id, prints ; its string from the pointer tables at $0500/$0524, restarts the $3C-tick timer and plays the ; message's sound. With nothing pending it either shows an arrived chat line or, in the last quarter ; of a battle, refreshes the CLICKS/US/THEM line whenever the game clock has ticked. ; In: zp_72 timer, zp_7A pending id, zp_7B shown id, $9277 chat length, $92FA last-quarter flag, ; zp_B1 phase, $91CB game clock, $9208 last drawn clock, tables $0500/$0524/$0612 ; Out: status bar updated, zp_72 = $3C, zp_7A marked consumed, $9208 = clock; the sound-suppress ; operand at $C54B is rewritten ; Called from: main program $0CCA/$1B9A/$1BDC/$40EB/$4131/$4DA9/$5716/$573E ; ---------------------------------------------------------------------- updateStatusMessage: nop ; C4EF spare byte; nothing patches it jsr serviceChatDisplay ; C4F0 chat first - while typing it pops our return address and takes the bar jsr handleMessageReviewKeys ; C4F3 CRSR keys: message scrollback and the score line lda messageTimer ; C4F6 how long has the message on the bar still got? beq L_C4FB ; C4F8 expired - time for the next one L_C4FA: rts ; C4FA still on display: leave the bar alone L_C4FB: ldx shownMessageId ; C4FB the message currently on the bar bmi L_C502 ; C4FD bit 7 set: the bar is free, nothing to retire jsr showNextQueuedMessage ; C4FF retire it and pick the next one out of the queue L_C502: ldx pendingMessageId ; C502 the id the queue handed us ... stx shownMessageId ; C504 ... is now the one on display bpl L_C536 ; C506 a real message id (bit 7 clear) - print it lda incomingChatLength ; C508 have chat characters arrived from the opponent? bne L_C524 ; C50B yes - the bar shows the chat line lda lastQuarterFlag ; C50D $92FA bit 7: has the last quarter started? bpl L_C4FA ; C510 no - leave the bar blank lda gamePhase ; C512 game phase: only during the battle ... bne L_C4FA ; C514 ... is the clock/score line drawn lda gameClock ; C516 the game clock (clicks left) ... cmp lastShownClock ; C519 ... against the value last drawn beq L_C4FA ; C51C unchanged - nothing to redraw sta lastShownClock ; C51E remember the clock we are about to draw jmp drawStatusScoreLine ; C521 redraw ' CLICKS:n US:n THEM:n' L_C524: bpl L_C531 ; C524 positive: characters are still arriving, keep the line live lda pendingMessageId ; C526 the incoming line is complete: the bar keeps ... and #$7F ; C528 ... the last id ... sta shownMessageId ; C52A ... as the one on display lda #$00 ; C52C the chat text has been taken ... sta incomingChatLength ; C52E ... so clear the incoming counter L_C531: stx L_C54A+1 ; C531 X is negative here, which suppresses the sound at $C54A ldx #$00 ; C534 message 0 = the chat line, whose text pointer is $0548 L_C536: lda messagePtrLoTable,x ; C536 message text pointer, low byte ($0500 + id) ldy messagePtrHiTable,x ; C539 message text pointer, high byte ($0524 + id) jsr showMessageString ; C53C print it centred on the status bar lda #$3C ; C53F $3C ticks (the timer counts down every 4th frame) sta messageTimer ; C541 start the display timer lda pendingMessageId ; C543 the id just printed ... tay ; C545 ... in Y for the sound look-up ora #$80 ; C546 set bit 7 = consumed, nothing pending any more sta pendingMessageId ; C548 store the marked id back L_C54A: lda #$04 ; C54A patched flag: negative = play no sound for this message bmi L_C551 ; C54C suppressed jsr playMessageSound ; C54E play the sound this message id is given in $0612 L_C551: lda #$04 ; C551 re-arm the flag positive ... sta L_C54A+1 ; C553 ... so the next message sounds again ($04 is just a positive value) rts ; C556 done for this frame ; ---------------------------------------------------------------------- ; playMessageSound - plays the sound effect assigned to message id Y: the sound number comes from the ; per-message table at $0612 and a negative entry ($80) means the message is silent. ; In: Y = message id 0-$23, table $0612 ; Out: the sound is queued through playSound ($C89A) ; Called from: updateStatusMessage ($C54E), showStatusMessage ($C701) ; ---------------------------------------------------------------------- playMessageSound: lda messageSoundTable,y ; C557 sound id assigned to this message bmi L_C55F ; C55A $80 = a silent message jsr playSound ; C55C queue the sound effect L_C55F: rts ; C55F done ; ---------------------------------------------------------------------- ; queueMessageUnlessFilm - queues message id A like queueMessage, but drops it silently while a game ; film is being replayed. Used for the combat messages ('UNIT RECYCLED', 'COMCEN STUNNED!', ...). ; In: A = message id, $0B9B bit 7 = film playback ; Out: the message is queued, or ignored during film playback ; Called from: main program $36F1/$3B3D/$3EA5/$3F8E ; ---------------------------------------------------------------------- queueMessageUnlessFilm: bit filmPlaybackMode ; C560 is a game film being replayed? bpl queueMessage ; C563 no - queue the message normally rts ; C565 during a replay combat messages are dropped ; ---------------------------------------------------------------------- ; queueMessage - inserts message id A into the 8-entry ring at $9249, keeping it sorted so that higher ; ids are shown first: it claims the slot after the head, scans from the tail for the first entry that ; is not larger than A, shifts the entries in between one slot forward and drops the new id in. A ; full ring silently discards the message. If the insert lands on the tail the display timer is ; zeroed so the message appears at once, and a status bar that was free is marked busy so that ; updateStatusMessage pops the queue on its next pass. Y is preserved. ; In: A = message id, $9249-$9250 ring, $9251 head (write index), $9252 tail (read index) ; Out: the ring is updated; zp_72 and zp_7B possibly updated; operands at $C5B0, $C5B4 and $C5BB ; are rewritten ; Called from: queueMessageUnlessFilm ($C563), handlePauseKey ($C730), showWaitingForOpponent ; ($C7D1), ~14 sites in the main program, the $6F00 overlays, $0743 and the modem driver ; ---------------------------------------------------------------------- queueMessage: sta L_C5B3+1 ; C566 patch the new id into the LDA at $C5B3 sty L_C5BA+1 ; C569 preserve Y in the LDY operand at $C5BA ldy messageQueueHead ; C56C head = the slot after the last queued message jsr incMessageIndex ; C56F one on, wrapping at 8 cpy messageQueueTail ; C572 would that run into the tail, i.e. is the ring full? bne L_C57B ; C575 no - there is room lda #$05 ; C577 (A is never used again - leftover) bne L_C5BA ; C579 ring full: drop this message L_C57B: sty messageQueueHead ; C57B claim the new slot as the head ldy messageQueueTail ; C57E start at the entry that will be shown next ; ---------------------------------------------------------------------- ; Find the insertion slot: the queue runs from the tail to the head in descending id order, so the new ; id goes in front of the first entry that is not larger than it. ; ---------------------------------------------------------------------- L_C581: cmp messageQueue,y ; C581 compare the new id with this entry bcs L_C591 ; C584 new id is the larger - insert here jsr incMessageIndex ; C586 next entry cpy messageQueueHead ; C589 reached the head? bne L_C581 ; C58C no - keep looking jsr decMessageIndex ; C58E nothing smaller found: append at the end of the queue L_C591: sty L_C5AF+1 ; C591 patch the insertion slot into the CPY at $C5AF cpy messageQueueTail ; C594 is the new message going to the front of the queue? bne L_C59D ; C597 no lda #$00 ; C599 then expire the display timer ... sta messageTimer ; C59B ... so it appears as soon as possible L_C59D: ldy messageQueueHead ; C59D start at the head ; ---------------------------------------------------------------------- ; Open the insertion slot: walk backwards from the head, copying each entry one slot forward, until ; the insertion slot is reached. ; ---------------------------------------------------------------------- L_C5A0: jsr decMessageIndex ; C5A0 the entry before it ... lda messageQueue,y ; C5A3 ... is read ... jsr incMessageIndex ; C5A6 step forward again ... sta messageQueue,y ; C5A9 ... and written one slot forward jsr decMessageIndex ; C5AC step back L_C5AF: cpy #$FF ; C5AF patched: the insertion slot bne L_C5A0 ; C5B1 keep shifting until it is free L_C5B3: lda #$FF ; C5B3 patched: the new message id sta messageQueue,y ; C5B5 into the slot that was opened for it lda #$0C ; C5B8 (A is never used again - leftover) L_C5BA: ldy #$FF ; C5BA patched: the caller's Y lda shownMessageId ; C5BC what is on the status bar? bpl L_C5C8 ; C5BE a message is on display; it will pop the queue when its timer expires and #$7F ; C5C0 the bar is free: clear that bit ... sta shownMessageId ; C5C2 ... so updateStatusMessage pops the queue on its next pass bne L_C5C8 ; C5C4 was the bar completely idle ($80)? sta messageTimer ; C5C6 then show the new message immediately L_C5C8: rts ; C5C8 done ; ---------------------------------------------------------------------- ; decMessageIndex - steps the message-queue index in Y back one slot, wrapping from 0 to 7. ; In: Y = ring index 0-7 ; Out: Y = (Y - 1) mod 8 ; Called from: queueMessage ($C58E/$C5A0/$C5AC), handleMessageReviewKeys ($C66A) ; ---------------------------------------------------------------------- decMessageIndex: dey ; C5C9 one slot back bpl L_C5CE ; C5CA still inside the ring ldy #$07 ; C5CC wrapped past 0: the ring has 8 slots L_C5CE: rts ; C5CE Y = the previous slot ; ---------------------------------------------------------------------- ; incMessageIndex - steps the message-queue index in Y on one slot, wrapping from 7 to 0. ; In: Y = ring index 0-7 ; Out: Y = (Y + 1) mod 8 ; Called from: queueMessage ($C56F/$C586/$C5A6), showNextQueuedMessage ($C621) ; ---------------------------------------------------------------------- incMessageIndex: iny ; C5CF one slot on cpy #$08 ; C5D0 the ring has 8 slots bcc L_C5D6 ; C5D2 still inside it ldy #$00 ; C5D4 wrap back to the first slot L_C5D6: rts ; C5D6 Y = the next slot ; ---------------------------------------------------------------------- ; redrawStatusMessage - puts the status bar back after a screen has been rebuilt: the message that was ; on display (its id with the 'bar free' bit masked off), or message 0 (the chat line) when chat text ; is buffered, or else the next message from the queue. ; In: zp_7B shown message id, $9277 incoming chat length ; Out: the status bar is redrawn ; Called from: main program $1562/$268C, $6F00 overlays $7131/$7A68 ; ---------------------------------------------------------------------- redrawStatusMessage: lda shownMessageId ; C5D7 the message that was on the bar bpl L_C5E2 ; C5D9 a real id - draw it again lda #$00 ; C5DB otherwise consider message 0, the chat line ldx incomingChatLength ; C5DD is any chat text buffered? beq showNextQueuedMessage ; C5E0 no - take the next message from the queue instead L_C5E2: and #$7F ; C5E2 strip the 'bar is free' bit to get the plain id ; ---------------------------------------------------------------------- ; showMessageById - looks message id A up in the pointer tables at $0500 (low) / $0524 (high) and ; tail-calls showMessageString, so the text appears centred on the status bar straight away without ; going through the queue. ; In: A = message id 0-$23 ; Out: the status bar is redrawn; X = the message id ; Called from: serviceChatDisplay ($C4BF), redrawStatusMessage (fall through at $C5E2 and the branch ; at $C619), showStatusMessageNoSound ($C707) ; ---------------------------------------------------------------------- showMessageById: tax ; C5E4 X = message id lda messagePtrLoTable,x ; C5E5 text pointer low byte, 36 messages ldy messagePtrHiTable,x ; C5E8 text pointer high byte jmp showMessageString ; C5EB print it centred on the status bar ; ---------------------------------------------------------------------- ; showNextQueuedMessage - chooses what the status line should show next and leaves the answer in ; pendingMessageId zp_7A; the actual printing is done by updateStatusMessage on its next pass. If the ; 8-entry message ring $9249 is not empty it pops the entry at the read index. If the ring is empty ; it either falls back to the chat line (message id 0) when one is waiting, or goes idle: zp_7A = $80, ; the chat line is blanked, and persistentMessageId $92A1 then decides - negative redraws the ; CLICKS/US/THEM score line, zero blanks the status line, anything else is printed as a message id. ; In: messageQueueTail $9252 (read index), messageQueueHead $9251 (write index), messageQueue $9249, ; pendingMessageId zp_7A, chatMessageLine $0548 (marker byte of message 0), persistentMessageId ; $92A1 ; Out: zp_7A = the next message id or $80, $9252 advanced, $0548 possibly blanked, the sound-suppress ; operand L_C54A+1 possibly set negative; the status line may be redrawn; A/Y clobbered ; Called from: updateStatusMessage ($C4FF), redrawStatusMessage ($C5E0 falls in), resetMessageQueue ; ($C6FC) ; ---------------------------------------------------------------------- showNextQueuedMessage: ldy messageQueueTail ; C5EE read index of the 8-entry message ring cpy messageQueueHead ; C5F1 compare it with the write index bne popQueuedMessage ; C5F4 differ = at least one message is queued, go and pop it lda #$80 ; C5F6 $80 = the 'nothing pending' marker of pendingMessageId cmp pendingMessageId ; C5F8 is the status line already idle? beq statusLineGoIdle ; C5FA yes - keep it idle (A is already the $80 that gets stored there) ldy chatMessageLine ; C5FC marker byte of message 0 at $0548: $1E = chat text present, $9E = empty bmi statusLineGoIdle ; C5FF bit 7 set ($9E) = no chat line waiting, so go idle lda #$00 ; C601 message id 0 = the chat line at $0548 sta pendingMessageId ; C603 show the chat line next ldy #$FF ; C605 $FF = negative sty L_C54A+1 ; C607 patch the 'lda #$04' operand at $C54A in updateStatusMessage negative so this message is printed without its sound bmi storePendingMessageId ; C60A always taken (Y = $FF); A is still 0, so the store at $C627 just repeats it ; ---------------------------------------------------------------------- ; Queue empty and no chat line: park the status line. A = $80 here. ; ---------------------------------------------------------------------- statusLineGoIdle: sta pendingMessageId ; C60C A = $80: nothing is pending any more jsr clearChatStatusLine ; C60E make message 0 print as an empty line ($0548 = $9E) lda persistentMessageId ; C611 $92A1, the message the status line falls back to when the ring drains bpl showPersistentMessage ; C614 positive = a plain message id (0 = blank line) jmp drawStatusScoreLine ; C616 negative = show the CLICKS / US / THEM score line instead showPersistentMessage: bne showMessageById ; C619 non-zero = print that message id right now jmp clearMessageLine ; C61B zero = simply blank the status line ; ---------------------------------------------------------------------- ; Queue not empty: pop the entry the read index points at. ; ---------------------------------------------------------------------- popQueuedMessage: lda messageQueue,y ; C61E take the oldest id out of the ring jsr incMessageIndex ; C621 advance the read index modulo 8 sty messageQueueTail ; C624 store the new read index storePendingMessageId: sta pendingMessageId ; C627 hand the id to updateStatusMessage rts ; C629 the caller prints it, this routine only selects ; ---------------------------------------------------------------------- ; clearChatStatusLine - empties message slot 0 (the chat line, text at $0548) by storing $9E in its ; first byte. printChar throws away every code below $20, and $9E is $1E with the string-terminator ; bit 7 set, so a string that starts with it prints absolutely nothing: the status line comes out ; blank. ; In: none ; Out: chatMessageLine $0548 = $9E, A = $9E ; Called from: showNextQueuedMessage ($C60E) and by JMP from the game's start-up init ($0C63) ; ---------------------------------------------------------------------- clearChatStatusLine: lda #$9E ; C62A $1E | $80: a control code (nothing is drawn) that also carries the end-of-string bit sta chatMessageLine ; C62C message slot 0 now terminates on its first byte = an empty status line rts ; C62F A = $9E on exit, which the start-up init at $0C63 relies on ; messageReviewIndex - the single state byte of the message-history scrollback. bit 7 = review mode ; active, bits 0-6 = the real messageQueueTail saved when review started, so it can be put back when ; the player stops scrolling. 0 = not reviewing. messageReviewIndex: .byte $00 ; C630 . review state: $00 = off, $80|savedReadIndex = scrolling back through old messages ; ---------------------------------------------------------------------- ; handleMessageReviewKeys - per-frame keyboard hook of the status-message service. CRSR up/down (key ; code $8B) enters and then steps the message history: the real ring read index is saved in ; messageReviewIndex with bit 7 set and each further press walks one entry backwards through the ; 8-entry ring, showing it at once (messageTimer = 0, sound suppressed). When the player stops ; pressing, the saved read index is restored and review mode ends. CRSR left/right ($8E) is consumed ; and, during the battle only, pops up the CLICKS/US/THEM score line for 60 ticks by falling into ; drawStatusScoreLine. ; In: messageReviewIndex $C630, lastTypedKey $9248, messageTimer zp_72, messageQueue/Head/Tail ; $9249/$9251/$9252, pendingMessageId zp_7A, shownMessageId zp_7B, gamePhase zp_B1 ; Out: $C630, $9252, $9248 = $FF (key consumed), zp_7A, zp_7B, zp_72, chatMessageLine $0548 = $1E, the ; sound-suppress operand L_C54A+1 = $80, inputLockoutTimer $0B7D; may draw the score line ; Called from: updateStatusMessage ($C4F3), i.e. from every polling loop in the game ; ---------------------------------------------------------------------- handleMessageReviewKeys: lda messageReviewIndex ; C631 review state byte bmi messageReviewActive ; C634 bit 7 set = already scrolling back, skip the entry test lda lastTypedKey ; C636 last key the IRQ decoded ($FF = none) cmp #$8B ; C639 $8B = CRSR up/down, the scrollback key bne checkScoreLineKey ; C63B not that key - try the score-line key instead lda messageQueueTail ; C63D remember where the ring read index really is ora #$80 ; C640 bit 7 marks review mode as active sta messageReviewIndex ; C642 save it so the ring can be rewound afterwards bmi stepMessageReviewBack ; C645 always taken (bit 7 was just set): show the previous message ; ---------------------------------------------------------------------- ; Review mode is running. Wait until the message on screen has had its minimum airtime ($3C down to ; $23), then either step further back or leave review mode. ; ---------------------------------------------------------------------- messageReviewActive: lda messageTimer ; C647 ticks left on the message currently displayed cmp #$23 ; C649 $3C is loaded on display; below $23 means it has been up for about 25 ticks (100 frames) bcs messageReviewDone ; C64B still fresh - do nothing, which rate-limits the scrollback and delays the exit lda lastTypedKey ; C64D has CRSR up/down been pressed again? cmp #$8B ; C650 $8B = CRSR up/down beq stepMessageReviewBack ; C652 yes - step one more entry backwards lda messageReviewIndex ; C654 no further press: recover the saved read index and #$7F ; C657 strip the 'review active' bit 7 sta messageQueueTail ; C659 put the ring read index back where it was sta messageReviewIndex ; C65C and clear bit 7, leaving review mode jmp messageReviewDone ; C65F done for this frame ; ---------------------------------------------------------------------- ; Step one entry backwards through the ring and display it immediately. ; ---------------------------------------------------------------------- stepMessageReviewBack: ldy #$FF ; C662 $FF = 'no key' sty lastTypedKey ; C664 consume the key so it is not seen again next frame ldy messageQueueTail ; C667 current ring read index jsr decMessageIndex ; C66A step one entry backwards modulo 8 cpy messageQueueHead ; C66D have we walked back onto the write index? beq beepNoOlderMessage ; C670 yes - there is no older message, beep lda messageQueue,y ; C672 id stored in that ring slot bpl showReviewedMessage ; C675 a real id (bit 7 clear) - show it beepNoOlderMessage: lda #$03 ; C677 sound 3 = the error/refusal beep jmp playSound ; C679 tail call: beep and return showReviewedMessage: lda #$1E ; C67C $1E = 'message 0 has text'; the code itself prints nothing sta chatMessageLine ; C67E keep the chat line marked as printable so the status line returns to it when review ends lda pendingMessageId ; C681 id currently selected ora #$80 ; C683 set bit 7 = 'the status line is free' sta shownMessageId ; C685 so updateStatusMessage prints the reviewed message immediately sty messageQueueTail ; C687 the ring read index now points at the reviewed entry lda messageQueue,y ; C68A fetch that entry again sta pendingMessageId ; C68D and make it the message to display lda #$00 ; C68F zero the display timer sta messageTimer ; C691 so the next updateStatusMessage pass prints it at once lda #$80 ; C693 $80 = negative sta L_C54A+1 ; C695 patch the 'lda #$04' operand at $C54A so the reviewed message is shown silently messageReviewDone: rts ; C698 nothing more to do this frame ; ---------------------------------------------------------------------- ; Not the scrollback key: CRSR left/right pops the score line up during the battle. ; ---------------------------------------------------------------------- checkScoreLineKey: lda lastTypedKey ; C699 last key again cmp #$8E ; C69C $8E = CRSR left/right, the 'show the score' key bne messageReviewDone ; C69E some other key - ignore it lda #$FF ; C6A0 $FF = 'no key' sta lastTypedKey ; C6A2 consume the key lda gamePhase ; C6A5 zp_B1 is 0 only while the battle is running bne messageReviewDone ; C6A7 in setup, film or menu phases there is no score line to show lda #$3C ; C6A9 $3C = 60 sta inputLockoutTimer ; C6AB lock the keyboard and joystick out for 60 frames sta messageTimer ; C6AE and keep the score line up for 60 message ticks lda shownMessageId ; C6B0 id on the status line and #$7F ; C6B2 clear bit 7 = 'the line is occupied' so the score line is not overwritten at once sta shownMessageId ; C6B4 store it back, then fall into drawStatusScoreLine ; ---------------------------------------------------------------------- ; drawStatusScoreLine - redraws the bottom status line as ' CLICKS: US: ; THEM:'. The three labels are three consecutive bit-7 terminated strings at ; $065E/$0668/$066F, so after the first one printString's pointer is simply run on with ; printStringAtPointer. sideScore is indexed by the side that was scored against, so entry ; [playerSide EOR 1] is the local player's own point total. ; In: gameClock $91CB, playerSide $0B9F, sideScoreLo/Hi $92A2/$92A4, the label strings at $065E ; Out: status line redrawn; the caller's text window is saved to $92B6 and restored; decimalValueHigh ; zp_7E, the digit buffer $92DA and zp_18-zp_1F are clobbered ; Called from: by JMP only - updateStatusMessage ($C521, when the clock changed while the last-quarter ; flag $92FA is set) and handleMessageReviewKeys (fall-through at $C6B4) ; ---------------------------------------------------------------------- drawStatusScoreLine: jsr saveTextWindow ; C6B6 stash the caller's text window in $92B6 and select the full screen jsr setMessageLineWindow ; C6B9 switch to the one-row status bar window (columns 2-37 of row 22 or 23) lda #$5E ; C6BC low byte of ' CLICKS:' at $065E ldy #$06 ; C6BE high byte of $065E jsr printString ; C6C0 print it, leaving the string pointer just past its terminator lda gameClock ; C6C3 $91CB, the game clock in clicks (it counts down to 0) ldy #$00 ; C6C6 middle byte 0; the high byte zp_7E was left 0 by the previous conversion jsr printDecimal ; C6C8 print it as decimal without leading zeros jsr printStringAtPointer ; C6CB carry straight on with the next string in memory, ' US:' at $0668 lda playerSide ; C6CE $0B9F = the side this machine plays eor #$01 ; C6D1 the other side, which is the index of the points WE scored jsr printPlayerPoints ; C6D3 print our 16-bit point total jsr printStringAtPointer ; C6D6 carry on with ' THEM:' at $066F lda playerSide ; C6D9 our own side index = the points scored against us jsr printPlayerPoints ; C6DC print the opponent's point total jsr clearToEndOfLine ; C6DF pad the rest of the 36-column status line with the blank glyph jmp restoreTextWindow ; C6E2 restore the caller's text window and return ; ---------------------------------------------------------------------- ; printPlayerPoints - prints one side's 16-bit point total as decimal. The two point totals are ; indexed by the side that was scored against, so A = playerSide EOR 1 gives our score and A = ; playerSide gives the opponent's. ; In: A = score index 0/1; sideScoreLo $92A2, sideScoreHi $92A4 ; Out: digits printed at the cursor; X = the index, zp_18-zp_1F, zp_7E and the digit buffer $92DA ; clobbered ; Called from: drawStatusScoreLine ($C6D3, $C6DC) ; ---------------------------------------------------------------------- printPlayerPoints: tax ; C6E5 use the side index as a table index lda sideScoreLo,x ; C6E6 low byte of that side's point total ldy sideScoreHi,x ; C6E9 high byte (printDecimal takes A = low, Y = middle, zp_7E = high) jmp printDecimal ; C6EC tail call the decimal printer; zp_7E is left 0 by it for the next call ; ---------------------------------------------------------------------- ; resetMessageQueue - throws away every queued status message: the read index is set equal to the ; write index (empty ring), the fallback message and the display timer are cleared, and it falls into ; showNextQueuedMessage so the status line is refreshed immediately. ; In: messageQueueHead $9251 ; Out: messageQueueTail $9252 = head, persistentMessageId $92A1 = 0, messageTimer zp_72 = 0, status ; line redrawn ; Called from: a dozen places that change screen or game phase - $0AB5, $0B6A, $0CA0, $0CE1 (jmp), ; $1BE9, $28F4, $5258 in the main program and $6F8E, $7B0F, $7DD5, $7F9E, $820B in the $6F00 ; overlays ; ---------------------------------------------------------------------- resetMessageQueue: lda messageQueueHead ; C6EF ring write index sta messageQueueTail ; C6F2 make the read index equal to it: the queue is now empty lda #$00 ; C6F5 0 sta persistentMessageId ; C6F7 no fallback message either (0 = blank status line) sta messageTimer ; C6FA clear the display timer so the next message appears at once jmp showNextQueuedMessage ; C6FC pick whatever should be shown now (normally a blank line) ; ---------------------------------------------------------------------- ; showStatusMessage - prints message id A on the status line immediately, bypassing the queue, and ; plays the sound the per-message table $0612 assigns to it ($80 = silent). ; In: A = message id 0-$23; messageSoundTable $0612, messagePtrLoTable/HiTable $0500/$0524 ; Out: shownMessageId zp_7B = the id, the text is drawn centred in the status line, a sound request ; may be queued ; Called from: the disk-error retry path ($082D), the end-of-game and link-status screens ($1AC4, ; $1B81, $1BC9, $5A34, $5A52), the modem driver ($EDFE) and handlePauseKey ($C728) ; ---------------------------------------------------------------------- showStatusMessage: pha ; C6FF keep the id for the print below tay ; C700 playMessageSound indexes its table with Y jsr playMessageSound ; C701 look the id up in $0612 and start that sound unless it is $80 = silent pla ; C704 get the message id back ; ---------------------------------------------------------------------- ; showStatusMessageNoSound - the tail of showStatusMessage, also used as an entry point of its own ; when the message should be silent: records A as the message now on the status line and prints it. ; In: A = message id ; Out: shownMessageId zp_7B = A, message printed ; Called from: by JMP from showWorkingMessage ($C858); fall-through from showStatusMessage ; ---------------------------------------------------------------------- showStatusMessageNoSound: sta shownMessageId ; C705 bit 7 clear = the status line is now busy with this id jmp showMessageById ; C707 look the id up in $0500/$0524 and draw it centred ; ---------------------------------------------------------------------- ; handlePauseKey - RUN/STOP pause handling, called once per frame from updateGameFrame. It does ; nothing unless the battle is actually running (gamePhase zp_B1 = 0 and gameEndReason $0BA8 = 0). In ; a solo or film game RUN/STOP simply toggles bit 0 of pauseState zp_BE; while paused the persistent ; message is $19 'TIMEOUT. RUN/STOP TO RESUME.', only the menu-level link poll is serviced, and two ; PLAs throw away this routine's return address so the rest of the frame update is skipped. In a live ; modem game the first RUN/STOP shows 'WORKING...' and sends command $8C reason 3 (request pause); the ; opponent's answer sets zp_BE, after which this routine owns the frame: it saves and clears the ; screen, then either performs a voice pause or spins on serviceCommandExchange until zp_BE clears, ; sending $8C reason 4 (request resume) on every further RUN/STOP. On release it redraws the ; battlefield and puts the sprites back. ; In: gamePhase zp_B1, gameEndReason $0BA8, filmPlaybackMode $0B9B, pauseState zp_BE, ; spriteEnableShadow $9022, currentScreen $90FB, inputLockoutTimer $0B7D ; Out: zp_BE, persistentMessageId $92A1, commands $8C/3 and $8C/4 queued, screen cleared and redrawn, ; sprite mask restored through enableSprites; may return to its caller's caller ; Called from: updateGameFrame ($40BF and $413D) ; ---------------------------------------------------------------------- handlePauseKey: lda gamePhase ; C70A zp_B1 is 0 only during the battle itself bne pauseKeyDone ; C70C in setup, film or menu phases RUN/STOP is not the pause key lda gameEndReason ; C70E $0BA8 is 0 only while a game is actually running bne pauseKeyDone ; C711 the game is already over or was never started - nothing to pause lda filmPlaybackMode ; C713 $0B9B bit 7 = a game film is being replayed (also set for the solo trainer) bpl livePauseEntry ; C716 bit 7 clear = a live opponent, use the negotiated pause protocol ; ---------------------------------------------------------------------- ; Solo trainer / film playback: no opponent to negotiate with, so RUN/STOP just toggles bit 0 of ; pauseState and the frame update is skipped while it is set. ; ---------------------------------------------------------------------- jsr consumeRunStopKey ; C718 was RUN/STOP pressed this frame? (Z = 1 if yes, key consumed) bne checkSoloPauseActive ; C71B not pressed - just check whether we are still paused lda pauseState ; C71D pause flags eor #$01 ; C71F toggle bit 0 = our own pause request sta pauseState ; C721 store the new state bne L_C72B ; C723 non-zero = we have just paused sta persistentMessageId ; C725 A = 0: no fallback message any more jmp showStatusMessage ; C728 show message 0 (the chat line, silent) to wipe the TIMEOUT text off the status line L_C72B: lda #$19 ; C72B message $19 = 'TIMEOUT. RUN/STOP TO RESUME.' sta persistentMessageId ; C72D make it the fallback message so it stays on screen jsr queueMessage ; C730 and queue it so it appears right now checkSoloPauseActive: lda pauseState ; C733 are we paused? beq pauseKeyDone ; C735 no - let updateGameFrame carry on normally jsr handleMenuSessionPacket ; C737 paused: only the menu-level link poll keeps running pla ; C73A discard this routine's return address... pla ; C73B ...so the RTS below returns into updateGameFrame's caller and the rest of the frame is skipped pauseKeyDone: rts ; C73C normal exit ; ---------------------------------------------------------------------- ; Live modem game. zp_BE is 0 until the pause has been agreed with the opponent, so the first ; RUN/STOP only sends the request; the command interpreter sets zp_BE when the answer arrives and the ; next call falls into the pause loop below. ; ---------------------------------------------------------------------- livePauseEntry: lda pauseState ; C73D pause flags: bit 0 own pause, bit 1 opponent pause, bit 7 voice pause bne enterPauseLoop ; C73F already in a pause - run the pause loop jsr consumeRunStopKey ; C741 was RUN/STOP pressed? bne pauseKeyExit ; C744 no - carry on with the frame jsr showWorkingMessage ; C746 put 'WORKING...' on the status line while the request is in flight lda #$03 ; C749 reason 3 = request pause jmp sendPauseCommand ; C74B queue the 2-byte command $8C,3 for the opponent and return enterPauseLoop: lda spriteEnableShadow ; C74E current sprite-enable mask sta restorePauseSprites+1 ; C751 patch it into the 'lda #$FF' operand at $C79D so it can be restored on resume lda currentScreen ; C754 0 battlefield, 1 missile screen, 2 drone screen, 3 console/menu cmp #$03 ; C757 screen 3 is the console/menu beq choosePauseKind ; C759 the menu screen keeps its windows jsr saveTextWindow ; C75B save the caller's text window jsr clearCurrentScreenWindows; C75E blank the windows of the current screen (this also zeroes $9022) jsr restoreTextWindow ; C761 and restore the text window choosePauseKind: lda pauseState ; C764 pause flags again bpl pauseSpinLoop ; C766 bit 7 clear = an ordinary pause jsr voicePauseReconnect ; C768 bit 7 = voice pause: hang up, let the players talk, then dial back jmp resumeFromPause ; C76B and go straight to the redraw ; ---------------------------------------------------------------------- ; Pause spin loop: keep the link alive and watch for RUN/STOP until the pause flags clear (which ; happens when the command interpreter receives the opponent's resume). serviceCommandExchange also ; keeps the status line and the IRQ fed. ; ---------------------------------------------------------------------- pauseSpinLoop: jsr serviceCommandExchange ; C76E keep exchanging command packets with the opponent while paused lda pauseState ; C771 still paused? beq resumeFromPause ; C773 no - both sides have resumed lda inputLockoutTimer ; C775 keys are ignored while this 60-frame lockout runs bne pauseSpinLoop ; C778 still locked out - keep spinning jsr consumeRunStopKey ; C77A RUN/STOP pressed again? bne pauseSpinLoop ; C77D no - keep spinning lda pauseState ; C77F pass the pause flags to the message helper jsr showWaitingForOpponent ; C781 show 'WAITING FOR OPPONENT...' if the pause is ours and the link is real lda #$04 ; C784 reason 4 = request resume jsr sendPauseCommand ; C786 queue command $8C,4 jmp pauseSpinLoop ; C789 and go back to spinning until the opponent agrees ; ---------------------------------------------------------------------- ; Resume: the battlefield screen was blanked above, so rebuild it. ; ---------------------------------------------------------------------- resumeFromPause: lda currentScreen ; C78C which screen are we on? bne restorePauseSprites ; C78F only the battlefield map (screen 0) was cleared and needs rebuilding jsr saveTextWindow ; C791 save the text window around the redraw jsr drawOverviewMap ; C794 redraw the 40x40 overview map in the left panel jsr drawViewport ; C797 redraw the 7x5 tactical viewport jsr restoreTextWindow ; C79A restore the text window restorePauseSprites: lda #$FF ; C79D operand patched at $C751 with the sprite-enable mask saved before the screen was cleared jsr enableSprites ; C79F OR the sprites back into $9022 pauseKeyExit: rts ; C7A2 back to updateGameFrame ; ---------------------------------------------------------------------- ; sendPauseCommand - queues the 2-byte network command $8C (pause/session control) with A as its ; reason byte. Reason 3 = request pause, 4 = request resume; reason 2 (ready for a new game) and 5 ; (voice pause / hang up) are sent from elsewhere. ; In: A = reason code ; Out: cmdParam0 zp_73 = A, two bytes appended to the outgoing command ring ; Called from: handlePauseKey ($C74B by JMP, $C786 by JSR) ; ---------------------------------------------------------------------- sendPauseCommand: sta cmdParam0 ; C7A3 the command's single argument byte lda #$8C ; C7A5 $8C = the pause / session-control command jmp queueCmd2 ; C7A7 append [$8C, reason] to the outgoing command ring and return ; ---------------------------------------------------------------------- ; consumeRunStopKey - edge test for RUN/STOP. If the last decoded key was $93 (RUN/STOP) it clears ; the key, arms a 60-frame input lockout, plays the click sound and returns A = 0 (Z = 1). Otherwise ; it returns with the key code still in A and Z = 0. ; In: lastTypedKey $9248 ; Out: Z = 1 when RUN/STOP was pressed; $9248 = $FF, inputLockoutTimer $0B7D = $3C, sound 2 queued ; Called from: handlePauseKey only ($C718, $C741, $C77A) ; ---------------------------------------------------------------------- consumeRunStopKey: lda lastTypedKey ; C7AA last key code the IRQ decoded ($FF = none) cmp #$93 ; C7AD $93 = RUN/STOP in the game's own key table bne runStopKeyReturn ; C7AF some other key - return with Z = 0 lda #$3C ; C7B1 $3C = 60 frames sta inputLockoutTimer ; C7B3 ignore the keyboard and joystick for about a second lda #$FF ; C7B6 $FF = 'no key' sta lastTypedKey ; C7B8 consume the key so the press is only acted on once lda #$02 ; C7BB sound 2 = the key click jsr playSound ; C7BD click (X and Y are preserved by playSound) lda #$00 ; C7C0 A = 0 so the caller's BNE falls through runStopKeyReturn: rts ; C7C2 Z = 1 means 'RUN/STOP was pressed' ; ---------------------------------------------------------------------- ; showWaitingForOpponent - if this is a real modem game and the pause is our own (bit 0 of the flags ; passed in A), makes message $0B 'WAITING FOR OPPONENT...' the persistent status message and queues ; it. In a solo game there is nobody to wait for, so it does nothing. ; In: A = pauseState flags, isSoloTrainer $0BA5 (bit 7 set = solo trainer) ; Out: persistentMessageId $92A1 = $0B and the message queued, or nothing ; Called from: handlePauseKey ($C781) and the setup overlay ($7C00) ; ---------------------------------------------------------------------- showWaitingForOpponent: bit isSoloTrainer ; C7C3 $0BA5 bit 7 = the solo trainer is the opponent bmi waitingMessageDone ; C7C6 solo game - there is no remote player to wait for and #$01 ; C7C8 bit 0 = we are the side that asked for the pause beq waitingMessageDone ; C7CA the opponent paused us - do not claim to be waiting lda #$0B ; C7CC message $0B = 'WAITING FOR OPPONENT...' sta persistentMessageId ; C7CE keep it on the line until something else replaces it jsr queueMessage ; C7D1 and queue it so it is shown now waitingMessageDone: rts ; C7D4 shared exit of both 'nothing to say' paths ; ---------------------------------------------------------------------- ; handleTextEntryKey - the one-key line editor shared by the in-game chat line and the SAVE GAME FILM ; name field. It consumes at most one key event per call: RETURN ($8D) ends the line (bit 7 is set in ; the returned cursor position), INST/DEL ($88) backspaces, and printable codes $20-$5F are checked ; against the ASCII-to-glyph table before being stored in the 36-byte buffer at $9278, which is always ; re-terminated with $9E. When the line is full the buffer either scrolls one character left (bit 7 ; of A) or the key is refused with a beep. ; In: A = maximum length, bit 7 = 'scroll left when full'; Y = cursor position in $9278; newKeyEvent ; $90EA; the ASCII-to-glyph table $985E (= $987E - $20, indexed by the character code itself) ; Out: A = the accepted key code or $FF, Z = 1 when nothing was accepted; Y advanced (bit 7 set on ; RETURN); typedLineBuffer $9278 updated; $90EA and lastTypedKey $9248 = $FF; sound 2 (accepted) ; or 3 (refused); self-modifies the operands at $C813, $C817 and $C84F ; Called from: handleChatSend ($1387, inside the raster IRQ, A = $A3 = 35 characters with scrolling) ; and the film-name field of the setup overlay ($8029, A = $10 = 16 characters, no scrolling) ; Note: because this runs inside the IRQ for the chat line, its calls to playSound can corrupt the ; static X/Y save slots of a playSound call in progress on the main thread. ; ---------------------------------------------------------------------- handleTextEntryKey: sta textEntryScrollCheck+1 ; C7D5 keep the raw parameter (bit 7 = may scroll) in the LDX operand at $C816 and #$7F ; C7D8 strip the scroll flag sta textEntryLengthCheck+1 ; C7DA leaving the maximum length, which becomes the CPY operand at $C812 lda newKeyEvent ; C7DD key event produced this frame ($FF = none) cmp #$FF ; C7E0 $FF = nothing typed beq textEntryReturn ; C7E2 no key - return with Z = 1 and A = $FF sta textEntryReturnCode+1 ; C7E4 remember the key code as this call's return value (operand of the LDA at $C84E) cmp #$8D ; C7E7 $8D = RETURN bne textEntryCheckDelete ; C7E9 not RETURN - test the other special keys tya ; C7EB cursor position ora #$80 ; C7EC set bit 7 of the cursor position as the 'line finished' flag tay ; C7EE bit 7 set tells the caller the line has been completed jmp textEntryAccepted ; C7EF click and finish without touching the buffer textEntryCheckDelete: cmp #$88 ; C7F2 $88 = INST/DEL bne textEntryCheckPrintable ; C7F4 not a delete either - treat it as a printable character cpy #$00 ; C7F6 already at the start of the line? beq textEntryRefuse ; C7F8 nothing to delete - refuse with a beep dey ; C7FA step the cursor back over the last character jmp terminateTypedLine ; C7FB re-terminate the buffer there and click textEntryCheckPrintable: and #$7F ; C7FE key codes carry bit 7; strip it to get plain ASCII tax ; C800 keep the character code for the table lookup and the store sec ; C801 prepare the range test sbc #$20 ; C802 codes below space are control keys bcc textEntryNoKey ; C804 not printable - swallow it silently, no beep cmp #$40 ; C806 $20..$5F is the printable range ($40 codes) bcs textEntryRefuse ; C808 above 'Z' - refuse with a beep lda titleColorRow02Cell14,x ; C80A $985E,x = asciiToGlyphTable[code - $20] (also graphicsData9300:asciiToGlyphTableBiased) cmp #$3B ; C80D $3B is the 'no glyph' entry (backslash, caret and underscore) beq textEntryRefuse ; C80F the font cannot draw it - refuse with a beep txa ; C811 get the character code back textEntryLengthCheck: cpy #$FF ; C812 operand = the maximum line length patched at $C7DA bcc storeTypedChar ; C814 still room in the line - just store it textEntryScrollCheck: ldx #$FF ; C816 operand = the caller's parameter patched at $C7D5; bit 7 = 'scroll when full' bpl textEntryRefuse ; C818 no scrolling allowed - the line is full, beep ; ---------------------------------------------------------------------- ; The line is full but scrolling is allowed (chat line): shift the whole 36-byte buffer one character ; to the left, then store the new character at the end. ; ---------------------------------------------------------------------- pha ; C81A keep the character while the buffer is shifted ldx #$00 ; C81B start at the first byte of the buffer scrollTypedLineLoop: lda typedLineScrollSrc,x ; C81D read one byte further on ($9279 = buffer + 1) sta typedLineBuffer,x ; C820 and write it one place to the left inx ; C823 next byte cpx #$24 ; C824 $24 = 36, the length of the line buffer bcc scrollTypedLineLoop ; C826 keep shifting; note the last pass reads $929C (chatState) one byte past the buffer, but that byte is overwritten by the terminator below dey ; C828 the whole line moved left, so the cursor did too pla ; C829 recover the character storeTypedChar: sta typedLineBuffer,y ; C82A store it at the cursor iny ; C82D and advance the cursor bne terminateTypedLine ; C82E always taken (Y can never wrap to 0 here) textEntryRefuse: lda #$03 ; C830 sound 3 = the refusal beep jsr playSound ; C832 beep textEntryNoKey: lda #$FF ; C835 $FF = 'no key accepted' sta textEntryReturnCode+1 ; C837 overwrite the return value stored at $C7E4 bne textEntryConsumeKey ; C83A always taken terminateTypedLine: lda #$9E ; C83C $9E = the terminator that prints nothing sta typedLineBuffer,y ; C83E terminate the line at the cursor textEntryAccepted: lda #$02 ; C841 sound 2 = the key click jsr playSound ; C843 click textEntryConsumeKey: lda #$FF ; C846 $FF = 'no key' sta newKeyEvent ; C848 consume the key event... sta lastTypedKey ; C84B ...and the decoded key, so no other handler sees it textEntryReturnCode: lda #$FF ; C84E operand = the accepted key code, or $FF when nothing was taken textEntryReturn: cmp #$FF ; C850 Z = 1 tells the caller that nothing was accepted rts ; C852 A = the key code (or $FF), Y = the new cursor position ; ---------------------------------------------------------------------- ; showWorkingMessage - puts message 1 'WORKING...' on the status line at once and makes it the ; persistent message, so it stays there until something clears it. Message 1's sound entry is $80, ; i.e. silent. ; In: none ; Out: persistentMessageId $92A1 = 1, shownMessageId zp_7B = 1, message drawn ; Called from: before every slow operation - $10CE (jmp) and $1AA4 in the main program, $6F2D, $79A4, ; $79EF, $7B1D in the setup overlay, and handlePauseKey ($C746) ; ---------------------------------------------------------------------- showWorkingMessage: lda #$01 ; C853 message id 1 = 'WORKING...' ($056C) sta persistentMessageId ; C855 keep it on the line until the queue or a screen change replaces it jmp showStatusMessageNoSound; C858 print it now, without a sound lookup ; ---------------------------------------------------------------------- ; promptPressButtonWhenReady - shows 'PRESS BUTTON WHEN READY.' centred on the status line and ; busy-waits for a fresh fire-button click, stirring the 24-bit game RNG on every pass. Because the ; number of passes depends on the player, this is also what randomises the map seed before a game. ; In: the fire button (through checkFirePressed), RNG state $57-$59 ; Out: returns after a click; the RNG has been advanced an unpredictable number of times and the ; status line is blanked ; Called from: waitForGameDiskInserted ($10E8) ; ---------------------------------------------------------------------- promptPressButtonWhenReady: lda #$BE ; C85B low byte of 'PRESS BUTTON WHEN READY.' at $CBBE ldy #$CB ; C85D high byte of $CBBE jsr showMessageString ; C85F draw it centred in the 36-column status line waitForFireLoop: jsr nextGameRandom ; C862 advance the pseudo-random shift register - the player's reaction time is the entropy jsr checkFirePressed ; C865 Z = 1 as soon as a fresh press is seen (it also plays click sound 1) bne waitForFireLoop ; C868 no click yet - keep stirring jmp clearMessageLine ; C86A blank the status line and return ; ---------------------------------------------------------------------- ; exitIfGameEnded - if the end-of-game display has been up long enough this routine never returns: it ; transfers control to the main menu instead. gameEndCountdown $92C9 is set to a start value when the ; game ends and counted UP by the IRQ until it reaches $80, so bit 7 set means 'the result has been ; shown, get out'. ; In: gameEndCountdown $92C9, gamePhase zp_B1 ; Out: returns normally, or jumps to the menu (which rebuilds the stack, so the caller is abandoned) ; Called from: the top of updateGameFrame ($40B2) and the command-exchange loop ($4E41) ; ---------------------------------------------------------------------- exitIfGameEnded: lda gameEndCountdown ; C86D $92C9: 0 = no game over pending, otherwise it counts up to $80 bpl gameStillRunning ; C870 still counting (or nothing pending) - just go back lda gamePhase ; C872 zp_B1 is 0 only during the battle beq endBattleToMenu ; C874 in the battle the session has to be tidied up first jmp returnToMainMenu ; C876 setup, film or menu phase: reset the stack and show the main menu endBattleToMenu: jmp endGameToMainMenu ; C879 battle: close the film, free the link and then go to the menu gameStillRunning: rts ; C87C the game is still running ; soundClassTable - one class/priority byte per sound id 0-28, read by requestSound. ; $00/$01/$02 = ordinary request for SID voice 0/1/2: only accepted when that request ; slot is free and the voice is silent or busy with another ordinary sound. ; $80/$81/$82 = force the sound onto voice 0/1/2, overriding whatever plays there. ; $FF = force it into request slot 3, the whole-chip slot used by the three-voice ; fanfares (ids $0D, $0E, $19). ; requestSound only uses the low two bits as the slot number, so $FF selects slot 3. Entry 0 is never ; used as a class: requestSound(0) is the 'silence everything' request. soundClassTable: .byte $FF,$00,$00,$00,$00,$00,$FF,$80; C87D ........ ids 0-7: 0 unused (silence request), 1-5 ordinary voice 0, 6 whole-chip, 7 forced voice 0 .byte $81 ; C885 ids 8-$0F: 8 forced voice 1, 9-$0B ordinary voice 1, $0C forced voice 0, $0D/$0E whole-chip fanfares, $0F forced voice 2 .byte $01,$01,$01,$80,$FF,$FF,$82; C886 ....... .byte $82 ; C88D ids $10-$17: $10-$13 forced voice 2, $14 ordinary voice 2 (comcen engine), $15 whole-chip, $16/$17 forced voice 2 .byte $82,$82,$82,$02,$FF,$82,$82; C88E ....... .byte $82 ; C895 ids $18-$1C: $18 forced voice 2, $19 whole-chip fanfare, $1A forced voice 0, $1B forced voice 1, $1C forced voice 2 (comcen engine stop) .byte $FF,$80,$81,$82 ; C896 .... ; ---------------------------------------------------------------------- ; playSound - the public sound entry used by about fifty call sites: queues sound id A and gives X and ; Y back to the caller. X and Y are preserved in two self-modified operands rather than on the stack, ; so this entry is NOT re-entrant - the IRQ-side callers at $1370 and $140E deliberately call ; requestSound directly. ; In: A = sound id 0-28 ; Out: the sound is queued in one of the four request slots; X and Y unchanged, A clobbered ; Called from: everywhere - see XREF; within this file $C55C, $C679 (jmp), $C7BD, $C832, $C843, $C910 ; (jmp), $C920 (jmp) ; ---------------------------------------------------------------------- playSound: sty playSoundRestoreY+1 ; C89A save Y in the operand of the LDY at $C8A3 stx playSoundRestoreX+1 ; C89D save X in the operand of the LDX at $C8A5 jsr requestSound ; C8A0 queue the request playSoundRestoreY: ldy #$FF ; C8A3 operand patched at $C89A: the caller's Y playSoundRestoreX: ldx #$FF ; C8A5 operand patched at $C89D: the caller's X rts ; C8A7 only A is clobbered ; ---------------------------------------------------------------------- ; requestSound - the core of the sound request queue. A = 0 is the 'silence everything' request: ; slots 0-2 become $FF (empty) and slot 3 gets id 0, so the raster IRQ dequeues sound 0 and stops the ; chip. Otherwise the class byte from soundClassTable decides where the request goes: a negative ; class forces it into slot (class AND 3), while a positive class is a plain voice number that is only ; taken when that request slot is free and the voice is either silent or playing another low-priority ; sound. Ids of $1D and above are dropped. ; In: A = sound id 0-28; soundClassTable $C87D, the four request slots $92E7-$92EA, sndVoiceSoundId ; $67C8 ; Out: one of $92E7-$92EA holds the id (or all four are reset); self-modifies the operand at $C8DC; ; A/X/Y clobbered ; Called from: playSound ($C8A0) and directly from the two IRQ-side call sites $1370 (jmp) and $140E ; ---------------------------------------------------------------------- requestSound: tay ; C8A8 Y = the sound id for the table lookups below bne classifySoundRequest ; C8A9 id 0 is the request to silence everything sty soundRequestMultiVoice ; C8AB slot 3 (the whole-chip slot) receives id 0 = 'stop all voices' dey ; C8AE Y = $FF sty soundRequestVoice2 ; C8AF voice 2 request slot: empty sty soundRequestVoice1 ; C8B2 voice 1 request slot: empty sty soundRequestSlots ; C8B5 voice 0 request slot: empty rts ; C8B8 the IRQ will pick up the id 0 in slot 3 on its next pass classifySoundRequest: lda soundClassTable,y ; C8B9 class/priority byte of this sound (ids >= $1D read past the table, but the range test at $C8E1 drops them before anything is stored) sta queueSoundInSlot+1 ; C8BC patch it into the LDA operand at $C8DB - the slot number is derived from it below bmi queueSoundInSlot ; C8BF negative class = forced, no priority test needed tax ; C8C1 X = the voice number 0-2 lda soundRequestSlots,x ; C8C2 is a request already waiting for that voice? bmi checkVoiceBusy ; C8C5 $FF = free, so only the voice itself has to be checked tax ; C8C7 X = the sound id already queued there lda soundClassTable,x ; C8C8 its class bmi soundRequestDone ; C8CB the queued sound is a forced one - do not displace it ldx queueSoundInSlot+1 ; C8CD restore X = our own voice number checkVoiceBusy: lda sndVoiceSoundId,x ; C8D0 $67C8+voice = the id the SID voice is playing right now (0 = silent) beq queueSoundInSlot ; C8D3 silent - the request can be taken tax ; C8D5 X = the sound currently playing lda soundClassTable,x ; C8D6 its class bmi soundRequestDone ; C8D9 a forced sound is playing - do not interrupt it queueSoundInSlot: lda #$FF ; C8DB operand = the class byte patched at $C8BC and #$03 ; C8DD the low two bits are the request slot: 0-2 = SID voices, 3 = the whole-chip slot ($FF AND 3) tax ; C8DF X = slot tya ; C8E0 A = the sound id again cmp #$1D ; C8E1 29 sounds are defined (ids 0-$1C) bcs soundRequestDone ; C8E3 out of range - drop it sta soundRequestSlots,x ; C8E5 queue the id; the raster IRQ dequeues it with dequeueSoundRequest soundRequestDone: rts ; C8E8 shared exit: either the request was queued or it was dropped ; ---------------------------------------------------------------------- ; dequeueSoundRequest - takes one pending sound request out of the four slots, scanning from slot 3 ; (the whole-chip slot, highest priority) down to slot 0. The first non-empty slot is emptied and its ; id returned in X; X = $FF when all four are empty. ; In: the four request slots $92E7-$92EA ($FF = empty) ; Out: X = the sound id or $FF, that slot reset to $FF; A/Y clobbered ; Called from: the raster IRQ ($1283), which hands X straight to startSound ($6728) ; ---------------------------------------------------------------------- dequeueSoundRequest: ldy #$03 ; C8E9 start at slot 3, the whole-chip slot scanSoundSlotsLoop: ldx soundRequestSlots,y ; C8EB id waiting in this slot bmi nextSoundSlot ; C8EE $FF = empty, try the next slot down lda #$FF ; C8F0 $FF = empty sta soundRequestSlots,y ; C8F2 take the request out of the slot rts ; C8F5 X = the id to start nextSoundSlot: dey ; C8F6 next slot down (2, 1, 0) bpl scanSoundSlotsLoop ; C8F7 keep scanning while the index is still non-negative rts ; C8F9 X = $FF: nothing to start this frame ; ---------------------------------------------------------------------- ; updateComcenMoveSound - keeps the engine sound of the local player's command centre in step with its ; movement. It only acts during a live battle. When the comcen is still moving and SID voice 2 is ; silent it starts the engine loop (sound $14); when the comcen has arrived and voice 2 is still ; running the engine ($14) or its variant ($16) it plays the engine-stop sound ($1C). ; In: gamePhase zp_B1, filmPlaybackMode $0B9B, comcenUnit $9236, unitDisplayFlagsTable $FB54 (bit 7 = ; the unit has reached its destination), sndVoiceSoundId+2 $67CA (the sound on SID voice 2) ; Out: sound $14 or $1C requested through playSound ; Called from: processUnitStep ($42A0), i.e. once per unit movement step ; ---------------------------------------------------------------------- updateComcenMoveSound: lda gamePhase ; C8FA zp_B1 is 0 only during the battle ora filmPlaybackMode ; C8FC $0B9B is 0 only in a live game (not film playback, not solo) bne comcenSoundDone ; C8FF any other state - stay quiet ldy comcenUnit ; C901 unit index of our own comcen (49 for side 0, 99 for side 1) lda unitDisplayFlagsTable,y ; C904 its UI flag byte; bit 7 = the unit is standing on its destination bmi comcenHasStopped ; C907 bit 7 set = the comcen has stopped lda sndVoice2SoundId ; C909 the sound SID voice 2 is playing (0 = silent) bne comcenSoundDone ; C90C voice 2 is busy - do not start the engine over it lda #$14 ; C90E sound $14 = the comcen engine loop jmp playSound ; C910 start it (its class $02 means it yields to forced sounds) comcenHasStopped: lda sndVoice2SoundId ; C913 the sound on voice 2 cmp #$14 ; C916 $14 = the engine loop beq requestComcenStopSound ; C918 yes - stop it cmp #$16 ; C91A $16 = the other comcen movement sound bne comcenSoundDone ; C91C voice 2 is playing something else - leave it alone requestComcenStopSound: lda #$1C ; C91E sound $1C = the engine-stop sound (forced onto voice 2) jmp playSound ; C920 play it comcenSoundDone: rts ; C923 nothing to do: wrong game phase, or voice 2 is busy with another sound ; ---------------------------------------------------------------------- ; setMessageSlot - installs a string address into the run-time message pointer tables, so a message id ; can be re-pointed at different text. Used for the dynamic slots $1E, $20 and $21. ; In: X = message id, A = string low byte, Y = string high byte ; Out: messagePtrLoTable $0500[X] = A, messagePtrHiTable $0524[X] = Y; A = the high byte on exit ; Called from: the start-up code ($0B1D, $0B26, $0C8E, $0C97), the setup overlay ($7BAC, $81C2, $821C, ; $86B5, $8724) and the modem driver tail ($EED6, $EEFF) ; ---------------------------------------------------------------------- setMessageSlot: sta messagePtrLoTable,x ; C924 low byte of the string address into slot X tya ; C927 high byte sta messagePtrHiTable,x ; C928 into the parallel table $24 bytes further on rts ; C92B A = the high byte, which some callers rely on ; ------------------------------------------------------------------- ; The game's vocabulary: $C92C-$CCFF is one pool of text, most of it in fixed 8-byte-stride tables ; that printTableEntry ($C100) indexes with A/Y = base address and X = entry number, the rest bit-7 ; terminated strings printed in sequence with printString ($C133) / printStringAtPointer ($C137). ; Several tables are deliberately truncated because the next block starts immediately - see the notes ; on each one. ; ------------------------------------------------------------------- ; groupNameTable - seven 8-byte group names, index = the group id in bits 2-4 of the unit's UI flag ; byte $FB54: 0 ALPHA, 1 BAKER, 2 CHARLIE, 3 DELTA, 4 EASY, 5 FOXTROT, 6 GROVER. Read by ; printUnitStatusLines ($23B1), runJoinGroupMenu ($6162) and runGroupOptionsMenu ($634E). Index 7 ; does not exist and would run into unitTypeNameTable below, printing GRUNT. groupNameTable: .byte "ALPHA ",$A0,$00 ; C92C ; msgBaker - message text "BAKER " (the last character carries bit 7 as the terminator). msgBaker: .byte "BAKER ",$A0 ; C934 .byte $00 ; C93B . ; msgCharlie - message text "CHARLIE" (the last character carries bit 7 as the terminator). msgCharlie: .byte "CHARLI",'E'|$80 ; C93C .byte $00 ; C943 . ; msgDelta - message text "DELTA " (the last character carries bit 7 as the terminator). msgDelta: .byte "DELTA ",$A0 ; C944 .byte $00 ; C94B . ; msgEasy - message text "EASY " (the last character carries bit 7 as the terminator). msgEasy: .byte "EASY ",$A0 ; C94C .byte $00 ; C953 . ; msgFoxtrot - message text "FOXTROT" (the last character carries bit 7 as the terminator). msgFoxtrot: .byte "FOXTRO",'T'|$80 ; C954 .byte $00 ; C95B . ; msgGrover - message text "GROVER " (the last character carries bit 7 as the terminator). msgGrover: .byte "GROVER",$A0 ; C95C .byte $00 ; C963 . ; unitTypeNameTable - five 8-byte unit type names indexed by the unit type in bits 0-2 of ; unitTypeTable $F76C: 0 GRUNT, 1 RIDER, 2 BOOMER, 3 SPY, 4 COMCEN. Each name is right-aligned inside ; its 8-byte slot with leading spaces, so no padding is needed at the print site (showUnitInfoPanel ; $22C9). unitTypeNameTable: .byte "GRUN",'T'|$80 ; C964 ; msgRider - message text " RIDER" (the last character carries bit 7 as the terminator). msgRider: .byte " RIDE",'R'|$80 ; C969 ; msgBoomer - message text " BOOMER" (the last character carries bit 7 as the terminator). msgBoomer: .byte " BOOME",'R'|$80 ; C971 ; msgSpy - message text " SPY" (the last character carries bit 7 as the terminator). msgSpy: .byte " SP",'Y'|$80 ; C97A ; msgComcen - message text " COMCEN" (the last character carries bit 7 as the terminator). msgComcen: .byte " COMCE",'N'|$80 ; C97F ; msgReady - message text " READY " (the last character carries bit 7 as the terminator). msgReady: .byte " " ; C98A ; unitStatusNameTable - nine 8-byte status words used by the two status lines of ; the unit info panel: 0 READY, 1 RELOAD, 2 FIRED, 3 ' - ', 4 REPAIR, 5 MOVE, ; 6 PINNED, 7 STUN'D, 8 DIGGIN. The WEAPON: line indexes the table from $C98C (entries 0-2 plus ; STUN'D), the ACTION: line from entry 3 at $C9A4, so its ; indices 0-5 read ' - '/REPAIR/MOVE/PINNED/STUN'D/DIGGIN. The last entry is ; only 6 bytes long because unitOptionMenuText starts right after it. unitStatusNameTable: .byte "READY",$A0,$00,$00 ; C98C ; msgReload - message text "RELOAD" (the last character carries bit 7 as the terminator). msgReload: .byte "RELOA",'D'|$80 ; C994 .byte $00,$00 ; C99A .. ; msgFired - message text "FIRED " (the last character carries bit 7 as the terminator). msgFired: .byte "FIRED",$A0 ; C99C .byte $00,$00,$20,$2D,$20,$20,$20,$A0; C9A2 .. - . .byte $00,$00 ; C9AA .. ; msgRepair - message text "REPAIR" (the last character carries bit 7 as the terminator). msgRepair: .byte "REPAI",'R'|$80 ; C9AC .byte $00,$00 ; C9B2 .. ; msgMove - message text "MOVE " (the last character carries bit 7 as the terminator). msgMove: .byte "MOVE ",$A0 ; C9B4 .byte $00,$00 ; C9BA .. ; msgPinned - message text "PINNED" (the last character carries bit 7 as the terminator). msgPinned: .byte "PINNE",'D'|$80 ; C9BC .byte $00,$00,$53,$54,$55,$4E,$27,$C4; C9C2 ..STUN'. .byte $00,$00 ; C9CA .. ; msgDiggin - message text "DIGGIN" (the last character carries bit 7 as the terminator). msgDiggin: .byte "DIGGI",'N'|$80 ; C9CC ; unitOptionMenuText - the twelve lines of the UNIT OPTIONS and GROUP OPTIONS menus, each terminated ; with $8D (carriage return with bit 7 set), so the menu printers at $5FCA and $6387 just call ; printStringAtPointer repeatedly and the pointer walks from one line to the next: FORMATION, MOVE TO ; GOAL, CLOAKING ON, NO CLOAKING, CLEAR TARGET, SET TARGET, MEMBERSHIP, JOIN GROUP, DIRECT UNIT, SELF ; DESTRCT, SET BLITZ, BLITZ OFF. unitOptionMenuText: .byte "FORMATION",$8D ; C9D2 ; msgMoveToGoal - message text " MOVE TO GOAL " (the last character carries bit 7 as the ; terminator). msgMoveToGoal: .byte " MOVE TO GOAL",$8D; C9DC ; msgCloakingOn - message text " CLOAKING ON " (the last character carries bit 7 as the terminator). msgCloakingOn: .byte " CLOAKING ON",$8D ; C9EF ; msgNoCloaking - message text " NO CLOAKING " (the last character carries bit 7 as the ; terminator). msgNoCloaking: .byte " NO CLOAKING",$8D ; C9FE ; msgClearTarget - message text " CLEAR TARGET " (the last character carries bit 7 as the ; terminator). msgClearTarget: .byte " CLEAR TARGET",$8D ; CA0E ; msgSetTarget - message text " SET TARGET " (the last character carries bit 7 as the terminator). msgSetTarget: .byte " SET TARGET",$8D ; CA1F ; msgMembership - message text " MEMBERSHIP " (the last character carries bit 7 as the ; terminator). msgMembership: .byte " MEMBERSHIP",$8D ; CA2D ; msgJoinGroup - message text " JOIN GROUP " (the last character carries bit 7 as the terminator). msgJoinGroup: .byte " JOIN GROUP",$8D ; CA3D ; msgDirectUnit - message text " DIRECT UNIT " (the last character carries bit 7 as the ; terminator). msgDirectUnit: .byte " DIRECT UNIT",$8D ; CA4D ; msgSelfDestrct - message text " SELF DESTRCT " (the last character carries bit 7 as the ; terminator). msgSelfDestrct: .byte " SELF DESTRCT",$8D ; CA5E ; msgSetBlitz - message text " SET BLITZ " (the last character carries bit 7 as the terminator). msgSetBlitz: .byte " SET BLITZ",$8D ; CA6F ; msgBlitzOff - message text " BLITZ OFF " (the last character carries bit 7 as the terminator). msgBlitzOff: .byte " BLITZ OFF",$8D ; CA7C ; formationMenuText - the formation sub-menu as a single string with embedded ; $0D line breaks, so one printString call draws all five rows: ; SWEEP LEFT / SWEEP RIGHT / REVERSE / CUSTOM / (EXIT). formationMenuText: .byte "SWEEP LEFT",$0D,"SWEEP RIGHT",$0D,"REVERSE",$0D,"CUSTOM"; CA8C .byte $0D," (EXIT",')'|$80 ; CA8C ; consoleMiscMenuText - six $8D-terminated lines printed in sequence by the command console: ' ; ENERGY', ' BATTLE', ' RADAR', ' DRONE', ' DIG OUT', 'DIG IN'. The leading spaces do the centring ; inside the narrow console column. consoleMiscMenuText: .byte " ENERGY",$8D ; CAB9 ; msgBattle - message text " BATTLE " (the last character carries bit 7 as the terminator). msgBattle: .byte " BATTLE",$8D ; CAC1 ; msgRadar - message text " RADAR " (the last character carries bit 7 as the terminator). msgRadar: .byte " RADAR",$8D ; CAC9 ; msgDrone3 - message text " DRONE " (the last character carries bit 7 as the terminator). msgDrone3: .byte " DRONE",$8D ; CAD0 ; msgDigOut - message text " DIG OUT " (the last character carries bit 7 as the terminator). msgDigOut: .byte " DIG OUT",$8D ; CAD8 ; msgDigIn - message text "DIG IN " (the last character carries bit 7 as the terminator). msgDigIn: .byte "DIG IN",$8D ; CAE2 ; unitInfoLabelText - the label column of the unit info panel as one string with ; $0D line breaks and a bit-7 terminator: 'GROUP:', three blank lines (where the ; unit picture sprites sit), 'ENERGY:', 'WEAPON:', 'ACTION:'. Printed by showUnitInfoPanel ($22E0); ; the values are printed alongside by printUnitStatusLines ($2384). unitInfoLabelText: .byte "GROUP:",$0D,$0D,$0D,$0D,"ENERGY:",$0D,"WEAPON:",$0D; CAE9 .byte "ACTION",':'|$80 ; CAE9 ; consoleHeadingText - two $8D-terminated headings drawn above the console menus: ; ' UNIT' ($CB0A) and ' OPTIONS' followed by a blank line ($CB13). consoleHeadingText: .byte " UNIT",$8D ; CB0A ; msgOptions - message text " OPTIONS " (the last character carries bit 7 as the terminator). msgOptions: .byte " OPTIONS",$0D,$8D ; CB13 ; uiPromptStrings - a pool of short bit-7 terminated words and prompts shared by the unit/group menus, ; the three command-console tabs and the info panel. They are addressed individually, not as a table: ; GROUP $CB1F, 'WHICH GROUP:' $CB25, ' TOO FAR!' $CB31, ADDED $CB3B, REMOVED $CB40, FINISHED $CB47, ; SETTING $CB4F, DESTINATION $CB56, TARGET $CB61, 'FUEL:' $CB67, MISC $CB6C, STATS $CB70, REPAIR ; $CB75, CLOAKED $CB7B, BLITZ $CB82, 'DUG IN' $CB87, 'NOT IN GROUP' $CB8D, RE-POSITIONED $CB99, 'NEW ; POSITION' $CBA6, ' EXIT ' $CBB2, ENERGY $CBB8, 'PRESS BUTTON WHEN READY.' $CBBE, 'REPAIR/WHICH:' ; $CBD6 and 'ATTEMPTING REPAIR...' $CBE5. MISC/STATS/REPAIR are the three console tab labels drawn ; around $1655-$1675. uiPromptStrings: .byte "GROUP",$8D ; CB1F ; msgWhichGroup - message text "WHICH GROUP:" (the last character carries bit 7 as the terminator). msgWhichGroup: .byte "WHICH GROUP",':'|$80 ; CB25 ; msgTooFar - message text " TOO FAR!" (the last character carries bit 7 as the terminator). msgTooFar: .byte " TOO FAR",'!'|$80 ; CB31 ; msgAdded - message text "ADDED" (the last character carries bit 7 as the terminator). msgAdded: .byte "ADDE",'D'|$80 ; CB3B ; msgRemoved - message text "REMOVED" (the last character carries bit 7 as the terminator). msgRemoved: .byte "REMOVE",'D'|$80 ; CB40 ; msgFinished - message text "FINISHED" (the last character carries bit 7 as the terminator). msgFinished: .byte "FINISHE",'D'|$80 ; CB47 ; msgSetting - message text "SETTING" (the last character carries bit 7 as the terminator). msgSetting: .byte "SETTIN",'G'|$80 ; CB4F ; msgDestination - message text "DESTINATION" (the last character carries bit 7 as the terminator). msgDestination: .byte "DESTINATIO",'N'|$80 ; CB56 ; msgTarget - message text "TARGET" (the last character carries bit 7 as the terminator). msgTarget: .byte "TARGE",'T'|$80 ; CB61 .byte $46,$55,$45,$4C,$BA,$4D,$49,$53; CB67 FUEL.MIS .byte $C3 ; CB6F . ; msgStats - message text "STATS" (the last character carries bit 7 as the terminator). msgStats: .byte "STAT",'S'|$80 ; CB70 ; msgRepair2 - message text "REPAIR" (the last character carries bit 7 as the terminator). msgRepair2: .byte "REPAI",'R'|$80 ; CB75 ; msgCloaked - message text "CLOAKED" (the last character carries bit 7 as the terminator). msgCloaked: .byte "CLOAKE",'D'|$80 ; CB7B ; msgBlitz - message text "BLITZ" (the last character carries bit 7 as the terminator). msgBlitz: .byte "BLIT",'Z'|$80 ; CB82 ; msgDugIn - message text "DUG IN" (the last character carries bit 7 as the terminator). msgDugIn: .byte "DUG I",'N'|$80 ; CB87 ; msgNotInGroup - message text "NOT IN GROUP" (the last character carries bit 7 as the terminator). msgNotInGroup: .byte "NOT IN GROU",'P'|$80 ; CB8D ; msgRePositioned - message text "RE-POSITIONED" (the last character carries bit 7 as the terminator). msgRePositioned: .byte "RE-POSITIONE",'D'|$80 ; CB99 ; msgNewPosition - message text "NEW POSITION" (the last character carries bit 7 as the terminator). msgNewPosition: .byte "NEW POSITIO",'N'|$80 ; CBA6 ; msgExit - message text " EXIT " (the last character carries bit 7 as the terminator). msgExit: .byte " EXIT",$A0 ; CBB2 ; msgEnergy2 - message text "ENERGY" (the last character carries bit 7 as the terminator). msgEnergy2: .byte "ENERG",'Y'|$80 ; CBB8 ; msgPressButtonWhenReady - message text "PRESS BUTTON WHEN READY." (the last character carries bit 7 ; as the terminator). msgPressButtonWhenReady: .byte "PRESS BUTTON WHEN READY",'.'|$80; CBBE ; msgRepair3 - message text, 2 lines separated by $0D; the last character carries bit 7: ; "REPAIR" ; "WHICH:" msgRepair3: .byte "REPAIR",$0D,"WHICH:",$0D,$8D; CBD6 ; 'ATTEMPTING REPAIR...' - shown by the REPAIR tab while it tries to un-stun the comcen ($17DA). ; NOTE: the two labels inside this string, at $CBED and $CBF2, are spurious. They come from XREF ; entries 'call:C3D0' and 'call:C3D9', which belong to the boot loader that used to occupy ; $C000-$C3FF; in this overlay ; $C3D0 and $C3D9 are operand bytes of LDY #$00 and LDX #$24 inside ; printOnMessageLine. Nothing in the running game calls $CBED or $CBF2 - they are the middle of a ; string, not code. msgAttemptingRepair: .byte "ATTEMPTI" ; CBE5 strAttemptingRepairMid: .byte $4E,$47,$20,$52,$45 ; CBED NG RE strAttemptingRepairEnd: .byte $50,$41,$49,$52,$2E,$2E,$AE; CBF2 PAIR... ; gameHeaderText - the fragments of the play/scenario header on the console: ' PLAY' ($CBF9), '1ST' ; ($CBFE) and '2ND' ($CC01) - the last two are 3-byte entries selected as a pair - and a $0D-wrapped ' ; SCENARIO' ($CC04). gameHeaderText: .byte " PLA",'Y'|$80,"1S",'T'|$80,"2N",'D'|$80; CBF9 ; msgScenario - message text " SCENARIO " (the last character carries bit 7 as the terminator). msgScenario: .byte $0D," SCENARIO",$0D,$8D; CC04 ; rulesNameTable - two entries on the usual 8-byte stride, selected with printTableEntry at $15C9: 0 ; STANDARD, 1 CUSTOM. The CUSTOM entry is only 6 bytes long because mainMenuItemText starts ; immediately after it, which is safe because it is the last entry. rulesNameTable: .byte "STANDAR",'D'|$80 ; CC11 ; msgCustom - message text "CUSTOM" (the last character carries bit 7 as the terminator). msgCustom: .byte "CUSTO",'M'|$80 ; CC19 ; mainMenuItemText - the main menu, printed line by line: 'COMPETE WITH MODEM OPPONENT', 'PRACTICE ; WITH SOLO TRAINER', 'WATCH GAME FILM' and 'SAVE GAME FILM' are one $0D-separated run ending in $8D ; at $CC74, then 'LOAD GAME FILM' ($CC75) and 'DISCONNECT/SET VOICE' ($CC84) follow as separate ; $8D-terminated lines. mainMenuItemText: .byte "COMPETE WITH MODEM OPPONENT",$0D; CC1F .byte "PRACTICE WITH SOLO TRAINER",$0D,"WATCH GAME FILM",$0D; CC1F .byte "SAVE GAME FILM",$8D ; CC1F ; msgLoadGameFilm - message text "LOAD GAME FILM " (the last character carries bit 7 as the ; terminator). msgLoadGameFilm: .byte "LOAD GAME FILM",$8D ; CC75 ; msgDisconnectSetVoice - message text "DISCONNECT/SET VOICE " (the last character carries bit 7 as ; the terminator). msgDisconnectSetVoice: .byte "DISCONNECT/SET VOICE",$8D; CC84 ; inGameMenuText - ENEMY ($CC99) and MISSILES ($CC9E) are panel captions, 'ABORT GAME' ($CCA6) and ; 'VOICE PAUSE' ($CCB1) are the two items of the in-game MISC options menu. The modem driver reads ; this block too ($ED6F). inGameMenuText: .byte "ENEM",'Y'|$80 ; CC99 ; msgMissiles - message text "MISSILES" (the last character carries bit 7 as the terminator). msgMissiles: .byte "MISSILE",'S'|$80 ; CC9E ; msgAbortGame - message text "ABORT GAME " (the last character carries bit 7 as the terminator). msgAbortGame: .byte "ABORT GAME",$8D ; CCA6 ; msgVoicePause - message text "VOICE PAUSE " (the last character carries bit 7 as the terminator). msgVoicePause: .byte "VOICE PAUSE",$8D ; CCB1 ; strGameTypeLabel - 'GAME TYPE:' printed at $15C5 just before the scenario name. strGameTypeLabel: .byte "GAME TYPE:",$8D ; CCBD ; gameTypeNameTable - the seven scenario names, 8 bytes each, indexed by the game type in $0BA3 bits ; 0-2. Note the first entry is spelt SCRIMAGE with one M in the shipped game; the manual spells it ; SCRIMMAGE. The other six (QB SNEAK, THE BOMB, FACE-OFF, SLUGGERS, FULL WAR, DEFENDER) match the ; manual exactly. scenarioNameTable: .byte "SCRIMAG",'E'|$80 ; CCC8 ; msgQbSneak - message text "QB SNEAK" (the last character carries bit 7 as the terminator). msgQbSneak: .byte "QB SNEA",'K'|$80 ; CCD0 ; msgTheBomb - message text "THE BOMB" (the last character carries bit 7 as the terminator). msgTheBomb: .byte "THE BOM",'B'|$80 ; CCD8 ; msgFaceOff - message text "FACE-OFF" (the last character carries bit 7 as the terminator). msgFaceOff: .byte "FACE-OF",'F'|$80 ; CCE0 ; msgSluggers - message text "SLUGGERS" (the last character carries bit 7 as the terminator). msgSluggers: .byte "SLUGGER",'S'|$80 ; CCE8 ; msgFullWar - message text "FULL WAR" (the last character carries bit 7 as the terminator). msgFullWar: .byte "FULL WA",'R'|$80 ; CCF0 ; msgDefender - message text "DEFENDER" (the last character carries bit 7 as the terminator). msgDefender: .byte "DEFENDE",'R'|$80 ; CCF8 ; unitPictureHiTable / unitPictureLoTable - the addresses of the five unit-picture RLE blocks, split ; into a high-byte table at $CD00 and a low-byte table at $CD05, both indexed by unit type 0-4. They ; resolve to $CD52 GRUNT, $CDD3 RIDER, ; $CE3B BOOMER, $CECD SPY, $CF3A COMCEN. ; $CD00 doubles as an inert build hook: the start-up code at $0C0D tests whether ; it holds $4C (a JMP opcode) and would turn the VIC IRQ off and jump there. In the shipped overlay ; it holds $CD, so the hook never fires; showUnitInfoPanel ($22EA) only tests that $CD00 is negative, ; meaning 'unit pictures are loaded'. unitPictureHiTable: .byte $CD,$CD,$CE,$CE,$CF ; CD00 ..... high bytes for types 0-4: $CD52, $CDD3, $CE3B, $CECD, $CF3A ; unitPictureLoTable: byteTable, 5 bytes. Low bytes of the five unit-picture RLE block addresses ($52, ; $D3, $3B, $CD, $3A) giving $CD52, $CDD3, $CE3B, $CECD and $CF3A. unitPictureLoTable: .byte $52,$D3,$3B,$CD,$3A ; CD05 R.;.: low bytes of the same five addresses ; ---------------------------------------------------------------------- ; showUnitPictureSprites - draws the picture of one unit type in the info panel. It unpacks that ; type's RLE block into the scratch page $0200-$03FF, copies the first 192 bytes (three 24x21 sprite ; shapes) to $8B40 - the shape area addressed by sprite pointers $2D/$2E/$2F, i.e. sprites 5, 6 and 7 ; - and then positions and enables those three sprites side by side across the panel. ; In: X = unit type 0-4 (GRUNT/RIDER/BOOMER/SPY/COMCEN) ; Out: $0200-$03FF overwritten with the unpacked block, $8B40-$8BFF = the three sprite frames, sprite ; shadows $900D-$900F (colour), $9015-$9017 (X), $901D-$901F (Y), $9020 (X MSBs) and $9022 ; (enable) updated; zp_B2/zp_B3/zp_1A/zp_1C clobbered by the unpacker ; Called from: showUnitInfoPanel ($22F1), whose LDX operand at $22EF is patched with the unit type ; ---------------------------------------------------------------------- showUnitPictureSprites: lda unitPictureLoTable,x ; CD0A low byte of this type's RLE block ldy unitPictureHiTable,x ; CD0D high byte of it jsr unpackRleTo0200 ; CD10 expand the run-length stream into $0200-$03FF (512 bytes) ldy #$00 ; CD13 copy from the start of the unpacked page ; ---------------------------------------------------------------------- ; Copy the first three sprite frames (3 x 64 bytes) out of the unpacked page into the shape area of ; sprites 5, 6 and 7; the remaining 320 unpacked bytes are padding and are ignored. ; ---------------------------------------------------------------------- copyUnitPictureLoop: lda contourRuleTable,y ; CD15 unpacked byte (the RLE destination is the contour-rule page $0200) (also trainerPlaybook0200:trainerPlay0) sta sprite5Shape,y ; CD18 $8B40 = $8000 + $2D*64, the shape block sprite pointer $2D selects iny ; CD1B next byte cpy #$C0 ; CD1C $C0 = 192 = three 64-byte sprite shapes bcc copyUnitPictureLoop ; CD1E keep copying lda #$01 ; CD20 VIC colour 1 = white sta sprite5ColourShadow ; CD22 sprite 5 colour sta sprite6ColourShadow ; CD25 sprite 6 colour sta sprite7ColourShadow ; CD28 sprite 7 colour lda #$EC ; CD2B X = 236: the left-hand picture, still below 256 so its MSB stays clear sta sprite5XShadow ; CD2D sprite 5 X position lda #$0C ; CD30 X = 268 ($10C): low byte $0C with the MSB set below sta sprite6XShadow ; CD32 sprite 6 X position lda #$2C ; CD35 X = 300 ($12C): low byte $2C with the MSB set below sta sprite7XShadow ; CD37 sprite 7 X position lda #$A2 ; CD3A Y = 162, the picture row of the info panel sta sprite5YShadow ; CD3C sprite 5 Y position sta sprite6YShadow ; CD3F sprite 6 Y position sta sprite7YShadow ; CD42 sprite 7 Y position lda spriteXMsbShadow ; CD45 shadow of VIC_SPR_HI_X $D010 ora #$C0 ; CD48 bits 6 and 7 = the X MSBs of sprites 6 and 7 sta spriteXMsbShadow ; CD4A store it back lda #$E0 ; CD4D $E0 = sprites 5, 6 and 7 jmp enableSprites ; CD4F OR them into the sprite-enable shadow $9022 and return ; ------------------------------------------------------------------- ; The five unit pictures, in unit-type order, as run-length coded blocks for unpackRleTo0200 ($67CB). ; The first byte of a block is its escape marker; in the stream 'marker, count, value' emits value ; count times and every other byte is a literal. Each block unpacks to 512 bytes but only the first ; 192 matter: three 24x21 monochrome sprite frames of 63 bytes plus one pad byte, shown side by side ; in the info panel by showUnitPictureSprites. Each block's stream ends exactly where the next one ; begins. ; ------------------------------------------------------------------- ; unitPictureGrunt - type 0, escape marker $05: a helmeted figure, a running figure and a burst. unitPictureGrunt: .byte $05,$05,$10 ; CD52 .....#.#.....#.#...#.... .byte $00,$3C,$00 ; CD55 ..........####.......... .byte $00,$FF,$00 ; CD58 ........########........ .byte $01,$99,$80 ; CD5B .......##..##..##....... .byte $01,$A5,$80 ; CD5E .......##.#..#.##....... .byte $03,$E7,$C0 ; CD61 ......#####..#####...... .byte $07,$FF,$E0 ; CD64 .....##############..... .byte $07,$3C,$E0 ; CD67 .....###..####..###..... .byte $06,$00,$60 ; CD6A .....##..........##..... .byte $06,$00,$60 ; CD6D .....##..........##..... .byte $06,$00,$60 ; CD70 .....##..........##..... .byte $03,$00,$C0 ; CD73 ......##........##...... .byte $03,$00,$C0 ; CD76 ......##........##...... .byte $03,$00,$C0 ; CD79 ......##........##...... .byte $07,$00,$E0 ; CD7C .....###........###..... .byte $05,$17,$00 ; CD7F .....#.#...#.###........ .byte $1F,$80,$00 ; CD82 ...######............... .byte $27,$C0,$00 ; CD85 ..#..#####.............. .byte $6F,$E0,$03 ; CD88 .##.#######...........## .byte $FF,$F0,$03 ; CD8B ############..........## .byte $FD,$70,$00 ; CD8E ######.#.###............ .byte $3B,$B0,$00 ; CD91 ..###.###.##............ .byte $07,$C0,$00 ; CD94 .....#####.............. .byte $07,$80,$00 ; CD97 .....####............... .byte $07,$00,$00 ; CD9A .....###................ .byte $06,$00,$00 ; CD9D .....##................. .byte $07,$00,$00 ; CDA0 .....###................ .byte $03,$80,$00 ; CDA3 ......###............... .byte $03,$80,$00 ; CDA6 ......###............... .byte $0F,$C0,$05 ; CDA9 ....######...........#.# .byte $14,$00,$18 ; CDAC ...#.#.............##... .byte $00,$00,$18 ; CDAF ...................##... .byte $00,$00,$18 ; CDB2 ...................##... .byte $00,$00,$3C ; CDB5 ..................####.. .byte $00,$00,$FF ; CDB8 ................######## .byte $00,$00,$BD ; CDBB ................#.####.# .byte $00,$03,$99 ; CDBE ..............###..##..# .byte $C0,$03,$FF ; CDC1 ##............########## .byte $C0,$03,$FF ; CDC4 ##............########## .byte $C0,$03,$FF ; CDC7 ##............########## .byte $C0,$01,$FF ; CDCA ##.............######### .byte $80,$00,$7E ; CDCD #................######. .byte $05,$10,$00 ; CDD0 .....#.#...#............ ; unitPictureRider - type 1, escape marker $07: three 24x21 frames of the mounted/wheeled unit. unitPictureRider: .byte $07,$07,$1F ; CDD3 .....###.....###...##### .byte $00,$1C,$00 ; CDD6 ...........###.......... .byte $00,$36,$00 ; CDD9 ..........##.##......... .byte $00,$3E,$00 ; CDDC ..........#####......... .byte $00,$1C,$00 ; CDDF ...........###.......... .byte $0E,$1C,$38 ; CDE2 ....###....###....###... .byte $0F,$7F,$78 ; CDE5 ....####.#######.####... .byte $0F,$C1,$F8 ; CDE8 ....######.....######... .byte $0F,$00,$78 ; CDEB ....####.........####... .byte $0D,$00,$58 ; CDEE ....##.#.........#.##... .byte $07,$26,$00 ; CDF1 .....###..#..##......... .byte $38,$00,$03 ; CDF4 ..###.................## .byte $FC,$00,$00 ; CDF7 ######.................. .byte $7C,$00,$00 ; CDFA .#####.................. .byte $30,$F0,$00 ; CDFD ..##....####............ .byte $7D,$98,$0F ; CE00 .#####.##..##.......#### .byte $CF,$68,$1A ; CE03 ##..####.##.#......##.#. .byte $01,$68,$16 ; CE06 .......#.##.#......#.##. .byte $01,$98,$0C ; CE09 .......##..##.......##.. .byte $00,$F0,$07 ; CE0C ........####.........### .byte $16,$00,$03 ; CE0F ...#.##...............## .byte $00,$60,$03 ; CE12 .........##...........## .byte $08,$60,$03 ; CE15 ....#....##...........## .byte $08,$60,$00 ; CE18 ....#....##............. .byte $88,$80,$00 ; CE1B #...#...#............... .byte $5D,$00,$00 ; CE1E .#.###.#................ .byte $3E,$00,$00 ; CE21 ..#####................. .byte $3E,$00,$00 ; CE24 ..#####................. .byte $3E,$00,$0E ; CE27 ..#####.............###. .byte $5D,$38,$0F ; CE2A .#.###.#..###.......#### .byte $F7,$F8,$0F ; CE2D ####.########.......#### .byte $00,$78,$0E ; CE30 .........####.......###. .byte $00,$38,$0E ; CE33 ..........###.......###. .byte $00,$38,$07 ; CE36 ..........###........### .byte $09,$00 ; CE39 ....#..#........ ; unitPictureBoomer - type 2, escape marker $08: launcher, missile in flight and emplacement. unitPictureBoomer: .byte $08,$08,$0A ; CE3B ....#.......#.......#.#. .byte $00,$3C,$00 ; CE3E ..........####.......... .byte $00,$66,$00 ; CE41 .........##..##......... .byte $00,$3C,$00 ; CE44 ..........####.......... .byte $00,$18,$00 ; CE47 ...........##........... .byte $00,$18,$00 ; CE4A ...........##........... .byte $00,$18,$00 ; CE4D ...........##........... .byte $00,$18,$00 ; CE50 ...........##........... .byte $00,$18,$00 ; CE53 ...........##........... .byte $00,$3C,$00 ; CE56 ..........####.......... .byte $00,$5A,$00 ; CE59 .........#.##.#......... .byte $00,$DB,$00 ; CE5C ........##.##.##........ .byte $0F,$FF,$F0 ; CE5F ....################.... .byte $0B,$DB,$D0 ; CE62 ....#.####.##.####.#.... .byte $0F,$FF,$F0 ; CE65 ....################.... .byte $0A,$3C,$50 ; CE68 ....#.#...####...#.#.... .byte $0E,$00,$70 ; CE6B ....###..........###.... .byte $08,$13,$00 ; CE6E ....#......#..##........ .byte $02,$00,$00 ; CE71 ......#................. .byte $04,$00,$00 ; CE74 .....#.................. .byte $0E,$00,$00 ; CE77 ....###................. .byte $13,$00,$00 ; CE7A ...#..##................ .byte $01,$80,$00 ; CE7D .......##............... .byte $00,$C0,$00 ; CE80 ........##.............. .byte $00,$60,$00 ; CE83 .........##............. .byte $00,$3F,$C0 ; CE86 ..........########...... .byte $00,$19,$E0 ; CE89 ...........##..####..... .byte $00,$1F,$F0 ; CE8C ...........#########.... .byte $00,$3F,$F8 ; CE8F ..........###########... .byte $00,$6A,$AC ; CE92 .........##.#.#.#.#.##.. .byte $00,$51,$14 ; CE95 .........#.#...#...#.#.. .byte $00,$6A,$AC ; CE98 .........##.#.#.#.#.##.. .byte $00,$3F,$F8 ; CE9B ..........###########... .byte $08,$14,$00 ; CE9E ....#......#.#.......... .byte $3F,$00,$00 ; CEA1 ..######................ .byte $0C,$00,$00 ; CEA4 ....##.................. .byte $0C,$00,$00 ; CEA7 ....##.................. .byte $0C,$00,$00 ; CEAA ....##.................. .byte $0C,$00,$00 ; CEAD ....##.................. .byte $0C,$00,$00 ; CEB0 ....##.................. .byte $0C,$00,$07 ; CEB3 ....##...............### .byte $3F,$38,$05 ; CEB6 ..######..###........#.# .byte $ED,$E8,$07 ; CEB9 ###.##.####.#........### .byte $FF,$F8,$05 ; CEBC #############........#.# .byte $FF,$E8,$07 ; CEBF ###########.#........### .byte $FF,$F8,$05 ; CEC2 #############........#.# .byte $FF,$E8,$07 ; CEC5 ###########.#........### .byte $3F,$38,$08 ; CEC8 ..######..###.......#... .byte $09,$00 ; CECB ....#..#........ ; unitPictureSpy - type 3, escape marker $05: three antenna/aerial frames. unitPictureSpy: .byte $05,$05,$19 ; CECD .....#.#.....#.#...##..# .byte $00,$41,$00 ; CED0 .........#.....#........ .byte $00,$22,$00 ; CED3 ..........#...#......... .byte $00,$14,$00 ; CED6 ...........#.#.......... .byte $00,$08,$00 ; CED9 ............#........... .byte $03,$1C,$60 ; CEDC ......##...###...##..... .byte $03,$FF,$E0 ; CEDF ......#############..... .byte $00,$00,$00 ; CEE2 ........................ .byte $01,$00,$40 ; CEE5 .......#.........#...... .byte $00,$08,$00 ; CEE8 ............#........... .byte $01,$00,$40 ; CEEB .......#.........#...... .byte $05,$23,$00 ; CEEE .....#.#..#...##........ .byte $50,$00,$00 ; CEF1 .#.#.................... .byte $50,$00,$00 ; CEF4 .#.#.................... .byte $20,$00,$00 ; CEF7 ..#..................... .byte $20,$00,$0C ; CEFA ..#.................##.. .byte $70,$60,$0F ; CEFD .###.....##.........#### .byte $FF,$F0,$00 ; CF00 ############............ .byte $00,$00,$04 ; CF03 .....................#.. .byte $05,$04,$00 ; CF06 .....#.#.....#.......... .byte $20,$02,$05 ; CF09 ..#...........#......#.# .byte $18,$00,$03 ; CF0C ...##.................## .byte $80,$E0,$02 ; CF0F #.......###...........#. .byte $80,$A0,$03 ; CF12 #.......#.#...........## .byte $80,$E0,$00 ; CF15 #.......###............. .byte $80,$80,$00 ; CF18 #.......#............... .byte $D5,$80,$00 ; CF1B ##.#.#.##............... .byte $77,$00,$00 ; CF1E .###.###................ .byte $1C,$00,$00 ; CF21 ...###.................. .byte $08,$00,$00 ; CF24 ....#................... .byte $08,$00,$00 ; CF27 ....#................... .byte $08,$00,$00 ; CF2A ....#................... .byte $1C,$00,$00 ; CF2D ...###.................. .byte $14,$00,$00 ; CF30 ...#.#.................. .byte $1C,$00,$00 ; CF33 ...###.................. .byte $08,$05,$0A ; CF36 ....#........#.#....#.#. .byte $00 ; CF39 ........ ; unitPictureComcen - type 4, escape marker $0A: three frames of the command centre building. The ; block runs out at $CFDC, so the 192nd unpacked byte - the unused pad byte of sprite 7 - is the first ; byte of the leftover code fragment that follows. unitPictureComcen: .byte $0A,$0A,$0A ; CF3A ....#.#.....#.#.....#.#. .byte $00,$82,$00 ; CF3D ........#.....#......... .byte $00,$85,$00 ; CF40 ........#....#.#........ .byte $00,$82,$00 ; CF43 ........#.....#......... .byte $00,$8F,$80 ; CF46 ........#...#####....... .byte $03,$FF,$E0 ; CF49 ......#############..... .byte $07,$55,$70 ; CF4C .....###.#.#.#.#.###.... .byte $06,$AA,$B0 ; CF4F .....##.#.#.#.#.#.##.... .byte $07,$FF,$F0 ; CF52 .....###############.... .byte $05,$FF,$D0 ; CF55 .....#.###########.#.... .byte $06,$FF,$B0 ; CF58 .....##.#########.##.... .byte $07,$55,$70 ; CF5B .....###.#.#.#.#.###.... .byte $06,$FF,$B0 ; CF5E .....##.#########.##.... .byte $07,$FF,$F0 ; CF61 .....###############.... .byte $05,$FF,$D0 ; CF64 .....#.###########.#.... .byte $04,$41,$10 ; CF67 .....#...#.....#...#.... .byte $07,$C1,$F0 ; CF6A .....#####.....#####.... .byte $0A,$11,$00 ; CF6D ....#.#....#...#........ .byte $10,$10,$00 ; CF70 ...#.......#............ .byte $08,$10,$00 ; CF73 ....#......#............ .byte $10,$10,$00 ; CF76 ...#.......#............ .byte $7E,$10,$00 ; CF79 .######....#............ .byte $3F,$FE,$00 ; CF7C ..#############......... .byte $7F,$FF,$15 ; CF7F .###############...#.#.# .byte $7F,$FF,$3F ; CF82 .###############..###### .byte $FF,$EB,$7F ; CF85 ###########.#.##.####### .byte $FF,$D7,$FF ; CF88 ##########.#.########### .byte $FF,$EB,$FF ; CF8B ###########.#.########## .byte $FF,$FF,$6A ; CF8E ################.##.#.#. .byte $BE,$AB,$2F ; CF91 #.#####.#.#.#.##..#.#### .byte $ED,$FD,$1D ; CF94 ###.##.#######.#...###.# .byte $73,$AE,$1A ; CF97 .###..###.#.###....##.#. .byte $B3,$56,$0F ; CF9A #.##..##.#.#.##.....#### .byte $E1,$FC,$0A ; CF9D ###....#######......#.#. .byte $0B,$00,$3E ; CFA0 ....#.##..........#####. .byte $00,$00,$7F ; CFA3 .................####### .byte $00,$00,$FF ; CFA6 ................######## .byte $80,$01,$FF ; CFA9 #..............######### .byte $C0,$03,$55 ; CFAC ##............##.#.#.#.# .byte $60,$06,$AA ; CFAF .##..........##.#.#.#.#. .byte $B0,$07,$FF ; CFB2 #.##.........########### .byte $F0,$07,$FC ; CFB5 ####.........#########.. .byte $F0,$07,$FB ; CFB8 ####.........########.## .byte $70,$07,$FB ; CFBB .###.........########.## .byte $70,$05,$FC ; CFBE .###.........#.#######.. .byte $D0,$07,$FF ; CFC1 ##.#.........########### .byte $F0,$05,$B6 ; CFC4 ####.........#.##.##.##. .byte $D0,$07,$FF ; CFC7 ##.#.........########### .byte $F0,$05,$FF ; CFCA ####.........#.######### .byte $D0,$07,$AF ; CFCD ##.#.........####.#.#### .byte $F0,$05,$FF ; CFD0 ####.........#.######### .byte $D0,$07,$FF ; CFD3 ##.#.........########### .byte $F0,$01,$FF ; CFD6 ####...........######### .byte $C0,$00,$00 ; CFD9 ##...................... .byte $00 ; CFDC ........ ; ---------------------------------------------------------------------- ; ------------------------------------------------------------------- ; DEAD CODE. The last 35 bytes of the overlay are a byte-for-byte copy of the tail of ; updateFrameTimers ($12DD-$12FF in the main program) that the build system left in the sector. ; Nothing references $CFDD, the game's timers are decremented by the copy at $12DD inside the raster ; IRQ, and the fragment is not even complete: its relative branches were assembled for $12xx, so here ; they point at $D001 (VIC_SPR0_Y), and the final DEC runs off the end of the page after two of its ; three bytes. ; ------------------------------------------------------------------- ; ---------------------------------------------------------------------- leftoverFrameTimerTail: lda linkPollDelay ; CFDD leftover: link poll delay $9163 beq L_CFE5 ; CFE0 leftover: skip if it has already expired dec linkPollDelay ; CFE2 leftover: count it down L_CFE5: lda stepDelayTimer ; CFE5 leftover: unit step delay $90FC beq L_CFED ; CFE8 leftover: skip if zero dec stepDelayTimer ; CFEA leftover: count it down L_CFED: lda shotLineTimer ; CFED leftover: shot-line display timer $91C8 beq L_CFF5 ; CFF0 leftover: skip if zero dec shotLineTimer ; CFF2 leftover: count it down L_CFF5: lda viewRedrawTimer ; CFF5 leftover: viewport redraw timer $9234 beq VIC_SPR0_Y ; CFF8 leftover: the branch target of the $12DD original lands at $D001 here (VIC_SPR0_Y), not at any code cmp #$80 ; CFFA leftover: $80 means 'hold the redraw indefinitely', so it is not counted down beq VIC_SPR0_Y ; CFFC leftover: same stray branch to $D001 .byte $CE ; CFFE leftover: the first two bytes of 'dec $9234'; the third byte would be at $D000, so the copy is truncated by the end of the page .byte $34 ; CFFF 4