- 239
- Posts
- 9
- Years
- Seen Apr 17, 2025
I know this was implemented in the Emerald battle engine upgrade, and there was some work done by FBI a few years ago on it, but I want to have a collaborative effort on getting this working for Fire Red, especially for hacks that won't use the dissasembly projects.
EDIT: Delta posted a link to a more completed version.
I researched this topic a year ago or so and was able to get a working version with some graphical side effects that I will explain later.
Background info for the curious:
Implementation:
So wherever we want to have the trainer interrupt, we need a battle script to be run at that point in the battle. A logical place for the end-of-turn would be after all of the weather, etc checks that occur. Fortunately, there is a table at 0x17bc4 that runs end-of-turn routines and battle scripts if necessary. There is a limiter at 0x17b98 so we can easily repoint and expand this table to add more global end-of-turn effects (note that the last entry in the original table must always be last)
We can run a battle script from here to make the trainer enter the screen, play a custom string, and then use my whirlwind animation hack to push the trainer offscreen, resetting all of the object targets (introduced in the spoiler above).
Note: this works from a functional standpoint, but the trainer sprite is still prevalent in the VRAM or object data structure somehow and will show up again in several attacks such as Pound, Agility, etc. Entering the pokemon menu and returning fixes this problem.
Here is a test case using the global end-of-turn battle scripts. The following code expands the global effect table and loads a string and battle script if a specific byte in RAM is set (so you can load a certain number of strings). It includes the whirlwind anim hack. It deletes the trainer object afterwards but the aforementioned graphical issue is not solved.
Main code: compile and insert, noting the .org locations
You'll need to download the attached bs_macros.asm and insert this sethalfword command I wrote as well (or can just use setbyte since Whirlwind is 1 byte). I have it as command 0xFB
Of course, there are plenty of other applications and ways to implement this design. The meat of the research is around cmd53 and the pokemon objects. The main holdup for this to be complete is finding a way to completely remove the trainer sprite from the graphics after the fact. This may be a trivial solution with a simple in-game function, but I have limited experience with graphics-based asm. I will note that going into the pokemon party menu and returning fixes the problem, making me think that it has something to do with the object data.
EDIT: Delta posted a link to a more completed version.
I researched this topic a year ago or so and was able to get a working version with some graphical side effects that I will explain later.
Background info for the curious:
Spoiler:
There are likely multiple ways to do this; the easiest in my opinion is to use battle scripts. There is already a battle script command that brings the trainer sprite out. This command is cmd53, and it takes an argument either 0x0 or 0x1: the former will bring the player sprite out (there is potentially utility here..), or 0x1 for the opposing trainer (not sure what would happen if used in a wild battle).
The problem is that the game never intended to have a battle continue if this command was called, so that the object defined for animation targets gets set to the trainer sprite. These obj IDs are stored in 0x02023d44 in the same order as the banks would be (player 1, enemy 1, player 2, enemy 2).
The other problem is that (as hard as I was willing to look back when I originally researched this) there is no trainer_slide_out command. There is certainly a function to do it, as that is what happens at the beginning of the the trainer battle, but in lieu of finding this, I used whirlwind's animation.
Of course, the trainer sprite object still exists, so we should delete it so that it does not get used in attack animations. The aforementioned graphical issue is related to this; the trainer sprite gets stuck in VRAM, so several moves (Pound, Agility, Quick Attack, etc), generate the trainer sprite again even when the object is deleted. I do not know enough about VRAM functions or the object data structure to know how to fix this at the moment, though it's been a while since I looked at most of this stuff.
The problem is that the game never intended to have a battle continue if this command was called, so that the object defined for animation targets gets set to the trainer sprite. These obj IDs are stored in 0x02023d44 in the same order as the banks would be (player 1, enemy 1, player 2, enemy 2).
The other problem is that (as hard as I was willing to look back when I originally researched this) there is no trainer_slide_out command. There is certainly a function to do it, as that is what happens at the beginning of the the trainer battle, but in lieu of finding this, I used whirlwind's animation.
Of course, the trainer sprite object still exists, so we should delete it so that it does not get used in attack animations. The aforementioned graphical issue is related to this; the trainer sprite gets stuck in VRAM, so several moves (Pound, Agility, Quick Attack, etc), generate the trainer sprite again even when the object is deleted. I do not know enough about VRAM functions or the object data structure to know how to fix this at the moment, though it's been a while since I looked at most of this stuff.
Implementation:
So wherever we want to have the trainer interrupt, we need a battle script to be run at that point in the battle. A logical place for the end-of-turn would be after all of the weather, etc checks that occur. Fortunately, there is a table at 0x17bc4 that runs end-of-turn routines and battle scripts if necessary. There is a limiter at 0x17b98 so we can easily repoint and expand this table to add more global end-of-turn effects (note that the last entry in the original table must always be last)
We can run a battle script from here to make the trainer enter the screen, play a custom string, and then use my whirlwind animation hack to push the trainer offscreen, resetting all of the object targets (introduced in the spoiler above).
Note: this works from a functional standpoint, but the trainer sprite is still prevalent in the VRAM or object data structure somehow and will show up again in several attacks such as Pound, Agility, etc. Entering the pokemon menu and returning fixes this problem.
Here is a test case using the global end-of-turn battle scripts. The following code expands the global effect table and loads a string and battle script if a specific byte in RAM is set (so you can load a certain number of strings). It includes the whirlwind anim hack. It deletes the trainer object afterwards but the aforementioned graphical issue is not solved.
Spoiler:
Main code: compile and insert, noting the .org locations
Code:
.text
.align 2
.thumb
.thumb_func
.global TrainerInterrupt_EndTurn
.include "bs_macros.asm"
.equ Offset, 0xYYYYYY @ insert loc (no 0x08 prefix)
.equ romsize, 0x08000000
.equ var8001, 0x020370ba
.equ var8002, 0x020370bc
.equ ATK_WHIRLWIND, 0x12
@ change this if you want
.equ SlideInRAM, 0x0203e05d
.org 0x17b98, 0xff
.byte 0xb
.org 0x17bc0, 0xff
.word (romsize+GlobalEffects)
.org 0x72688, 0xff
.byte 0x0, 0x48, 0x0, 0x47
.word (romsize+anim_hack+1)
.org Offset, 0xff
.align 2
anim_hack:
ldr r1, [sp] @move anim table
add r0, r3, r1
CheckTrainerSlide:
ldr r1, =SlideInRAM
ldrb r1, [r1, #0x1] @0x0203e05e - changes whirlwind anim (change if you want)
cmp r1, #0x0
bne TrainerSlideOut
NormalAnim:
ldr r0, [r0, #0x0]
b StoreAnim
TrainerSlideOut:
ldr r0, =(0x081c9a24)
StoreAnim:
str r0, [r5]
Return:
ldr r0, =(0x08072690 +1)
bx r0
battle_script:
copyarray var8002 0x02023d44 0x4 @ save object banks into var8002-3
cmd53 0x1 @ trainer slide out
waitstate
printstring 0x184 @ print string
pause 0x20
sethalfword AttackRAM ATK_WHIRLWIND
copyarray var8001 TargetBank 0x1 @ save target bank into var8001
setbyte TargetBank 0x1
setbyte 0x0203e05e 0x1 @ whirlwind anim hack
attackanimation
waitanimation
pause 0x20
setbyte SlideInRAM 0x0 @ reset trainer slide
setbyte 0x0203e05e 0x0 @ reset whirlwind anim hack
copyarray TargetBank var8001 0x1 @ return target bank
callasm romsize+reset_objs+1 @ reset object banks
end2
.align 2
GlobalEffects:
.word 0x08017bf0
.word 0x08017c76
.word 0x08017d3c
.word 0x08017e08
.word 0x08017ee4
.word 0x08017f9c
.word 0x08018050
.word 0x080180d4
.word 0x08018144
.word 0x0801819c
.word (romsize+slide_in)
.word 0x08018220 @must be last
slide_in:
ldr r0, =SlideInRAM @ram to activate, load string from table
ldrb r0, [r0]
cmp r0, #0x0 @ does nothing if set to zero
beq NoSlide
LoadString:
sub r0, #0x1 @ start at 0
lsl r0, r0, #0x2
ldr r1, =(romsize+StringTable)
add r0, r0, r1
ldr r0, [r0]
ldr r1, .Setword
str r0, [r1] @ set string to play
LoadScript:
ldr r1, .ScriptPointer
ldr r0, =(romsize+battle_script)
str r0, [r1]
ldr r3, =(0x080181dc +1)
bx r3
NoSlide:
ldr r2, =(0x080181fc +1)
bx r2
.align 2
StringTable:
.word (romsize+String)
@etc...
.align 2
reset_objs:
push {r4, lr}
ldr r0, .ObjectBanks
ldrb r1, [r0, #0x1] @ trainer sprite obj
mov r4, r1
ldr r5, =var8002
ldr r0, [r5] @ old object banks
ldr r1, .ObjectBanks
str r0, [r1] @ return object bank data
mov r0, #68
mul r0, r1
ldr r1, .Objects
add r0, r0, r1
bl ObjDelete @ delete trainer sprite obj
exit_loop:
pop {r4}
pop {r0}
bx r0
ObjDelete:
ldr r2, =(0x08076030 +1)
bx r2
.align 2
.Setword: .word 0x0203c020
.ScriptPointer: .word 0x02023d74
.ObjectBanks: .word 0x02023d44
.Objects: .word 0x0202063c
String: .byte S_, c_, a_, r_, e_, d_, Space, P_, o_, t_, t_, e_, r_, QMark, NewBox, 0xff
You'll need to download the attached bs_macros.asm and insert this sethalfword command I wrote as well (or can just use setbyte since Whirlwind is 1 byte). I have it as command 0xFB
Code:
.text
.align 2
.thumb
.thumb_func
.global sethalfword_battle
/*
battle command to set half word
command 0xfb
insert pointer+1 at [battle_commands + 4*0xFB] to make this battle script command 0xFB
*/
main:
push {r1-r4, lr}
ldr r0, .ScriptPointer
ldr r0, [r0, #0x0] @script loc
ldrb r1, [r0, #0x1]
ldrb r2, [r0, #0x2]
lsl r2, r2, #0x08
orr r1, r2
ldrb r2, [r0, #0x3]
lsl r2, r2, #0x10
orr r1, r2
ldrb r2, [r0, #0x4]
lsl r2, r2, #0x18
orr r1, r2
ldr r0, .ScriptPointer
ldr r2, [r0]
ldrb r3, [r2, #0x5]
ldrb r4, [r2, #0x6]
lsl r4, r4, #0x8
orr r3, r4
strh r3, [r1]
add r2, #0x7
str r2, [r0]
end:
pop {r1-r4}
pop {r0}
bx r0
.align 2
.ScriptPointer: .word 0x02023d74
Of course, there are plenty of other applications and ways to implement this design. The meat of the research is around cmd53 and the pokemon objects. The main holdup for this to be complete is finding a way to completely remove the trainer sprite from the graphics after the fact. This may be a trivial solution with a simple in-game function, but I have limited experience with graphics-based asm. I will note that going into the pokemon party menu and returning fixes the problem, making me think that it has something to do with the object data.
Attachments
Last edited: