; 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