3238 lines
204 KiB
ArmAsm
3238 lines
204 KiB
ArmAsm
; ============================================================================
|
|
; $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:<clock> US:<points>
|
|
; THEM:<points>'.
|
|
; $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<?....
|
|
.byte $F0 ; C247 nibble $C-$D doubled
|
|
.byte $F3 ; C248 .
|
|
L_C249:
|
|
.byte $FC ; C249 nibble $E-$F doubled
|
|
.byte $FF ; C24A .
|
|
|
|
; ----------------------------------------------------------------------
|
|
; plotGlyphDoubleSize - draws the glyph at zp_B2/zp_B3 magnified 2x2 into four character cells
|
|
; starting at zp_B4/zp_B5 (top-left, +8 top-right, +$138 more = one bitmap row down for bottom-left,
|
|
; +8 bottom-right) and colours them through colourDoubleSizeCell. The 8 source bytes are copied to
|
|
; the work buffer $90B8 first; each quadrant expands 4 source rows and one nibble of each row. The
|
|
; mask zp_BA suppresses quadrants (bit 0 top cells, bit 1 bottom cells, bit 2 left cells, bit 3 right
|
|
; cells) so a zoom rectangle can keep half-size edges - note zp_BA is only ever written by
|
|
; magnifyWindow2x, so double-size text uses whatever mask it left behind. A, X and Y are preserved in
|
|
; self-modified operands ($C301, $C303, $C305).
|
|
; In: zp_B2/zp_B3 = glyph, zp_B4-zp_B7 = destination cell, zp_3D = colour, zp_BA = quadrant mask
|
|
; Out: up to 4 cells (32 bitmap bytes) and their colour bytes written; $90B8-$90BF and zp_1A-zp_1D
|
|
; clobbered; the operands at $C33A/$C33B/$C33D/$C33E are rewritten as it goes
|
|
; Called from: plotGlyphCell ($C224), magnifyWindow2x ($2E7B/$2EA5/$2EEA/$2F14)
|
|
; ----------------------------------------------------------------------
|
|
plotGlyphDoubleSize:
|
|
sta L_C304+1 ; C24B preserve A in the LDA operand at $C305
|
|
sty L_C302+1 ; C24E preserve Y in the LDY operand at $C303
|
|
stx L_C300+1 ; C251 preserve X in the LDX operand at $C301
|
|
ldy #$07 ; C254 8 glyph rows ...
|
|
L_C256:
|
|
lda (glyphPtr),y ; C256 ... read from the font ...
|
|
sta glyphBuildBuffer,y ; C258 ... into the work buffer, where the expander can index them freely
|
|
dey ; C25B next row up
|
|
bpl L_C256 ; C25C until all 8 rows are in the buffer
|
|
lda #$00 ; C25E source rows 0-3 = the top half of the glyph
|
|
sta scratch1B ; C260 row offset into the work buffer
|
|
|
|
; ----------------------------------------------------------------------
|
|
; Point the two STA operands in expandGlyphNibbles at the top-left cell: $C33A gets the cell address
|
|
; and $C33D that address + 1, so every expanded row is written to two consecutive bitmap rows.
|
|
; ----------------------------------------------------------------------
|
|
lda bitmapPtr ; C262 destination cell, low byte ...
|
|
sta L_C339+1 ; C264 ... into the STA operand at $C33A
|
|
clc ; C267 clear carry for the +1
|
|
adc #$01 ; C268 the next bitmap row of the same cell ...
|
|
sta L_C33C+1 ; C26A ... into the STA operand at $C33D
|
|
lda bitmapPtrHi ; C26D destination cell, high byte ...
|
|
sta L_C339+2 ; C26F ... into the STA at $C33B
|
|
adc #$00 ; C272 carry of the low byte
|
|
sta L_C33C+2 ; C274 ... and $C33E
|
|
lda magnifyEdgeMask ; C277 quadrant mask ...
|
|
and #$01 ; C279 ... bit 0 = skip the top pair of cells
|
|
bne L_C280 ; C27B top row suppressed - jump straight to the bottom half
|
|
jsr plotGlyphLeftHalf ; C27D top-left cell: the high nibble of source rows 0-3
|
|
|
|
; ----------------------------------------------------------------------
|
|
; Step both destination addresses on by 8 bytes = one character cell to the right.
|
|
; ----------------------------------------------------------------------
|
|
L_C280:
|
|
clc ; C280 clear carry for the 16-bit add
|
|
lda L_C339+1 ; C281 first destination ...
|
|
adc #$08 ; C284 ... plus 8 = the next cell
|
|
sta L_C339+1 ; C286 back into the STA operand
|
|
lda L_C339+2 ; C289 its high byte ...
|
|
adc #$00 ; C28C carry into the high byte
|
|
sta L_C339+2 ; C28E back into the STA operand
|
|
lda L_C33C+1 ; C291 second destination ... (relies on the carry left clear above)
|
|
adc #$08 ; C294 ... plus 8
|
|
sta L_C33C+1 ; C296 back into the second STA operand
|
|
lda L_C33C+2 ; C299 its high byte ...
|
|
adc #$00 ; C29C carry of the low byte
|
|
sta L_C33C+2 ; C29E back into the STA operand
|
|
lda magnifyEdgeMask ; C2A1 quadrant mask ...
|
|
and #$01 ; C2A3 ... top pair of cells still enabled?
|
|
bne L_C2AA ; C2A5 no
|
|
jsr plotGlyphRightHalf ; C2A7 top-right cell: the low nibble of source rows 0-3
|
|
L_C2AA:
|
|
lda #$04 ; C2AA source rows 4-7 = the bottom half of the glyph
|
|
sta scratch1B ; C2AC row offset into the work buffer
|
|
|
|
; ----------------------------------------------------------------------
|
|
; Step both destinations on by $138; together with the +8 above that is +320 = one character row down,
|
|
; i.e. the bottom-left cell.
|
|
; ----------------------------------------------------------------------
|
|
clc ; C2AE clear carry for the 16-bit add
|
|
lda L_C339+1 ; C2AF first destination ...
|
|
adc #$38 ; C2B2 ... plus $138 ...
|
|
sta L_C339+1 ; C2B4 back into the STA operand
|
|
lda L_C339+2 ; C2B7 its high byte ...
|
|
adc #$01 ; C2BA ... = +320 with the +8 above: one character row down
|
|
sta L_C339+2 ; C2BC back into the STA operand
|
|
clc ; C2BF clear carry for the second 16-bit add
|
|
lda L_C33C+1 ; C2C0 second destination ...
|
|
adc #$38 ; C2C3 ... plus $138 ...
|
|
sta L_C33C+1 ; C2C5 back into the STA operand
|
|
lda L_C33C+2 ; C2C8 its high byte ...
|
|
adc #$01 ; C2CB ... = one character row down
|
|
sta L_C33C+2 ; C2CD back into the STA operand
|
|
lda magnifyEdgeMask ; C2D0 quadrant mask ...
|
|
and #$02 ; C2D2 ... bit 1 = skip the bottom pair of cells
|
|
bne L_C2FD ; C2D4 bottom row suppressed - go and colour what was drawn
|
|
jsr plotGlyphLeftHalf ; C2D6 bottom-left cell: the high nibble of source rows 4-7
|
|
clc ; C2D9 clear carry for the 16-bit add
|
|
sub_C2DA:
|
|
lda L_C339+1 ; C2DA first destination ...
|
|
adc #$08 ; C2DD ... plus 8 = the bottom-right cell
|
|
sta L_C339+1 ; C2DF back into the STA operand
|
|
lda L_C339+2 ; C2E2 its high byte ...
|
|
adc #$00 ; C2E5 carry of the low byte
|
|
sta L_C339+2 ; C2E7 back into the STA operand
|
|
lda L_C33C+1 ; C2EA second destination ... (again relying on the clear carry)
|
|
adc #$08 ; C2ED ... plus 8
|
|
sta L_C33C+1 ; C2EF back into the second STA operand
|
|
lda L_C33C+2 ; C2F2 its high byte ...
|
|
adc #$00 ; C2F5 carry of the low byte
|
|
sta L_C33C+2 ; C2F7 back into the STA operand
|
|
jsr plotGlyphRightHalf ; C2FA bottom-right cell: the low nibble of source rows 4-7
|
|
L_C2FD:
|
|
jsr colourDoubleSizeCell ; C2FD write the colour byte into all four cells
|
|
L_C300:
|
|
ldx #$FF ; C300 patched by $C251: the caller's X
|
|
L_C302:
|
|
ldy #$FF ; C302 patched by $C24E: the caller's Y
|
|
L_C304:
|
|
lda #$FF ; C304 patched by $C24B: the caller's A (the glyph number)
|
|
rts ; C306 done
|
|
|
|
|
|
; ----------------------------------------------------------------------
|
|
; plotGlyphLeftHalf - quadrant helper of plotGlyphDoubleSize for a left-hand cell: returns at once
|
|
; when the mask suppresses left cells, otherwise selects the high nibble of each source row and runs
|
|
; the expander.
|
|
; In: zp_BA quadrant mask, zp_1B = source row offset (0 top half, 4 bottom half)
|
|
; Out: 8 bitmap bytes of one cell written; zp_1D = $F0
|
|
; Called from: plotGlyphDoubleSize ($C27D top-left, $C2D6 bottom-left)
|
|
; ----------------------------------------------------------------------
|
|
plotGlyphLeftHalf:
|
|
lda magnifyEdgeMask ; C307 quadrant mask ...
|
|
and #$04 ; C309 ... bit 2 = left-hand cells suppressed?
|
|
beq L_C30E ; C30B no - draw it
|
|
rts ; C30D suppressed: nothing to do
|
|
|
|
L_C30E:
|
|
lda #$F0 ; C30E the high nibble is the left half of the glyph row
|
|
sta scratch1D ; C310 $F0 = keep the high nibble of every source row
|
|
L_C312:
|
|
jmp expandGlyphNibbles ; C312 expand it into the destination cell
|
|
|
|
|
|
; ----------------------------------------------------------------------
|
|
; plotGlyphRightHalf - the same for a right-hand cell: returns at once when the mask suppresses right
|
|
; cells, otherwise selects the low nibble of each source row and falls into the expander.
|
|
; In: zp_BA quadrant mask, zp_1B = source row offset
|
|
; Out: 8 bitmap bytes of one cell written; zp_1D = $0F
|
|
; Called from: plotGlyphDoubleSize ($C2A7 top-right, $C2FA bottom-right)
|
|
; ----------------------------------------------------------------------
|
|
plotGlyphRightHalf:
|
|
lda magnifyEdgeMask ; C315 quadrant mask ...
|
|
and #$08 ; C317 ... bit 3 = right-hand cells suppressed?
|
|
beq L_C31C ; C319 no - draw it
|
|
rts ; C31B suppressed: nothing to do
|
|
|
|
L_C31C:
|
|
lda #$0F ; C31C the low nibble is the right half of the glyph row
|
|
sta scratch1D ; C31E $0F = keep the low nibble of every source row
|
|
|
|
; ----------------------------------------------------------------------
|
|
; expandGlyphNibbles - inner loop of the 2x magnifier: takes 4 glyph rows from the work buffer $90B8
|
|
; starting at offset zp_1B, masks each with zp_1D (shifting the high nibble down), doubles its pixels
|
|
; horizontally through pixelDoubleTable and stores the result to two bitmap rows one after the other
|
|
; (the self-modified absolute addresses at $C33A and $C33D), doubling it vertically as well.
|
|
; In: $90B8 glyph rows, zp_1B = 0 or 4, zp_1D = $F0 or $0F, destination operands $C33A/$C33D
|
|
; Out: 8 bitmap bytes of one cell written; zp_1A = destination row counter
|
|
; Called from: plotGlyphLeftHalf ($C312), plotGlyphRightHalf (fall through)
|
|
; ----------------------------------------------------------------------
|
|
expandGlyphNibbles:
|
|
ldy scratch1B ; C320 first source row: 0 for the top half, 4 for the bottom half
|
|
D_C322:
|
|
ldx #$00 ; C322 zero ...
|
|
stx scratch1A ; C324 destination row within the cell = 0
|
|
L_C326:
|
|
lda glyphBuildBuffer,y ; C326 one row of the glyph
|
|
and scratch1D ; C329 keep only the half being magnified
|
|
bit scratch1D ; C32B was that the high nibble ($F0 is negative)?
|
|
bpl L_C333 ; C32D no - the low nibble is already in place
|
|
lsr a ; C32F shift the high nibble down, 1 ...
|
|
lsr a ; C330 ... 2 ...
|
|
lsr a ; C331 ... 3 ...
|
|
lsr a ; C332 ... 4 places, into bits 0-3
|
|
L_C333:
|
|
tax ; C333 index the doubling table with the nibble
|
|
lda pixelDoubleTable,x ; C334 the same 4 pixels, each one twice as wide
|
|
ldx scratch1A ; C337 X = destination row within the cell
|
|
L_C339:
|
|
sta irqVectorHi,x ; C339 patched destination: cell address + X
|
|
L_C33C:
|
|
sta irqVectorHi,x ; C33C patched destination: cell address + 1 + X - the same row again, doubling vertically
|
|
iny ; C33F next source row
|
|
inc scratch1A ; C340 two destination rows ...
|
|
inc scratch1A ; C342 ... per source row
|
|
ldx scratch1A ; C344 destination row counter
|
|
cpx #$08 ; C346 8 bitmap rows = 4 source rows
|
|
bcc L_C326 ; C348 loop over the 4 source rows
|
|
rts ; C34A done
|
|
|
|
|
|
; ----------------------------------------------------------------------
|
|
; colourDoubleSizeCell - writes the colour byte zp_3D into the colour cells of the 2x2 block just
|
|
; drawn: the top pair unless the mask suppresses top cells, then the pointer is advanced by 40 (one
|
|
; screen row) and the bottom pair is written unless the mask suppresses bottom cells.
|
|
; In: zp_B6/zp_B7 = colour cell pointer, zp_3D = colour, zp_BA = quadrant mask
|
|
; Out: up to 4 colour bytes written; zp_B6/zp_B7 advanced by one screen row
|
|
; Called from: plotGlyphDoubleSize ($C2FD)
|
|
; ----------------------------------------------------------------------
|
|
colourDoubleSizeCell:
|
|
ldy #$00 ; C34B redundant - colourCellPair sets Y itself
|
|
lda magnifyEdgeMask ; C34D quadrant mask ...
|
|
and #$01 ; C34F ... bit 0 = top cells suppressed?
|
|
bne L_C356 ; C351 yes - try the bottom pair
|
|
jsr colourCellPair ; C353 colour the top-left and top-right cells
|
|
L_C356:
|
|
lda magnifyEdgeMask ; C356 quadrant mask ...
|
|
and #$02 ; C358 ... bit 1 = bottom cells suppressed?
|
|
bne L_C37E ; C35A yes - nothing more to colour
|
|
clc ; C35C clear carry for the +40
|
|
lda screenPtr ; C35D advance the colour pointer ...
|
|
D_C35F:
|
|
adc #$28 ; C35F ... by 40 = one character row down
|
|
sta screenPtr ; C361 colour pointer, low byte
|
|
bcc colourCellPair ; C363 no carry - colour the bottom pair
|
|
inc screenPtrHi ; C365 carry into the high byte, then fall into colourCellPair
|
|
|
|
; ----------------------------------------------------------------------
|
|
; colourCellPair - writes zp_3D into the two horizontally adjacent colour cells at (zp_B6),0 and
|
|
; (zp_B6),1, skipping the left one when the mask suppresses left cells and the right one when it
|
|
; suppresses right cells.
|
|
; In: zp_B6/zp_B7, zp_3D = colour, zp_BA = quadrant mask
|
|
; Out: up to 2 colour bytes written
|
|
; Called from: colourDoubleSizeCell ($C353 and the fall-through at $C363/$C365)
|
|
; ----------------------------------------------------------------------
|
|
colourCellPair:
|
|
ldy #$00 ; C367 the left cell of the pair
|
|
lda magnifyEdgeMask ; C369 quadrant mask ...
|
|
and #$04 ; C36B ... bit 2 = left cells suppressed?
|
|
bne L_C373 ; C36D yes - skip it
|
|
lda textColour ; C36F the text colour ...
|
|
D_C371:
|
|
sta (screenPtr),y ; C371 ... into the left cell
|
|
L_C373:
|
|
iny ; C373 the right cell of the pair
|
|
lda magnifyEdgeMask ; C374 quadrant mask ...
|
|
and #$08 ; C376 ... bit 3 = right cells suppressed?
|
|
bne L_C37E ; C378 yes - skip it
|
|
lda textColour ; C37A the text colour ...
|
|
sta (screenPtr),y ; C37C ... into the right cell
|
|
L_C37E:
|
|
rts ; C37E done
|
|
|
|
|
|
; ----------------------------------------------------------------------
|
|
; waitFrames - busy-waits A frames by polling the raster register: per frame it waits until
|
|
; $D012 reads $F0 (line 240, in the lower border) and then until the raster leaves that line.
|
|
; Returns at once for A = 0. Used for the pauses in the boot and menu sequences, in sound effects and
|
|
; in the modem dialogue.
|
|
; In: A = number of frames
|
|
; Out: returns A frames later with A = 0
|
|
; Called from: main program $0CAF/$0F58/$1028/$1BA8/$1C14/$1C65/$409E/$5A3C, $6F00 overlay $7E76/
|
|
; $80BB, modem driver $EE03
|
|
; ----------------------------------------------------------------------
|
|
waitFrames:
|
|
cmp #$00 ; C37F a count of 0 ...
|
|
beq L_C396 ; C381 ... means no wait at all
|
|
L_C383:
|
|
pha ; C383 keep the remaining frame count on the stack
|
|
L_C384:
|
|
lda VIC_RASTER ; C384 current raster line ...
|
|
cmp #$F0 ; C387 ... is it line $F0 (240), below the visible screen?
|
|
bne L_C384 ; C389 no - keep polling
|
|
L_C38B:
|
|
cmp VIC_RASTER ; C38B wait while the raster is still on line $F0 ...
|
|
beq L_C38B ; C38E ... one whole frame has passed once it moves on
|
|
pla ; C390 frame count back
|
|
sec ; C391 set carry so the subtract does not borrow
|
|
sbc #$01 ; C392 one frame done
|
|
bne L_C383 ; C394 wait for the rest
|
|
L_C396:
|
|
rts ; C396 done
|
|
|
|
|
|
; ----------------------------------------------------------------------
|
|
; waitRasterBelowScreen - spins until the raster is at line $E0 (224) or below, i.e. in the lower
|
|
; border, so the caller can rewrite the bitmap without tearing.
|
|
; In: none
|
|
; Out: returns with A = the raster line, which is >= $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:<clock> US:<points>
|
|
; THEM:<points>'. 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
|