8bitworkshop/presets/nes/road.dasm

408 lines
8.8 KiB
Plaintext

include "nesdefs.dasm"
;;;;; ZERO-PAGE VARIABLES
seg.u ZEROPAGE
org $0
Ptr1 .word ; for temporary pointer storage
TrackFrac .byte ; fractional position along track
Speed .byte ; speed of car
TimeOfDay .word ; 16-bit time of day counter
; Variables for preprocessing step
XPos .word ; 16-bit X position
XVel .word ; 16-bit X velocity
TPos .word ; 16-bit track position
TrackLookahead .byte ; current fractional track increment
; Variables for track generation
Random .byte ; random counter
GenTarget .byte ; target of current curve
GenDelta .byte ; curve increment
GenCur .byte ; current curve value
ZOfs .byte ; counter to draw striped center line
Weather .byte ; bitmask for weather
Heading .word ; sky scroll pos.
NMITmp .byte
NumRoadScanlines = 112
NumRoadSegments = 56
; Preprocessing result: X positions for all track segments
RoadX0 ds NumRoadSegments
; Generated track curve data
TrackLen equ 5
TrackData ds TrackLen
InitialSpeed equ 10 ; starting speed
;;;;; OTHER VARIABLES
seg.u RAM
org $300
;;;;; NES CARTRIDGE HEADER
NES_HEADER 0,2,1,1 ; mapper 0, 2 PRGs, 1 CHR, vert mirror
;;;;; START OF CODE
Start:
NES_INIT ; set up stack pointer, turn off PPU
jsr WaitSync ; wait for VSYNC
jsr ClearRAM ; clear RAM
jsr WaitSync ; wait for VSYNC (and PPU warmup)
jsr FillVRAM ; set PPU video RAM
jsr RoadSetup
lda #0
sta PPU_ADDR
sta PPU_ADDR ; PPU addr = $0000
sta PPU_SCROLL
sta PPU_SCROLL ; scroll = $0000
lda #MASK_BG|MASK_SPR|MASK_BG_CLIP
sta PPU_MASK ; enable rendering (first!)
lda #CTRL_NMI
sta PPU_CTRL ; enable NMI
.endless
jmp .endless ; endless loop
;;;;; ROAD SUBS
RoadSetup: subroutine
lda #1
sta Random
lda #InitialSpeed
sta Speed
lda #0
sta TimeOfDay+1
lda #0
sta Weather
rts
RoadPreSetup: subroutine
; Set up some values for road curve computation,
; since we have some scanline left over.
lda #0
sta XVel
sta XVel+1
sta XPos
sta XPos+1
lda TrackFrac
sta TPos
lda #0
sta TPos+1
lda #10 ; initial lookahead
sta TrackLookahead
rts
RoadPostFrame: subroutine
; Advance position on track
; TrackFrac += Speed
lda TrackFrac
clc
adc Speed
sta TrackFrac
bcc .NoGenTrack ; addition overflowed?
jsr GenTrack ; yes, generate new track segment
.NoGenTrack
; TimeOfDay += 1
inc TimeOfDay
bne .NoTODInc
inc TimeOfDay+1
lda TimeOfDay+1
; See if it's nighttime yet, and if the stars come out
clc
adc #8
and #$3f
cmp #$35
ror
sta Weather
.NoTODInc
lda Heading
sec
sbc XPos+1
sta Heading
bit XPos+1
bmi .NegHeading
lda Heading+1
sbc #0
sta Heading+1
rts
.NegHeading
lda Heading+1
sbc #$ff
sta Heading+1
rts
; Compute road curve from bottom of screen to horizon.
PreprocessCurve subroutine
ldx #NumRoadSegments-1
.CurveLoop
; Modify X position
; XPos += XVel (16 bit add)
lda XPos
clc
adc XVel
sta XPos
lda XPos+1
adc XVel+1
sta XPos+1
sta RoadX0,x ; store in RoadX0 array
; Modify X velocity (slope)
; XVel += TrackData[TPos]
ldy TPos+1
lda TrackData,y
clc ; clear carry for ADC
bmi .CurveLeft ; track slope negative?
adc XVel
sta XVel
lda XVel+1
adc #0 ; carry +1
jmp .NoCurveLeft
.CurveLeft
adc XVel
sta XVel
lda XVel+1
sbc #0 ; carry -1
nop ; make the branch timings are the same
.NoCurveLeft
sta XVel+1
; Advance TPos (TrackData index)
; TPos += TrackLookahead
lda TPos
clc
adc TrackLookahead
sta TPos
lda TPos+1
adc #0
sta TPos+1
; Go to next segment
inc TrackLookahead ; see further along track
dex
bpl .CurveLoop
rts
; Generate next track byte
GenTrack subroutine
; Shift the existing track data one byte up
; (a[i] = a[i+1])
ldx #0
.ShiftTrackLoop
lda TrackData+1,x
sta TrackData,x
inx
cpx #TrackLen-1
bne .ShiftTrackLoop
; Modify our current track value and
; see if it intersects the target value
lda GenCur
clc
adc GenDelta
cmp GenTarget
beq .ChangeTarget ; target == cur?
bit GenTarget ; we need the sign flag
bmi .TargetNeg ; target<0?
bcs .ChangeTarget ; target>=0 && cur>=target?
bcc .NoChangeTarget ; branch always taken
.TargetNeg
bcs .NoChangeTarget ; target<0 && cur<target?
; Generate a new target value and increment value,
; and make sure the increment value is positive if
; the target is above the current value, and negative
; otherwise
.ChangeTarget
lda Random
jsr NextRandom ; get a random value
sta Random
and #$3f ; range 0..63
sec
sbc #$1f ; range -31..32
sta GenTarget ; -> target
cmp GenCur
bmi .TargetBelow ; current > target?
lda Random
jsr NextRandom ; get a random value
sta Random
and #$f ; mask to 0..15
jmp .TargetAbove
.TargetBelow
lda Random
jsr NextRandom
sta Random
ora #$f0 ; mask to -16..0
.TargetAbove
ora #1 ; to avoid 0 values
sta GenDelta ; -> delta
lda GenCur
.NoChangeTarget
; Store the value in GenCur, and also
; at the end of the TrackData array
sta GenCur
sta TrackData+TrackLen-1
rts
; fill video RAM
FillVRAM: subroutine
PPU_SETADDR $2000
lda #<RoadTables
sta Ptr1
lda #>RoadTables
sta Ptr1+1
ldx #8
ldy #0
.loop:
lda (Ptr1),y
sta PPU_DATA
iny
bne .loop
inc Ptr1+1
dex
bne .loop
rts
; set palette colors
SetPalette: subroutine
PPU_SETADDR $3f00
ldx #16
.loop:
lda Palette,y
sta PPU_DATA
iny
dex
bne .loop
rts
; set sprite 0 (A = top scanline)
SetSprite0: subroutine
sta $200 ;ypos
lda #$00 ;code
sta $201
lda #$20 ;flags
sta $202
lda #$f0 ;xpos
sta $203
rts
;;;;; COMMON SUBROUTINES
include "nesppu.dasm"
;;;;; INTERRUPT HANDLERS
MAC SLEEP ;usage: SLEEP n (n>1)
.CYCLES SET {1}
IF .CYCLES < 2
ECHO "MACRO ERROR: 'SLEEP': Duration must be > 1"
ERR
ENDIF
IF .CYCLES & 1
bit $00
.CYCLES SET .CYCLES - 3
ENDIF
REPEAT .CYCLES / 2
nop
REPEND
ENDM
;;;
;;; NMI HANDLER
;;;
NMIHandler: subroutine
; save registers
SAVE_REGS
; cycle palette colors
lda TPos ; track position
lsr ; / 2
and #16 ; now either 0 or 16
tay ; Y is palette offset
jsr SetPalette ; call SetPalette
PPU_SETADDR $2000 ; reset PPU address
; load sprite zero
lda #109
jsr SetSprite0 ; set sprite 0 position
lda #$02
sta PPU_OAM_DMA ; send sprites to PPU
; setup sky scroll
lda Heading+1 ; heading scroll pos.
sta PPU_SCROLL ; X scroll
lda #0
sta PPU_SCROLL ; Y scroll = 0
; do road calculations
jsr RoadPreSetup
jsr PreprocessCurve
jsr RoadPostFrame
; wait for sprite 0
.wait0 bit PPU_STATUS
bvs .wait0 ; until bit 6 set
.wait1 bit PPU_STATUS
bvc .wait1 ; until bit 6 clear
; start scanline loop
ldy #0 ; scanline counter
lda #3
sta NMITmp ; count every 3 lines
.loop
; take back 1 cycle every 3rd line
dec NMITmp ; decrement 3-line counter
beq .resetx ; has it been 3 lines?
; pad out rest of the scanline
SLEEP 10
.skipcyc
SLEEP 70
; change scroll register
tya ; Y -> A
lsr ; A / 2
tax ; A -> X
lda RoadX0,x ; get X offset of scanline
eor #$80 ; + 128
sta PPU_SCROLL ; horiz byte
sta PPU_SCROLL ; vert byte (ignored)
; next loop iteration
iny
cpy #NumRoadScanlines
bcc .loop ; do next scanline
; restore registers and return from interrupt
RESTORE_REGS
rti
; this resets the 3-line counter
; and returns to a different loop entry point
; since we wasted 8 cycles doing this
.resetx
lda #3
sta NMITmp ; reset 3-line counter
bne .skipcyc ; go back to loop
;;;;; CONSTANT DATA
;;{pal:"nes",layout:"nes"};;
Palette:
hex 0F3F06300F19063F0F1916063F180801
hex 0F30163F0F19163F0F1906160F180801
;;
RoadTables:
incbin "road/nametable.dat"
incbin "road/attribute.dat"
incbin "road/nametable1.dat"
incbin "road/attribute1.dat"
;;;;; CPU VECTORS
NES_VECTORS
;;;;; TILE SETS
org $10000
incbin "road/road.chr"
incbin "road/road.chr"