|
|
 |
Definitive Combat Disassembly
Download Source Separately (right click to save)
; Combat for Atari by Larry Wagner
;
; Original disassembly by Harry Dodgson
; Commented further by Nick Bensema (1997)
; Major overhaul by Roger Williams (2002)
;
; My intent in overhauling this classic disassembly is to finish it
; so that the purpose of every instruction, memory location, and
; table is made completely clear.
;
; For some reason the NBCOMBAT file ORG statements all point to
; the region $1000-$1FFF; this would play in a VCS but the cartridge
; .BIN I have is mapped from $F000-$FFFF. This file compiles with
; DASM to an image which differs from this ROM only in the few
; unwritten bytes between the end of data and the startup vectors.
; DASM sets these to zero, typical of unwritten RAM, but in the cart
; they are $FF, typical of unprogrammed PROM.
;
; Thanks to Brian Prescott for pointing me to Joe DeCuir's
; presentation notes, which revealed Atari's original names
; for the main loop toplevel routines and offered some guidance
; on their separation of function.
;
; I have removed some of the breathless intro-to-VCS and historical
; comments. This version assumes a basic familiarity with VCS
; programming, and is meant as a basis for hacking the COMBAT game
; itself. There are plenty of resources outside of this file if
; you don't know how the VCS works.
;
; For reference, as this is rather important when reading the,
; code, here is the game variation matrix (it is not trivially
; obvious how this corresponds to GAMVAR):
;
; Game No. Open Field
; | Straight Missiles | Easy Maze
; | | Guided Missiles | | Complex Maze
; | | | Machine Guns | | | Clouds
; | | | | Direct Hit | | | |
; | | | | | Billiard | | | |
; | | | | | | Hit | | | |
; | | | | | | | | | |
; | | | | | | | | | |
;
;TANK 1 - X - - - X - - -
; 2 - X - - - - X - -
; 3 X - - - - - X - -
; 4 - X - - - - - X -
; 5 X - - - - - - X -
;--------------------------------------------------
;TANK-PONG 6 - - - X X - X - -
; 7 - - - X X - - X -
; 8 - - - - X X - - -
; 9 - - - - X - X - -
;--------------------------------------------------
;INVISIBLE TANK 10 - X - - - X - - -
; 11 - X - - - - X - -
;--------------------------------------------------
;INVISIBLE 12 - - - X X - X - -
;TANK-PONG 13 - - - - X X - - -
; 14 - - - - X - X - -
;--------------------------------------------------
;BI-PLANE 15 - X - - - - - - X
; 16 X - - - - - - - X
; 17 - - X - - - - - X
; 18 - - X - - X - - -
; 2 vs. 2 19 - X - - - X - - -
; 1 vs. 3 20 X - - - - X - - -
;--------------------------------------------------
;JET 21 - X - - - - - - X
; 22 X - - - - - - - X
; 23 - X - - - X - - -
; 24 X - - - - X - - -
; 2 vs. 2 25 - X - - - - - - X
; 1 vs. 3 26 - X - - - X - - -
; 2 vs. 2 27 X - - - - X - - -
processor 6502
include vcs.h
; RAM is cleared in blocks beginning at various addresses and
; always ending at $A2 (though this isn't the highest address
; used). I have placed \\\/// comments to mark these points.
BINvar = $80 ; Master Game Variation Control (binary)
; (When BINvar is reset or incremented,
; BCDvar is reset or BCD-imcremented and
; GAMVAR flag is read from VARMAP+BINvar)
BCDvar = $81 ; Game Variation in BCD
;
;\\\///
;
; $82 thru $85 contain flags built from GAMVAR for quick testing via BIT.
;
PF_PONG = $82 ; bit 7 DIS-able playfield flag
; ; bit 6 Pong missiles (bounce off playfield)
GUIDED = $83 ; bit 7 = guided missile game
; ; bit 6 = machine gun game
BILLIARD = $84 ; Just bit 6 = billiard hit game (missiles can't
; ; hit tank until at least 1 bounce off playfield)
GAMSHP = $85 ; Shape of player and game type
; ; 0 = Tank
; ; 1 = Biplane
; ; 2 = Jet Fighter
;
CLOCK = $86 ; Master timer inc'd every frame during VSYNC
; ; in NBCOMBAT this was misleadingly labelled GTIMER
SHOWSCR = $87 ; Show/hide RIGHT player score (left only is used
; ; to indicate game selection in attract mode) To
; ; inhibit both scores, KLskip is set to $0E vs. $02
GameOn = $88 ; $00=attract mode, $FF=game going on. Bits 7, 1,
; ; and "all" tested in various places. Incorrectly set
; ; to $10 at START, but must not be a problem :-)
;\\\///
;
SelDbnce = $89 ; Select Switch Debounce flag which prevents a
; ; hold-down from registering as 60 presses/second
StirTimer = $8A ; Bit 0 = identity of loser during tank stir
; ; Bits 2-7 = countdown timer controlling stir after loss
Vtemp = $8B ; Temp storage for current velocity
FwdTimer = $8D ; FwdTimer must count $F0 to $00 between changes in
; thru $8E ; forward motion control; also used for momentum pacing
; $8F ; ...
; thru $90 ; seem to be reserved too (missiles?) but not used
LastTurn = $91 ; Flag indicating direction of last turn, used
; thru $92 ; to inhibit whipsaw direction changes (may
; ; have been intended for rotational momentum)
TurnTimer = $93 ; Countdown timer between 22.5-degree rotates
; thru $94 ; for P0 and P1
DIRECTN = $95 ; Players and missiles' current bearing.
; thru $98 ; (4 bytes P0,P1,M0,M1)
MisLife = $99 ; Missile Lifetime down-counters
; thru $9A
BounceCount = $9B ; (1) Billiard bounced-once flag, via any value other
; thru $9C ; than $1F init value; (2) Pong sound tone freq, which
; ; ascends in tone as BounceCount DECed with each bounce
MxPFcount = $9D ; During Pong bounce, count of collision duration in
; thru $9E ; frames, used to try different heading adjustments
; ; until "desired" reflection achieved
AltSnd = $9F ; Alt Player Sound flag/counter; 0=normal motor sound,
; thru $A0 ; else counts up to $04 to time Pong sound
SCORE = $A1 ; Player scores in BCD.
; thru $A2 ;
;
;\\\/// Addresses beyond here aren't ever cleared by ClearMem.
;
GAMVAR = $A3 ; Game Variation bitwise descriptor via VARMAP
TankY0 = $A4 ; Tank 0's Y-position
TankY1 = $A5 ; and tank 1
MissileY0 = $A6 ; Missile 0's Y-position
MissileY1 = $A7 ; and missile 1
MVadjA = $A8 ; First-half FwdTimer-Velocity adjustments
; thru $A9 ; for each player. By an amazing coincidence
; ; in all games these seem to be the same as
; ; the *current* velocity.
MVadjB = $AA ; Second-half FwdTimer-Velocity adjustments,
; thru $AB ; which seem to be the same as the *final* velocity.
MPace = $AC ; Pacing counter; never initialized! INC'd and
; ; masked to pace certain actions slower than
; thru $AF ; once/frame, for each player & missile
XOFFS = $B0 ; X-offset for pending Hmove.
XoffBase = $B1 ; $0, $10, $20, or $30 offset into X-offset tbl
OldMisDir = $B2 ; Missile bearing before a Pong-bounce began
; thru $B3 ;
ScanLine = $B4 ; Current scanline on the playfield.
LORES = $B5 ; lo-res indirect addresses.
; thru $BA ; 6 bytes / 3 16-bit pointers
SHAPES = $BB ; Pointer to player sprites
HIRES = $BD ; Hi-res (sprite) shape buffer. Left player's shape
; thru $CC ; stored in even bytes, right player's in odd.
TEMP1 = $D1 ; Temp storage for several quick save/math operations
TEMP = $D2 ; "score conversion temporary"
TMPSTK = $D3 ; Temporary storage for stack.
DIFSWCH = $D5 ; Hold & shift temp for console switches
Color0 = $D6 ; Colors loaded from ColorTbl for player 0 and 1
Color1 = $D7 ; These may be changed e.g. invisible tanks
XColor0 = $D8 ; Repeated P0 and P1 Colors for reference, used
XColor1 = $D9 ; to restore ColorX after a change
ColorPF = $DA ; BK and PF colors loaded in same block as XColorX.
ColorBK = $DB ; Never changed, so no reference versions are kept.
KLskip = $DC ; Kernal lines to skip before score, or main w/o score
; ; (Also used in Kernal as flag whether to show score)
GameTimer = $DD ; Master game timer set to $80 when game starts,
; ; incremented until overflow at $FF-->$00 ends game
; ; Bit 7 indicates game in play, also used w/GameOn to
; ; flash score. During attract mode GameTimer is used
; ; to cycle colors; this is OK since it only assumes
; ; its game-timing function if GameOn != $00.
NUMG0 = $DE ; Storage for current byte
NUMG1 = $DF ; of score number graphics.
SCROFF = $E0 ; Score pattern offsets (4 bytes)
; thru $E3 ; lo nibble 0, lo 1, hi 0, hi 1
COLcount = $E4 ; Counter keeps tank-tank and tank-PF collisions from
; thru $E5 ; affecting a stationary tank's bearing unless the
; ; collision lasts at least 4 cycles
;
StkTop = $FF ; Top of stack (which IS used, at least 8 bytes)
;
; So much for the RAM. Here's the ROM:
org $F000
START SEI ; Disable interrupts
CLD ; Clear decimal bit
LDX #StkTop
TXS ; Init Stack
LDX #$5D
JSR ClearMem ; zero out RAM except address $A2
LDA #$10 ;
STA SWCHB+1 ; Port B data direction register and
STA GameOn ; GameOn (tho not quite a valid value)...
JSR ClrGam ; clear game RAM $82-$A2
;
MLOOP JSR VCNTRL ; Generate a VSYNC and begin VBLANK
;
; VBLANK logic:
;
JSR GSGRCK ; Parse console switches
JSR LDSTEL ; Load Stella Registers
JSR CHKSW ; Check Joystick Switches
JSR COLIS ; Check Collision Registers
JSR STPMPL ; Setup Player, Missile Motion
JSR ROT ; Rotate Sprites
JSR SCROT ; Calculate Score Offsets
;
JSR VOUT ; do the Kernal (trashes the stack ptr,
; but then restores it because it IS
JMP MLOOP ; used when we reiterate this loop)
;
; ------------------------------------------------------------
;
; Vertical CoNTRoL
;
; Vertical sync, basic frame-start housekeeping
;
VCNTRL INC CLOCK ; Master frame count timer
STA HMCLR ; Clear horizontal move registers.
LDA #2 ; Get this ready...
STA WSYNC ; for start of next line...
STA VBLANK ; Start vertical blank.
STA WSYNC
STA WSYNC ; and do three lines
STA WSYNC
STA VSYNC ; Now start vertical sync
STA WSYNC
STA WSYNC ; and do three lines
LDA #0 ; get this ready
STA WSYNC
STA VSYNC ; End of vertical sync pulse
LDA #43 ; And set VBLANK timer
STA TIM64T ; with 64 clock interval.
RTS
;
; ------------------------------------------------------------
;
; Video OUT -- THE KERNAL
;
; We start with the score, then we render the playfield, players,
; and missiles simultaneously. All in all, an average day for a VCS.
;
VOUT LDA #$20
STA ScanLine ; We're assuming scanline $20.
STA WSYNC
STA HMOVE ; Move sprites horizontally.
VOUT_VB LDA INTIM
BNE VOUT_VB ; Wait for INTIM to time-out.
STA WSYNC
STA CXCLR ; Clear collision latches
STA VBLANK ; End vertical blank
TSX
STX TMPSTK ; Save stack pointer
LDA #$02
STA CTRLPF ; Double, instead of reflect.
LDX KLskip
Vskip1 STA WSYNC ; Skip a few scanlines...
DEX
BNE Vskip1
LDA KLskip
CMP #$0E ; "No Score" value of KLskip
BEQ Vmain
;
; KLskip is set as such so that when the score is
; to be displayed, it waits for just the right time
; to start drawing the score, but if the score is
; not to be displayed, as when the score flashes
; signifying "time's almost up", it waits for just
; the right time to start drawing the rest of the
; screen.
;
; Draw the score:
;
LDX #$05 ; Score is five bytes high.
LDA #$00 ; Clear number graphics.
STA NUMG0 ; They won't be calculated yet,
STA NUMG1 ; but first time through the loop
; the game will try to draw with
; them anyway.
VSCOR STA WSYNC ; Start with a fresh scanline.
LDA NUMG0 ; Take last scanline's left score,
STA PF1 ; and recycle it,
;
; Here, we begin drawing the next scanline's
; left score, as the electron beam moves towards
; the right score's position in this scanline.
;
LDY SCROFF+2
LDA NUMBERS,Y ; Get left digit.
AND #$F0
STA NUMG0
LDY SCROFF
LDA NUMBERS,Y ; Get right digit.
AND #$0F
ORA NUMG0
STA NUMG0 ; Left score is ready to ship.
LDA NUMG1 ; Take last scanline's right score,
STA PF1 ; and recycle it.
LDY SCROFF+3
LDA NUMBERS,Y ; Left digit...
AND #$F0
STA NUMG1
LDY SCROFF+1
LDA NUMBERS,Y ; right digit...
AND SHOWSCR
;
; Now, we use our fresh, new score graphics in this next scanline.
;
STA WSYNC ; *COUNT*
ORA NUMG1 ;Finish calculating (0) +3
STA NUMG1 ;right score. (3) +3
LDA NUMG0 ; (6) +3
STA PF1 ; *9* +3
;
; We use this time to check whether we're at the end of our loop.
;
DEX ; (12)+2
BMI Vmain ; (14)+2 No Branch
;
; If so, we're out of here. Don't worry, the score will be
; cleared immediately, so nobody will know that we've gone
; past five bytes and are displaying garbage.
;
INC SCROFF ; (16)+5
INC SCROFF+2 ; Get ready to draw the next
INC SCROFF+1 ; line of the byte.
INC SCROFF+3
LDA NUMG1
STA PF1 ; Right score is in place.
JMP VSCOR ; Go to next scanline,
;
; Main Kernal Display loop for the game itself
;
Vmain LDA #$00 ; Inner Display Loop
STA PF1 ; Clear the score.
STA WSYNC
LDA #$05
STA CTRLPF ; Reflecting playfield.
LDA Color0
STA COLUP0 ; How often must THIS be done?
LDA Color1
STA COLUP1
Vfield LDX #$1E ; Very Sneaky -
TXS ; Set stack to missile registers
SEC
;
; This yields which line of player 0 to draw.
;
LDA TankY0
SBC ScanLine ; A=TankY0-ScanLine
AND #$FE ; Force an even number
TAX ; Only sixteen bytes of
AND #$F0 ; sprite memory, so...
BEQ VdoTank ; If not valid,
LDA #$00 ; blank the tank.
BEQ VnoTank ; (unconditional branch)
VdoTank LDA HIRES,X ; Else, load the appropriate byte.
VnoTank STA WSYNC ; ----END OF ONE LINE----
STA GRP0 ; Just for player 0.
;
; The infamous Combat Stack Trick:
;
; Keep in mind that at this point, the stack pointer
; is set to the missile registers, and the "zero-result"
; bit of the P register is the same at the bit ENAM0/1
; looks at.
;
LDA MissileY1
EOR ScanLine
AND #$FE
PHP ; This turns the missle 1 on/off
LDA MissileY0
EOR ScanLine
AND #$FE
PHP ; This turns the missle 0 on/off
;
; We've got the missile taken care of.
; Now let's see which line of the playfield to draw.
;
LDA ScanLine
BPL VvRefl ; If on the bottom half of the screen,
EOR #$F8 ; reverse direction so we can mirror.
VvRefl CMP #$20
BCC VfDone ; Branch if at bottom.
LSR
LSR
LSR ; Divide by eight,
TAY ; and stow it in the Y-register.
;
; By now, the electron beam is already at the next
; scanline, so we don't have to do a STA WSYNC.
;
; This yields which line of Tank 1 to draw.
;
VfDone LDA TankY1 ; TankY1 is other player's position.
SEC
SBC ScanLine ; A=TankY1 - ScanLine
INC ScanLine ; Increment the loop.
NOP
ORA #$01 ; Add bit 0, force odd number.
TAX
;
AND #$F0 ; There are only sixteen bytes of
BEQ VdoT1 ; sprite memory, so...
LDA #$00 ; If tank is not ready, blank it.
BEQ VnoT1
VdoT1 LDA HIRES,X ; Else, draw the tank
VnoT1 BIT PF_PONG
STA GRP1
BMI VnoPF ; If PF_PONG bit 7 set, don't write PF
LDA (LORES),Y ; (this means game variation has blank
STA PF0 ; background)
LDA (LORES+2),Y
STA PF1
LDA (LORES+4),Y
STA PF2
VnoPF INC ScanLine ; One more up in the loop.
LDA ScanLine
EOR #$EC ; When we've reached the $ECth line,
BNE Vfield ; we've had enough.
LDX TMPSTK ; Restore stack pointer, which is
TXS ; is used for calls in main game loop
STA ENAM0 ; Clear a bunch of registers.
STA ENAM1
STA GRP0
STA GRP1
STA GRP0 ; In case GRP0 isn't COMPLETELY zeroed.
STA PF0
STA PF1
STA PF2
RTS
; ------------------------------------------------------------
;
; Game Select Game Reset ChecK
;
; Executed immediately after VCNTRL, this subroutine parses all
; the console switches.
;
GSGRCK ;
LDA SWCHB ; Start/Reset button....
LSR ; Shove bit 0 into carry flag,
BCS NoNewGM ; and if it's pushed...
;
; Start a new game.
;
LDA #$0F
STA SHOWSCR ; Show right score.
LDA #$FF ; Set all bits
STA GameOn ; in GameOn.
LDA #$80
STA GameTimer ; and bit 7 of GameTimer (this is not too
; significant, as GameTimer rollover is
; only checked if GameOn<>$00)
LDX #$E6
JSR ClearMem ; zero out $89 thru $A2
BEQ ResetField ; Unconditional branch
;
NoNewGM LDY #$02 ; Assume score to be drawn
LDA GameTimer ; If game in play (GameOn=$FF) AND
AND GameOn ; GameTimer < 7/8 finished @ $F0,
CMP #$F0 ; draw the score unconditionally.
BCC SCdrawn
LDA CLOCK ; CLOCK used to flash score near end
AND #$30 ; of play, note the peripheral synchronization
BNE SCdrawn ; with GameTimer's timing of the game, which
; always ends when CLOCK & $3F = 0. CLOCK
; is used here because the score blink
; off duty cycle is a too quick for
; GameTimer to handle, being about 1/3 sec.
LDY #$0E ; Set this for no score
SCdrawn STY KLskip ; where the Kernal will find it
LDA CLOCK
AND #$3F ; CLOCK also used to slow debounce reset
BNE ChkSel
;
; GameTimer is incremented and SelDbnce reset when
; CLOCK & $3F = 0. This occurs 1 frame out of 64 or
; about once/second. Thus the game is 128*64 frames
; or about 2 minutes long.
;
STA SelDbnce ; Reset Select Debounce Flag. This is
; what keeps incrementing the selection
; if you hold Select down for a long time.
INC GameTimer ; increment the Main Game ~1-sec Timer.
BNE ChkSel ; if GameTimer rolls over,
STA GameOn ; zero GameOn -- game over
;
ChkSel LDA SWCHB ; Select button???
AND #$02
BEQ SelDown
STA SelDbnce ; Set flag: Sel has not been down
BNE CS_RTS ; Unconditional branch
;
SelDown BIT SelDbnce ; If Sel has been down,
BMI CS_RTS ; don't select a new game.
;
INC BINvar ; SELECT: Go to next game.
ClrGam LDX #$DF ; Clear data from current game ($82-$A2)
ClrGRST JSR ClearMem
LDA #$FF
STA SelDbnce ; Set flag: Sel has been down.
LDY BINvar
LDA VARMAP,Y ; Get feature bits for this variation.
STA GAMVAR
EOR #$FF ; #$FF signifies end of variations
BNE SelGO ; Not at end yet, set up new game
LDX #$DD ; Clear $80-$A2; resets BINvar, BCDvar
BNE ClrGRST ; so we start over. BNE is unconditional.
;
SelGO LDA BCDvar ; Since we have incremented BINvar, we
SED ; must increment BCDvar in BCD to keep
CLC ; it in sync. Note BCDvar is actually
ADC #1 ; BinVar+1, since it's incremented when
STA BCDvar ; we reset but don't increment BINvar.
STA SCORE ; Display variation as score 0
CLD
BIT GAMVAR ; GAMSHP was reset at ClrGam...
BPL ResetField ; if this is a plane game,
INC GAMSHP ; increase GAMSHP.
BVC ResetField ; if this is a jet game,
INC GAMSHP ; increase GAMSHP further still.
;
; Branches here when game is started, too.
;
ResetField
JSR InitPF
;
; Assuming plane game for now, we set the right player
; at a slightly higher position than the left player,
; and the position of the right player is irrelevant.
;
LDA #50
STA TankY1
LDA #134
STA TankY0
BIT GAMVAR ; Check to see if it is a tank game.
BMI CS_RTS ; Nope, bail.
; It is a tank game, so
STA TankY1 ; Right tank has same Y value,
STA RESP1 ; and tank is at opposite side.
LDA #$08
STA DIRECTN+1 ; and right player faces left.
LDA #$20
STA HMP0
STA HMP1
STA WSYNC
STA HMOVE
CS_RTS RTS
; ------------------------------------------------------------
;
; SCoRe OffseT
;
; Convert BCD scores to score pattern offset.
; This involves the horrible, horrible implications
; involved in multiplying by five.
;
; If it weren't for the geniuses at NMOS using BCD,
; this routine would be a nightmare.
;
; This routine starts with Player 1, writes bytes 1 & 3 of
; the table, then decrements X to write bytes 0 & 2 for P0.
;
SCROT LDX #$01
SCROT0 LDA SCORE,X
AND #$0F ; Lo nibble
STA TEMP
ASL ; *2
ASL ; *4
CLC
ADC TEMP ; + original * 1 = original * 5
STA SCROFF,X
LDA SCORE,X
AND #$F0 ; Repeat for hi nibble. Starts *16
LSR ; *8
LSR ; *4
STA TEMP
LSR ; *2
LSR ; *1
CLC
ADC TEMP ; + (*4) = original * 5
STA SCROFF+2,X
DEX
BPL SCROT0 ;Decrement & repeat once for P0
RTS
; ------------------------------------------------------------
;
; SeTuP Motion for PLayers
;
; Apply horizontal and vertical motion
;
STPMPL BIT GUIDED
BVC STPnoMG ; Branch if not machine gun game.
LDA #$30 ; (Machine gun bullets move faster)
BPL STPMG ; Unconditional JMP.
STPnoMG LDA #$20
STPMG STA XoffBase ; $30=machine gun, $20=normal
LDX #$03
JSR STPM ; Do the honors for X=3, Missile 1
DEX
JSR STPM ; Now X=2, M0
;
DEX ; Now X=1, P1; we will DEX and loop
STPnext LDA FwdTimer,X ; back to run this code block again
AND #$08 ; with X=0 for P0.
LSR ; (to 4) This bit on means FwdTimer has
LSR ; (to 2) run half of the FwdTimer period
; ($F0 to $FF and roll)
; This bit will index MVadjA or MVadjB
STX TEMP1 ; Player # --> TEMP1
CLC
ADC TEMP1
TAY ; Player # + FwdTimer half done*2 --> Y
LDA MVadjA,Y ; And retrieve MVadjA or MVadjB via Y
;
SEC ; assume bit 7 on
BMI STP7set ; OK, it is
CLC ; whoops, backtrack
STP7set ROL ; carry=bit 7, now ROL; net effect is to
; ; rotate left inserting duplicate MSB
STA MVadjA,Y ; instead of original Carry, and save it
BCC STPnoV ; Skip next code block if bit wasn't 1
;
LDA MPace,X ; Tweak velocity by changing XoffBase
AND #$01 ; but only every other time we get here
ASL
ASL
ASL
ASL
STA XoffBase ; XoffBase=$0 or $10 via (MPace & 1) << 4
JSR STPM ; Note this is where we INC MPace
STPnoV
DEX ; Move to _previous_ player.
BEQ STPnext ; Stop if about to do player -1. :)
RTS
;
; This routine will move both tanks and missiles.
; Special cases are made for missiles, which are
; otherwise treated as players 2 and 3.
;
; It doesn't change the X register, but it does
; utilize it.
;
STPM INC MPace,X
LDA DIRECTN,X
AND #$0F
CLC
ADC XoffBase ; Pick table offset by game condition
TAY
LDA Xoffsets,Y ; X-offset by orientation.
STA XOFFS ; Store the default HMPV code.
BIT PF_PONG
BVS STPgo ; Branch if (fast) Pong missiles
LDA DIRECTN,X
SEC
SBC #$02 ; If motion is near X or Y axis,
AND #$03
BNE STPgo ; don't apply delay
LDA MPace,X ; but if very diagonal, slow a bit by
AND #$03 ; moving only 3 of every 4 frames
BNE STPgo ;
LDA #$08 ; HMPV for no motion X or Y
STA XOFFS ; no motion this frame
STPgo LDA XOFFS
;
; (This falls through, but PhMove is also called from elsewhere)
;
; Physically move a tank (0,1) or missile (2,3)
; according to the HMPV code in A
;
PhMove STA HMP0,X ; Hi nibble sets HMPx horizontal motion
AND #$0F ; Lo nibble...
SEC
SBC #$08 ; less 8 for 2's complement 4-bit...
STA $D4 ; (save this offset)
CLC
ADC TankY0,X ; add to Y-coordinate
BIT GAMVAR
BMI PhNoTank ; Branch if a plane game.
CPX #$02
BCS PhNoWrap ; Branch if moving a tank player
PhNoTank
CMP #$DB ; Perform vertical wrap-around
BCS PhNoWrapTop ; branch if over top (wrap)
CMP #$25
BCS PhNoWrap ; branch if over bottom (no wrap)
PhNoWrapTop
LDA #$D9 ; Assume we wrapped bottom to top
BIT $D4 ; Meaning offset was negative
BMI PhNoWrap
LDA #$28 ; Otherwise, we wrapped top to bottom
PhNoWrap
STA TankY0,X ; The tank/missile is moved here.
CPX #$02
BCS PhnoVD ; Skip if moving a missile.
STA VDELP0,X ; Vertical Delay Player X...
PhnoVD RTS
; ------------------------------------------------------------
;
; ROTate player sprites
;
; This subroutine sets up the sprite data for each player by copying
; them into sixteen bytes of RAM.
;
; The X-register starts at $0E plus player number and goes down by two
; each time through the loop, until it hits zero. This way, after calling
; this subroutine twice, every even-numbered byte contains the left player
; shape, and every odd-numbered byte contains the right player shape. Since
; each player is updated every two scanlines, this saves us some math.
;
; Only the first 180 degrees of rotation has been drawn into ROM. In the
; case of the other 180 degrees, this subroutine renders a flipped version
; by doing the following:
;
; 1. It sets the TIA's reflection flag for that player, taking care of
; the horizontal aspect rather easily.
;
; 2. It copies the bytes into memory last-to-first instead of first-to-
; last, using the carry bit as a flag for which to do.
;
ROT LDA #$01 ; The LO byte of CLOCK used to
AND CLOCK ; select alternate players on
TAX ; alternate frames
LDA DIRECTN,X
STA REFP0,X ; Step 1 taken care of.
AND #$0F
TAY ; Y = DIRECTN[X] & 0x0F.
BIT GUIDED
BPL ROTnoGM ; If it's a guided missile game,
STY DIRECTN+2,X ; copy player bearings to missile
ROTnoGM TXA ; X ^= $0E,
EOR #$0E
TAX
TYA
ASL
ASL
ASL
CMP #$3F ; And so step 2 begins...
CLC
BMI ROTnoFlip ; Branch if <180 deg.
SEC
EOR #$47 ;The EOR sets bits 0-2, and clears bit 4
; to subtract 180 degrees from the memory
; pointer, too.
ROTnoFlip TAY
;
;Put all the shapes where they ought to be.
;
ROTnext LDA (SHAPES),Y
STA HIRES,X
BCC ROTinc
DEY ; Decrement instead of increment
DEY ; plus cancel the upcoming INY.
ROTinc INY ; More of step 2.
DEX
DEX ; X-=2.
BPL ROTnext ; Do for both, 1 then 0 then stop.
RTS
; ------------------------------------------------------------
;
; CHecK joystick SWitches
;
; If we are in the interval while a loser's tank is stirring,
; he stirs and the winner freezes or goes forward. Otherwise,
; parse the joystick inputs and move the tanks appropriately.
;
CHKSW LDA StirTimer ; We must dec StirTimer by 2
SEC ; since bit 0 is identity of
SBC #$02 ; the stirree
BCC NoStir ; If no tank is exploding,
; parse joystick instead.
STA StirTimer
CMP #$02
BCC StirRTS ; RTS if tank has
; just finished exploding.
AND #$01 ; Stir the LOSER's tank.
TAX
;One of these is the tank's bearings.
INC DIRECTN,X
LDA XColor0,X
STA Color0,X
LDA StirTimer
CMP #$F7 ; We only rush the tank for a
BCC NoStirRush ; small part of the stir interval
JSR RushTank
NoStirRush
LDA StirTimer
BPL StirRTS ; Don't start decrementing
; volume until halfway through.
LSR
LSR ; StirTimer scales audio volume
LSR ;
BoomSnd STA AUDV0,X ; Set explosion sound to volume in A
LDA #$08 ; and pitch according to player X
STA AUDC0,X
LDA AudPitch,X
STA AUDF0,X
StirRTS RTS
;
; Process joysticks.
;
NoStir LDX #$01 ; Start with P1
LDA SWCHB ; Console switches.
STA DIFSWCH ; Store switches. Before we return
; via DEX to do P0, we will ASL this
; byte so difficulty bit for working
; player appears in bit 7.
LDA SWCHA ; Joysticks. Before we return via
; DEX to do P0, we will reload and
; LSR this 4 times so controls for
; the working player appear in the
NextPJS BIT GameOn ; LO nibble.
BMI NoFreezeJS ; Branch if game on (via bit 7).
LDA #$FF ; Freeze all joystick movement.
NoFreezeJS
EOR #$FF ; Reverse all bits
AND #$0F ; Keep low four bits (working player)
;
; At this point, the joystick's switches are in
; the A-register, with a bit set wherever the
; joystick is pointed.
;
; Bit 0 = up Bit 1 = down
; Bit 2 = left Bit 3 = right
;
STA TEMP
LDY GAMSHP
LDA CtrlBase,Y ; Account for two-dimensional array
CLC
ADC TEMP
TAY
LDA CTRLTBL,Y
AND #$0F ; Get rotation from CTRLTBL.
STA TEMP1 ; Stash it here
BEQ NoTurn ; Branch if no turn.
CMP LastTurn,X ; If new turn is different direction
BNE TurnReset ; from last turn, reset the...
NoTurn DEC TurnTimer,X ; ...turn pacing delay and...
BNE DoFwdMotion ; ...inhibit turn this interval.
TurnReset ; We do turn-wait counts even when
STA LastTurn,X ; we aren't turning, for consistency
LDA #$0F ; Initial countdown value to delay
STA TurnTimer,X ; 22.5-degree turns
;
LDA TEMP1 ; Retrieve rotation code
CLC ; Turn +/- 22.5-degrees or zero,
ADC DIRECTN,X ; per DIRECTN
STA DIRECTN,X
;
; For reference, we do get here every frame (~60Hz) during game.
; COMBAT does not change player speed instantaneously; it has
; an elaborate momentum system, which is just barely noticeable
; in the course of game play.
;
DoFwdMotion
INC FwdTimer,X ; Inc FwdTImer and if it doesn't
BMI SkipFwdCtrl ; roll over, don't acknowledge velocity
LDA CTRLTBL,Y ; changes yet
LSR
LSR
LSR
LSR ; Get forward velocity from CTRLTBL
;
; This is the desired _final_ velocity of the player. If
; it is different from the player's _current_ velocity, we
; won't reach it until the end of the FwdTimer period.
;
BIT DIFSWCH
BMI FwdPro ; Branch if difficulty="Pro"
; (reduces A and branches back to FwdNorm)
FwdNorm STA Vtemp,X ; Stash velocity in Vtemp
ASL ; Multiply by two
TAY ; Stash in Y.
LDA MVtable,Y ; Indexed by velocity * 2, even
STA MVadjA,X ; V+MVtable goes to MVadjA+X
INY ; Why not LDA MVtable+1,Y?
LDA MVtable,Y
STA MVadjB,X ; odd V+MVtable goes to MVadjB+X
LDA #$F0 ; Initialize FwdTimer
STA FwdTimer,X ; (Counts up to $00 before fwd
; ; motion change is final)
SkipFwdCtrl
JSR ChkVM
LDA SWCHA ; Joysticks..
LSR
LSR
LSR
LSR ; Keep bottom four bits (Left Player)
ASL DIFSWCH ; Use other difficulty switch.
DEX
BEQ NextPJS
RTS
;
FwdPro SEC ; Velocity is in A
SBC GAMSHP ; subtract 0/tank, 1/biplane, 2/jet
BPL FwdNorm ; Not obvious, but this is unconditional
; ------------------------------------------------------------
;
; Check invisible tank visibility, missile lifetime expiration;
; read trigger if appropriate and launch a new missile
;
ChkVM LDA GAMVAR
BMI NoInvis ; Branch if plane game
AND #$01 ; check also for bit 0 (invisible).
BEQ NoInvis
LDA ColorBK ; Make invisible tank invisible
STA Color0,X
NoInvis LDA MisLife,X
BEQ RdTrig ; Branch if no missile in flight
LDA XColor0,X ; Reset tank to normal color
STA Color0,X
LDA MisLife,X ; How long does missile have to go?
CMP #$07
BCC MisKill ; Branch to go ahead and kill it
BIT DIFSWCH ; Check difficulty
BPL MisEZ ; If game is hard,
CMP #$1C ; Compare mislife to this
BCC MisKill ; and expire it early.
MisEZ CMP #$30 ; If MisLife < 30 do motor
BCC MotMis ; do motor, not shot sound
CMP #$37 ; If MisLife >= 37
BCS MisFly ; do sliding boom sound (shot)
BIT GUIDED
BVC MisFly ; Branch if machine gun.
MisKill LDA #$00 ; Reset missile's life, killing it
STA MisLife,X
LDA #$FF ; And reset its position
ResRTS STA RESMP0,X ; to player.
RTS
;
; If game in progress, Read the trigger
;
RdTrig BIT GameOn ; Branch if no game on
BPL RDnoGame ; (via bit 7 being clear)
LDA INPT4,X ; Read Input (Trigger) X.
BPL Launch ; unconditional branch -- Launch missile
;
RDnoGame
JSR MOTORS
JMP MisKill
MotMis JSR MOTORS
JMP MisAge
MisFly LDA AltSnd,X
BEQ MisBoom
JSR MOTORS
LDA #$30
STA MisLife,X
JMP MisAge
;
MisBoom LDA MisLife,X
JSR BoomSnd
MisAge LDA CLOCK ; Missile aging rate depends on type
AND #$03
BEQ MisDec ; Only do this test 3/4 of the time
BIT BILLIARD
BVS MisDSkp ; branch if Billiard (must bounce before hit)
BIT PF_PONG
BVC BMisDec ; branch if not Pong game (PF_PONG bit 6)
AND #$01 ; Upshot of this is, in non-billiard Pong
BNE MisDSkp ; game, missiles last about twice as long
MisDec DEC MisLife,X ; I'm getting older!
MisDSkp LDA #$00
BEQ ResRTS ; Unconditional -- DO NOT Reset missile to tank
; ; (we'd need $02 on to do that) but RTS
;
; Launch a missile
;
Launch LDA #$3F
STA MisLife,X ; Init MisLife to $3F
SEC
LDA TankY0,X ; Copy Y-position... Tank Y-position points
; to top of sprite, but missile is launched
SBC #$06 ; from its center 6 scanlines down.
STA MissileY0,X
LDA DIRECTN,X ; Copy player bearing to missile.
STA DIRECTN+2,X
LDA #$1F
STA BounceCount,X ; Init BounceCount to $1F
LDA #$00
STA MxPFcount,X ; Reset MxPFcount
JMP MisFly ; Proceed w/missile in flight
; ------------------------------------------------------------
;
; This routine generates engine or Pong sound as appropriate.
;
MOTORS LDA AltSnd,X
BEQ DOMOTOR
; Pong sound.
LDA #$04
STA AUDC0,X
LDA #$07
STA AUDV0,X
LDA BounceCount,X
STA AUDF0,X
RTS
; Engine sound.
DOMOTOR LDY GAMSHP
LDA SNDV,Y
AND GameOn ; Kills sound if no game on by ANDing
STA AUDV0,X ; volume value w/$00 no-game value
LDA SNDC,Y
STA AUDC0,X
CLC
LDA #$00
MOPIT0 DEY ; This loop sets start value for sound
BMI MOPIT1 ; pitch based on GAMSHP in Y (tank,
ADC #$0C ; biplane, or jet)
BPL MOPIT0
MOPIT1 ADC Vtemp,X ; Use saved velocity to adjust
TAY ; sound pitch via SNDP table
TXA
ASL
ADC SNDP,Y
STA AUDF0,X
RTS
; ------------------------------------------------------------
;
; COLISion check
;
; 150 lines of angel-hair spaghetti code
;
; Check to see whether, during all that drawing,
; a missile hit one of the tanks, or a tank hit
; the wall or the other tank, and if so let
; the consequences fall.
;
COLIS LDX #$01 ; Do first for P1, DEX, P0, etc.
COLnext LDA CXM0P,X
BPL COLnoHit ; No missile collision
BIT BILLIARD
BVC COLDET ; Not Billiard game, go ahead & do it
LDA BounceCount,X
CMP #$1F
BEQ COLnoHit ; Billiard 1st bounce not satisfied
;
; A touch, a touch! I do confess.
;
COLDET INC DIRECTN,X ; Turn both tanks 22.5 degrees.
INC DIRECTN+2,X
;
; Increase player's score. A simple INC SCORE,X
; won't do because we're doing it in BCD.
;
SED
LDA SCORE,X
CLC
ADC #$01
STA SCORE,X
CLD
TXA
CLC
ADC #$FD
STA StirTimer
;
; Now StirTimer contains loser's ID in bit 0,
; victor's ID in bit 1, and set bits 2-7.
; Bit 1 ID is never used, and just creates a
; slight, unnoticeable difference in stir time.
;
LDA #$FF
STA RESMP0 ; Reset both missiles.
STA RESMP1
LDA #$00
STA AUDV0,X ; Turn off the victor's engine.
STA MisLife ; clear MisLife (no missile)
STA $9A ; and 9A.
RTS
;
; We didn't just end the game, so we deal with some
; sound and bounce logic
;
COLnoHit
BIT GAMVAR
BPL COLTNK ; Branch if a tank game.
JMP COLPD ; Skip this code if NOT a tank game
COLTNK LDA AltSnd,X
BEQ COLnoAlt
CMP #$04 ; See if alt sound has played out
INC AltSnd,X ; Increment if it has not
BCC COLnoAlt
LDA #$00 ; if played out, reset to 0 "no alt sound"
STA AltSnd,X
COLnoAlt
LDA CXM0FB,X ; Missile collision with playfield?
BMI COLMPF ; If true, bounce or obliterate...
LDA #$00
STA MxPFcount,X ; ...else clear MxPFcount
JMP COLTCK
;
COLMPF BIT PF_PONG
BVC COLMISX ; Branch if not Pong (bit 6 clear)
;
LDA MxPFcount,X ; It's Pong, so we bounce
BNE COLMPFX ; Branch if collision is already ongoing
INC AltSnd,X ; NEW COLLISION, set alt sound flag
DEC BounceCount,X
LDA DIRECTN+2,X ; First try at reflecting
STA OldMisDir,X ; Stash current missile heading
EOR #$FF ; reverse heading by complement,
STA DIRECTN+2,X ; then increment=additive inverse
INC DIRECTN+2,X ; same as subtracting from zero
LDA DIRECTN+2,X ; check new heading
AND #$03 ; See if it's moving exactly N,S,E, or W
BNE COLXY0
INC DIRECTN+2,X ; and add 22.5 degrees if so
COLXY0 JMP COLMPFdone
;
; I always wondered how this works. Stella does not know the
; orientation of the wall that was hit, so this is how it
; reflects:
;
; Immediately after a collision, it tries a vertical reflection,
; jiggering the result so that it won't be exactly vertical or
; exactly horizontal.
;
; If this is the next frame (MxPFcount=$01) that failed, so
; we reverse direction 180 degrees to turn it into a horizontal
; reflection.
;
; On MxPfcount=$02 we take no action, since the missile may need
; the cycle to re-emerge from a wall.
;
; On MxPFcount=$03 or higher, we retrieve the original heading and
; turn it 180 degrees, assuming a corner reflection. And we keep
; applying this same bearing until it's out of the #*%@ wall.
;
COLMPFX CMP #$01 ; branch if
BEQ Rev180 ; exactly 1 previous collision frame
CMP #$03 ; branch if
BCC COLMPFdone ; less than 3 collision frames
BNE COLMPFdone ; or more than three
LDA OldMisDir,X ; retrieve pre-bounce missile heading
JMP Bump180 ; and reverse it 180 degrees
;
; Exactly 1 previous collision: Do a 180-degree reversal, meaning
; 90 degrees the *other* way from our initial course.
;
Rev180 LDA DIRECTN+2,X ; Here to add 180 degrees
Bump180 CLC ; Here to add A to missile dir
ADC #$08
STA DIRECTN+2,X
JMP COLMPFdone
;
COLMISX LDA #$01 ; If it's not Pong, we come here and
STA MisLife,X ; set the missile's life to 1 to kill it.
;
COLMPFdone ; When we're done, increase collision
INC MxPFcount,X ; frame count & move on.
;
; Check for tank collisions
;
COLTCK LDA CXP0FB,X
BMI COLTW ; check if tank collided with a wall.
LDA CXPPMM ; check for a tank-tank collision.
BPL COLTCLR ; branch if NO tank collisions at all
COLTW LDA StirTimer ; See if we are stirring a tank
CMP #$02
BCC COLTnk1 ; No, branch & block
JSR RushTank ; We are stirring, send it scooting
;
COLTCLR LDA #$03 ; No tank collision, reset counter
STA COLcount,X
BNE COLPD ; unconditional branch, player done
;
COLTnk1 DEC COLcount,X ; Tank colliding
BMI COLbonk ; COLcount rolled, ignore collision
LDA Vtemp,X
BEQ COLPD ; No boink if velocity=0, player done
BNE COLreverse ; else skip INC, needed for elsewhere
;
COLbonk INC DIRECTN,X ; Jigger direction 22.5 for disorientation
COLreverse
LDA DIRECTN,X
CLC
ADC #$08 ; Add 180 degrees to direction
JSR BumpTank ; to bump tank back
;
; COLIS Player Done
;
COLPD DEX
BMI COLrts ;Return if X<0.
JMP COLnext ;Else do the other player
COLrts RTS
;
; Bump the tank in the direction
; the other player's missile is moving
;
RushTank
TXA
EOR #$01 ; Get OTHER player #
TAY ; in Y
LDA DIRECTN+2,Y ; OTHER player Missile's Direction
;
; Bump the tank in the direction of a standard
; 22.5-degree bearing code
;
BumpTank
AND #$0F
TAY
LDA HDGTBL,Y ;Nove
JSR PhMove ;Move object in that direction.
LDA #$00
STA MVadjA,X
STA MVadjB,X
STA FwdTimer,X ;Stop it dead in its tracks....
LDA XColor0,X
STA Color0,X
RTS
; ------------------------------------------------------------
;
; This was probably a toplevel routine early in development,
; but ended up getting called from GSGRCK. It sets everything
; up to draw the playfield based on the current game selection.
;
InitPF LDX GAMSHP ; 0=tank, 1=biplane, 2=jet
LDA SPRLO,X ; Set up base pointer to all
STA SHAPES ; sprite shapes which will
LDA SPRHI,X ; be used in this game.
STA SHAPES+1
;
LDA GAMVAR ; Now set up PF_PONG and playfield type
LSR
LSR
AND #$03 ; bits 0,1=maze (playfield) type.
TAX ; send it to X.
LDA GAMVAR
BPL IFgo ; Branch not plane game, PF_PONG=GAMVAR
AND #$08 ; Test for clouds
BEQ IF80 ; Branch if no clouds
LDX #$03 ; change "maze type" in X to 3 ("clouds")
BPL IFskip ; Unconditional skip to next test,
; leaving PF_PONG set to 0.
IF80 LDA #$80 ; Change PF_PONG to #$80
; (enable playfield, no Pong)
IFgo STA PF_PONG ; store GAMVAR or #$80 in PF_PONG.
IFskip LDA GAMVAR ; Next test..
ASL
ASL ; Do this again....
BIT GAMVAR
BMI IFnoPlane ; Branch if a plane game.
STA WSYNC ; This MUST be something that dropped
; through the cracks, there is NO reason!
STA BILLIARD ; Store GAMVAR*4 in 84 (bit 6 = Billiard Hit)
AND #$80 ; IF it's a tank game.
IFnoPlane
STA GUIDED ; set guided missile flag.
;
; GUIDED is ZERO if a tank game
; it is negative if a guided missile game,
; it is overflowed if a machine gun game.
; (Inapplicable in tank games, hence the
; previous branch trick)
;
LDA #>PF0_0 ; Store page of first PF map
STA LORES+1 ; as high order byte
STA LORES+3 ; for all of these pointers,
STA LORES+5 ; 'cause that's where it is.
;
; Store the proper offsets for each column of
; playfield from the vectors given
;
LDA PLFPNT,X
STA RESP0 ; Reset player 0 while we're at it.
STA LORES
LDA PLFPNT+4,X
STA LORES+2
LDA PLFPNT+8,X
STA LORES+4
RTS
; ------------------------------------------------------------
;
; LoaD STELla
;
; Set the number and size of player sprites, color, and
; disable the joysticks if game is not in play
;
LDSTEL LDA GAMVAR
AND #$87
BMI LDmult
;
; If bit 7 is set, we are playing with one or more
; planes. If not, well, we can only have one tank,
; so...
;
LDA #$00
LDmult ASL
TAX
LDA WIDTHS,X ; The TIA's NUSIZ registers make
STA NUSIZ0 ; it as easy to play with two or
LDA WIDTHS+1,X ; three planes as it is for one
STA NUSIZ1 ; freakin' huge bomber.
LDA GAMVAR
AND #$C0
LSR
LSR
LSR
LSR ; Our hardware is now in bits 3 and 2.
TAY ; Of the Y-register.
;
; Render joysticks immobile if game not in play, and
; select player and field colors according to Y
;
LDA GameOn ; Enable joysticks via bit 1
STA SWCHB ; of $FF game-on value
EOR #$FF ; now $FF=no game, $00=game on
AND GameTimer ; Cycle tank colors only when NO
STA TEMP1 ; game on (attract mode)
LDX #$FF
LDA SWCHB
AND #$08 ; Color/BW switch
BNE LDcolor ; Branch if set to Color
LDY #$10 ; Force B&W colors
LDX #$0F
LDcolor STX TEMP
LDX #$03 ; We loop 3 times to get 4 values
LDcol0 LDA ColorTbl,Y
EOR TEMP1 ; Apply color-cycle if no game on
AND TEMP ; Apply B&W massage
STA COLUP0,X ; Color the real item.
STA Color0,X ; Color the virtual item. This can
; be changd, e.g. invisible tanks
STA XColor0,X ; Color the deep virtual item. This
; is used to restore ColorX.
INY
DEX
BPL LDcol0
RTS
;
; ------------------------------------------------------------
;
; Zero out zero-page memory starting with ($A3+X) MOD $100,
; through $A2 wrapping around at $100.
;
; Calling with:
; X=$5D will clear $00-$A2
; X=$DD will clear $80-$A2
; X=$DF will clear $82-$A2
; X=$E6 will clear $89-$A2
;
; Returns with zero bit set.
;
ClearMem
LDA #$00
ClrLoop INX
STA $A2,X
BNE ClrLoop ;Continue until X rolls over.
RTS
; Patterns for numbers
;
NUMBERS .byte $0E ; | XXX | $F5C5 Leading zero is not drawn
.byte $0A ; | X X | $F5C6 because it's never used.
.byte $0A ; | X X | $F5C7
.byte $0A ; | X X | $F5C8
.byte $0E ; | XXX | $F5C9
.byte $22 ; | X X | $F5CA
.byte $22 ; | X X | $F5CB
.byte $22 ; | X X | $F5CC
.byte $22 ; | X X | $F5CD
.byte $22 ; | X X | $F5CE
.byte $EE ; |XXX XXX | $F5CF
.byte $22 ; | X X | $F5D0
.byte $EE ; |XXX XXX | $F5D1
.byte $88 ; |X X | $F5D2
.byte $EE ; |XXX XXX | $F5D3
.byte $EE ; |XXX XXX | $F5D4
.byte $22 ; | X X | $F5D5
.byte $66 ; | XX XX | $F5D6
.byte $22 ; | X X | $F5D7
.byte $EE ; |XXX XXX | $F5D8
.byte $AA ; |X X X X | $F5D9
.byte $AA ; |X X X X | $F5DA
.byte $EE ; |XXX XXX | $F5DB
.byte $22 ; | X X | $F5DC
.byte $22 ; | X X | $F5DD
.byte $EE ; |XXX XXX | $F5DE
.byte $88 ; |X X | $F5DF
.byte $EE ; |XXX XXX | $F5E0
.byte $22 ; | X X | $F5E1
.byte $EE ; |XXX XXX | $F5E2
.byte $EE ; |XXX XXX | $F5E3
.byte $88 ; |X X | $F5E4
.byte $EE ; |XXX XXX | $F5E5
.byte $AA ; |X X X X | $F5E6
.byte $EE ; |XXX XXX | $F5E7
.byte $EE ; |XXX XXX | $F5E8
.byte $22 ; | X X | $F5E9
.byte $22 ; | X X | $F5EA
.byte $22 ; | X X | $F5EB
.byte $22 ; | X X | $F5EC
.byte $EE ; |XXX XXX | $F5ED
.byte $AA ; |X X X X | $F5EE
.byte $EE ; |XXX XXX | $F5EF
.byte $AA ; |X X X X | $F5F0
.byte $EE ; |XXX XXX | $F5F1
.byte $EE ; |XXX XXX | $F5F2
.byte $AA ; |X X X X | $F5F3
.byte $EE ; |XXX XXX | $F5F4
.byte $22 ; | X X | $F5F5
.byte $EE ; |XXX XXX | $F5F6
;
; Horizontal and vertical offsets for movement by orientation.
; Basic table is $10 bytes long (22.5-degree increments), but
; XoffBase is added to it to alter for game options. High
; nibble is raw HMPx value for horizontal offset, low nibble
; is vertical offset in scan lines.
;
Xoffsets
.BYTE $F8 ,$F7 ,$F6 ,$06 ;XoffBase=0
.BYTE $06 ,$06 ,$16 ,$17
.BYTE $18 ,$19 ,$1A ,$0A
.BYTE $0A ,$0A ,$FA ,$F9
.BYTE $F8 ,$F7 ,$F6 ,$F6 ;XoffBase=$10
.BYTE $06 ,$16 ,$16 ,$17
.BYTE $18 ,$19 ,$1A ,$1A
.BYTE $0A ,$FA ,$FA ,$F9
.BYTE $E8 ,$E6 ,$E4 ,$F4 ;XoffBase=$20
.BYTE $04 ,$14 ,$24 ,$26 ;normal missiles
.BYTE $28 ,$2A ,$2C ,$1C
.BYTE $0C ,$FC ,$EC ,$EA
; This Xoffsets entry is also used directly for "bumping"
; a player after a hit or to back away from playfield collision
;
HDGTBL .BYTE $C8 ,$C4 ,$C0 ,$E0 ;XoffBase=$30
.BYTE $00 ,$20 ,$40 ,$44 ;machine guns, "bump"
.BYTE $48 ,$4C ,$4F ,$2F
.BYTE $0F ,$EF ,$CF ,$CC
;
; Player velocity momentum adjustments. Table of two-byte
; entries, indexed by player's desired final velocity. Even
; locations go to MVadjA to be applied during the first half of
; the FwdTimer cycle, and odd locations goe to MVadjB to be
; applied during the second half.
;
; During each half, the byte is rotated left one bit; if
; the bit which emerges is 1, XoffBase is tweaked by $10
; to adjust the velocity for that frame only. Since FwdTimer
; goes through 16 cycles or 2 8-bit halves in its course from,
; $F0 to $00, this gives us a bitwise "adjust this frame" flag
; for each frame in the course of FwdTimer's run. This is
; used to obscure the suddenness of transition from one
; velocity to another.
;
; The adjustment is only done once for each two ON bits
; since the MPace 1 bit is used for the adjustment, and
; MPace is INCed in the same code block that does the
; tweak. The tweak consists of replacing whatever XoffBase
; the final velocity calls for with $10, an intermediate value.
;
MVtable .BYTE $00 ,$00
.BYTE $80 ,$80
.BYTE $84 ,$20
.BYTE $88 ,$88
.BYTE $92 ,$48
.BYTE $A4 ,$A4
.BYTE $A9 ,$52
.BYTE $AA ,$AA
.BYTE $D5 ,$AA
.BYTE $DA ,$DA
.BYTE $DB ,$6D
.BYTE $EE ,$EE
;
; These are all the sprite shapes.
; The most I suspect any of you will do is
; modify these. And/or the number shapes.
;
TankShape
.byte $00 ; | | $F64F
.byte $FC ; |XXXXXX | $F650
.byte $FC ; |XXXXXX | $F651
.byte $38 ; | XXX | $F652
.byte $3F ; | XXXXXX| $F653
.byte $38 ; | XXX | $F654
.byte $FC ; |XXXXXX | $F655
.byte $FC ; |XXXXXX | $F656
.byte $1C ; | XXX | $F657
.byte $78 ; | XXXX | $F658
.byte $FB ; |XXXXX XX| $F659
.byte $7C ; | XXXXX | $F65A
.byte $1C ; | XXX | $F65B
.byte $1F ; | XXXXX| $F65C
.byte $3E ; | XXXXX | $F65D
.byte $18 ; | XX | $F65E
.byte $19 ; | XX X| $F65F
.byte $3A ; | XXX X | $F660
.byte $7C ; | XXXXX | $F661
.byte $FF ; |XXXXXXXX| $F662
.byte $DF ; |XX XXXXX| $F663
.byte $0E ; | XXX | $F664
.byte $1C ; | XXX | $F665
.byte $18 ; | XX | $F666
.byte $24 ; | X X | $F667
.byte $64 ; | XX X | $F668
.byte $79 ; | XXXX X| $F669
.byte $FF ; |XXXXXXXX| $F66A
.byte $FF ; |XXXXXXXX| $F66B
.byte $4E ; | X XXX | $F66C
.byte $0E ; | XXX | $F66D
.byte $04 ; | X | $F66E
.byte $08 ; | X | $F66F
.byte $08 ; | X | $F670
.byte $6B ; | XX X XX| $F671
.byte $7F ; | XXXXXXX| $F672
.byte $7F ; | XXXXXXX| $F673
.byte $7F ; | XXXXXXX| $F674
.byte $63 ; | XX XX| $F675
.byte $63 ; | XX XX| $F676
.byte $24 ; | X X | $F677
.byte $26 ; | X XX | $F678
.byte $9E ; |X XXXX | $F679
.byte $FF ; |XXXXXXXX| $F67A
.byte $FF ; |XXXXXXXX| $F67B
.byte $72 ; | XXX X | $F67C
.byte $70 ; | XXX | $F67D
.byte $20 ; | X | $F67E
.byte $98 ; |X XX | $F67F
.byte $5C ; | X XXX | $F680
.byte $3E ; | XXXXX | $F681
.byte $FF ; |XXXXXXXX| $F682
.byte $FB ; |XXXXX XX| $F683
.byte $70 ; | XXX | $F684
.byte $38 ; | XXX | $F685
.byte $18 ; | XX | $F686
.byte $38 ; | XXX | $F687
.byte $1E ; | XXXX | $F688
.byte $DF ; |XX XXXXX| $F689
.byte $3E ; | XXXXX | $F68A
.byte $38 ; | XXX | $F68B
.byte $F8 ; |XXXXX | $F68C
.byte $7C ; | XXXXX | $F68D
.byte $18 ; | XX | $F68E
JetShape
.byte $60 ; | XX | $F68F
.byte $70 ; | XXX | $F690
.byte $78 ; | XXXX | $F691
.byte $FF ; |XXXXXXXX| $F692
.byte $78 ; | XXXX | $F693
.byte $70 ; | XXX | $F694
.byte $60 ; | XX | $F695
.byte $00 ; | | $F696
.byte $00 ; | | $F697
.byte $C1 ; |XX X| $F698
.byte $FE ; |XXXXXXX | $F699
.byte $7C ; | XXXXX | $F69A
.byte $78 ; | XXXX | $F69B
.byte $30 ; | XX | $F69C
.byte $30 ; | XX | $F69D
.byte $30 ; | XX | $F69E
.byte $00 ; | | $F69F
.byte $03 ; | XX| $F6A0
.byte $06 ; | XX | $F6A1
.byte $FC ; |XXXXXX | $F6A2
.byte $FC ; |XXXXXX | $F6A3
.byte $3C ; | XXXX | $F6A4
.byte $0C ; | XX | $F6A5
.byte $0C ; | XX | $F6A6
.byte $02 ; | X | $F6A7
.byte $04 ; | X | $F6A8
.byte $0C ; | XX | $F6A9
.byte $1C ; | XXX | $F6AA
.byte $FC ; |XXXXXX | $F6AB
.byte $FC ; |XXXXXX | $F6AC
.byte $1E ; | XXXX | $F6AD
.byte $06 ; | XX | $F6AE
.byte $10 ; | X | $F6AF
.byte $10 ; | X | $F6B0
.byte $10 ; | X | $F6B1
.byte $38 ; | XXX | $F6B2
.byte $7C ; | XXXXX | $F6B3
.byte $FE ; |XXXXXXX | $F6B4
.byte $FE ; |XXXXXXX | $F6B5
.byte $10 ; | X | $F6B6
.byte $40 ; | X | $F6B7
.byte $20 ; | X | $F6B8
.byte $30 ; | XX | $F6B9
.byte $38 ; | XXX | $F6BA
.byte $3F ; | XXXXXX| $F6BB
.byte $3F ; | XXXXXX| $F6BC
.byte $78 ; | XXXX | $F6BD
.byte $60 ; | XX | $F6BE
.byte $40 ; | X | $F6BF
.byte $60 ; | XX | $F6C0
.byte $3F ; | XXXXXX| $F6C1
.byte $1F ; | XXXXX| $F6C2
.byte $1E ; | XXXX | $F6C3
.byte $1E ; | XXXX | $F6C4
.byte $18 ; | XX | $F6C5
.byte $18 ; | XX | $F6C6
.byte $00 ; | | $F6C7
.byte $83 ; |X XX| $F6C8
.byte $7F ; | XXXXXXX| $F6C9
.byte $3E ; | XXXXX | $F6CA
.byte $1E ; | XXXX | $F6CB
.byte $0C ; | XX | $F6CC
.byte $0C ; | XX | $F6CD
.byte $0C ; | XX | $F6CE
PlaneShape
.byte $00 ; | | $F6CF
.byte $8E ; |X XXX | $F6D0
.byte $84 ; |X X | $F6D1
.byte $FF ; |XXXXXXXX| $F6D2
.byte $FF ; |XXXXXXXX| $F6D3
.byte $04 ; | X | $F6D4
.byte $0E ; | XXX | $F6D5
.byte $00 ; | | $F6D6
.byte $00 ; | | $F6D7
.byte $0E ; | XXX | $F6D8
.byte $04 ; | X | $F6D9
.byte $8F ; |X XXXX| $F6DA
.byte $7F ; | XXXXXXX| $F6DB
.byte $72 ; | XXX X | $F6DC
.byte $07 ; | XXX| $F6DD
.byte $00 ; | | $F6DE
.byte $10 ; | X | $F6DF
.byte $36 ; | XX XX | $F6E0
.byte $2E ; | X XXX | $F6E1
.byte $0C ; | XX | $F6E2
.byte $1F ; | XXXXX| $F6E3
.byte $B2 ; |X XX X | $F6E4
.byte $E0 ; |XXX | $F6E5
.byte $40 ; | X | $F6E6
.byte $24 ; | X X | $F6E7
.byte $2C ; | X XX | $F6E8
.byte $5D ; | X XXX X| $F6E9
.byte $1A ; | XX X | $F6EA
.byte $1A ; | XX X | $F6EB
.byte $30 ; | XX | $F6EC
.byte $F0 ; |XXXX | $F6ED
.byte $60 ; | XX | $F6EE
.byte $18 ; | XX | $F6EF
.byte $5A ; | X XX X | $F6F0
.byte $7E ; | XXXXXX | $F6F1
.byte $5A ; | X XX X | $F6F2
.byte $18 ; | XX | $F6F3
.byte $18 ; | XX | $F6F4
.byte $18 ; | XX | $F6F5
.byte $78 ; | XXXX | $F6F6
.byte $34 ; | XX X | $F6F7
.byte $36 ; | XX XX | $F6F8
.byte $5A ; | X XX X | $F6F9
.byte $78 ; | XXXX | $F6FA
.byte $2C ; | X XX | $F6FB
.byte $0C ; | XX | $F6FC
.byte $06 ; | XX | $F6FD
.byte $0C ; | XX | $F6FE
.byte $08 ; | X | $F6FF
.byte $6C ; | XX XX | $F700
.byte $70 ; | XXX | $F701
.byte $B8 ; |X XXX | $F702
.byte $DC ; |XX XXX | $F703
.byte $4E ; | X XXX | $F704
.byte $07 ; | XXX| $F705
.byte $06 ; | XX | $F706
.byte $38 ; | XXX | $F707
.byte $10 ; | X | $F708
.byte $F0 ; |XXXX | $F709
.byte $7C ; | XXXXX | $F70A
.byte $4F ; | X XXXX| $F70B
.byte $E3 ; |XXX XX| $F70C
.byte $02 ; | X | $F70D
.byte $00 ; | | $F70E
;
; These are sub-pointers, used to set up the
; two-dimensional array at CTRLTBL.
;
CtrlBase .BYTE $00 ,$0B ,$16
;
; Two-dimensional array, 12x3.
;
; This array specifies what the joystick does
; in each game. Looking at it now the format looks
; like this:
;
; Low nybble = Amount to rotate object (signed)
; $00 = Not at all
; $01 = Clockwise (+1)
; $0F = Counter-clockwise (-1)
; High nybble = Speed to move object (unsigned)
; $00 = Not moving
; $F0 = Warp speed
;
; Observe the $FF's. Notice how indexing out of bounds with impossible
; joystick movements will cause strange behavior.
;
; Tank movement
; UP DOWN (No reverse)
CTRLTBL .BYTE $00 ,$10 ,$00 ,$FF
.BYTE $01 ,$11 ,$01 ,$FF ;LEFT
.BYTE $0F ,$1F ,$0F ;RIGHT
;
; Biplane movement (This is why controls are sideways)
; UP DOWN
.BYTE $50 ,$5F ,$51 ,$FF ;
.BYTE $30 ,$3F ,$31 ,$FF ;LEFT
.BYTE $70 ,$7F ,$71 ;RIGHT
;
; Jet fighter movement
; UP DOWN
.BYTE $90 ,$B0 ,$70 ,$FF ;
.BYTE $91 ,$B1 ,$71 ,$FF ;LEFT
.BYTE $9F ,$BF ,$7F ;RIGHT
;
;
; Sound information for different game types.
; Different tools of destruction make different
; sound.
;
; There is some more data below which looks to
; be other information; different machines at
; different speeds. The pitch table is 3D,
; having 12-entry records for each GAMSHP.
;
; Tanks Biplane, Jet Fighter
SNDV .BYTE $08 ,$02 ,$02 ; sound volumes
SNDC .BYTE $02 ,$03 ,$08 ; sound types
SNDP .BYTE $1D ,$05 ,$00 ; sound pitches indexed by velocity
.BYTE $00 ,$00 ,$00 ; for TANKS
.BYTE $00 ,$00 ,$00
.BYTE $00 ,$00 ,$00
.BYTE $00 ,$00 ,$1D ; for BIPLANES
.BYTE $1D ,$16 ,$16
.BYTE $0F ,$0F ,$00
.BYTE $00 ,$00 ,$00
.BYTE $00 ,$00 ,$00 ; for JETS
.BYTE $00 ,$00 ,$12
.BYTE $10 ,$10 ,$0C
.BYTE $0C ,$07 ,$07
;
; Player widths for various plane games.
; Through the miracle of the Atari 2600's NUSIZ
; register, the difference between a 1 vs. 1 game
; and a Bomber vs. 3 game is contained in just
; two bytes.
;
WIDTHS .BYTE $00 ,$00 ;1 vs. 1
.BYTE $01 ,$01 ;2 vs. 2
.BYTE $00 ,$03 ;1 vs. 3
.BYTE $27 ,$03 ;Bomber vs. 3
; Table of color combinations. Each 4 byte entry specifies
; Player 0, Player1, Playfield, and Background colors.
; (By a not-so-odd coincidence, these 4 color registers are
; addressed consecutively in the same order in the TIA.)
; Table is indexed by the high 2 bits of GAMVAR << 2, or
; forced to +$10 if B&W switch selected.
;
ColorTbl
byte $EA ,$3C ,$82 ,$44 ; 00 = Regular Tanks
.byte $32 ,$2C ,$8A ,$DA ; 01 = Tank Pong
.byte $80 ,$9C ,$DA ,$3A ; 10 = Jets
.byte $64 ,$A8 ,$DA ,$4A ; 11 = Biplanes
.byte $08 ,$04 ,$00 ,$0E ; special B&W
PF0_0 .byte $F0 ; |XXXX | $F779
.byte $10 ; | X | $F77A
.byte $10 ; | X | $F77B
.byte $10 ; | X | $F77C
.byte $10 ; | X | $F77D
.byte $10 ; | X | $F77E
.byte $10 ; | X | $F77F
.byte $10 ; | X | $F780
.byte $10 ; | X | $F781
.byte $10 ; | X | $F782
.byte $10 ; | X | $F783
.byte $10 ; | X | $F784
PF1_0 .byte $FF ; |XXXXXXXX| $F785
.byte $00 ; | | $F786
.byte $00 ; | | $F787
.byte $00 ; | | $F788
.byte $38 ; | XXX | $F789
.byte $00 ; | | $F78A
.byte $00 ; | | $F78B
.byte $00 ; | | $F78C
.byte $60 ; | XX | $F78D
.byte $20 ; | X | $F78E
.byte $20 ; | X | $F78F
.byte $23 ; | X XX| $F790
PF2_0 .byte $FF ; |XXXXXXXX| $F791
.byte $80 ; |X | $F792
.byte $80 ; |X | $F793
.byte $00 ; | | $F794
.byte $00 ; | | $F795
.byte $00 ; | | $F796
.byte $1C ; | XXX | $F797
.byte $04 ; | X | $F798
.byte $00 ; | | $F799
.byte $00 ; | | $F79A
.byte $00 ; | | $F79B
.byte $00 ; | | $F79C
PF1_1 .byte $FF ; |XXXXXXXX| $F79D
PF0_3 .byte $00 ; | | $F79E
.byte $00 ; | | $F79F
.byte $00 ; | | $F7A0
PF1_3 .byte $00 ; | | $F7A1
.byte $00 ; | | $F7A2
.byte $00 ; | | $F7A3
.byte $00 ; | | $F7A4
.byte $00 ; | | $F7A5
.byte $00 ; | | $F7A6
.byte $00 ; | | $F7A7
.byte $00 ; | | $F7A8
.byte $00 ; | | $F7A9
.byte $07 ; | XXX| $F7AA
.byte $1F ; | XXXXX| $F7AB
.byte $3F ; | XXXXXX| $F7AC
.byte $7F ; | XXXXXXX| $F7AD
PF1_2 .byte $FF ; |XXXXXXXX| $F7AE
.byte $00 ; | | $F7AF
.byte $00 ; | | $F7B0
.byte $00 ; | | $F7B1
.byte $00 ; | | $F7B2
.byte $00 ; | | $F7B3
.byte $00 ; | | $F7B4
.byte $00 ; | | $F7B5
.byte $00 ; | | $F7B6
.byte $60 ; | XX | $F7B7
.byte $20 ; | X | $F7B8
.byte $21 ; | X X| $F7B9
PF2_2 .byte $FF ; |XXXXXXXX| $F7BA
.byte $00 ; | | $F7BB
.byte $00 ; | | $F7BC
.byte $00 ; | | $F7BD
.byte $80 ; |X | $F7BE
.byte $80 ; |X | $F7BF
.byte $80 ; |X | $F7C0
.byte $80 ; |X | $F7C1
.byte $00 ; | | $F7C2
.byte $00 ; | | $F7C3
.byte $00 ; | | $F7C4
.byte $07 ; | XXX| $F7C5
; Addresses for Sprite Graphics
SPRLO .BYTE #PlaneShape, #TankShape, #>PlaneShape, #>JetShape
; Playfield address data. Kernal timing requires that
; these addresses point 4 bytes before the real start
; of data.
;
; Complex , None
; Simple , Clouds
PLFPNT .BYTE #<(PF0_0-4) ,#<(PF0_0-4)
.BYTE #<(PF0_0-4) ,#<(PF0_3-4) ;PF0
.BYTE #<(PF1_0-4) ,#<(PF1_1-4)
.BYTE #<(PF1_2-4) ,#<(PF1_3-4) ;PF1
.BYTE #<(PF2_0-4) ,#<(PF1_1-4)
.BYTE #<(PF2_2-4) ,#<(PF1_3-4) ;PF2
; Game features, indexed by game number-1.
;
; bits
; 1,0: TANKS PLANES
; X0 = Normal
; X1 = Invisible
; 00 = 1 vs. 1
; 01 = 2 vs. 2
; 10 = 3 vs. 1
; 11 = 3 vs. Giant
; 3,2: 01 = No maze
; 10 = Simple maze
; 00 = Complex maze
; 1X = Clouds
; 0X = No clouds
; 4: 0 = Direct Hit Normal Gun
; 1 = Billiard Hit Machine Gun
; 5: 0 = Straight Missiles
; 1 = Guided Missiles
; 6: 0 = Tanks Jets
; 1 = Tank Pong Biplanes
; 7: 0 = Tank Game
; 1 = Plane Game
;
VARMAP .BYTE $24 ;Game 1: 0010 0100 TANK
.BYTE $28 ;Game 2: 0010 1000
.BYTE $08 ;Game 3: 0000 1000
.BYTE $20 ;Game 4: 0010 0000
.BYTE $00 ;Game 5: 0000 0000
.BYTE $48 ;Game 6: 0100 1000 TANK PONG
.BYTE $40 ;Game 7: 0100 0000
.BYTE $54 ;Game 8: 0101 0100
.BYTE $58 ;Game 9: 0101 1000
.BYTE $25 ;Game 10: 0010 0101 INVISIBLE TANK
.BYTE $29 ;Game 11: 0010 1001
.BYTE $49 ;Game 12: 0100 1001 INVISIBLE TANK-PONG
.BYTE $55 ;Game 13: 0101 0101
.BYTE $59 ;Game 14: 0101 1001
.BYTE $A8 ;Game 15: 1010 1000 BIPLANE
.BYTE $88 ;Game 16: 1000 1000
.BYTE $98 ;Game 17: 1001 1000
.BYTE $90 ;Game 18: 1001 0000
.BYTE $A1 ;Game 19: 1010 0001
.BYTE $83 ;Game 20: 1000 0011
.BYTE $E8 ;Game 21: 1110 1000 JET FIGHTER
.BYTE $C8 ;Game 22: 1100 1000
.BYTE $E0 ;Game 23: 1110 0000
.BYTE $C0 ;Game 24: 1100 0000
.BYTE $E9 ;Game 25: 1110 1001
.BYTE $E2 ;Game 26: 1110 0010
.BYTE $C1 ;Game 27: 1100 0001
;
; $FF to signify end of game variations.
;
.BYTE $FF
; If you were changing this to a 4K cart, you'd
; want to change this ORG to $FFFC. You might also
; want to move AudPitch out of the interrupt vector...
;
ORG $F7FC
.word $f000 ; Reset IRQ
;
AudPitch
.BYTE $0F, $11 ; Motor sound pitch table by player
|
 |