trainer 脚本指令详解

在 Pokémon 反编译项目中,"trainer" 相关的脚本指令用于触发训练家对战。核心指令是 trainerbattle ,它通过一系列宏封装来简化使用。下面从底层到上层逐一说明。

一、底层操作码

在 script_commands.h 中定义了 4 个相关的操作码:

image

二、核心宏 trainerbattle (最底层)

定义在 asm/macros/event.inc 。

.macro trainerbattle type:req, localIdA:req, trainer_a:req, intro_text_a:req,
                     lose_text_a:req, event_script_a:req, localIdB:req,
                     trainer_b:req, intro_text_b:req, lose_text_b:req,
                     event_script_b:req, victory_text:req, cannot_battle:req,
                     isDouble:req, playMusicA:req, playMusicB:req,
                     isRematch:req, rival_battle_flags=0

它编码成一个二进制结构,C 端的对应结构体定义在 battle_setup.h :

typedef union PACKED TrainerBattleParameter

{

struct PACKED _TrainerBattleParameter
{
    u8 isDoubleBattle:1;     // 位0:是否双打
    u8 isRematch:1;          // 位1:是否再战
    u8 playMusicA:1;         // 位2:是否播放训练家A的音乐
    u8 playMusicB:1;         // 位3:是否播放训练家B的音乐
    u8 mode:4;               // 位4-7:战斗模式
    u8 objEventLocalIdA;     // 训练家A的地图对象ID
    u16 opponentA;           // 训练家A的ID(TRAINER_xxx)
    u8 *introTextA;          // 训练家A的出战文本指针
    u8 *defeatTextA;         // 训练家A的战败文本指针
    u8 *battleScriptRetAddrA;// 训练家A的战后脚本指针
    u8 objEventLocalIdB;     // 训练家B的地图对象ID
    u16 opponentB;           // 训练家B的ID
    u8 *introTextB;          // 训练家B的出战文本
    u8 *defeatTextB;         // 训练家B的战败文本
    u8 *battleScriptRetAddrB;// 训练家B的战后脚本
    u8 *victoryText;         // 胜利文本(早期劲敌使用)
    u8 *cannotBattleText;    // 无法对战时的文本(双打时PM不足)
    u8 rivalBattleFlags;     // 劲敌战斗标志位
} params;
u8 data[sizeof(struct _TrainerBattleParameter)];

} TrainerBattleParameter;

三、战斗模式常量

定义在 battle_setup.h :

#define TRAINER_BATTLE_SINGLE                       0  // 普通单打

define TRAINER_BATTLE_CONTINUE_SCRIPT_NO_MUSIC 1 // 单打+继续脚本(无音乐切换)

define TRAINER_BATTLE_CONTINUE_SCRIPT 2 // 单打+继续脚本

define TRAINER_BATTLE_SINGLE_NO_INTRO_TEXT 3 // 单打(无出战文本)

define TRAINER_BATTLE_DOUBLE 4 // 双打

define TRAINER_BATTLE_REMATCH 5 // 再战(单打)

define TRAINER_BATTLE_CONTINUE_SCRIPT_DOUBLE 6 // 双打+继续脚本

define TRAINER_BATTLE_REMATCH_DOUBLE 7 // 再战(双打)

define TRAINER_BATTLE_CONTINUE_SCRIPT_DOUBLE_NO_MUSIC 8 // 双打+继续脚本(无音乐切换)

define TRAINER_BATTLE_TWO_TRAINERS_NO_INTRO 13 // 双打(两名独立训练家,无出场文本)

define TRAINER_BATTLE_EARLY_RIVAL 14 // 早期劲敌战斗

四、常用封装的宏(实际开发中使用的)

4.1 trainerbattle_single — 普通单打对战

.macro trainerbattle_single trainer:req, intro_text:req, lose_text:req,
                        event_script=FALSE, music=TRUE</code></pre><p>参数说明:</p><p>- trainer — 训练家 ID,如 TRAINER_ROXANNE<br>- intro_text — 训练家出战时的对话文本<br>- lose_text — 训练家战败时的对话文本<br>- event_script — 选填。战后执行的脚本(不需要则填 FALSE )<br>- music — 选填。是否切换为训练家战斗音乐(默认 TRUE )<br>实际用法示例:</p><p>最简单的用法 (战后不执行额外脚本):</p><pre class="enlighter-pre"><code class="gl">Route127_EventScript_Henry::
trainerbattle_single TRAINER_HENRY, Route127_Text_HenryIntro, Route127_Text_HenryDefeat
msgbox Route127_Text_HenryPostBattle, MSGBOX_AUTOCLOSE
end</code></pre><p>战后流程:战斗结束后 → 显示战后消息 → 结束。</p><p>带战后脚本的用法 (用于注册 Match Call 等):</p><pre class="enlighter-pre"><code class="gl">Route127_EventScript_Koji::
trainerbattle_single TRAINER_KOJI_1, Route127_Text_KojiIntro,
                     Route127_Text_KojiDefeat, Route127_EventScript_RegisterKoji
specialvar VAR_RESULT, ShouldTryRematchBattle
goto_if_eq VAR_RESULT, TRUE, Route127_EventScript_RematchKoji
msgbox Route127_Text_KojiPostBattle, MSGBOX_DEFAULT
release
end</code></pre><p>战后流程:战斗结束 → 执行 Route127_EventScript_RegisterKoji (注册对战寻呼)→ 回到继续执行后续脚本。</p><p>trainerbattle_single 的内部逻辑:</p><p>- 不传 event_script → 使用 TRAINER_BATTLE_SINGLE (模式0)<br>- 传了 event_script + music=TRUE → 使用 TRAINER_BATTLE_CONTINUE_SCRIPT (模式2)<br>- 传了 event_script + music=FALSE → 使用 TRAINER_BATTLE_CONTINUE_SCRIPT_NO_MUSIC (模式1)</p><p><strong>4.2 trainerbattle_double — 双打对战(与同一训练家)</strong></p><pre class="enlighter-pre"><code class="gl">.macro trainerbattle_double trainer:req, intro_text:req, lose_text:req,
                        not_enough_pkmn_text:req, event_script=FALSE,
                        music=TRUE</code></pre><p>参数说明:</p><p>- 相较单打多了一个 not_enough_pkmn_text — 玩家 PM 不足 2 只时显示的文本<br>实际用法示例:</p><pre class="enlighter-pre"><code class="gl">Route103_EventScript_Amy::
trainerbattle_double TRAINER_AMY_AND_LIV_1, Route103_Text_AmyIntro,
                     Route103_Text_AmyDefeated, Route103_Text_AmyNotEnoughPokemon,
                     Route102_EventScript_AmyRegisterMatchCallAfterBattle
specialvar VAR_RESULT, ShouldTryRematchBattle
goto_if_eq VAR_RESULT, TRUE, Route102_EventScript_AmyRematch
msgbox Route103_Text_AmyPostBattle, MSGBOX_AUTOCLOSE
end</code></pre><p>注意这里的 TRAINER_AMY_AND_LIV_1 是 一个 训练家数据条目,但其队伍设置中包含多只 PM,战斗时强制双打。</p><p><strong>4.3 trainerbattle_rematch — 再战(单打)</strong></p><pre class="enlighter-pre"><code class="gl">.macro trainerbattle_rematch trainer:req, intro_text:req, lose_text:req</code></pre><p>实际用法示例:</p><pre class="enlighter-pre"><code class="gl">Route127_EventScript_RematchKoji::
trainerbattle_rematch TRAINER_KOJI_1, Route127_Text_KojiRematchIntro,
                      Route127_Text_KojiRematchDefeat
msgbox Route127_Text_KojiPostRematch, MSGBOX_AUTOCLOSE
end</code></pre><p>它会自动将 isRematch 标志位置 1,并且 BattleSetup_ConfigureTrainerBattle 中会调用 GetRematchTrainerId() 来获取强化后的训练家数据。</p><p><strong>4.4 trainerbattle_rematch_double — 再战(双打)</strong></p><pre class="enlighter-pre"><code class="gl">.macro trainerbattle_rematch_double trainer:req, intro_text:req, lose_text:req,
                                not_enough_pkmn_text:req</code></pre><p>与再战单打类似,但强制双打。</p><p><strong>4.5 trainerbattle_no_intro — 无开场白的单打</strong></p><pre class="enlighter-pre"><code class="gl">.macro trainerbattle_no_intro trainer:req, lose_text:req</code></pre><p>不显示训练家的出场对话,直接进入战斗。</p><p><strong>4.6 trainerbattle_two_trainers — 两名独立训练家的双打</strong></p><pre class="enlighter-pre"><code class="gl">.macro trainerbattle_two_trainers trainer_a:req, lose_text_a:req,
                              trainer_b:req, lose_text_b:req</code></pre><p>用于两名独立的 NPC 组队与玩家双打的情况。 localIdA 和 localIdB 分别对应两个地图对象。</p><p><strong>4.7 trainerbattle_earlyrival — 早期劲敌战斗</strong></p><pre class="enlighter-pre"><code class="gl">.macro trainerbattle_earlyrival trainer:req, flags:req, lose_text:req, victory_text:req</code></pre><p>- flags — 劲敌标志位(控制治疗后是否复活等行为)<br>- victory_text — 玩家胜利后的文本</p><h2>五、执行流程(数据流)</h2><pre class="enlighter-pre"><code class="gl">脚本引擎执行 trainerbattle 指令
    │
    ▼

ScrCmd_trainerbattle() ← [src/scrcmd.c]

  1. TrainerBattleLoadArgs() — 解析二进制参数 → gTrainerBattleParameter
  2. BattleSetup_ConfigureTrainerBattle()
    根据 mode 返回对应的脚本入口

进入战斗流程脚本 ← [data/scripts/trainer_battle.inc]
EventScript_TryDoNormalTrainerBattle:

lock → faceplayer → 训练家转身动画
→ 检查是否已打过(GetTrainerFlag)
→ 播放音乐(PlayTrainerEncounterMusic)
→ 显示训练家出场文本
→ dotrainerbattle ← 真正触发战斗!
    │
    ▼

ScrCmd_dotrainerbattle() → BattleSetup_StartTrainerBattle()

    │
    ▼

战斗结束

    │
    ▼

gotobeatenscript → ScrCmd_gotobeatenscript()
跳转到战斗结束后继续执行脚本

六、总结——如何选择正确的宏

image

核心要点是: event_script 参数决定了战斗结束后是否返回原来的脚本继续执行后面的指令 。如果传了 event_script ,战斗结束后会先跳转到该脚本执行,然后再回到 trainerbattle 的下一条指令继续。如果不传,则战斗结束后直接继续执行下一条指令。

支持这篇文章点赞、收藏或分享给朋友
DISCUSSION

发表评论

留下你的想法

输入 @用户名 可以提及站内用户0 / 5000