mirror of
https://github.com/pret/pokeemerald.git
synced 2026-09-14 22:18:15 -05:00
Merge branch '_RHH/upcoming' into _RHH/pr/upcoming/lighting-expansion-v2
This commit is contained in:
@@ -29,12 +29,14 @@ This section lists all of expansion’s AI Flags and briefly describes the effec
|
||||
|
||||
## Composite AI Flags
|
||||
|
||||
Expansion has two "composite" AI flags, `AI_FLAG_BASIC_TRAINER` and `AI_FLAG_SMART_TRAINER`. This means that these flags have no unique functionality themselves, and can instead be thought of as groups of other flags that are all enabled when this flag is enabled. The idea behind these flags is that if you don't care to manage the detailed behaviour of a particular trainer, you can use these as a baseline instead, and expansion will keep them updated for you.
|
||||
Expansion has a few "composite" AI flags. This means that these flags have no unique functionality themselves, and can instead be thought of as groups of other flags that are all enabled when this flag is enabled. The idea behind these flags is that if you don't care to manage the detailed behaviour of a particular trainer, you can use these as a baseline instead, and expansion will keep them updated for you.
|
||||
|
||||
`AI_FLAG_BASIC_TRAINER` is expansion's version of generic, normal AI behaviour. It includes `AI_FLAG_CHECK_BAD_MOVE` (don't use bad moves), `AI_FLAG_TRY_TO_FAINT` (faint the player where possible), and `AI_FLAG_CHECK_VIABILITY` (choose the most effective move to use in the current context). Trainers with this flag will still be smarter than they are in vanilla as there have been dramatic improvements made to move selection, but not incredibly so. Trainers with this flag should feel like normal trainers. In general we recommend these three flags be used in all cases, unless you specifically want a trainer who makes obvious mistakes in battle.
|
||||
|
||||
`AI_FLAG_SMART_TRAINER` is expansion's version of a "smart AI". It includes everything in `AI_FLAG_BASIC_TRAINER` along with `AI_FLAG_SMART_SWITCHING` (make smart decisions about when to switch), `AI_FLAG_SMART_MON_CHOICES` (make smart decisions about what mon to send in after a switch / KO), and `AI_FLAG_OMNISCIENT` (awareness of what moves, items, and abilities the player's mons have to better inform decisions). Expansion will keep this updated to represent the most objectively intelligent behaviour our flags are capable of producing.
|
||||
|
||||
`AI_FLAG_PREDICTION` will enable all of the prediction flags at once, so the AI can perform as well as possible. It is best paired with the flags in `AI_FLAG_SMART_TRAINER` for optimal behaviour. This currently includes `AI_FLAG_PREDICT_SWITCH` and `AI_FLAG_PREDICT_INCOMING_MON`, but will likely be expanded in the future.
|
||||
|
||||
Expansion has LOADS of flags, which will be covered in the rest of this guide. If you don't want to engage with detailed trainer AI tuning though, you can just use these two composite flags, and trust that expansion will keep their contents updated to always represent the most standard and the smartest behaviour we can.
|
||||
|
||||
## `AI_FLAG_CHECK_BAD_MOVE`
|
||||
@@ -171,3 +173,9 @@ AI will predict the player's ability based to its aiRating. Without this flag th
|
||||
|
||||
## `AI_FLAG_PREFER_HIGHEST_DAMAGE_MOVE`
|
||||
AI will add score to its highest damaging move, regardless of accuracy or secondary effects. Replaces deprecated `AI_FLAG_PREFER_STRONGEST_MOVE`.
|
||||
|
||||
## `AI_FLAG_PREDICT_SWITCH`
|
||||
AI will determine whether it would switch out in the player's situation or not, and predict the player to switch accordingly. In any case where the AI would consider switching, it will assume the player will switch. This is modulated by a 50% failure rate, so the behaviour is non-deterministic and can change from turn to turn to emulate the inconsistency in human predictions. This behaviour is improved significantly by using `AI_FLAG_SMART_SWITCHING` and `AI_FLAG_SMART_MON_CHOICES` as they improve the AI's ability to determine good situations to switch, and also by `AI_FLAG_OMNISCIENT` so the AI can use all its knowledge of the player's team to make the decision.
|
||||
|
||||
## `AI_FLAG_PREDICT_INCOMING_MON`
|
||||
This flag requires `AI_FLAG_PREDICT_SWITCH` to function. If the AI predicts that the player will switch, this flag allows the AI to run its move scoring calculation against the Pokémon it expects the player to switch into, instead of the Pokémon that it expects to switch out.
|
||||
|
||||
@@ -169,8 +169,6 @@ Each move can have up to 15 additional effects, allowing you to construct monstr
|
||||
.moveEffect = MOVE_EFFECT_ALL_STATS_UP,
|
||||
.chance = 40,
|
||||
.self = TRUE,
|
||||
},{
|
||||
.moveEffect = MOVE_EFFECT_RAPID_SPIN,
|
||||
},{
|
||||
.moveEffect = MOVE_EFFECT_DEF_MINUS_2,
|
||||
.chance = 50,
|
||||
|
||||
205
docs/tutorials/how_to_new_trainer_slide.md
Normal file
205
docs/tutorials/how_to_new_trainer_slide.md
Normal file
@@ -0,0 +1,205 @@
|
||||
# Adding New Trainer Slides
|
||||
## Define Slides Per Trainer
|
||||
|
||||
We are going to add a Trainer Slide to Wally's first Victory Road battle, before he Mega Evolves his Gallade. This battle takes place outside a Battle Facility, so `sTrainerSlides` must be edited.
|
||||
|
||||
### `src/trainer_slide.c`
|
||||
```diff
|
||||
+ const u8 gText_ThatsTheWay[] = _("That's the way, Gallade! Go!{PAUSE_UNTIL_PRESS}");
|
||||
|
||||
static const u8* const sTrainerSlides[DIFFICULTY_COUNT][TRAINERS_COUNT][TRAINER_SLIDE_COUNT] =
|
||||
{
|
||||
[DIFFICULTY_NORMAL] =
|
||||
{
|
||||
+ [TRAINER_WALLY_VR_1] = // use the Trainer's Id from include/constants/opponents.h
|
||||
+ {
|
||||
+ [TRAINER_SLIDE_MEGA_EVOLUTION] = COMPOUND_STRING("That's the way, Gallade! Go!{PAUSE_UNTIL_PRESS}"), // find the id for the slide to be used.
|
||||
+ //[TRAINER_SLIDE_MEGA_EVOLUTION] = gText_ThatsTheWay, // You can use globals or COMPOUND_STRING to define text here.
|
||||
+ }
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
If we were to edit a Trainer that appears in a Battle Facility, `sFrontierTrainerSlides` would be edited instead. Here, we'll give Anabel a line before she uses a Z-Move.
|
||||
|
||||
### `src/trainer_slide.c`
|
||||
```diff
|
||||
static const u8* const sFrontierTrainerSlides[DIFFICULTY_COUNT][FRONTIER_TRAINERS_COUNT][TRAINER_SLIDE_COUNT] =
|
||||
{
|
||||
[DIFFICULTY_NORMAL] =
|
||||
{
|
||||
+ [TRAINER_ANABEL] =
|
||||
+ {
|
||||
+ [TRAINER_SLIDE_Z_MOVE] = COMPOUND_STRING("Victory...is ours!"), //{PAUSE_UNTIL_PRESS} is omitted, so the battle will continue as soon as the next is finished printing.
|
||||
+ }
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Add New Slides
|
||||
* [Example Commit](<https://github.com/rh-hideout/pokeemerald-expansion/commit/d6424688007cbd923c861cfd35272e5ebfaa4016>)
|
||||
* [Patch](<https://github.com/rh-hideout/pokeemerald-expansion/commit/d6424688007cbd923c861cfd35272e5ebfaa4016.patch>)
|
||||
* [Diff](<https://github.com/rh-hideout/pokeemerald-expansion/commit/d6424688007cbd923c861cfd35272e5ebfaa4016.diff>)
|
||||
|
||||
### `include/constants/trainer_slide.h`
|
||||
```diff
|
||||
enum TrainerSlideType
|
||||
{
|
||||
TRAINER_SLIDE_BEFORE_FIRST_TURN,
|
||||
TRAINER_SLIDE_PLAYER_LANDS_FIRST_CRITICAL_HIT,
|
||||
+ TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT, // Each Trainer Slide has a unqiue id.
|
||||
TRAINER_SLIDE_PLAYER_LANDS_FIRST_SUPER_EFFECTIVE_HIT,
|
||||
TRAINER_SLIDE_PLAYER_LANDS_FIRST_STAB_MOVE,
|
||||
TRAINER_SLIDE_PLAYER_LANDS_FIRST_DOWN,
|
||||
TRAINER_SLIDE_ENEMY_MON_UNAFFECTED,
|
||||
TRAINER_SLIDE_LAST_SWITCHIN,
|
||||
TRAINER_SLIDE_LAST_HALF_HP,
|
||||
TRAINER_SLIDE_LAST_LOW_HP,
|
||||
TRAINER_SLIDE_MEGA_EVOLUTION,
|
||||
TRAINER_SLIDE_Z_MOVE,
|
||||
TRAINER_SLIDE_DYNAMAX,
|
||||
TRAINER_SLIDE_COUNT,
|
||||
};
|
||||
```
|
||||
|
||||
Each Trainer Slide has a unique id and needs to be added to this list.
|
||||
|
||||
### `include/trainer_slide.h`
|
||||
|
||||
If your new Trainer Slide needs to check for beforen initalized, a function is declared here to be used externally. Critical hits are used to initalize this Trainer Slide but the slide doesn't play instantly, so we will create an function to initialize it.
|
||||
|
||||
```diff
|
||||
void SetTrainerSlideMessage(enum DifficultyLevel, u32, u32);
|
||||
void TryInitalizeFirstSTABMoveTrainerSlide(u32, u32, u32);
|
||||
void TryInitalizeTrainerSlidePlayerLandsFirstCriticalHit(u32);
|
||||
+ void TryInitalizeTrainerSlideEnemyLandsFirstCriticalHit(u32);
|
||||
void TryInitalizeTrainerSlidePlayerLandsFirstSuperEffectiveHit(u32);
|
||||
void TryInitalizeTrainerSlideEnemyMonUnaffected(u32);
|
||||
bool32 IsTrainerSlideInitialized(enum TrainerSlideType);
|
||||
```
|
||||
### `src/trainer_slide.c`
|
||||
|
||||
```diff
|
||||
return IsTrainerSlideInitialized(slideId);
|
||||
}
|
||||
|
||||
+static bool32 ShouldRunTrainerSlideEnemyLandsFirstCriticalHit(enum TrainerSlideType slideId)
|
||||
+{
|
||||
+ return IsTrainerSlideInitialized(slideId);
|
||||
+}
|
||||
+
|
||||
static bool32 ShouldRunTrainerSlidePlayerLandsFirstSuperEffectiveHit(u32 battler, enum TrainerSlideType slideId)
|
||||
{
|
||||
if (!IsTrainerSlideInitialized(slideId))
|
||||
```
|
||||
|
||||
```diff
|
||||
case TRAINER_SLIDE_PLAYER_LANDS_FIRST_CRITICAL_HIT:
|
||||
shouldRun = ShouldRunTrainerSlidePlayerLandsFirstCriticalHit(slideId);
|
||||
break;
|
||||
+ case TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT:
|
||||
+ shouldRun = ShouldRunTrainerSlideEnemyLandsFirstCriticalHit(slideId);
|
||||
+ break;
|
||||
case TRAINER_SLIDE_PLAYER_LANDS_FIRST_SUPER_EFFECTIVE_HIT:
|
||||
shouldRun = ShouldRunTrainerSlidePlayerLandsFirstSuperEffectiveHit(battler, slideId);
|
||||
break;
|
||||
```
|
||||
|
||||
The function that determines if a Slide should play has different function for most Slides. We need to check if this Slide was initialized by a critical hit previously, so a function is created here. This function and the Id and then added to the switch statement in `ShouldDoTrainerSlide`.
|
||||
|
||||
```diff
|
||||
InitalizeTrainerSlide(slideId);
|
||||
}
|
||||
|
||||
+void TryInitalizeTrainerSlideEnemyLandsFirstCriticalHit(u32 target)
|
||||
+{
|
||||
+ enum TrainerSlideType slideId = TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT;
|
||||
+
|
||||
+ if (IsSlideInitalizedOrPlayed(slideId))
|
||||
+ return;
|
||||
+
|
||||
+ if (GetBattlerSide(target) == B_SIDE_OPPONENT)
|
||||
+ return;
|
||||
+
|
||||
+ InitalizeTrainerSlide(slideId);
|
||||
+}
|
||||
+
|
||||
```
|
||||
|
||||
The function to check if this slide SHOULD be initalized is added to the bottom of this file.
|
||||
|
||||
### `src/battle_main.c`
|
||||
|
||||
```diff
|
||||
BattleScriptExecute(i == 1 ? BattleScript_TrainerASlideMsgEnd2 : BattleScript_TrainerBSlideMsgEnd2);
|
||||
else if ((i = ShouldDoTrainerSlide(GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT), TRAINER_SLIDE_PLAYER_LANDS_FIRST_CRITICAL_HIT)))
|
||||
BattleScriptExecute(i == 1 ? BattleScript_TrainerASlideMsgEnd2 : BattleScript_TrainerBSlideMsgEnd2);
|
||||
+ else if ((i = ShouldDoTrainerSlide(GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT), TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT)))
|
||||
+ BattleScriptExecute(i == 1 ? BattleScript_TrainerASlideMsgEnd2 : BattleScript_TrainerBSlideMsgEnd2);
|
||||
else if ((i = ShouldDoTrainerSlide(GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT), TRAINER_SLIDE_PLAYER_LANDS_FIRST_SUPER_EFFECTIVE_HIT)))
|
||||
BattleScriptExecute(i == 1 ? BattleScript_TrainerASlideMsgEnd2 : BattleScript_TrainerBSlideMsgEnd2);
|
||||
else if ((i = ShouldDoTrainerSlide(GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT), TRAINER_SLIDE_PLAYER_LANDS_FIRST_STAB_MOVE)))
|
||||
diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c
|
||||
```
|
||||
|
||||
In `BattleTurnPassed`, most Trainer Slides are checked to see if they should run, so our new call is added here.
|
||||
|
||||
### `src/battle_script_commands.c`
|
||||
|
||||
```diff
|
||||
{
|
||||
PrepareStringBattle(STRINGID_CRITICALHIT, gBattlerAttacker);
|
||||
|
||||
+ TryInitalizeTrainerSlideEnemyLandsFirstCriticalHit(gBattlerTarget);
|
||||
TryInitalizeTrainerSlidePlayerLandsFirstCriticalHit(gBattlerTarget);
|
||||
|
||||
gBattleCommunication[MSG_DISPLAY] = 1;
|
||||
```
|
||||
|
||||
The actual usage of `TryInitalizeTrainerSlideEnemyLandsFirstCriticalHit` is added and is checked whenever a critical hit is scored.
|
||||
|
||||
### `test/battle/trainer_slides.c`
|
||||
```diff
|
||||
}
|
||||
}
|
||||
|
||||
+SINGLE_BATTLE_TEST("Trainer Slide: Enemy Lands First Critical Hit")
|
||||
+{
|
||||
+ gBattleTestRunnerState->data.recordedBattle.opponentA = TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT;
|
||||
+
|
||||
+ GIVEN {
|
||||
+ ASSUME(GetMoveEffect(MOVE_LASER_FOCUS) == EFFECT_LASER_FOCUS);
|
||||
+ PLAYER(SPECIES_WOBBUFFET);
|
||||
+ OPPONENT(SPECIES_WOBBUFFET);
|
||||
+ } WHEN {
|
||||
+ TURN { MOVE(opponent, MOVE_LASER_FOCUS); }
|
||||
+ TURN { MOVE(opponent, MOVE_TACKLE); }
|
||||
+ } SCENE {
|
||||
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_LASER_FOCUS, opponent);
|
||||
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
|
||||
+ MESSAGE("A critical hit!");
|
||||
+ MESSAGE("This message plays after the enemy lands their first critical hit.{PAUSE_UNTIL_PRESS}");
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
SINGLE_BATTLE_TEST("Trainer Slide: Player Lands First STAB Hit")
|
||||
{
|
||||
gBattleTestRunnerState->data.recordedBattle.opponentA = TRAINER_SLIDE_PLAYER_LANDS_FIRST_STAB_MOVE;
|
||||
diff --git a/test/battle/trainer_slides.h b/test/battle/trainer_slides.h
|
||||
```
|
||||
|
||||
### `test/battle/trainer_slides.h`
|
||||
```diff
|
||||
|
||||
[TRAINER_SLIDE_PLAYER_LANDS_FIRST_CRITICAL_HIT] = COMPOUND_STRING("This message plays after the player lands their first critical hit.{PAUSE_UNTIL_PRESS}"),
|
||||
},
|
||||
+ [TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT] =
|
||||
+ {
|
||||
+ [TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT] = COMPOUND_STRING("This message plays after the enemy lands their first critical hit.{PAUSE_UNTIL_PRESS}"),
|
||||
+ },
|
||||
[TRAINER_SLIDE_PLAYER_LANDS_FIRST_SUPER_EFFECTIVE_HIT] =
|
||||
{
|
||||
[TRAINER_SLIDE_PLAYER_LANDS_FIRST_SUPER_EFFECTIVE_HIT] = COMPOUND_STRING("This message plays after the player lands their first super effective hit.{PAUSE_UNTIL_PRESS}"),
|
||||
```
|
||||
|
||||
Tests are added to make sure the new Trainer Slide works. A test is added to the c file, and the trainer to run the entry in the test is added to `sTestTrainerSlides`.
|
||||
Reference in New Issue
Block a user