652 lines
48 KiB
ArmAsm
652 lines
48 KiB
ArmAsm
; ============================================================================
|
|
; $0400-$07FF - manual-check answer table, status message strings and shared scratch area
|
|
; ============================================================================
|
|
; Copied from $6F00 (track 27 sectors 13-16) by the boot loader. $0401 holds the 32 x 3-byte answers for
|
|
; the manual look-up check, $0461 the memory checksum routine the boot loader returns into, $0500-$0567
|
|
; the status message pointer tables and $056C.. the status message strings. Once the battle starts the
|
|
; three 50-byte arrays at $0400/$0432/$0464 are reused as unit scan lists and AI threat maps.
|
|
|
|
.setcpu "6502"
|
|
.include "c64.inc"
|
|
.include "zeropage.inc"
|
|
|
|
; ---- references to code/data outside this file ----
|
|
gameEntry := $0800
|
|
D_09C3 := $09C3
|
|
inputLockoutTimer := $0B7D
|
|
programChecksum := $0B7E
|
|
filmPlaybackMode := $0B9B
|
|
gameEndReason := $0BA8
|
|
resumeMainMenuScreen := $1414
|
|
enterMainMenu := $141D
|
|
enterMapScreen := $1FC3
|
|
runMapMainLoop := $1FD0
|
|
drawViewport := $26C1
|
|
initBitmapScreen := $2D41
|
|
clearMessageLineAndScreen := $2DDB
|
|
updateGameFrame := $40B2
|
|
drawOverviewMap := $5AD7
|
|
runMissileScreen := $7587
|
|
missileScreenResume := $759C
|
|
enterDroneScreen := $7FE6
|
|
resetDroneScreen := $7FF6
|
|
releaseDroneControl := $8230
|
|
msgGetOpponentOnPhoneAnd := $8800
|
|
spriteXMsbShadow := $9020
|
|
spriteEnableShadow := $9022
|
|
spriteXExpandShadow := $9023
|
|
spriteYExpandShadow := $9024
|
|
currentScreen := $90FB
|
|
stepDelayTimer := $90FC
|
|
unitOrderIndex := $9162
|
|
comcenStunStatus := $922A
|
|
chatState := $929C
|
|
gameEndCountdown := $92C9
|
|
droneArrivalFlags := $92F9
|
|
queueMessage := $C566
|
|
|
|
; Contents
|
|
; --------
|
|
; $0461 verifyProgramChecksum boot-time integrity check, and the address the boot loader falls into rather
|
|
; than calls.
|
|
; $06BA switchToRequestedScreen the game's screen dispatcher, called once per iteration from every screen's
|
|
; main loop to honour a pending F1/F3/F5/F7 keypress.
|
|
; $0709 runComcenStunnedBlackout the 'COMCEN STUNNED!' cut-scene: when your command centre has been knocked out,
|
|
; control is taken away, the screen goes blank and the engine keeps running unwatched for exactly one full round of all
|
|
; 100 units before the game hands control back.
|
|
|
|
.org $0400
|
|
|
|
|
|
; checksumXorConstant: 1 byte, $B0. The constant verifyProgramChecksum folds into the finished sum at
|
|
; $04A0 before storing it in programChecksum ($0B7E). It is deliberately OUTSIDE the checked range,
|
|
; which starts at $0402, so this byte alone shifts the reported build signature without changing the
|
|
; sum itself. The whole $0400-$07FF page is a copy of $6F00-$72FF (track 27 sectors 13-16), made by
|
|
; the boot loader's four-page block move at $C1B3-$C1C3 just before it drops into the checksum below.
|
|
; Once a battle starts the first half of the page is pure scratch and this byte becomes an array
|
|
; element - see the page map in the block comment at $04AE.
|
|
checksumXorConstant:
|
|
.byte $B0 ; 0400 $B0 = the checksum EOR constant; at run time this is also element 0 of the $0400 scratch arrays
|
|
|
|
; mapNameSeedTable: 32 entries x 3 bytes = 96 bytes ($0401-$0460), the manual look-up protection's
|
|
; question bank, used only by the copy-protection routine at $09A6.
|
|
; $09C2 executes 'LDA #seed' whose immediate operand at $09C3 is a SID noise byte the boot loader
|
|
; poked in; AND #$1F picks entry 0-31 and entry*3 indexes this table ($09CC/$09D3/$09DA). The three
|
|
; bytes are stored as the 24-bit map seed mapSeed/mapSeed1/mapSeed2 ($0B95-$0B97),
|
|
; generateBattlefieldMap draws that battlefield, and the player has to type the map's name from the
|
|
; manual; enterMapName packs what was typed back into a 24-bit number (zp_5A-zp_5C) and $09FC-$0A0F
|
|
; compares it with the very same three bytes. So there is no separate answer list: a map name IS the
|
|
; seed, written in base 32 with the 32-character alphabet at $890C. Three wrong answers and $0A1E
|
|
; wipes all of memory. Note that the checksum below starts at $0402, so the first byte of entry 0 is
|
|
; not covered by it. None of this data survives into the game: the labels inside the table ($0410,
|
|
; $0420, $0430, $0432,
|
|
; $0450) are there because other code uses those addresses as scratch arrays later on.
|
|
mapNameSeedTable:
|
|
.byte $0D,$0F,$AE,$97,$CD,$CE,$E4,$92; 0401 ........
|
|
.byte $24,$24,$D9,$40,$3C,$CC,$EC; 0409 $$.@<..
|
|
|
|
; Map-name entry 5 of the table above starts here. At run time $0410+g is the group summariser's
|
|
; 'worst movement cost any member of group g has to pay' (g = 0-15): cleared at $48ED, raised at $4942
|
|
; from terrainMoveCostTable[terrain class under the member].
|
|
D_0410:
|
|
.byte $CE,$1D,$AC,$F4,$89,$27,$54,$86; 0410 .....'T.
|
|
.byte $82,$BB,$B6,$86,$B2,$58,$C7,$E3; 0418 .....X..
|
|
|
|
; (This address falls inside map-name entry 10.) At run time $0420+g holds the lowest movement rate
|
|
; found in group g (g = 0-15): preset to $FF at
|
|
; $48F2 and lowered at $4960, so the whole group ends up moving at the speed of its slowest member.
|
|
D_0420:
|
|
.byte $6A,$5A,$B5,$94,$AF,$85,$CF,$DF; 0420 jZ......
|
|
.byte $3D,$DF,$84,$2E,$F3,$AA,$2E,$B0; 0428 =.......
|
|
|
|
; (The last byte of map-name entry 15.) At run time $0430+g is the copy of groupStatusTable[g] saved
|
|
; at $48E2 before the group table is rebuilt; only bits 6-7 of the old value are folded back into the
|
|
; new one at $4978.
|
|
D_0430:
|
|
.byte $4C,$B2 ; 0430 L.
|
|
|
|
; (Map-name entry 16 starts here.) At run time $0432+n is the squared-distance list that runs
|
|
; parallel to the unit-index list at $0400+n: collectUnitsInRadius ($37AA) fills both ($3806/$380E),
|
|
; and scanVisibleEnemies penalises an entry in place at $364F when the target is not in the unit's
|
|
; facing direction.
|
|
D_0432:
|
|
.byte $9C,$A0,$25,$39,$17,$9A,$26,$26; 0432 ..%9..&&
|
|
.byte $61,$6C,$6D,$B9,$15,$57,$CF,$25; 043A alm..W.%
|
|
.byte $32,$E7,$91,$8D,$90,$42,$5F,$2E; 0442 2....B_.
|
|
.byte $05,$C6,$B6,$2B,$1D,$68 ; 044A ...+.h
|
|
|
|
; At run time $0450+d is the row half of the map generator's flood-fill stack: $0400+d = column,
|
|
; $0450+d = row, $04A0+d = the direction bits still to try, d = 0..$4F (80 deep, $71C6-$72EF).
|
|
D_0450:
|
|
.byte $25,$27,$0E,$DB,$BC,$D3,$FB,$9B; 0450 %'......
|
|
.byte $87,$FE,$B9,$2F,$23,$C2,$E2,$D8; 0458 .../#...
|
|
.byte $81 ; 0460 .
|
|
|
|
; ----------------------------------------------------------------------
|
|
; verifyProgramChecksum - boot-time integrity check, and the address the boot loader falls into rather
|
|
; than calls. While loading, the loader rebuilt the stack (the RTS opcode $60 read from bootDone is
|
|
; pushed by the patched PHA at $C1B1, $04 was stored at $01FE by $C26C), so the RTS at $C218 pops the
|
|
; return address $0460 and execution resumes here at $0461. It sums $0402-$87FF - everything from the
|
|
; protection table up to the last byte of whichever $6F00-$87FF overlay is resident - with a
|
|
; carry-chained ADC/EOR/ROL word loop, parks the raw sum in $8800 (the first byte past the checked
|
|
; range) and stores sum EOR $B0 in programChecksum $0B7E, then enters the game. $0B7E is never tested
|
|
; locally: it is sent to the opponent in the settings packet ($ED89 in the modem driver) so both
|
|
; machines can notice they are running different builds.
|
|
; In: memory $0402-$87FF; $09C3 (the manual-check seed byte, zeroed for the duration so the sum does
|
|
; not depend on it); $0400 = $B0; a stack rigged to return to $0460
|
|
; Out: $8800 = the raw sum ($69 on this disk image), $0B7E = sum EOR $B0 ($D9 here), $09C3 restored,
|
|
; zp_19/zp_1A/zp_1B clobbered, the two self-modified operand high bytes at $047C and $0484 left
|
|
; pointing at page $88; ends with JMP $0800 (Y = 2, S = $FE)
|
|
; Called from: nobody - it is entered by the boot loader's obfuscated RTS. Recomputing the sum from
|
|
; the extracted image gives $69/$D9, while the byte mastered at $0B7E on this d64 is $DB and
|
|
; overlay A preloads the same $DB at $79CC, so this disk image has been altered somewhere in
|
|
; $0402-$87FF.
|
|
; ----------------------------------------------------------------------
|
|
verifyProgramChecksum:
|
|
lda D_09C3 ; 0461 read the manual-check seed byte - it is the immediate operand of the 'LDA #' at $09C2, poked in by the boot loader from SID noise
|
|
D_0464:
|
|
sta scratch19 ; 0464 park the seed in zp_19; it is zeroed below so that the sum is the same on every boot, and put back at $04A6 (the address $0464 itself becomes a scratch array later - see the page map at $04AE)
|
|
lda SID_OSC3_RANDOM ; 0466 read the SID voice-3 noise oscillator $D41B - free random bits
|
|
and #$7F ; 0469 keep bits 0-6
|
|
sta scratch1A ; 046B store it in zp_1A and never look at it again: no code in this routine reads zp_1A and every later user of it writes before reading, so these three instructions look like dead leftovers, an old random-seed grab
|
|
ldx #$84 ; 046D 132 pages to fold in: $04 up to and including $87, i.e. everything up to the end of the resident $6F00-$87FF overlay - at boot time that is the comcen-screens overlay the loader has just installed
|
|
lda #$00 ; 046F A = 0: the running sum starts empty (the same zero also clears the carry counter and the seed byte)
|
|
sta scratch1B ; 0471 zp_1B counts how many times the ADC carried out; the count is added back into the sum at $049B
|
|
tay ; 0473 Y = 0
|
|
iny ; 0474 Y = 1 ...
|
|
iny ; 0475 ... Y = 2: skip $0400 (the EOR constant) and $0401 (the first protection byte) so the sum starts at $0402
|
|
sta D_09C3 ; 0476 zero the seed byte inside the instruction at $09C2, otherwise every boot would checksum differently
|
|
clc ; 0479 clear the carry ONCE, outside the loop: the carry out of each word is deliberately carried into the next one
|
|
L_047A:
|
|
adc checksumXorConstant,y ; 047A add the even byte of the word; the operand high byte at $047C is bumped one page per outer pass, so this reads $0402, $0404 ... $87FE
|
|
bcc L_0481 ; 047D branch if this addition did not carry out
|
|
inc scratch1B ; 047F it did: count the carry instead of losing it
|
|
L_0481:
|
|
iny ; 0481 step to the odd byte of the word
|
|
L_0482:
|
|
eor checksumXorConstant,y ; 0482 mix the odd byte in with EOR (so byte order matters); its operand high byte at $0484 is bumped together with the other one
|
|
php ; 0485 save the ADC carry - the rotate below must not disturb it
|
|
asl a ; 0486 start of a hand-made ROL A ...
|
|
bcc L_048B ; 0487 ... branch if bit 7 was clear
|
|
ora #$01 ; 0489 ... bit 7 fell out of the top, so feed it back into bit 0
|
|
L_048B:
|
|
plp ; 048B restore the ADC carry for the next word (the address $048C is one of the trainer's accumulator arrays later - see $04AE)
|
|
D_048C:
|
|
iny ; 048C on to the next word
|
|
bne L_047A ; 048D 128 words = one page: keep going until Y wraps back to 0
|
|
inc L_047A+2 ; 048F self-modifying: bump the high byte of the ADC operand (the byte at $047C) to the next page
|
|
inc L_0482+2 ; 0492 self-modifying: bump the high byte of the EOR operand (the byte at $0484) the same way
|
|
dex ; 0495 one page done
|
|
bne L_047A ; 0496 loop over all 132 pages; both operands are left pointing at page $88 when this falls through
|
|
ldy #$02 ; 0498 Y = 2 on entry to the game - mainStart never reads Y, so this looks like a leftover
|
|
clc ; 049A clear carry for the final add
|
|
adc scratch1B ; 049B add the carry count back in, so no carry out of the chain is lost
|
|
sta msgGetOpponentOnPhoneAnd; 049D park the raw sum in $8800 - the byte immediately after the checked range. Nothing ever reads it back and the modem status strings are loaded over $8800 later, so this store is only a convenient scratch byte
|
|
D_04A0:
|
|
eor checksumXorConstant ; 04A0 fold in the $B0 at $0400, the one byte of this page that the sum deliberately skipped (this address is also the trainer's fourth accumulator array and the map generator's fill-stack direction array later)
|
|
sta programChecksum ; 04A3 $0B7E = the build signature handed to the opponent during the settings handshake; $D9 on this image, $DB on an unmodified one
|
|
lda scratch19 ; 04A6 take the seed byte back out of zp_19 ...
|
|
sta D_09C3 ; 04A8 ... and restore the immediate operand at $09C3 so the manual check still picks a random map
|
|
jmp gameEntry ; 04AB into the game proper: $0800 is a JMP to mainStart ($0A6C), which sets its own stack pointer to $FD
|
|
|
|
|
|
; scratchPageTail: 82 bytes of $00 filler on disk ($04AE-$04FF) - everything after the checksum
|
|
; routine. Nothing in $0400-$04FF is read as data by the running game: in a solo game the trainer's
|
|
; playbook (track 29 sectors 6-8) is loaded straight over $0200-$04FF, and in every game the page is
|
|
; scratch. The tenants of the page, all of which are only valid inside the routine that uses them,
|
|
; are:
|
|
; $0400+n / $0432+n / $0464+n unit scan lists (index, squared distance, direction) built by
|
|
; collectUnitsInRadius $37AA and scanVisibleEnemies $35FE
|
|
; $0400+unit / $0464+sp a per-unit deferral counter (a unit whose count is non-zero is put
|
|
; off) and the stack of units it is waiting on, while finishRound
|
|
; rebuilds the next round's process order ($4665-$4750)
|
|
; $0400+g / $0410+g / $0420+g / $0430+g per-group (0-15) member count, worst move cost, lowest
|
|
; move
|
|
; rate and saved status byte ($48DD-$4986)
|
|
; $0400+d / $0450+d / $04A0+d the map generator's flood-fill stack: column, row, remaining
|
|
; directions, up to 80 deep ($71C6-$72EF)
|
|
; $0464+i / $0478+i / $048C+i / $04A0+i the solo trainer's four 12-entry 16-bit accumulators -
|
|
; these
|
|
; are the high bytes, the low bytes live at $88D2/$88DE/$88EA/$88F6
|
|
; (all cleared together at $ED93)
|
|
; $04C8-$04E7 the 32-byte tile compose buffer described below
|
|
scratchPageTail:
|
|
.byte $00,$00,$00,$00,$00,$00,$00,$00; 04AE ........
|
|
.byte $00,$00,$00,$00,$00,$00,$00,$00; 04B6 ........
|
|
.byte $00,$00,$00,$00,$00,$00,$00,$00; 04BE ........
|
|
.byte $00,$00 ; 04C6 ..
|
|
|
|
; Tile compose buffer, 32 bytes ($04C8-$04E7): one battlefield cell drawn at 16x16 pixels, i.e. the
|
|
; 2x2 character cells of the multicolour bitmap that the tactical viewport uses. Layout: bytes
|
|
; $00-$07 = top-left character, $08-$0F = top-right, $10-$17 = bottom-left, $18-$1F = bottom-right.
|
|
; drawViewport builds a cell here: the 4x4-pixel terrain tile is expanded, each pixel doubled in both
|
|
; directions ($27C2-$27E5 - the high nibble of each source byte becomes the left character, the low
|
|
; nibble the right one, and every row is written to two consecutive scan lines), the unit glyph is
|
|
; ORed on top ($28B5-$28BC), and the finished 32 bytes are blitted into the bitmap by drawViewport
|
|
; ($2700, the full 7x5 redraw) and drawViewportCell ($5D54, a single cell), both of which step the
|
|
; bitmap pointer on to the next character row after the first 16 bytes.
|
|
D_04C8:
|
|
.byte $00 ; 04C8 .
|
|
|
|
; +$01: the odd scan lines of the top-left character; each expanded terrain row is stored at both
|
|
; $04C8+x and $04C9+x so that it is two pixels tall.
|
|
D_04C9:
|
|
.byte $00,$00,$00,$00,$00,$00,$00; 04C9 .......
|
|
|
|
; +$08: first scan line of the top-right character (the low nibble of the same source byte).
|
|
D_04D0:
|
|
.byte $00 ; 04D0 .
|
|
|
|
; +$09: its odd scan lines, written together with $04D0+x.
|
|
D_04D1:
|
|
.byte $00,$00,$00,$00,$00,$00,$00,$00; 04D1 ........
|
|
.byte $00,$00,$00,$00,$00,$00 ; 04D9 ......
|
|
|
|
; +$17: last scan line of the bottom-left character.
|
|
D_04DF:
|
|
.byte $00,$00,$00,$00,$00,$00,$00,$00; 04DF ........
|
|
|
|
; +$1F: last scan line of the bottom-right character. $2861-$286C stores $FF into $04C8, $04D0, $04DF
|
|
; and $04E7 when the unit's stun/dug-in byte has bit 7 set, which draws a solid 16-pixel bar across
|
|
; the top and the bottom of the cell - the dug-in marker.
|
|
D_04E7:
|
|
.byte $00,$00,$00,$00,$00,$00,$00,$00; 04E7 ........
|
|
.byte $00,$00,$00,$00,$00,$00,$00,$00; 04EF ........
|
|
.byte $00,$00,$00,$00,$00,$00,$00,$00; 04F7 ........
|
|
.byte $00 ; 04FF .
|
|
|
|
; messagePtrLoTable: 36 bytes, low half of the status-message pointer table, indexed by message id
|
|
; $00-$23 (the high halves are at $0524 and the sound effect for each id at $0612). showMessageString
|
|
; is called with A/Y taken from the two tables at $C536 (message id 0, the chat line) and $C5E5 (the
|
|
; id pulled off the 8-entry message ring at $9249); the printed message sits on screen for $3C = 60
|
|
; frames. Ids $1E, $20 and $21 are $FFFF placeholders on disk and are filled in at run time by
|
|
; setMessageSlot ($C924/$C928, X = id, A/Y = string) - for instance overlay A assembles its rules line
|
|
; from the template at $0636, copies it to $0332 and registers that copy as id $1E ($8714-$8724). The
|
|
; 36 messages: 00 the chat line ($0548), 01 WORKING..., 02 END OF FILM., 03 GAME ABORTED., 04 OPPONENT
|
|
; READY., 05 UNIT RECYCLED., 06/07/08 three SPOTTED RECYCLER! variants, 09 NOT A GAME FILM!, 0A SETUP
|
|
; UNITS. PRESS F 1 WHEN DONE., 0B WAITING FOR OPPONENT..., 0C COMCEN STUNNED!, 0D SPOTTED ENEMY
|
|
; COMCEN!, 0E SPY UNDER ATTACK!, 0F SPOTTED ENEMY SPY!, 10 WAITING FOR CONNECTION..., 11 WE LOST ON
|
|
; POINTS!, 12 WE WON ON POINTS!, 13 IT WAS A DRAW., 14 WE WERE KNOCKED OUT!, 15 WE WON BY A KNOCKOUT!,
|
|
; 16 GAME OVER., 17 PICK UP PHONE THEN PRESS SPACE., 18 PRESS SPACE, WAIT, HANGUP PHONE., 19 TIMEOUT.
|
|
; RUN/STOP TO RESUME., 1A OPPONENT PICKING GAME/MAP., 1B RESUMED, 1C THE BATTLE BEGINS!, 1D LAST QTR
|
|
; WARNING!, 1E/20/21 run-time slots, 1F OPPONENT READY FOR NEW GAME., 22 PHONE TROUBLE., 23 TRY AGAIN.
|
|
messagePtrLoTable:
|
|
.byte $48,$6C,$07,$76,$58,$13,$21,$2A; 0500 Hl.vX.!* ids $00-$07: $0548 chat line, $056C WORKING..., $8707 END OF FILM., $0576 GAME ABORTED., $8858 OPPONENT READY., $8713 UNIT RECYCLED., $8721 and $872A SPOTTED RECYCLER! variants
|
|
.byte $31,$83,$93,$67,$42,$51,$66,$77; 0508 1..gBQfw ids $08-$0F: $8731 SPOTTED RECYCLER!, $0583 NOT A GAME FILM!, $0593 SETUP UNITS..., $8867 WAITING FOR OPPONENT..., $8742 COMCEN STUNNED!, $8751 SPOTTED ENEMY COMCEN!, $8766 SPY UNDER ATTACK!, $8777 SPOTTED ENEMY SPY!
|
|
.byte $7E,$89,$9B,$AC,$BA,$CE,$E3,$97; 0510 ~....... ids $10-$17: $887E WAITING FOR CONNECTION..., $8789 WE LOST ON POINTS!, $879B WE WON ON POINTS!, $87AC IT WAS A DRAW., $87BA WE WERE KNOCKED OUT!, $87CE WE WON BY A KNOCKOUT!, $87E3 GAME OVER., $8897 PICK UP PHONE THEN PRESS SPACE.
|
|
.byte $B6,$B4,$D6,$D0,$D7,$E9,$FF,$F0; 0518 ........ ids $18-$1F: $88B6 PRESS SPACE WAIT HANGUP PHONE., $05B4 TIMEOUT..., $88D6 OPPONENT PICKING GAME/MAP., $05D0 RESUMED, $05D7 THE BATTLE BEGINS!, $05E9 LAST QTR WARNING!, $FFFF run-time slot, $88F0 OPPONENT READY FOR NEW GAME.
|
|
.byte $FF,$FF,$FA,$08 ; 0520 .... ids $20-$23: two more $FFFF run-time slots, then $05FA PHONE TROUBLE. and $0608 TRY AGAIN.
|
|
|
|
; messagePtrHiTable: 36 bytes, the high halves of messagePtrLoTable, same indexing. The three page
|
|
; values show where each message lives: $05/$06 = in this file, $87 = in the resident $6F00-$87FF
|
|
; overlay (so those ids only print correctly while overlay B is in memory), $88 = in the message block
|
|
; loaded from track 29 sectors 9-10, and $FF = an unfilled run-time slot.
|
|
messagePtrHiTable:
|
|
.byte $05,$05,$87,$05,$88,$87,$87,$87; 0524 ........ high bytes for ids $00-$07
|
|
.byte $87,$05,$05,$88,$87,$87,$87,$87; 052C ........ high bytes for ids $08-$0F
|
|
.byte $88,$87,$87,$87,$87,$87,$87,$88; 0534 ........ high bytes for ids $10-$17
|
|
.byte $88,$05,$88,$05,$05,$05,$FF,$88; 053C ........ high bytes for ids $18-$1F - $FF at id $1E marks the empty run-time slot
|
|
.byte $FF,$FF,$05,$06 ; 0544 .... high bytes for ids $20-$23 - two more empty slots, then this file's last two strings
|
|
|
|
; chatMessageLine: message id $00, the 36-byte chat line - exactly the width of the message panel.
|
|
; $0548 is a leading marker glyph: $1E while there is text to show (set at $13FD when an incoming line
|
|
; has been unpacked and at $C67E), $9E - the same glyph with bit 7 set, so the string ends immediately
|
|
; - to blank the line ($C62A). $C5FC tests it and treats a negative value as 'no chat line'. On disk
|
|
; the whole line is 35 spaces followed by $A0 (space with the terminator bit).
|
|
chatMessageLine:
|
|
.byte " " ; 0548 marker glyph: $1E = there is a line to print, $9E = terminator, print nothing
|
|
|
|
; The 35 text bytes of the chat line, rebuilt character by character at $13EF from the raw receive
|
|
; buffer incomingChatBuffer ($9253): the newest character is stored with bit 7 set (it terminates the
|
|
; string) and the earlier ones are copied back over it as the line grows. $0BC5 stores $9E here to
|
|
; clear the line at the start of a game.
|
|
D_0549:
|
|
.byte $20,$20,$20,$20,$20,$20,$20,$20; 0549
|
|
.byte $20,$20,$20,$20,$20,$20,$20,$20; 0551
|
|
.byte $20,$20,$20,$20,$20,$20,$20,$20; 0559
|
|
.byte $20,$20,$20,$20,$20,$20,$20,$20; 0561
|
|
.byte $20,$20,$A0 ; 0569 .
|
|
|
|
; msgWorking: message id $01, 'WORKING...', 10 bytes with the terminator on the final '.'; sound $80 =
|
|
; silent. Used as the sticky message (persistentMessageId) while a disk load or an overlay swap is in
|
|
; progress ($2988, $7989, $C853).
|
|
msgWorking:
|
|
.byte "WORKING..",'.'|$80 ; 056C
|
|
|
|
; msgGameAborted: message id $03, 'GAME ABORTED.', 13 bytes, sound $03. Queued at $525F when the
|
|
; opponent or the player abandons the game.
|
|
msgGameAborted:
|
|
.byte "GAME ABORTED" ; 0576
|
|
.byte $AE ; 0582 the terminating '.' ($AE). It is broken out of the string above because the data-type map is keyed by address alone and the 1541 fast loader has a data block at its own $0582
|
|
|
|
; msgNotAGameFilm: message id $09, 'NOT A GAME FILM!', 16 bytes, sound $03. Queued at $81F9 when the
|
|
; block just loaded from a save slot is not a game film.
|
|
msgNotAGameFilm:
|
|
.byte "NOT A GAME FILM",'!'|$80; 0583
|
|
|
|
; msgSetupUnitsPressF1WhenDone: message id $0A, 'SETUP UNITS. PRESS F 1 WHEN DONE.', 33 bytes, silent
|
|
; ($80). Queued at $1FF2 when the pre-battle setup phase starts; F1 (screen 0) ends it.
|
|
msgSetupUnitsPressF1WhenDone:
|
|
.byte "SETUP UNITS. PRESS F 1 WHEN DONE",'.'|$80; 0593
|
|
|
|
; msgTimeoutRunStopToResume: message id $19, 'TIMEOUT. RUN/STOP TO RESUME.', 28 bytes, sound $19.
|
|
; Shown as a sticky message ($5287, $C72B) when the opponent has not answered for too long; RUN/STOP
|
|
; restarts the exchange.
|
|
msgTimeoutRunStopToResume:
|
|
.byte "TIMEOUT. RUN/STOP TO RESUME",'.'|$80; 05B4
|
|
|
|
; msgResumed: message id $1B, 'RESUMED', 7 bytes, sound $19. Queued at $52A6 when the link comes back
|
|
; after a timeout or a pause.
|
|
msgResumed:
|
|
.byte "RESUME",'D'|$80 ; 05D0
|
|
|
|
; msgTheBattleBegins: message id $1C, 'THE BATTLE BEGINS!', 18 bytes, sound $03. Queued at $0B72 when
|
|
; the setup phase ends and the clock starts.
|
|
msgTheBattleBegins:
|
|
.byte "THE BATTLE " ; 05D7
|
|
.byte $42 ; 05E2 'BEGINS!' - the tail of the string above, broken out because the 1541 fast loader has a data block at its own $05E2 and data types are keyed by address alone
|
|
.byte $45,$47,$49,$4E,$53,$A1 ; 05E3 EGINS.
|
|
|
|
; msgLastQtrWarning: message id $1D, 'LAST QTR WARNING!', 17 bytes, sound $19. Queued at $4621 when
|
|
; the game clock enters the last of its four quarters.
|
|
msgLastQtrWarning:
|
|
.byte "LAST QTR WA" ; 05E9
|
|
.byte "RNING",'!'|$80 ; 05F4 'RNING!' - the tail of the string above, broken out the same way by the fast loader's hiddenSignature at its own $05F4
|
|
|
|
; msgPhoneTrouble: message id $22, 'PHONE TROUBLE.', 14 bytes, sound $15. Shown at $5A50 when the
|
|
; modem link fails.
|
|
msgPhoneTrouble:
|
|
.byte "PHONE TROUBLE",'.'|$80 ; 05FA
|
|
|
|
; msgTryAgain: message id $23, 'TRY AGAIN.', 10 bytes, sound $03. Shown at $5A32, usually right after
|
|
; PHONE TROUBLE.
|
|
msgTryAgain:
|
|
.byte "TRY AGAIN",'.'|$80 ; 0608
|
|
|
|
; messageSoundTable: 36 bytes, the sound effect played when message id X is put on screen.
|
|
; $C543-$C55E loads it with Y = the message id and passes it to playSound, except when bit 7 is set
|
|
; ($80 = silent). The sounds group the messages: $03 neutral notice, $04 spotted/alert, $06 the two
|
|
; phone prompts,
|
|
; $0D we won, $0E we lost, $15 phone trouble, $19 clock/status (GAME OVER, TIMEOUT, RESUMED, LAST
|
|
; QTR).
|
|
messageSoundTable:
|
|
.byte $80,$80,$03,$03,$03,$04,$03,$03; 0612 ........ ids $00-$07: chat and WORKING... silent; END OF FILM / GAME ABORTED / OPPONENT READY $03; UNIT RECYCLED $04; the recycler sightings $03
|
|
.byte $04,$03,$80,$03,$04,$04,$04,$04; 061A ........ ids $08-$0F: SPOTTED RECYCLER $04, NOT A GAME FILM $03, SETUP UNITS silent, WAITING FOR OPPONENT $03, then $04 for COMCEN STUNNED, ENEMY COMCEN, SPY UNDER ATTACK and ENEMY SPY
|
|
.byte $80,$0E,$0D,$80,$0E,$0D,$19,$06; 0622 ........ ids $10-$17: WAITING FOR CONNECTION silent, lost $0E, won $0D, draw silent, knocked out $0E, knockout win $0D, GAME OVER $19, PICK UP PHONE $06
|
|
.byte $06,$19,$03,$19,$03,$19,$03,$80; 062A ........ ids $18-$1F: PRESS SPACE $06, TIMEOUT $19, OPPONENT PICKING $03, RESUMED $19, THE BATTLE BEGINS $03, LAST QTR WARNING $19, run-time slot $03, OPPONENT READY FOR NEW GAME silent
|
|
.byte $80,$03,$15,$03 ; 0632 .... ids $20-$23: run-time slot silent, run-time slot $03, PHONE TROUBLE $15, TRY AGAIN $03
|
|
|
|
; optionSummaryTemplate: 23 bytes, ' MENUS XXXX DAMAGE XXX.' with the terminator on the '.'. It is a
|
|
; fill-in-the-blanks rules line, not a constant string: appendOnOffWord ($872E in overlay A) copies
|
|
; one of the words from optionWordTable over the XXXX field at offset 7 and the XXX field at offset
|
|
; $13, writes '.'|$80 straight after the last word it substituted and adds ',' / ', ' padding, then
|
|
; $8714 copies the finished line to $0332 and $8722 registers that copy as message id $1E. Offsets: 0
|
|
; space, 1-6 'MENUS ', 7-10 the OFF/ON field, 11 space, 12-17 'DAMAGE', 18 space, 19-21 the
|
|
; FULL/HALF/NONE field, 22 '.'.
|
|
optionSummaryTemplate:
|
|
.byte " MENUS XXXX DAMAGE XXX",'.'|$80; 0636
|
|
|
|
; optionWordTable: 17 bytes, five bit-7 terminated words at fixed offsets: +$00 'OFF' ($064D), +$03
|
|
; 'ON' ($0650), +$05 'FULL' ($0652), +$09 'HALF' ($0656), +$0D 'NONE' ($065A). Only the first two are
|
|
; used. appendOnOffWord ($8734) is the single reader of the table in the whole program and it enters
|
|
; with Y = 0 (OFF) or Y = 3 (ON), for both fields of optionSummaryTemplate, and
|
|
; $8632 prints 'OFF' and then - through printStringAtPointer, which carries on from where the previous
|
|
; string ended - the 'ON' behind it, as the two items of a menu. FULL, HALF and NONE have no reader
|
|
; at all: they look like leftovers from a damage option that ended up being just on/off. (XREF.txt
|
|
; shows this address as eaCopyrightMessage; that is the 1541 drive's own $064D, a different address
|
|
; space entirely.)
|
|
optionWordTable:
|
|
.byte "OF",'F'|$80,"O",'N'|$80,"FUL",'L'|$80,"HAL",'F'|$80,"NON"; 064D
|
|
.byte 'E'|$80 ; 064D
|
|
|
|
; msgClicks: 10 bytes, ' CLICKS:' - the header of the score line drawn under the map. Clicks are
|
|
; Modem Wars' score unit. $C6BC prints it (A = $5E, Y = $06), then the game clock, then the next two
|
|
; strings in memory through printStringAtPointer, each followed by a 16-bit score from
|
|
; sideScoreLo/sideScoreHi ($92A2/$92A4).
|
|
msgClicks:
|
|
.byte " CLICKS",':'|$80 ; 065E
|
|
|
|
; msgUs: 7 bytes, ' US:'. Printed straight after the clock by the printStringAtPointer at $C6CB,
|
|
; which continues from wherever the previous string ended - which is why these three labels have to
|
|
; stay adjacent and in this order. The number that follows it comes from sideScore indexed by
|
|
; playerSide EOR 1 ($C6CE), so the score arrays look as if they are indexed by the side that conceded
|
|
; the points.
|
|
msgUs:
|
|
.byte " US",':'|$80 ; 0668
|
|
|
|
; msgThem: 8 bytes, ' THEM:', printed by the same routine at $C6D6 and followed by the score indexed
|
|
; by playerSide itself.
|
|
msgThem:
|
|
.byte " THEM",':'|$80 ; 066F
|
|
|
|
; msgTypeMapName: 40 bytes, three lines separated by $0D: 'TYPE MAP NAME' / ' (F7 RANDOM)' / ' (F5
|
|
; SYM/RND)'. Printed by the map chooser at $76C9 (A = $77, Y = $06) while the player types the
|
|
; base-32 name of the battlefield both sides have agreed on, or picks a random / symmetrical-random
|
|
; one. (The manual look-up check at $09EF prints its own prompt from $0A47, not this one.)
|
|
msgTypeMapName:
|
|
.byte "TYPE MAP NAME",$0D," (F7 RANDOM)",$0D," (F5 SYM/RND"; 0677
|
|
.byte ')'|$80 ; 0677
|
|
|
|
; msgBtnToAccept: 27 bytes, two lines separated by $0D: 'BTN TO ACCEPT' / '(F 1 TO FLIP)'. Printed at
|
|
; $76D9 while the generated map is on screen; the fire button accepts it and F1 shows it rotated 180
|
|
; degrees (mapFlipFlag $0B9D).
|
|
msgBtnToAccept:
|
|
.byte "BTN TO ACCEPT",$0D,"(F 1 TO FLIP",')'|$80; 069F
|
|
|
|
; ----------------------------------------------------------------------
|
|
; switchToRequestedScreen - the game's screen dispatcher, called once per iteration from every
|
|
; screen's main loop to honour a pending F1/F3/F5/F7 keypress. It returns at once while the chat
|
|
; editor has the keyboard, while no function key is pending, or when the key names the screen that is
|
|
; already showing; otherwise it makes the request current, throws the caller's stack away, turns every
|
|
; sprite off, rebuilds the bitmap display and jumps into the new screen's entry point - so it never
|
|
; returns in that case.
|
|
; In: zp_66 requestedScreenKey ($00 F1, $01 F3, $02 F5, $03 F7, $FF or a typed character = no
|
|
; request), $90FB currentScreen, $929C chatState, $0B9B bit 7 filmPlaybackMode
|
|
; Out: either RTS with nothing changed, or: $90FB updated, sprites and their shadows ($9020, $9022,
|
|
; $9023, $9024) cleared, $92F9 cleared unless a film is replaying, S = $FD, and a JMP to
|
|
; enterMapScreen ($1FC3, screen 0, with $9020 preset to $C4), runMissileScreen ($7587, screen 1),
|
|
; enterDroneScreen ($7FE6, screen 2) or enterMainMenu ($141D, anything else)
|
|
; Called from: the console loop $1463, the battlefield loop $2033 and, in the comcen-screens overlay,
|
|
; the missile screen $75C3 and the drone screen $8040. Screens 1 and 2 live in that overlay, so
|
|
; they can only be reached while it is resident. (XREF.txt also lists the map generator as
|
|
; calling $75C3 and $8040; that is an artefact of the two $6F00-$87FF overlays sharing one address
|
|
; range - in the map generator those two addresses hold unrelated code.)
|
|
; ----------------------------------------------------------------------
|
|
switchToRequestedScreen:
|
|
lda chatState ; 06BA $929C is non-zero while the chat line editor owns the keyboard
|
|
bne L_06C8 ; 06BD refuse to switch in the middle of typing a message
|
|
lda requestedScreenKey ; 06BF zp_66 = the last key seen by the IRQ poll; the four function keys decode to 0-3 in keyMatrixCodeTable, everything else has bit 7 set
|
|
bmi L_06C8 ; 06C1 nothing pending: $FF (no key) and any typed character ($80-$FF) are both negative
|
|
cmp currentScreen ; 06C3 $90FB = the screen already on show: 0 battlefield map, 1 comcen missile, 2 drone, 3 console/main menu
|
|
bne L_06C9 ; 06C6 a different screen was asked for - go and switch
|
|
L_06C8:
|
|
rts ; 06C8 same screen or nothing to do: back into the caller's main loop
|
|
|
|
L_06C9:
|
|
sta currentScreen ; 06C9 the requested screen becomes the current one
|
|
ldx #$FD ; 06CC $FD is the game's 'empty stack' value ...
|
|
txs ; 06CE ... reset it, because this routine jumps into the new screen instead of returning (mainStart does the same at $0AAC)
|
|
lda #$00 ; 06CF zero for all the sprite registers below
|
|
sta spriteEnableShadow ; 06D1 $9022 = the shadow of $D015 that the IRQ copies out every frame
|
|
sta VIC_SPR_ENA ; 06D4 and switch all eight sprites off immediately as well
|
|
sta spriteXMsbShadow ; 06D7 $9020 = shadow of $D010, the sprite X bit 8 flags
|
|
sta spriteXExpandShadow ; 06DA $9023 = shadow of $D01D, sprite X expansion
|
|
sta spriteYExpandShadow ; 06DD $9024 = shadow of $D017, sprite Y expansion
|
|
bit filmPlaybackMode ; 06E0 $0B9B bit 7 = a game film is being replayed
|
|
bmi L_06E8 ; 06E3 during a replay leave the drone flags alone - the recorded command stream has to play back exactly as it was taken down
|
|
sta droneArrivalFlags ; 06E5 clear the drone handshake flags $92F9 (bit 0 = a 'drone finished' command $92 is already queued, bits 4-5 = the launch/arrival states) so the new screen starts clean
|
|
L_06E8:
|
|
jsr initBitmapScreen ; 06E8 rebuild the whole bitmap display: VIC registers, video matrix, colour RAM and the window frames
|
|
lda currentScreen ; 06EB dispatch on the new screen number
|
|
bne L_06F8 ; 06EE not the battlefield map
|
|
lda #$C4 ; 06F0 $C4 = %11000100: preset the X bit-8 flag for sprites 2, 6 and 7 - the map screen's sprites that live in the right-hand half of the display
|
|
sta spriteXMsbShadow ; 06F2 into the $D010 shadow, before the map screen puts its sprites up
|
|
jmp enterMapScreen ; 06F5 screen 0 (F1): the battlefield map at $1FC3
|
|
|
|
L_06F8:
|
|
cmp #$02 ; 06F8 screen 2?
|
|
bne L_06FF ; 06FA no
|
|
jmp enterDroneScreen ; 06FC screen 2 (F5): the drone screen at $7FE6, in the comcen-screens overlay
|
|
|
|
L_06FF:
|
|
cmp #$01 ; 06FF screen 1?
|
|
bne L_0706 ; 0701 no - then it can only be 3
|
|
jmp runMissileScreen ; 0703 screen 1 (F3): the comcen missile screen at $7587, in the comcen-screens overlay
|
|
|
|
L_0706:
|
|
jmp enterMainMenu ; 0706 screen 3 (F7): the console with its STATS / REPAIR / MISC panels at $141D
|
|
|
|
|
|
; ----------------------------------------------------------------------
|
|
; runComcenStunnedBlackout - the 'COMCEN STUNNED!' cut-scene: when your command centre has been
|
|
; knocked out, control is taken away, the screen goes blank and the engine keeps running unwatched for
|
|
; exactly one full round of all 100 units before the game hands control back. It releases the drone,
|
|
; clears the screen, resets the stack, then loops on updateGameFrame with the input lockout re-armed
|
|
; and bit 6 of comcenStunStatus set, which makes the sprite enabler at $31A6 and the cell plotter at
|
|
; $5C7C give up so that nothing is drawn at all, until unitOrderIndex comes back to the value it had
|
|
; on entry; then it queues message $0C, unlocks the input, clears bits 4-6 and re-enters whichever
|
|
; screen was showing.
|
|
; In: $9162 unitOrderIndex (captured into the CMP operand at $073E), $922A comcenStunStatus, $90FB
|
|
; currentScreen, $92C9 gameEndCountdown and $0BA8 gameEndReason (abort conditions), $90FC
|
|
; stepDelayTimer
|
|
; Out: one blind engine round; $0B7D re-armed each step and cleared at the end; $922A left at bit 7 +
|
|
; countdown; message $0C queued through $C566; never returns - it jumps to runMapMainLoop ($1FD0,
|
|
; after redrawing the map and viewport), missileScreenResume ($759C), resetDroneScreen ($7FF6) or
|
|
; enterMainMenuKeepSelection ($1414)
|
|
; Called from: JMP at $4546 only, taken after a unit step when comcenStunStatus has reached $FF
|
|
; (written by $730D when the missile hits the comcen) and no film is being replayed. During a
|
|
; replay the cut-scene is skipped and the overlay queues the message on its own at $72F6 instead.
|
|
; ----------------------------------------------------------------------
|
|
runComcenStunnedBlackout:
|
|
jsr releaseDroneControl ; 0709 if a drone is still in the air, hand it back ($8230 in the comcen-screens overlay) - the player is about to lose control of everything
|
|
jsr clearMessageLineAndScreen; 070C wipe the message line and the screen windows: this is the blackout
|
|
ldx #$FD ; 070F this routine never returns to the caller of $4546 ...
|
|
txs ; 0711 ... so reset the stack to the game's empty value
|
|
lda unitOrderIndex ; 0712 $9162 = how far the round's 100-unit process order has got; $4537 has just bumped it
|
|
sta L_073D+1 ; 0715 self-modifying: this is the operand byte of the CMP at $073D ($073E), so the loop below ends when the round order comes back round to this same unit - exactly one full pass of all 100 units
|
|
lda #$CF ; 0718 $CF = %11001111: keep bit 7 (comcen stunned), set bit 6 (run blind) and load the low nibble with a full $F stun countdown. Just as important, it is no longer $FF, so the test at $453A cannot start this cut-scene again on the next unit step
|
|
sta comcenStunStatus ; 071A arm the blackout
|
|
L_071D:
|
|
lda comcenStunStatus ; 071D top of the blackout loop: re-read the status byte, because updateGameFrame may have changed it
|
|
ora #$40 ; 0720 bit 6 suppresses all drawing: the sprite enabler at $31A6 and the viewport plotter at $5C7C both give up when it is set, and updateGameFrame itself skips the input handling at $40BA
|
|
sta comcenStunStatus ; 0722 store it back
|
|
sta inputLockoutTimer ; 0725 the same $CF value doubles as the input lockout count: the IRQ decrements $0B7D every frame and ignores the keyboard and joystick while it is non-zero. It is re-armed on every step, so the player stays locked out for the whole round
|
|
L_0728:
|
|
lda gameEndCountdown ; 0728 $92C9 counts down the end-of-game sequence; non-zero means the game is already over
|
|
bne L_0746 ; 072B the game is ending - stop the blackout now
|
|
lda gameEndReason ; 072D $0BA8 = 0 while a game is running (1 aborted, $08 time up, $A0/$C0 a comcen was knocked out)
|
|
bne L_0746 ; 0730 the game has ended - stop
|
|
lda stepDelayTimer ; 0732 $90FC = frames still to wait before the engine may take its next unit step
|
|
bne L_0728 ; 0735 not yet: spin here, re-testing both abort conditions while the IRQ ticks the timer down
|
|
jsr updateGameFrame ; 0737 one engine step: one unit is processed, with the input locked out and nothing drawn
|
|
lda unitOrderIndex ; 073A where has the round order got to now?
|
|
L_073D:
|
|
cmp #$FF ; 073D the immediate operand was patched at $0715 with the index the round was at when the comcen went down
|
|
bne L_071D ; 073F keep stepping until it comes round again - the index runs 0..99 and is reset to 0 at $474D when the round finishes
|
|
lda #$0C ; 0741 message id $0C = 'COMCEN STUNNED!' (the string lives at $8742 in the comcen-screens overlay; sound $04)
|
|
jsr queueMessage ; 0743 put it in the 8-entry message ring at $9249
|
|
L_0746:
|
|
lda #$00 ; 0746 0 = no lockout ...
|
|
sta inputLockoutTimer ; 0748 ... give the keyboard and the joystick back to the player
|
|
lda comcenStunStatus ; 074B and stop running blind:
|
|
and #$8F ; 074E clear bits 4-6 (bit 6 was the blackout), keeping bit 7 'comcen stunned' and the low-nibble countdown that the console's REPAIR panel shows
|
|
sta comcenStunStatus ; 0750 store the tidied status byte
|
|
ldy currentScreen ; 0753 return to whichever screen was showing when the comcen was hit
|
|
bne L_0761 ; 0756 not the battlefield map
|
|
jsr drawOverviewMap ; 0758 screen 0: redraw the 40x40 overview panel that the blackout wiped ...
|
|
jsr drawViewport ; 075B ... and the 7x5-cell tactical viewport
|
|
jmp runMapMainLoop ; 075E back into the map's main loop, not its entry point - the screen has just been set up by hand
|
|
|
|
L_0761:
|
|
dey ; 0761 screen 1?
|
|
bne L_0767 ; 0762 no
|
|
jmp missileScreenResume ; 0764 the missile screen has its own re-entry point for exactly this case ($759C)
|
|
|
|
L_0767:
|
|
dey ; 0767 screen 2?
|
|
bne L_076D ; 0768 no - then it is the console
|
|
jmp resetDroneScreen ; 076A re-initialise the drone panel; the drone itself was released at $0709
|
|
|
|
L_076D:
|
|
jmp resumeMainMenuScreen ; 076D screen 3: back to the console, keeping the menu item that was selected
|
|
|
|
|
|
; tileVariantMaskTable: 40 bytes ($0770-$0797), indexed by map cell value - $40, and by cell - $20 for
|
|
; cells below $40 (randomTileVariant $7872 subtracts $40 and adds $20 back if that went negative), so
|
|
; the first 32 entries serve both the $20-$3F and the $40-$5F cell ranges and the last 8 serve
|
|
; $60-$67. The byte is a bit mask: the map generator ANDs a fresh scenario random with it and adds the
|
|
; result to the base tile, so a mask of 0 means 'always this exact tile', 1 means 'one of two' and 3
|
|
; means 'one of four'. Only eight entries are non-zero: cells $49, $4B and $65 (and $29, $2B) get
|
|
; mask 1, cells $4D,
|
|
; $51, $59, $5D, $61 (and $2D, $31, $39, $3D) get mask 3.
|
|
tileVariantMaskTable:
|
|
.byte $00,$00,$00,$00,$00,$00,$00,$00; 0770 ........ cells $40-$4D (and $20-$2D): all fixed except $49 and $4B (mask 1) and $4D (mask 3)
|
|
.byte $00,$01,$00,$01,$00,$03 ; 0778 ......
|
|
|
|
; $077E is also the base address of terrainClassTable below: everything that wants the terrain class
|
|
; of a
|
|
; cell reads $077E + cell value, so the class bytes really start at $077E+$21 = $079F. The 26 bytes
|
|
; here are still tile variant masks (cells $4E-$67), which is why the two tables have to sit exactly
|
|
; this far apart.
|
|
D_077E:
|
|
.byte $00,$00,$00,$03,$00,$00,$00,$00; 077E ........ cells $4E-$67 (and $2E-$3F): mask 3 at $51, $59, $5D and $61, mask 1 at $65, everything else fixed
|
|
.byte $00,$00,$00,$03,$00,$00,$00,$03; 0786 ........
|
|
.byte $00,$00,$00,$03,$00,$00,$00,$01; 078E ........
|
|
.byte $00,$00 ; 0796 ..
|
|
|
|
; tileVariantPad: the single $00 that pads the variant masks out to the glyph table. As terrain class
|
|
; data it would be the class of cell $1A, which never occurs.
|
|
tileVariantPad:
|
|
.byte $00 ; 0798 . $00 pad; read as terrain class data through the $077E base this would be the class of cell $1A, which never occurs
|
|
|
|
; unitGlyphBaseTable: 5 bytes plus a pad, the base glyph number of the 16x16 unit picture for each
|
|
; unit type: $2C GRUNT, $30 RIDER, $34 BOOMER, $38 SPY, $3C COMCEN. The facing (unit flags AND 3) is
|
|
; added to it, then $2C is subtracted and the result doubled to index unitGlyphPointerTable, i.e.
|
|
; glyph = type*4 + facing ($289A-$28A8 for the tactical view, $7512 and $7538 for the radar screen,
|
|
; where the enemy version has bit 7 set). The trailing $00 at $079E is both the pad and, read as
|
|
; terrain class data through the $077E base, the class of cell $20 - class 0, blank ground.
|
|
unitGlyphBaseTable:
|
|
.byte $2C,$30,$34,$38,$3C,$00 ; 0799 ,048<. $2C GRUNT, $30 RIDER, $34 BOOMER, $38 SPY, $3C COMCEN, then the $00 pad at $079E
|
|
|
|
; terrainClassTable: map cell value -> terrain class 0-8, 75 bytes covering cells $21-$6B. Every
|
|
; reader addresses it as $077E + cell (see the note there); $5BC8 gets to the same bytes as $07BE +
|
|
; (cell-$40) after clamping the index below $21. The class is what the rest of the engine works with:
|
|
; terrainMoveCostTable $405E (move points charged per step: 20, 65, 30, 40, 20, 40, 20, 20),
|
|
; terrainHeightTable $38B3 (0, 0, 0, 1, 2, 3, 4, 0 - line of sight and the combat height bonus),
|
|
; terrainSightBonusTable $35BB (0, 0, 0, 1, 1, 2, 2, 0 - extra range, doubled when spotting) and
|
|
; terrainColourTable $07EA are all indexed by it. Class 2 (woods) is also tested by name at $36B5 and
|
|
; $3729 because it blocks line of sight. Note that those four engine tables only have eight entries,
|
|
; so class 8 - the off-map border - runs off the end of them; nothing ever walks or shoots there.
|
|
terrainClassTable:
|
|
.byte $03,$03,$03,$03,$03,$03,$03,$03; 079F ........ cells $21-$34 -> class 3, the lower contour band: costs 40 move points, height 1, +1 sight
|
|
.byte $03,$03,$03,$03,$03,$03,$03,$03; 07A7 ........
|
|
.byte $03,$03,$03,$03 ; 07AF ....
|
|
.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF; 07B3 ........ cells $35-$3F -> $FF, an invalid class: these cell values are never put on the map
|
|
.byte $FF,$FF,$FF ; 07BB ...
|
|
D_07BE:
|
|
.byte $00 ; 07BE . cell $40 -> class 0, plain open ground: 20 move points, no height, no sight bonus, drawn yellow
|
|
.byte $05,$05,$05,$05,$05,$05,$05,$05; 07BF ........ cells $41-$54 -> class 5, the upper contour band: 40 move points, height 3, +2 sight
|
|
.byte $05,$05,$05,$05,$05,$05,$05,$05; 07C7 ........
|
|
.byte $05,$05,$05,$05 ; 07CF ....
|
|
.byte $01,$01,$01,$01,$01,$01,$01,$01; 07D3 ........ cells $55-$5C -> class 1, river: 65 move points, the most expensive terrain in the game, drawn cyan
|
|
.byte $02,$02,$02,$02 ; 07DB .... cells $5D-$60 -> class 2, woods: 30 move points and the only terrain that blocks line of sight, drawn green
|
|
.byte $04,$04,$04,$04 ; 07DF .... cells $61-$64 -> class 4, hill: 20 move points, height 2, +1 sight
|
|
.byte $06,$06 ; 07E3 .. cells $65-$66 -> class 6, summit: 20 move points, height 4 (the best view in the game), +2 sight
|
|
.byte $07 ; 07E5 . cell $67 -> class 7, the recycler pad where damaged units are repaired ($3F27 stamps $67 into the map)
|
|
.byte $08,$08,$08,$08 ; 07E6 .... cells $68-$6B -> class 8, the off-map border: $278A builds these four values as $68 + ((row+col) AND 3) for any cell outside the 40x40 battlefield
|
|
|
|
; terrainColourTable: 9 bytes, the colour-RAM nibble for terrain class 0-8, read at $27AB (viewport)
|
|
; and
|
|
; $5BCB (overview map) straight after a class look-up: 7 yellow open ground, 3 cyan river, 5 green
|
|
; woods,
|
|
; 8 orange for the contour bands, hills and summits, 0 black for the recycler pad and the off-map
|
|
; border.
|
|
terrainColourTable:
|
|
.byte $07,$03,$05,$08,$08,$08,$08,$00; 07EA ........ class 0-8: $07 yellow, $03 cyan, $05 green, $08 orange x4, $00 black x2
|
|
.byte $00 ; 07F2 .
|
|
|
|
; table0400Tail: 13 bytes of $00 filler to the end of the page. The block move that installed the
|
|
; page copies four whole pages, so this is simply whatever followed the tables in the source file.
|
|
table0400Tail:
|
|
.byte $00,$00,$00,$00,$00,$00,$00,$00; 07F3 ........ unused filler
|
|
.byte $00,$00,$00,$00,$00 ; 07FB .....
|