iigs-game-engine/src/Script.s

176 lines
6.4 KiB
ArmAsm

; A simple scripting engine
;
; The scripting engine is driven by the GTE Timers, which are
; driven by the VBL Tick count.
;
; The scripting engine is composed of a sequence of simple commands, defined as follows
;
; COMMAND ARG1 ARG2 ARG3
;
; The COMMAND word has its bits defined as:
;
; bit 15 = 1 if the end of a sequence
; bit 14 = 0 proceed to next action, 1 jump
; bit 13 = 0 (Reserved)
; bit 12 = 0 (Reserved)
; bit 11 - 8 = signed jump displacement
; bit 8 - 0 = command number
;
; The defined commands are
;
; COMMANDS ARG1 ARG2 ARG3
; -----------------------------------------------------
; $0002 SET_PALETTE_ENTRY ADDR COLOR ---- : Set the palette entry at ARG1 to the color in ARG2
; $0004 SWAP_PALETTE_ENTRY ADDR1 ADDR2 ---- : Swap the palette entries in ADDR1 <-> ADDR2
; $0006 SET_DYN_TILE TILE_ID DTILE_ID ---- : Copy data from TileData into Dynamic Tile Area
; $0010 CALLBACK LONG_ADDR PARAM : Call a user-defined function (JSL) with a parameter value in accumulator
; Start a new script
;
; A = low word of script command array
; X = high word of script command array
; Y = number of ticks between each command step
;
; A pointer to the current command instruction is stored in the first 4 bytes of the
; timer's user data section.
_StartScript phx ; Save the script array address
pha
lda #_DoScriptSeq ; Try to create a timer for this script
ldx #^_DoScriptSeq
clc
jsr _AddTimer
bcs :err ; No timer slots available :(
tax ; Initialize the UserData with the command pointer
pla
sta Timers+8,x
pla
sta Timers+10,x
rts
:err
pla ; Pop the values and return with the carry flag set
pla
rts
; This routine executes script command until it encounters one with the STOP bit set. In some
; sense, the stop bit acts like a "yield" in high-level languages.
ARG1 equ 2
ARG2 equ 4
ARG3 equ 6
_DoScriptSeq
phx ; save the timer index; will need to update user data at the end
phb ; save the current data bank
sep #$20 ; push the bank byte of the command list pointer on the stack
lda Timers+10,x
pha
rep #$20
lda Timers+8,x ; get the current address of the command sequence
tax
plb ; pop the bank
; Now we are ready to process commands until reaching one with the STOP bit set. Each command
; is 8 bytes, so we just have to do a very simple fetch/execute/increment loop. The only
; exception is handling the JUMP bit which requires moving the script pc stored in the
; x-register.
_dss_loop phx ; Save the command address
txy ; Cache in the y-register
lda: 0,x ; Load the command word
pha ; Stash it
and #$001E ; Only have 16 built-in commands. Use the _UserCallback
tax ; command for custom functionality
jmp (_dss_commands,x)
_dss_commands dw _Null,_SetPalEntry,_SwapPalEntry,_SetDTile,_Null,_Null,_Null,_Null
dw _UserCallback,_Null,_Null,_Null,_Null,_Null,_Null,_Null
_dss_cmd_rtn
lda 1,s ; Reload the command word
; Move to the next instruction. If the JUMP bit is set, we move the address forward or
; backward N commands (8 bytes at a time). If the JUMP bit is not set, then just move
; to the next entry.
bit #JUMP ; Just do a fall through and set the jump offset to
bne :move_addr ; a hard-coded value of 1 if the jump bit is not set
:retry lda #$0040
:move_addr and #$0FC0 ; mask out the number of commands to move
beq :retry ; Don't allow zeros; will cause infinite loop. Just advance by one.
cmp #$0800 ; Sign-extend the 6-bit value
bcc *+5
ora #$F000
cmp #$8000 ; make it a multiple of 8 (preserve sign)
ror
cmp #$8000
ror
cmp #$8000
ror
clc
adc 3,s ; add it to the saved command address
sta 3,s
; Check to see if we stop on this instruction, or continue executing commands
pla ; Reload the command word
plx ; Pop off the update command address
bit #YIELD ; If the stop bit is set, we're done with this sequence
beq _dss_loop ; Otherwise, keep going and fetch the next command word
txa ; save the current command address
plb ; restore the data bank and the timer index
plx
sta Timers+8,x ; store the command address back into the timer user data space
rtl
; Implementation of the built-in commands
_Null brl _dss_cmd_rtn
_SetPalEntry
ldx: ARG1,y
lda: ARG2,y
stal SHR_PALETTES,x
brl _dss_cmd_rtn
_SwapPalEntry
ldx: ARG1,y ; Load palette values
ldal SHR_PALETTES,x
pha
ldx: ARG2,y
ldal SHR_PALETTES,x
ldx: ARG1,y ; and swap
stal SHR_PALETTES,x
ldx: ARG2,y
pla
stal SHR_PALETTES,x
brl _dss_cmd_rtn
_SetDTile
ldx: ARG1,y
lda: ARG2,y
tay
jsr CopyTileToDyn
brl _dss_cmd_rtn
_UserCallback
lda: ARG1,y
stal :dispatch+1
lda: ARG1+1,y
stal :dispatch+2
lda: ARG3,y
:dispatch jsl $000000
brl _dss_cmd_rtn