Users browsing this thread: 1 Guest(s)
Event Script Assembler Macros

#1
Posts: 179
Threads: 3
Thanks Received: 24
Thanks Given: 4
Joined: Apr 2015
Reputation: 18
Status
None
I've been working on a set of assembler macros for writing and modifying FF6 events using an assembler. Essentially this allows you to write events in an easily readable text file and then convert them to binary automatically (just like how an assembler converts asm files into machine code). It's still a WIP, but it's now integrated into my ff6 repo on GitHub. It can also be used to generate standalone events without building the whole ROM. src/event/README.md contains more details and an incomplete list of event command descriptions, include/event_cmd.inc contains the macro definitions that are used to convert the event script commands into binary using the assembler, and src/event/event_main.asm contains the actual event script code. Currently, only the beginning and end of the event script get generated using the new macros. The rest is still imported as a raw binary that gets ripped from a vanilla ROM. Eventually, I'd like to generate the entire event script using just assembly files (like how the CPU code is generated).

As an example, here's the script for the event that occurs when you use a Warp Stone or cast Warp (at CA/0108). The indentations are for readability only, they're not required.

Code:
; ca/0108
.proc Warp
       sfx 76
       loop 2
               obj_script SLOT_1
                       dir RIGHT
                       end
               obj_script SLOT_1
                       dir UP
                       end
               obj_script SLOT_1
                       dir LEFT
                       end
               obj_script SLOT_1
                       dir DOWN
                       end
               end_loop
       fade_out
       loop 2
               obj_script SLOT_1
                       dir RIGHT
                       end
               obj_script SLOT_1
                       dir UP
                       end
               obj_script SLOT_1
                       dir LEFT
                       end
               obj_script SLOT_1
                       dir DOWN
                       end
               end_loop
       wait_fade
       switch $01cc=0
       switch $01cd=0
       switch $02bc=0
       if_switch $02bf=1, phoenix_cave_warp
       if_switch $02be=1, kefkas_tower_warp
       call reset_turtles
       load_map $01ff, 0, 0, UP, $24, 0
       set_script_mode WORLD
       end
       set_script_mode EVENT

; warp event when in kefka's tower
kefkas_tower_warp:
       call $cc0ff6
       return

; warp event when in phoenix cave
phoenix_cave_warp:
       call $cc1001
       return

; reset some turtles when in darill's tomb
reset_turtles:
       switch $02b4=0
       switch $02b6=0
       return
.endproc  ; Warp

Compare to Imzogelmo's event script dump, which has a bit more detail, but can't be easily converted back into binary.

Code:
CA/0108: F4    Play sound effect 76
CA/010A: B0    Execute the following commands until $B1 2 times
CA/010C: 31        Begin action queue for character $31 (Party Character 0), 2 bytes long (Wait until complete)
CA/010E: CD            Turn vehicle/entity right
CA/010F: FF            End queue
CA/0110: 31        Begin action queue for character $31 (Party Character 0), 2 bytes long (Wait until complete)
CA/0112: CC            Turn vehicle/entity up
CA/0113: FF            End queue
CA/0114: 31        Begin action queue for character $31 (Party Character 0), 2 bytes long (Wait until complete)
CA/0116: CF            Turn vehicle/entity left
CA/0117: FF            End queue
CA/0118: 31        Begin action queue for character $31 (Party Character 0), 2 bytes long (Wait until complete)
CA/011A: CE            Turn vehicle/entity down
CA/011B: FF            End queue
CA/011C: B1        End block of repeating commands
CA/011D: 97    Fade screen to black
CA/011E: B0    Execute the following commands until $B1 2 times
CA/0120: 31        Begin action queue for character $31 (Party Character 0), 2 bytes long (Wait until complete)
CA/0122: CD            Turn vehicle/entity right
CA/0123: FF            End queue
CA/0124: 31        Begin action queue for character $31 (Party Character 0), 2 bytes long (Wait until complete)
CA/0126: CC            Turn vehicle/entity up
CA/0127: FF            End queue
CA/0128: 31        Begin action queue for character $31 (Party Character 0), 2 bytes long (Wait until complete)
CA/012A: CF            Turn vehicle/entity left
CA/012B: FF            End queue
CA/012C: 31        Begin action queue for character $31 (Party Character 0), 2 bytes long (Wait until complete)
CA/012E: CE            Turn vehicle/entity down
CA/012F: FF            End queue
CA/0130: B1        End block of repeating commands
CA/0131: 5C    Pause execution until fade in or fade out is complete
CA/0132: D3    Clear event bit $1E80($1CC) [$1EB9, bit 4]
CA/0134: D3    Clear event bit $1E80($1CD) [$1EB9, bit 5]
CA/0136: D5    Clear event bit $1E80($2BC) [$1ED7, bit 4]
CA/0138: C0    If ($1E80($2BF) [$1ED7, bit 7] is set), branch to $CA0154
CA/013E: C0    If ($1E80($2BE) [$1ED7, bit 6] is set), branch to $CA014F
CA/0144: B2    Call subroutine $CA0159
CA/0148: 6B    Load map $01FF (<unknown or out-of-range map index>) instantly, (upper bits $2400), place party at (0, 0), facing down
CA/014E: FF        End map script

CA/014F: B2    Call subroutine $CC0FF6
CA/0153: FE    Return

CA/0154: B2    Call subroutine $CC1001
CA/0158: FE    Return

CA/0159: D5    Clear event bit $1E80($2B4) [$1ED6, bit 4]
CA/015B: D5    Clear event bit $1E80($2B6) [$1ED6, bit 6]
CA/015D: FE    Return

So, why make this? The main reason is so that the assembler has access to event addresses/labels for my ff6 disassembly, but it can also be used to write shorter standalone events. Hackers often write events using an event editor built into one of the ROM editors (ZoneDoctor and FF6Tools have one) or by generating the raw binary code manually. These methods have issues of their own, but more importantly for me there's no easy way to reference event addresses in the CPU code, event triggers, NPCs, etc. I also think writing events this way is easier to read and understand.

Another cool thing is that I'm pretty sure this is how the original devs wrote these scripts. The editors they used were probably pretty primitive, so they would have relied heavily on macro assemblers to do most of the script and data generation when developing the game.

Like with anything new, there's going to be a learning curve if you want to write events with this. If you're already proficient with other methods for writing event code, this might not be worth learning. Also, Because ff6 contains such a large amount of code and data, the more familiar assemblers like Asar and xkas aren't really suitable. Instead, I've been using ca65 which is better suited to large projects, but nonetheless has issues of its own and is less familiar to most hacking folks.

My next goals are to implement the remaining event commands, finish the documentation, convert the rest of the event script into this format, and then do lots of testing with custom events. I don't have as much time to spend on this stuff as I used to so if anyone would like to contribute or help out in any way I would really appreciate it. Let me know here or on GitHub and sorry in advance if I'm slow to reply.
  Find
Quote  
[-] The following 1 user says Thank You to Everything for this post:
  • madsiur (04-05-2024)

#2
Posts: 3,970
Threads: 279
Thanks Received: 236
Thanks Given: 58
Joined: Oct 2011
Reputation: 65
Status
Tissue-aware
Awesome!!

Edit: (I might have some time in the future to contribute again)
  Find
Quote  



Forum Jump:

Users browsing this thread: 1 Guest(s)


Theme by Madsiur2017Custom Graphics by JamesWhite