From 99f1a8208d27caf6336fb8c41f59db2ed212dc09 Mon Sep 17 00:00:00 2001 From: Eduardo Quezada D'Ottone Date: Thu, 16 Nov 2023 07:30:58 -0300 Subject: [PATCH 01/18] Point to the configs easier (#3564) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 520dd5930f1a..abadccd0e824 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ The pokeemerald Expansion is a feature branch meant to be integrated into existing Pokémon Emerald hacks based off pret's [pokeemerald](https://github.com/pret/pokeemerald) decompilation project. This is ***NOT*** a standalone romhack, and as such, most features will be unavailable and/or unbalanced if played as is. ## What features are included? -- Configuration files that allows you to choose generation-specific behaviors. Full contents here: +- ***IMPORTANT*❗❗ Read through these to learn what features you can toggle**: - [Battle configurations](/include/config/battle.h) - [Pokémon configurations](/include/config/pokemon.h) - [Item configurations](/include/config/item.h) From b33c4952af2ef864ec76d8cced05b5da976f75be Mon Sep 17 00:00:00 2001 From: Eduardo Quezada D'Ottone Date: Thu, 16 Nov 2023 19:32:58 -0300 Subject: [PATCH 02/18] Clarify project's description to avoid people cloning vanilla pokeemerald (#3570) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index abadccd0e824..19d4f23f5a04 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## What is the pokeemerald Expansion? -The pokeemerald Expansion is a feature branch meant to be integrated into existing Pokémon Emerald hacks based off pret's [pokeemerald](https://github.com/pret/pokeemerald) decompilation project. This is ***NOT*** a standalone romhack, and as such, most features will be unavailable and/or unbalanced if played as is. +The pokeemerald Expansion is a decomp hack base project based off pret's [pokeemerald](https://github.com/pret/pokeemerald) decompilation project. It's recommended that any new projects that plan on using it, to clone this repository instead of pret's vanilla repository, as we regurlarly incorporate pret's documentation changes. This is ***NOT*** a standalone romhack, and as such, most features will be unavailable and/or unbalanced if played as is. ## What features are included? - ***IMPORTANT*❗❗ Read through these to learn what features you can toggle**: From 5f24bf6e776ea6955963515e7aa425b9306ded6d Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Fri, 17 Nov 2023 11:49:01 -0300 Subject: [PATCH 03/18] PR template test --- .github/{ => PULL_REQUEST_TEMPLATE}/pull_request_template.md | 0 .github/PULL_REQUEST_TEMPLATE/pull_request_template_2.md | 1 + 2 files changed, 1 insertion(+) rename .github/{ => PULL_REQUEST_TEMPLATE}/pull_request_template.md (100%) create mode 100644 .github/PULL_REQUEST_TEMPLATE/pull_request_template_2.md diff --git a/.github/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md similarity index 100% rename from .github/pull_request_template.md rename to .github/PULL_REQUEST_TEMPLATE/pull_request_template.md diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template_2.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template_2.md new file mode 100644 index 000000000000..3bfb99293162 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template_2.md @@ -0,0 +1 @@ +## Example From 8f3eb1c9a55763346970a4348a208af7b134f111 Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Fri, 17 Nov 2023 11:53:25 -0300 Subject: [PATCH 04/18] PR template test 2 --- .github/pull_request_template.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000000..d4e3d6cd2876 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,4 @@ +Please go the the `Preview` tab and select the appropriate sub-template: + +* [Group A](?expand=1&template=pull_request_template.md) +* [Group B](?expand=1&template=pull_request_template_2.md) From 508332a93f1f04eacdc3a4128310851378463ea5 Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Fri, 17 Nov 2023 11:59:34 -0300 Subject: [PATCH 05/18] Reverted PR template tests --- .../pull_request_template.md | 16 ---------------- .../pull_request_template_2.md | 1 - .github/pull_request_template.md | 18 +++++++++++++++--- 3 files changed, 15 insertions(+), 20 deletions(-) delete mode 100644 .github/PULL_REQUEST_TEMPLATE/pull_request_template.md delete mode 100644 .github/PULL_REQUEST_TEMPLATE/pull_request_template_2.md diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md deleted file mode 100644 index 930d20a01858..000000000000 --- a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +++ /dev/null @@ -1,16 +0,0 @@ - - -## Description - - -## Images - - - -## Issue(s) that this PR fixes - - - -## **Discord contact info** - - \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template_2.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template_2.md deleted file mode 100644 index 3bfb99293162..000000000000 --- a/.github/PULL_REQUEST_TEMPLATE/pull_request_template_2.md +++ /dev/null @@ -1 +0,0 @@ -## Example diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index d4e3d6cd2876..930d20a01858 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,4 +1,16 @@ -Please go the the `Preview` tab and select the appropriate sub-template: + -* [Group A](?expand=1&template=pull_request_template.md) -* [Group B](?expand=1&template=pull_request_template_2.md) +## Description + + +## Images + + + +## Issue(s) that this PR fixes + + + +## **Discord contact info** + + \ No newline at end of file From c9c0827ba2031602c948549be0478c1d234dd504 Mon Sep 17 00:00:00 2001 From: Bassoonian Date: Mon, 20 Nov 2023 19:33:06 +0100 Subject: [PATCH 06/18] Fix 3568 (#3569) --- src/battle_util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/battle_util.c b/src/battle_util.c index 7eb4aa192082..e9bb83e19b28 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -6099,7 +6099,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 break; } - if (effect && gLastUsedAbility != 0xFF) + if (effect && gLastUsedAbility != 0xFFFF) RecordAbilityBattle(battler, gLastUsedAbility); if (effect && caseID <= ABILITYEFFECT_MOVE_END) gBattlerAbility = battler; From ca39889587281cfda54d2dd554cebbd9f00f7e67 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 22 Nov 2023 21:55:08 +0100 Subject: [PATCH 07/18] Fixes Howl --- data/battle_scripts_1.s | 8 +++++ test/battle/move_effect/attack_up_user_ally.c | 33 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 898be404b5c6..07e0bf065aa2 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -596,6 +596,7 @@ BattleScript_EffectAttackUpUserAlly_TryAlly: BattleScript_EffectAttackUpUserAlly_End: goto BattleScript_MoveEnd BattleScript_EffectAttackUpUserAlly_TryAlly_: + jumpifability BS_ATTACKER_PARTNER, ABILITY_SOUNDPROOF, BattleScript_EffectAttackUpUserAlly_TryAllyBlocked setstatchanger STAT_ATK, 1, FALSE statbuffchange STAT_CHANGE_ALLOW_PTR, BattleScript_EffectAttackUpUserAlly_End jumpifbyte CMP_NOT_EQUAL, cMULTISTRING_CHOOSER, B_MSG_STAT_WONT_INCREASE, BattleScript_EffectAttackUpUserAlly_AllyAnim @@ -610,6 +611,13 @@ BattleScript_EffectAttackUpUserAlly_AllyAnim: waitmessage B_WAIT_TIME_LONG goto BattleScript_EffectAttackUpUserAlly_End +BattleScript_EffectAttackUpUserAlly_TryAllyBlocked: + copybyte sBATTLER, gBattlerTarget + call BattleScript_AbilityPopUpTarget + printstring STRINGID_PKMNSXBLOCKSY2 + waitmessage B_WAIT_TIME_LONG + goto BattleScript_MoveEnd + BattleScript_EffectTeatime:: attackcanceler attackstring diff --git a/test/battle/move_effect/attack_up_user_ally.c b/test/battle/move_effect/attack_up_user_ally.c index 6691b49ed8f7..4b7fdae084de 100644 --- a/test/battle/move_effect/attack_up_user_ally.c +++ b/test/battle/move_effect/attack_up_user_ally.c @@ -63,3 +63,36 @@ DOUBLE_BATTLE_TEST("Howl raises user's and partner's Attack", s16 damageLeft, s1 EXPECT_MUL_EQ(results[0].damageRight, Q_4_12(1.5), results[1].damageRight); } } + +DOUBLE_BATTLE_TEST("Howl does not work on partner if it has Soundproof") +{ + s16 damage[2]; + + GIVEN { + ASSUME(gBattleMoves[MOVE_TACKLE].split == SPLIT_PHYSICAL); + PLAYER(SPECIES_WOBBUFFET) { Speed(15); } + PLAYER(SPECIES_VOLTORB) { Speed(10); Ability(ABILITY_SOUNDPROOF); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(SPECIES_WYNAUT) { Speed(1); } + } WHEN { + TURN { MOVE(playerRight, MOVE_TACKLE, target: opponentLeft); } + TURN { MOVE(playerLeft, MOVE_HOWL); MOVE(playerRight, MOVE_TACKLE, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight); + HP_BAR(opponentLeft, captureDamage: &damage[0]); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_HOWL, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Wobbuffet's Attack rose!"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Wynaut's Attack rose!"); + } + ABILITY_POPUP(playerRight, ABILITY_SOUNDPROOF); + MESSAGE("Voltorb's Soundproof blocks Howl!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight); + HP_BAR(opponentLeft, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + } +} From 71fd95e6d46d255c3fec0dfadd7407d36fc6cdfb Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Thu, 23 Nov 2023 19:49:21 +0100 Subject: [PATCH 08/18] Fixes Protosynthesis and Quark Drive ability pop up (#3595) --- include/battle.h | 4 +-- src/battle_util.c | 23 +++++++------ test/battle/ability/protosynthesis.c | 47 +++++++++++++++++++++++++++ test/battle/ability/quark_drive.c | 48 ++++++++++++++++++++++++++++ 4 files changed, 111 insertions(+), 11 deletions(-) diff --git a/include/battle.h b/include/battle.h index 5b2ed5c6de74..0ad6c172a41a 100644 --- a/include/battle.h +++ b/include/battle.h @@ -103,6 +103,8 @@ struct DisableStruct u8 toxicSpikesDone:1; u8 stickyWebDone:1; u8 stealthRockDone:1; + u8 weatherAbilityDone:1; + u8 terrainAbilityDone:1; }; struct ProtectStruct @@ -193,8 +195,6 @@ struct SpecialStatus u8 dancerUsedMove:1; u8 dancerOriginalTarget:3; // End of byte - u8 weatherAbilityDone:1; - u8 terrainAbilityDone:1; u8 emergencyExited:1; u8 afterYou:1; }; diff --git a/src/battle_util.c b/src/battle_util.c index e9bb83e19b28..bdce8fbc9dc3 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -1968,12 +1968,13 @@ u32 GetBattlerFriendshipScore(u32 battler) return GetMonFriendshipScore(&party[gBattlerPartyIndexes[battler]]); } -static void TryToRevertMimicry(void) +static void TryToRevertMimicryAndFlags(void) { u32 i; for (i = 0; i < gBattlersCount; i++) { + gDisableStructs[i].terrainAbilityDone = FALSE; if (GetBattlerAbility(i) == ABILITY_MIMICRY) RESTORE_BATTLER_TYPE(i); } @@ -2022,7 +2023,7 @@ static bool32 EndTurnTerrain(u32 terrainFlag, u32 stringTableId) if (!(gFieldStatuses & STATUS_FIELD_TERRAIN_PERMANENT) && --gFieldTimers.terrainTimer == 0) { gFieldStatuses &= ~terrainFlag; - TryToRevertMimicry(); + TryToRevertMimicryAndFlags(); gBattleCommunication[MULTISTRING_CHOOSER] = stringTableId; BattleScriptExecute(BattleScript_TerrainEnds); return TRUE; @@ -2322,6 +2323,8 @@ u8 DoFieldEndTurnEffects(void) && --gWishFutureKnock.weatherDuration == 0) { gBattleWeather &= ~B_WEATHER_SUN_TEMPORARY; + for (i = 0; i < gBattlersCount; i++) + gDisableStructs[i].weatherAbilityDone = FALSE; gBattlescriptCurrInstr = BattleScript_SunlightFaded; } else @@ -4051,6 +4054,7 @@ static bool32 TryChangeBattleTerrain(u32 battler, u32 statusFlag, u8 *timer) { gFieldStatuses &= ~(STATUS_FIELD_MISTY_TERRAIN | STATUS_FIELD_GRASSY_TERRAIN | STATUS_FIELD_ELECTRIC_TERRAIN | STATUS_FIELD_PSYCHIC_TERRAIN); gFieldStatuses |= statusFlag; + gDisableStructs[battler].terrainAbilityDone = FALSE; if (GetBattlerHoldEffect(battler, TRUE) == HOLD_EFFECT_TERRAIN_EXTENDER) *timer = 8; @@ -6061,10 +6065,11 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITY_PROTOSYNTHESIS: - if (!gSpecialStatuses[battler].weatherAbilityDone && IsBattlerWeatherAffected(battler, B_WEATHER_SUN)) + if (!gDisableStructs[battler].weatherAbilityDone && IsBattlerWeatherAffected(battler, B_WEATHER_SUN)) { - gSpecialStatuses[battler].weatherAbilityDone = TRUE; + gDisableStructs[battler].weatherAbilityDone = TRUE; PREPARE_STAT_BUFFER(gBattleTextBuff1, GetHighestStatId(battler)); + gBattlerAbility = gBattleScripting.battler = battler; BattleScriptPushCursorAndCallback(BattleScript_ProtosynthesisActivates); effect++; } @@ -6076,9 +6081,9 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 switch (gLastUsedAbility) { case ABILITY_MIMICRY: - if (!gSpecialStatuses[battler].terrainAbilityDone && ChangeTypeBasedOnTerrain(battler)) + if (!gDisableStructs[battler].terrainAbilityDone && ChangeTypeBasedOnTerrain(battler)) { - gSpecialStatuses[battler].terrainAbilityDone = TRUE; + gDisableStructs[battler].terrainAbilityDone = TRUE; ChangeTypeBasedOnTerrain(battler); gBattlerAbility = gBattleScripting.battler = battler; BattleScriptPushCursorAndCallback(BattleScript_MimicryActivates_End3); @@ -6086,11 +6091,11 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITY_QUARK_DRIVE: - if (!gSpecialStatuses[battler].terrainAbilityDone && IsBattlerTerrainAffected(battler, STATUS_FIELD_ELECTRIC_TERRAIN)) + if (!gDisableStructs[battler].terrainAbilityDone && IsBattlerTerrainAffected(battler, STATUS_FIELD_ELECTRIC_TERRAIN)) { - gSpecialStatuses[battler].terrainAbilityDone = TRUE; - gBattlerAbility = gBattleScripting.battler = battler; + gDisableStructs[battler].terrainAbilityDone = TRUE; PREPARE_STAT_BUFFER(gBattleTextBuff1, GetHighestStatId(battler)); + gBattlerAbility = gBattleScripting.battler = battler; BattleScriptPushCursorAndCallback(BattleScript_QuarkDriveActivates); effect++; } diff --git a/test/battle/ability/protosynthesis.c b/test/battle/ability/protosynthesis.c index 0ed74d4f65ce..40711ecced73 100644 --- a/test/battle/ability/protosynthesis.c +++ b/test/battle/ability/protosynthesis.c @@ -85,3 +85,50 @@ SINGLE_BATTLE_TEST("Protosynthesis either boosts Defense or Special Defense, not EXPECT_EQ(damage[0], damage[1]); } } + +SINGLE_BATTLE_TEST("Protosynthesis ability pop up activates only once during the duration of sunny day") +{ + u16 turns; + + GIVEN { + PLAYER(SPECIES_BELLSPROUT) { Ability(ABILITY_PROTOSYNTHESIS); } + OPPONENT(SPECIES_NINETALES) { Ability(ABILITY_DROUGHT); }; + } WHEN { + for (turns = 0; turns < 5; turns++) + TURN {} + TURN { MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + MESSAGE("The harsh sunlight activated Bellsprout's Protosynthesis!"); + MESSAGE("Bellsprout's Attack was heightened!"); + NONE_OF { + for (turns = 0; turns < 4; turns++) { + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + MESSAGE("The harsh sunlight activated Bellsprout's Protosynthesis!"); + MESSAGE("Bellsprout's Attack was heightened!"); + } + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + MESSAGE("The harsh sunlight activated Bellsprout's Protosynthesis!"); + MESSAGE("Bellsprout's Attack was heightened!"); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis activates on switch-in") +{ + KNOWN_FAILING; // Fails because of wrong species + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_BELLSPROUT) { Ability(ABILITY_PROTOSYNTHESIS); } + OPPONENT(SPECIES_NINETALES) { Ability(ABILITY_DROUGHT); }; + } WHEN { + TURN { SWITCH(player, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + MESSAGE("The harsh sunlight activated Bellsprout's Protosynthesis!"); + MESSAGE("Bellsprout's Attack was heightened!"); + } +} diff --git a/test/battle/ability/quark_drive.c b/test/battle/ability/quark_drive.c index bce6795caeac..abf0b7f5cb8f 100644 --- a/test/battle/ability/quark_drive.c +++ b/test/battle/ability/quark_drive.c @@ -85,3 +85,51 @@ SINGLE_BATTLE_TEST("Quark Drive either boosts Defense or Special Defense, not bo EXPECT_EQ(damage[0], damage[1]); } } + +SINGLE_BATTLE_TEST("Quark Drive ability pop up activates only once during the duration of electric terrain") +{ + u16 turns; + + GIVEN { + PLAYER(SPECIES_BELLSPROUT) { Ability(ABILITY_QUARK_DRIVE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_ELECTRIC_TERRAIN); } + for (turns = 0; turns < 4; turns++) + TURN {} + TURN { MOVE(opponent, MOVE_ELECTRIC_TERRAIN); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIC_TERRAIN, opponent); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + MESSAGE("The Electric Terrain activated Bellsprout's Quark Drive!"); + MESSAGE("Bellsprout's Attack was heightened!"); + NONE_OF { + for (turns = 0; turns < 4; turns++) { + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + MESSAGE("The Electric Terrain activated Bellsprout's Quark Drive!"); + MESSAGE("Bellsprout's Attack was heightened!"); + } + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIC_TERRAIN, opponent); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + MESSAGE("The Electric Terrain activated Bellsprout's Quark Drive!"); + MESSAGE("Bellsprout's Attack was heightened!"); + } +} + +SINGLE_BATTLE_TEST("Quark Drive activates on switch-in") +{ + KNOWN_FAILING; // Fails because of wrong species + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_BELLSPROUT) { Ability(ABILITY_QUARK_DRIVE); } + OPPONENT(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); }; + } WHEN { + TURN { SWITCH(player, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_ELECTRIC_SURGE); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + MESSAGE("The Electric Terrain activated Bellsprout's Quark Drive!"); + MESSAGE("Bellsprout's Attack was heightened!"); + } +} From b7cd6502d42de3bcdb0a9591881c1f9ac3299756 Mon Sep 17 00:00:00 2001 From: Frank DeBlasio <35279583+fdeblasio@users.noreply.github.com> Date: Fri, 24 Nov 2023 09:57:08 -0500 Subject: [PATCH 09/18] Add config for times of day (#3579) * Added initial config for times of day * Changed TIME_DUSK/DUSK_EVO to EVENING * Added morning and evening to Gens 3/4 * Renamed *_EVO_HOUR_BEGIN/END to just *_HOUR_BEGIN/END * Incorporated comments --- include/config/overworld.h | 1 + include/rtc.h | 72 +++++++++++++++++++++++++++++++----- src/battle_script_commands.c | 2 +- src/pokemon.c | 2 +- src/rtc.c | 8 ++-- 5 files changed, 70 insertions(+), 15 deletions(-) diff --git a/include/config/overworld.h b/include/config/overworld.h index cac35413ded0..34880644211d 100644 --- a/include/config/overworld.h +++ b/include/config/overworld.h @@ -6,6 +6,7 @@ // Other settings #define OW_POISON_DAMAGE GEN_LATEST // In Gen4, Pokémon no longer faint from Poison in the overworld. In Gen5+, they no longer take damage at all. +#define OW_TIMES_OF_DAY GEN_LATEST // Different generations have the times of day change at different times // PC settings #define OW_PC_PRESS_B GEN_LATEST // In Gen4, pressing B when holding a Pokémon is equivalent to placing it. In Gen3, it gives the "You're holding a Pokémon!" error. diff --git a/include/rtc.h b/include/rtc.h index d94beafcdce4..9882d702b6fb 100644 --- a/include/rtc.h +++ b/include/rtc.h @@ -17,21 +17,75 @@ #define RTC_ERR_FLAG_MASK 0x0FF0 -#define MORNING_EVO_HOUR_BEGIN 6 -#define MORNING_EVO_HOUR_END 10 +//Morning and evening don't exist in Gen 3 +#if OW_TIMES_OF_DAY == GEN_3 + #define MORNING_HOUR_BEGIN 0 + #define MORNING_HOUR_END 0 -#define DAY_EVO_HOUR_BEGIN 10 -#define DAY_EVO_HOUR_END 19 + #define DAY_HOUR_BEGIN 12 + #define DAY_HOUR_END HOURS_PER_DAY -#define DUSK_EVO_HOUR_BEGIN 19 -#define DUSK_EVO_HOUR_END 20 + #define EVENING_HOUR_BEGIN 0 + #define EVENING_HOUR_END 0 -#define NIGHT_EVO_HOUR_BEGIN 20 -#define NIGHT_EVO_HOUR_END 6 + #define NIGHT_HOUR_BEGIN 0 + #define NIGHT_HOUR_END 12 +//Evening doesn't exist in Gen 4 +#elif OW_TIMES_OF_DAY == GEN_4 + #define MORNING_HOUR_BEGIN 4 + #define MORNING_HOUR_END 10 + + #define DAY_HOUR_BEGIN 10 + #define DAY_HOUR_END 20 + + #define EVENING_HOUR_BEGIN 0 + #define EVENING_HOUR_END 0 + + #define NIGHT_HOUR_BEGIN 20 + #define NIGHT_HOUR_END 4 +//Gen 5 currently not included as the seasons change the times of day +#elif OW_TIMES_OF_DAY <= GEN_6 + #define MORNING_HOUR_BEGIN 4 + #define MORNING_HOUR_END 11 + + #define DAY_HOUR_BEGIN 11 + #define DAY_HOUR_END 18 + + #define EVENING_HOUR_BEGIN 18 + #define EVENING_HOUR_END 21 + + #define NIGHT_HOUR_BEGIN 21 + #define NIGHT_HOUR_END 4 +//These are the Sun/Ultra Sun times +#elif OW_TIMES_OF_DAY == GEN_7 + #define MORNING_HOUR_BEGIN 6 + #define MORNING_HOUR_END 10 + + #define DAY_HOUR_BEGIN 10 + #define DAY_HOUR_END 17 + + #define EVENING_HOUR_BEGIN 17 + #define EVENING_HOUR_END 18 + + #define NIGHT_HOUR_BEGIN 18 + #define NIGHT_HOUR_END 6 +#elif OW_TIMES_OF_DAY >= GEN_8 + #define MORNING_HOUR_BEGIN 6 + #define MORNING_HOUR_END 10 + + #define DAY_HOUR_BEGIN 10 + #define DAY_HOUR_END 19 + + #define EVENING_HOUR_BEGIN 19 + #define EVENING_HOUR_END 20 + + #define NIGHT_HOUR_BEGIN 20 + #define NIGHT_HOUR_END 6 +#endif #define TIME_MORNING 0 #define TIME_DAY 1 -#define TIME_DUSK 2 +#define TIME_EVENING 2 #define TIME_NIGHT 3 extern struct Time gLocalTime; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 8de129f474e6..780f630ed82e 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -14858,7 +14858,7 @@ static void Cmd_handleballthrow(void) ballMultiplier = 400; break; case ITEM_DUSK_BALL: - if ((GetTimeOfDay() == TIME_DUSK || GetTimeOfDay() == TIME_NIGHT) || gMapHeader.cave || gMapHeader.mapType == MAP_TYPE_UNDERGROUND) + if ((GetTimeOfDay() == TIME_EVENING || GetTimeOfDay() == TIME_NIGHT) || gMapHeader.cave || gMapHeader.mapType == MAP_TYPE_UNDERGROUND) ballMultiplier = (B_DUSK_BALL_MODIFIER >= GEN_7 ? 300 : 350); break; case ITEM_QUICK_BALL: diff --git a/src/pokemon.c b/src/pokemon.c index 97577efbb06f..a9c22ae8e708 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -7076,7 +7076,7 @@ u16 GetEvolutionTargetSpecies(struct Pokemon *mon, u8 mode, u16 evolutionItem, s } break; case EVO_LEVEL_DUSK: - if (GetTimeOfDay() == TIME_DUSK && gEvolutionTable[species][i].param <= level) + if (GetTimeOfDay() == TIME_EVENING && gEvolutionTable[species][i].param <= level) targetSpecies = gEvolutionTable[species][i].targetSpecies; break; case EVO_LEVEL: diff --git a/src/rtc.c b/src/rtc.c index d6f5377d9831..64e6734552ce 100644 --- a/src/rtc.c +++ b/src/rtc.c @@ -304,11 +304,11 @@ bool8 IsBetweenHours(s32 hours, s32 begin, s32 end) u8 GetTimeOfDay(void) { RtcCalcLocalTime(); - if (IsBetweenHours(gLocalTime.hours, MORNING_EVO_HOUR_BEGIN, MORNING_EVO_HOUR_END)) + if (IsBetweenHours(gLocalTime.hours, MORNING_HOUR_BEGIN, MORNING_HOUR_END)) return TIME_MORNING; - else if (IsBetweenHours(gLocalTime.hours, DUSK_EVO_HOUR_BEGIN, DUSK_EVO_HOUR_END)) - return TIME_DUSK; - else if (IsBetweenHours(gLocalTime.hours, NIGHT_EVO_HOUR_BEGIN, NIGHT_EVO_HOUR_END)) + else if (IsBetweenHours(gLocalTime.hours, EVENING_HOUR_BEGIN, EVENING_HOUR_END)) + return TIME_EVENING; + else if (IsBetweenHours(gLocalTime.hours, NIGHT_HOUR_BEGIN, NIGHT_HOUR_END)) return TIME_NIGHT; return TIME_DAY; } From 74bb0f05f529535fccaec42cff1cd66d7f1f6eb3 Mon Sep 17 00:00:00 2001 From: Bassoonian Date: Fri, 24 Nov 2023 16:19:32 +0100 Subject: [PATCH 10/18] Battle Dome Automation (#3596) * MOVE_POINTS_STRONG * MOVE_POINTS_POWERFUL * MOVE_POINTS_LOW_PP * MOVE_POINTS_STAT_RAISE * Removed duplicate function * MOVE_POINTS_STAT_LOWER * MOVE_POINTS_DMG * MOVE_POINTS_ACCURATE * MOVE_POINTS_HEAL * MOVE_POINTS_DEF * MOVE_POINTS_RISKY * MOVE_POINTS_EFFECT * MOVE_POINTS_LUCK * MOVE_POINTS_POPULAR * MOVE_POINTS_STATUS * MOVE_POINTS_RARE * MOVE_POINTS_COMBO * Apply review suggestions * More implemented review suggestions --------- Co-authored-by: Eduardo Quezada --- include/constants/battle_dome.h | 28 +- src/battle_ai_main.c | 10 +- src/battle_ai_util.c | 26 +- src/battle_dome.c | 660 ++++++++++++++------------------ src/battle_main.c | 23 +- 5 files changed, 319 insertions(+), 428 deletions(-) diff --git a/include/constants/battle_dome.h b/include/constants/battle_dome.h index adac0a938da3..c855c1ab57b3 100644 --- a/include/constants/battle_dome.h +++ b/include/constants/battle_dome.h @@ -105,22 +105,22 @@ #define DOME_TEXT_SPEED 12 #define DOME_TEXT_SPATK 14 -// Move point indexes for sBattleStyleMovePoints[][], to determine Battle Dome trainers battle styles -#define MOVE_POINTS_COMBO 0 // Moves that work well in combination (e.g. Rain Dance + Hydro Pump) +// Determine Battle Dome trainers battle styles +#define MOVE_POINTS_COMBO 0 // Moves that work well in combination: moves that cause or are affected by weather/terrain, Stockpile+, entry hazards, sleep inflictions & effects benefiting from it, offensive boosts of a single stat with at least two stages or at least two stats, and several other effects #define MOVE_POINTS_STAT_RAISE 1 #define MOVE_POINTS_STAT_LOWER 2 -#define MOVE_POINTS_RARE 3 // Uncommon moves. Mostly arbitrary -#define MOVE_POINTS_HEAL 4 -#define MOVE_POINTS_RISKY 5 -#define MOVE_POINTS_STATUS 6 -#define MOVE_POINTS_DMG 7 -#define MOVE_POINTS_DEF 8 // Defensive moves, like Amnesia, Light Screen, or accuracy-lowers -#define MOVE_POINTS_ACCURATE 9 -#define MOVE_POINTS_POWERFUL 10 // Most of the moves that are >= 100 power -#define MOVE_POINTS_POPULAR 11 // Group seems arbitrary. All using it are TM/HMs, but its only 11/58 -#define MOVE_POINTS_LUCK 12 -#define MOVE_POINTS_STRONG 13 // Most of the moves that are >= 90 power -#define MOVE_POINTS_LOW_PP 14 +#define MOVE_POINTS_RARE 3 // Moves that appear in less than 5% of levelup learnsets +#define MOVE_POINTS_HEAL 4 // Moves that heal +#define MOVE_POINTS_RISKY 5 // Move effects deemed risky by the Emerald developers (excluding High Jump Kick and others for some reason) +#define MOVE_POINTS_STATUS 6 // Moves that cause status effects without dealing damage +#define MOVE_POINTS_DMG 7 // Moves that deal damage (BP > 0) +#define MOVE_POINTS_DEF 8 // Moves like screens, accuracy-lowers or evasiveness-raisers, (special) defense raisers, protect etc. +#define MOVE_POINTS_ACCURATE 9 // Moves with 100% accuracy (or that are guaranteed hits) +#define MOVE_POINTS_POWERFUL 10 // Moves with 100 BP or more +#define MOVE_POINTS_POPULAR 11 // TM/HM moves with 90 BP or more or those that raise a single offensive stat by at least 2 stages +#define MOVE_POINTS_LUCK 12 // Move effects that depend on luck and moves with Accuracy of <= 50% +#define MOVE_POINTS_STRONG 13 // Moves with 90 BP or more +#define MOVE_POINTS_LOW_PP 14 // Moves with 5 PP or less #define MOVE_POINTS_EFFECT 15 // Moves with additional effects #define NUM_MOVE_POINT_TYPES 16 diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index ea789a79a6a7..4f7d04438b3f 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -850,7 +850,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) RETURN_SCORE_MINUS(10); break; case ABILITY_FLOWER_VEIL: - if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS) && (IsNonVolatileStatusMoveEffect(moveEffect) || IsStatLoweringMoveEffect(moveEffect))) + if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS) && (IsNonVolatileStatusMoveEffect(moveEffect) || IsStatLoweringEffect(moveEffect))) RETURN_SCORE_MINUS(10); break; case ABILITY_MAGIC_BOUNCE: @@ -858,13 +858,13 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) RETURN_SCORE_MINUS(20); break; case ABILITY_CONTRARY: - if (IsStatLoweringMoveEffect(moveEffect)) + if (IsStatLoweringEffect(moveEffect)) RETURN_SCORE_MINUS(20); break; case ABILITY_CLEAR_BODY: case ABILITY_FULL_METAL_BODY: case ABILITY_WHITE_SMOKE: - if (IsStatLoweringMoveEffect(moveEffect)) + if (IsStatLoweringEffect(moveEffect)) RETURN_SCORE_MINUS(10); break; case ABILITY_HYPER_CUTTER: @@ -886,7 +886,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) break; case ABILITY_DEFIANT: case ABILITY_COMPETITIVE: - if (IsStatLoweringMoveEffect(moveEffect) && !IS_TARGETING_PARTNER(battlerAtk, battlerDef)) + if (IsStatLoweringEffect(moveEffect) && !IS_TARGETING_PARTNER(battlerAtk, battlerDef)) RETURN_SCORE_MINUS(8); break; case ABILITY_COMATOSE: @@ -927,7 +927,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) RETURN_SCORE_MINUS(20); break; case ABILITY_FLOWER_VEIL: - if ((IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS)) && (IsNonVolatileStatusMoveEffect(moveEffect) || IsStatLoweringMoveEffect(moveEffect))) + if ((IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS)) && (IsNonVolatileStatusMoveEffect(moveEffect) || IsStatLoweringEffect(moveEffect))) RETURN_SCORE_MINUS(10); break; case ABILITY_AROMA_VEIL: diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 255da22cd2ff..0ff7b403c2f0 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -1422,30 +1422,6 @@ bool32 IsConfusionMoveEffect(u32 moveEffect) } } -bool32 IsStatLoweringMoveEffect(u32 moveEffect) -{ - switch (moveEffect) - { - case EFFECT_ATTACK_DOWN: - case EFFECT_DEFENSE_DOWN: - case EFFECT_SPEED_DOWN: - case EFFECT_SPECIAL_ATTACK_DOWN: - case EFFECT_SPECIAL_DEFENSE_DOWN: - case EFFECT_ACCURACY_DOWN: - case EFFECT_EVASION_DOWN: - case EFFECT_ATTACK_DOWN_2: - case EFFECT_DEFENSE_DOWN_2: - case EFFECT_SPEED_DOWN_2: - case EFFECT_SPECIAL_ATTACK_DOWN_2: - case EFFECT_SPECIAL_DEFENSE_DOWN_2: - case EFFECT_ACCURACY_DOWN_2: - case EFFECT_EVASION_DOWN_2: - return TRUE; - default: - return FALSE; - } -} - bool32 IsHazardMoveEffect(u32 moveEffect) { switch (moveEffect) @@ -2055,6 +2031,8 @@ bool32 IsHealingMoveEffect(u32 effect) case EFFECT_HEAL_PULSE: case EFFECT_REST: case EFFECT_JUNGLE_HEALING: + case EFFECT_ABSORB: + case EFFECT_DREAM_EATER: return TRUE; default: return FALSE; diff --git a/src/battle_dome.c b/src/battle_dome.c index 24bad30f6b4d..5657d4a3edb3 100644 --- a/src/battle_dome.c +++ b/src/battle_dome.c @@ -1,6 +1,7 @@ #include "global.h" #include "battle_dome.h" #include "battle.h" +#include "battle_ai_util.h" #include "battle_main.h" #include "battle_setup.h" #include "battle_tower.h" @@ -32,6 +33,7 @@ #include "script_pokemon_util.h" #include "graphics.h" #include "constants/battle_dome.h" +#include "constants/battle_move_effects.h" #include "constants/frontier_util.h" #include "constants/moves.h" #include "constants/trainers.h" @@ -162,367 +164,6 @@ static void InitDomeTrainers(void); static EWRAM_DATA struct TourneyTreeInfoCard *sInfoCard = {0}; static EWRAM_DATA u8 *sTilemapBuffer = NULL; -// Each move has an array of points for different move characteristics which contribute to a tourney trainers listed battle style (see sBattleStyleThresholds) -// All move points are either 1 or 0, so theyre essentially flags saying whether or not the move has that characteristic -static const u8 sBattleStyleMovePoints[MOVES_COUNT][NUM_MOVE_POINT_TYPES] = -{ - [MOVE_NONE] = {0}, - [MOVE_POUND] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_KARATE_CHOP] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_DOUBLE_SLAP] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_COMET_PUNCH] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_MEGA_PUNCH] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_PAY_DAY] = {[MOVE_POINTS_RARE] = 1, [MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_FIRE_PUNCH] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_ICE_PUNCH] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_THUNDER_PUNCH] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_SCRATCH] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_VISE_GRIP] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_GUILLOTINE] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_RAZOR_WIND] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_SWORDS_DANCE] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_STAT_RAISE] = 1, [MOVE_POINTS_POPULAR] = 1}, - [MOVE_CUT] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_GUST] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_WING_ATTACK] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_WHIRLWIND] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_FLY] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_BIND] = {[MOVE_POINTS_STATUS] = 1, [MOVE_POINTS_DMG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_SLAM] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_VINE_WHIP] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_STOMP] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_DOUBLE_KICK] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_MEGA_KICK] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_JUMP_KICK] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_ROLLING_KICK] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_SAND_ATTACK] = {[MOVE_POINTS_STAT_LOWER] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_HEADBUTT] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_HORN_ATTACK] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_FURY_ATTACK] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_HORN_DRILL] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_TACKLE] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_BODY_SLAM] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_WRAP] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_TAKE_DOWN] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_THRASH] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_DOUBLE_EDGE] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_TAIL_WHIP] = {[MOVE_POINTS_STAT_LOWER] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_POISON_STING] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_TWINEEDLE] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_PIN_MISSILE] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_LEER] = {[MOVE_POINTS_STAT_LOWER] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_BITE] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_GROWL] = {[MOVE_POINTS_STAT_LOWER] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_ROAR] = {[MOVE_POINTS_ACCURATE] = 1}, - [MOVE_SING] = {[MOVE_POINTS_STATUS] = 1}, - [MOVE_SUPERSONIC] = {[MOVE_POINTS_STATUS] = 1}, - [MOVE_SONIC_BOOM] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_DISABLE] = {[MOVE_POINTS_STATUS] = 1}, - [MOVE_ACID] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_EMBER] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_FLAMETHROWER] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_POPULAR] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_MIST] = {0}, - [MOVE_WATER_GUN] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_HYDRO_PUMP] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_DMG] = 1, [MOVE_POINTS_POWERFUL] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_SURF] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_POPULAR] = 1, [MOVE_POINTS_STRONG] = 1}, - [MOVE_ICE_BEAM] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_POPULAR] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_BLIZZARD] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_POWERFUL] = 1, [MOVE_POINTS_LOW_PP] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_PSYBEAM] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_BUBBLE_BEAM] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_AURORA_BEAM] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_HYPER_BEAM] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_POWERFUL] = 1, [MOVE_POINTS_POPULAR] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_PECK] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_DRILL_PECK] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_SUBMISSION] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_LOW_KICK] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_COUNTER] = {[MOVE_POINTS_DEF] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_LUCK] = 1}, - [MOVE_SEISMIC_TOSS] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_STRENGTH] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_ABSORB] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_MEGA_DRAIN] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_LEECH_SEED] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_STATUS] = 1}, - [MOVE_GROWTH] = {[MOVE_POINTS_STAT_RAISE] = 1}, - [MOVE_RAZOR_LEAF] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_SOLAR_BEAM] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_POWERFUL] = 1, [MOVE_POINTS_POPULAR] = 1, [MOVE_POINTS_STRONG] = 1}, - [MOVE_POISON_POWDER] = {[MOVE_POINTS_STATUS] = 1}, - [MOVE_STUN_SPORE] = {[MOVE_POINTS_STATUS] = 1}, - [MOVE_SLEEP_POWDER] = {[MOVE_POINTS_STATUS] = 1}, - [MOVE_PETAL_DANCE] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_STRING_SHOT] = {[MOVE_POINTS_STAT_LOWER] = 1}, - [MOVE_DRAGON_RAGE] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_FIRE_SPIN] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_THUNDER_SHOCK] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_THUNDERBOLT] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_POPULAR] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_THUNDER_WAVE] = {[MOVE_POINTS_STATUS] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_THUNDER] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_POWERFUL] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_ROCK_THROW] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_EARTHQUAKE] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_POWERFUL] = 1, [MOVE_POINTS_POPULAR] = 1, [MOVE_POINTS_STRONG] = 1}, - [MOVE_FISSURE] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_LUCK] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_DIG] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_TOXIC] = {[MOVE_POINTS_STATUS] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_CONFUSION] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_PSYCHIC] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_POPULAR] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_HYPNOSIS] = {[MOVE_POINTS_COMBO] = 1}, - [MOVE_MEDITATE] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_STAT_RAISE] = 1}, - [MOVE_AGILITY] = {[MOVE_POINTS_STAT_RAISE] = 1}, - [MOVE_QUICK_ATTACK] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_RAGE] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_TELEPORT] = {0}, - [MOVE_NIGHT_SHADE] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_MIMIC] = {[MOVE_POINTS_RARE] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_SCREECH] = {[MOVE_POINTS_STAT_LOWER] = 1}, - [MOVE_DOUBLE_TEAM] = {[MOVE_POINTS_STAT_RAISE] = 1, [MOVE_POINTS_DEF] = 1}, - [MOVE_RECOVER] = {0}, - [MOVE_HARDEN] = {[MOVE_POINTS_STAT_RAISE] = 1, [MOVE_POINTS_DEF] = 1}, - [MOVE_MINIMIZE] = {[MOVE_POINTS_STAT_RAISE] = 1, [MOVE_POINTS_DEF] = 1}, - [MOVE_SMOKESCREEN] = {[MOVE_POINTS_STAT_LOWER] = 1, [MOVE_POINTS_DEF] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_CONFUSE_RAY] = {[MOVE_POINTS_ACCURATE] = 1}, - [MOVE_WITHDRAW] = {[MOVE_POINTS_STAT_RAISE] = 1, [MOVE_POINTS_DEF] = 1}, - [MOVE_DEFENSE_CURL] = {[MOVE_POINTS_STAT_RAISE] = 1, [MOVE_POINTS_DEF] = 1}, - [MOVE_BARRIER] = {[MOVE_POINTS_DEF] = 1}, - [MOVE_LIGHT_SCREEN] = {[MOVE_POINTS_DEF] = 1}, - [MOVE_HAZE] = {0}, - [MOVE_REFLECT] = {[MOVE_POINTS_DEF] = 1}, - [MOVE_FOCUS_ENERGY] = {[MOVE_POINTS_COMBO] = 1}, - [MOVE_BIDE] = {[MOVE_POINTS_ACCURATE] = 1}, - [MOVE_METRONOME] = {[MOVE_POINTS_RARE] = 1, [MOVE_POINTS_LUCK] = 1}, - [MOVE_MIRROR_MOVE] = {[MOVE_POINTS_RARE] = 1, [MOVE_POINTS_LUCK] = 1}, - [MOVE_SELF_DESTRUCT] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_POWERFUL] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_EGG_BOMB] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_POWERFUL] = 1, [MOVE_POINTS_STRONG] = 1}, - [MOVE_LICK] = {[MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_SMOG] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_SLUDGE] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_BONE_CLUB] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_FIRE_BLAST] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_POWERFUL] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_LOW_PP] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_WATERFALL] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_CLAMP] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_SWIFT] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_SKULL_BASH] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_STRONG] = 1}, - [MOVE_SPIKE_CANNON] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_CONSTRICT] = {[MOVE_POINTS_STATUS] = 1, [MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_AMNESIA] = {[MOVE_POINTS_STAT_RAISE] = 1, [MOVE_POINTS_DEF] = 1}, - [MOVE_KINESIS] = {[MOVE_POINTS_STAT_LOWER] = 1}, - [MOVE_SOFT_BOILED] = {[MOVE_POINTS_HEAL] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_HIGH_JUMP_KICK] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_GLARE] = {[MOVE_POINTS_STAT_LOWER] = 1}, - [MOVE_DREAM_EATER] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_RARE] = 1, [MOVE_POINTS_HEAL] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_STRONG] = 1}, - [MOVE_POISON_GAS] = {[MOVE_POINTS_STATUS] = 1}, - [MOVE_BARRAGE] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_LEECH_LIFE] = {[MOVE_POINTS_HEAL] = 1, [MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_LOVELY_KISS] = {[MOVE_POINTS_STATUS] = 1}, - [MOVE_SKY_ATTACK] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_POWERFUL] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_LOW_PP] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_TRANSFORM] = {[MOVE_POINTS_RARE] = 1}, - [MOVE_BUBBLE] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_DIZZY_PUNCH] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_SPORE] = {[MOVE_POINTS_STATUS] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_FLASH] = {0}, - [MOVE_PSYWAVE] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_SPLASH] = {[MOVE_POINTS_RARE] = 1}, - [MOVE_ACID_ARMOR] = {[MOVE_POINTS_STAT_RAISE] = 1, [MOVE_POINTS_DEF] = 1}, - [MOVE_CRABHAMMER] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_STRONG] = 1}, - [MOVE_EXPLOSION] = {[MOVE_POINTS_RISKY] = 1, [MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_POWERFUL] = 1, [MOVE_POINTS_POPULAR] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_FURY_SWIPES] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_BONEMERANG] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_REST] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_HEAL] = 1}, - [MOVE_ROCK_SLIDE] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_HYPER_FANG] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_SHARPEN] = {[MOVE_POINTS_STAT_RAISE] = 1, [MOVE_POINTS_DEF] = 1}, - [MOVE_CONVERSION] = {[MOVE_POINTS_DEF] = 1}, - [MOVE_TRI_ATTACK] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_SUPER_FANG] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_SLASH] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_SUBSTITUTE] = {[MOVE_POINTS_RARE] = 1, [MOVE_POINTS_DEF] = 1}, - [MOVE_STRUGGLE] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_LOW_PP] = 1}, // Odd that this is assigned qualities - [MOVE_SKETCH] = {[MOVE_POINTS_RARE] = 1, [MOVE_POINTS_LUCK] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_TRIPLE_KICK] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_THIEF] = {[MOVE_POINTS_RARE] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_SPIDER_WEB] = {[MOVE_POINTS_STAT_LOWER] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_MIND_READER] = {[MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_NIGHTMARE] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_STATUS] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_FLAME_WHEEL] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_SNORE] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_CURSE] = {[MOVE_POINTS_STATUS] = 1}, - [MOVE_FLAIL] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_CONVERSION_2] = {[MOVE_POINTS_ACCURATE] = 1}, - [MOVE_AEROBLAST] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_POWERFUL] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_COTTON_SPORE] = {[MOVE_POINTS_STAT_LOWER] = 1}, - [MOVE_REVERSAL] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_SPITE] = {[MOVE_POINTS_RARE] = 1, [MOVE_POINTS_RISKY] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_POWDER_SNOW] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_PROTECT] = {[MOVE_POINTS_DEF] = 1, [MOVE_POINTS_POPULAR] = 1}, - [MOVE_MACH_PUNCH] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_SCARY_FACE] = {0}, - [MOVE_FEINT_ATTACK] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_SWEET_KISS] = {0}, - [MOVE_BELLY_DRUM] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_STAT_RAISE] = 1}, - [MOVE_SLUDGE_BOMB] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_MUD_SLAP] = {[MOVE_POINTS_STAT_LOWER] = 1, [MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_OCTAZOOKA] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_SPIKES] = {[MOVE_POINTS_COMBO] = 1}, - [MOVE_ZAP_CANNON] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_POWERFUL] = 1, [MOVE_POINTS_LUCK] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_LOW_PP] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_FORESIGHT] = {[MOVE_POINTS_ACCURATE] = 1}, - [MOVE_DESTINY_BOND] = {[MOVE_POINTS_RISKY] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_PERISH_SONG] = {[MOVE_POINTS_RISKY] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_ICY_WIND] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_DETECT] = {[MOVE_POINTS_DEF] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_BONE_RUSH] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_LOCK_ON] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_OUTRAGE] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_SANDSTORM] = {0}, - [MOVE_GIGA_DRAIN] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_ENDURE] = {[MOVE_POINTS_DEF] = 1}, - [MOVE_CHARM] = {[MOVE_POINTS_ACCURATE] = 1}, - [MOVE_ROLLOUT] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_FALSE_SWIPE] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_SWAGGER] = {[MOVE_POINTS_EFFECT] = 1}, - [MOVE_MILK_DRINK] = {[MOVE_POINTS_HEAL] = 1}, - [MOVE_SPARK] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_FURY_CUTTER] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_STEEL_WING] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_MEAN_LOOK] = {[MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_ATTRACT] = {[MOVE_POINTS_STATUS] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_SLEEP_TALK] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_LUCK] = 1}, - [MOVE_HEAL_BELL] = {[MOVE_POINTS_LOW_PP] = 1}, - [MOVE_RETURN] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_PRESENT] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_LUCK] = 1}, - [MOVE_FRUSTRATION] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_SAFEGUARD] = {[MOVE_POINTS_DEF] = 1}, - [MOVE_PAIN_SPLIT] = {[MOVE_POINTS_RARE] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_SACRED_FIRE] = {[MOVE_POINTS_POWERFUL] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_LOW_PP] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_MAGNITUDE] = {[MOVE_POINTS_ACCURATE] = 1}, - [MOVE_DYNAMIC_PUNCH] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_LUCK] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_LOW_PP] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_MEGAHORN] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_STRONG] = 1}, - [MOVE_DRAGON_BREATH] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_BATON_PASS] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_RARE] = 1}, - [MOVE_ENCORE] = {[MOVE_POINTS_STATUS] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_PURSUIT] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_RAPID_SPIN] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_SWEET_SCENT] = {[MOVE_POINTS_STAT_LOWER] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_IRON_TAIL] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_METAL_CLAW] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_VITAL_THROW] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_MORNING_SUN] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_HEAL] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_SYNTHESIS] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_HEAL] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_MOONLIGHT] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_HEAL] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_HIDDEN_POWER] = {[MOVE_POINTS_RARE] = 1, [MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_CROSS_CHOP] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_TWISTER] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_RAIN_DANCE] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_SUNNY_DAY] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_CRUNCH] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_MIRROR_COAT] = {[MOVE_POINTS_DEF] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_PSYCH_UP] = {[MOVE_POINTS_STAT_RAISE] = 1}, - [MOVE_EXTREME_SPEED] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_ANCIENT_POWER] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_LOW_PP] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_SHADOW_BALL] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_FUTURE_SIGHT] = {[MOVE_POINTS_RARE] = 1, [MOVE_POINTS_DMG] = 1}, - [MOVE_ROCK_SMASH] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_WHIRLPOOL] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_BEAT_UP] = {[MOVE_POINTS_RARE] = 1, [MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_FAKE_OUT] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_UPROAR] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_STOCKPILE] = {[MOVE_POINTS_COMBO] = 1}, - [MOVE_SPIT_UP] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_STRONG] = 1}, - [MOVE_SWALLOW] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_HEAL] = 1}, - [MOVE_HEAT_WAVE] = {[MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_HAIL] = {0}, - [MOVE_TORMENT] = {[MOVE_POINTS_STATUS] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_FLATTER] = {[MOVE_POINTS_ACCURATE] = 1}, - [MOVE_WILL_O_WISP] = {[MOVE_POINTS_STATUS] = 1}, - [MOVE_MEMENTO] = {[MOVE_POINTS_RARE] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_FACADE] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_FOCUS_PUNCH] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_STRONG] = 1}, - [MOVE_SMELLING_SALTS] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_FOLLOW_ME] = {[MOVE_POINTS_RARE] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_NATURE_POWER] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_CHARGE] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_TAUNT] = {[MOVE_POINTS_STATUS] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_HELPING_HAND] = {[MOVE_POINTS_RARE] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_TRICK] = {[MOVE_POINTS_RARE] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_ROLE_PLAY] = {[MOVE_POINTS_ACCURATE] = 1}, - [MOVE_WISH] = {[MOVE_POINTS_HEAL] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_ASSIST] = {[MOVE_POINTS_RARE] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_LUCK] = 1}, - [MOVE_INGRAIN] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_HEAL] = 1, [MOVE_POINTS_DEF] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_SUPERPOWER] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_MAGIC_COAT] = {[MOVE_POINTS_DEF] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_LUCK] = 1}, - [MOVE_RECYCLE] = {[MOVE_POINTS_ACCURATE] = 1}, - [MOVE_REVENGE] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_LUCK] = 1}, - [MOVE_BRICK_BREAK] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_YAWN] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_STATUS] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_KNOCK_OFF] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_ENDEAVOR] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_ERUPTION] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_POWERFUL] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_SKILL_SWAP] = {[MOVE_POINTS_RARE] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_IMPRISON] = {[MOVE_POINTS_RARE] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_LUCK] = 1}, - [MOVE_REFRESH] = {[MOVE_POINTS_HEAL] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_GRUDGE] = {[MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_SNATCH] = {[MOVE_POINTS_RARE] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_LUCK] = 1}, - [MOVE_SECRET_POWER] = {[MOVE_POINTS_RARE] = 1, [MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_DIVE] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_ARM_THRUST] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_CAMOUFLAGE] = {[MOVE_POINTS_RARE] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_TAIL_GLOW] = {[MOVE_POINTS_ACCURATE] = 1}, - [MOVE_LUSTER_PURGE] = {[MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_LOW_PP] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_MIST_BALL] = {[MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_LOW_PP] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_FEATHER_DANCE] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_TEETER_DANCE] = {[MOVE_POINTS_ACCURATE] = 1}, - [MOVE_BLAZE_KICK] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_MUD_SPORT] = {[MOVE_POINTS_ACCURATE] = 1}, - [MOVE_ICE_BALL] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_NEEDLE_ARM] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_SLACK_OFF] = {[MOVE_POINTS_HEAL] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_HYPER_VOICE] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_STRONG] = 1}, - [MOVE_POISON_FANG] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_CRUSH_CLAW] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_BLAST_BURN] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_POWERFUL] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_HYDRO_CANNON] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_POWERFUL] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_METEOR_MASH] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_POWERFUL] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_ASTONISH] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_WEATHER_BALL] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_AROMATHERAPY] = {[MOVE_POINTS_LOW_PP] = 1}, - [MOVE_FAKE_TEARS] = {[MOVE_POINTS_ACCURATE] = 1}, - [MOVE_AIR_CUTTER] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_OVERHEAT] = {[MOVE_POINTS_POWERFUL] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_LOW_PP] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_ODOR_SLEUTH] = {[MOVE_POINTS_ACCURATE] = 1}, - [MOVE_ROCK_TOMB] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_SILVER_WIND] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_LOW_PP] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_METAL_SOUND] = {0}, - [MOVE_GRASS_WHISTLE] = {0}, - [MOVE_TICKLE] = {[MOVE_POINTS_ACCURATE] = 1}, - [MOVE_COSMIC_POWER] = {0}, - [MOVE_WATER_SPOUT] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_POWERFUL] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_SIGNAL_BEAM] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_SHADOW_PUNCH] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_EXTRASENSORY] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_SKY_UPPERCUT] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_SAND_TOMB] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_SHEER_COLD] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_LUCK] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_MUDDY_WATER] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_BULLET_SEED] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_AERIAL_ACE] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_ICICLE_SPEAR] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_IRON_DEFENSE] = {[MOVE_POINTS_DEF] = 1}, - [MOVE_BLOCK] = {[MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_HOWL] = {0}, - [MOVE_DRAGON_CLAW] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_FRENZY_PLANT] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_POWERFUL] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_BULK_UP] = {[MOVE_POINTS_COMBO] = 1}, - [MOVE_BOUNCE] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_LOW_PP] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_MUD_SHOT] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_POISON_TAIL] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_COVET] = {[MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_VOLT_TACKLE] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_POWERFUL] = 1, [MOVE_POINTS_STRONG] = 1}, - [MOVE_MAGICAL_LEAF] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_WATER_SPORT] = {[MOVE_POINTS_ACCURATE] = 1}, - [MOVE_CALM_MIND] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_STAT_RAISE] = 1}, - [MOVE_LEAF_BLADE] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1}, - [MOVE_DRAGON_DANCE] = {[MOVE_POINTS_COMBO] = 1, [MOVE_POINTS_STAT_RAISE] = 1}, - [MOVE_ROCK_BLAST] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_SHOCK_WAVE] = {[MOVE_POINTS_DMG] = 1}, - [MOVE_WATER_PULSE] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_ACCURATE] = 1, [MOVE_POINTS_EFFECT] = 1}, - [MOVE_DOOM_DESIRE] = {[MOVE_POINTS_RARE] = 1, [MOVE_POINTS_DMG] = 1, [MOVE_POINTS_POWERFUL] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_LOW_PP] = 1}, - [MOVE_PSYCHO_BOOST] = {[MOVE_POINTS_DMG] = 1, [MOVE_POINTS_POWERFUL] = 1, [MOVE_POINTS_STRONG] = 1, [MOVE_POINTS_LOW_PP] = 1, [MOVE_POINTS_EFFECT] = 1}, -}; - // This array is searched in-order to determine what battle style a tourney trainer uses. // If the sum of the points for the party's moves meets/exceeds all the point totals of an element, then they use that battle style static const u8 sBattleStyleThresholds[NUM_BATTLE_STYLES - 1][NUM_MOVE_POINT_TYPES] = @@ -4287,6 +3928,244 @@ static u8 Task_GetInfoCardInput(u8 taskId) #undef tUsingAlternateSlot +static bool32 IsDomeHealingMoveEffect(u32 effect) +{ + if (IsHealingMoveEffect(effect)) + return TRUE; + // Check extra effects not considered plain healing by AI + switch(effect) + { + case EFFECT_INGRAIN: + case EFFECT_REFRESH: + case EFFECT_AQUA_RING: + return TRUE; + default: + return FALSE; + } +} + +static bool32 IsDomeDefensiveMoveEffect(u32 effect) +{ + switch(effect) + { + case EFFECT_COUNTER: + case EFFECT_EVASION_UP: + case EFFECT_DEFENSE_UP: + case EFFECT_DEFENSE_UP_2: + case EFFECT_SPECIAL_DEFENSE_UP: + case EFFECT_SPECIAL_DEFENSE_UP_2: + case EFFECT_MINIMIZE: + case EFFECT_ACCURACY_DOWN: + case EFFECT_DEFENSE_CURL: + case EFFECT_LIGHT_SCREEN: + case EFFECT_REFLECT: + case EFFECT_AURORA_VEIL: + case EFFECT_CONVERSION: + case EFFECT_PROTECT: + case EFFECT_MAT_BLOCK: + case EFFECT_ENDURE: + case EFFECT_SAFEGUARD: + case EFFECT_MIRROR_COAT: + case EFFECT_MAGIC_COAT: + case EFFECT_INGRAIN: + case EFFECT_AQUA_RING: + case EFFECT_SUBSTITUTE: + return TRUE; + default: + return FALSE; + } +} + +static bool32 IsDomeRiskyMoveEffect(u32 effect) +{ + switch(effect) + { + case EFFECT_EXPLOSION: + case EFFECT_SPITE: + case EFFECT_DESTINY_BOND: + case EFFECT_PERISH_SONG: + return TRUE; + default: + return FALSE; + } +} + +static bool32 IsDomeLuckyMove(u32 move) +{ + if (gBattleMoves[move].accuracy <= 50) + return TRUE; + switch(gBattleMoves[move].effect) + { + case EFFECT_COUNTER: + case EFFECT_OHKO: // Technically redundant because of the above accuracy check + case EFFECT_METRONOME: + case EFFECT_MIRROR_MOVE: + case EFFECT_SKETCH: + case EFFECT_SLEEP_TALK: + case EFFECT_PRESENT: + case EFFECT_ASSIST: + case EFFECT_MAGIC_COAT: + case EFFECT_REVENGE: + case EFFECT_IMPRISON: + case EFFECT_SNATCH: + return TRUE; + default: + return FALSE; + } +} + +static bool32 IsDomePopularMove(u32 move) +{ + u8 i; + for (i = 0; i < NUM_TECHNICAL_MACHINES + NUM_HIDDEN_MACHINES; i++) + { + if (ItemIdToBattleMoveId(ITEM_TM01 + i) == move) + return TRUE; + } + if (i == NUM_TECHNICAL_MACHINES + NUM_HIDDEN_MACHINES) + return FALSE; + // Filter in TMs/HMs + if (gBattleMoves[move].power >= 90) + return TRUE; + + switch(gBattleMoves[move].effect) + { + case EFFECT_PROTECT: + case EFFECT_MAT_BLOCK: + case EFFECT_ATTACK_UP_2: + case EFFECT_SPECIAL_ATTACK_UP_2: + case EFFECT_SPECIAL_ATTACK_UP_3: + return TRUE; + default: + return FALSE; + } +} + +static bool32 IsDomeStatusMoveEffect(u32 effect) +{ + switch(effect) + { + case EFFECT_TRAP: + case EFFECT_SLEEP: + case EFFECT_CONFUSE: + case EFFECT_DISABLE: + case EFFECT_POISON: + case EFFECT_PARALYZE: + case EFFECT_TOXIC: + case EFFECT_LEECH_SEED: + case EFFECT_TAUNT: + case EFFECT_TORMENT: + case EFFECT_WILL_O_WISP: + case EFFECT_ENCORE: + case EFFECT_ATTRACT: + case EFFECT_NIGHTMARE: + case EFFECT_YAWN: + case EFFECT_CURSE: + return TRUE; + default: + return FALSE; + } +} + +static bool32 IsDomeRareMove(u32 move) +{ + u16 i, j; + u16 species = 0; + for(i = 0; i < NUM_SPECIES; i++) + { + for(j = 0; gLevelUpLearnsets[i][j].move != LEVEL_UP_MOVE_END; j++) + { + if (gLevelUpLearnsets[i][j].move == move) + { + species++; + break; + } + } + if (species >= NUM_SPECIES / 20) // At least 5% of all mons can learn this move + return FALSE; + } + return TRUE; +} + +static bool32 IsDomeComboMoveEffect(u32 effect) +{ + switch(effect) + { + // Weather moves + case EFFECT_SUNNY_DAY: + case EFFECT_RAIN_DANCE: + case EFFECT_SANDSTORM: + case EFFECT_HAIL: + case EFFECT_SNOWSCAPE: + // Terrain moves + case EFFECT_GRASSY_TERRAIN: + case EFFECT_ELECTRIC_TERRAIN: + case EFFECT_MISTY_TERRAIN: + case EFFECT_PSYCHIC_TERRAIN: + // Moves dependent on weather + case EFFECT_SYNTHESIS: + case EFFECT_MORNING_SUN: + case EFFECT_MOONLIGHT: + case EFFECT_SHORE_UP: + case EFFECT_THUNDER: + case EFFECT_HURRICANE: + //case EFFECT_BLIZZARD: (needs a unique effect in gBattleMoves!) + case EFFECT_SOLAR_BEAM: + case EFFECT_GROWTH: + case EFFECT_AURORA_VEIL: + case EFFECT_WEATHER_BALL: + // Moves dependent on terrain + case EFFECT_EXPANDING_FORCE: + case EFFECT_GRASSY_GLIDE: + //case EFFECT_MISTY_EXPLOSION: (needs a unique effect in gBattleMoves!) + case EFFECT_PSYBLADE: + case EFFECT_RISING_VOLTAGE: + case EFFECT_TERRAIN_PULSE: + // Stockpile group + case EFFECT_STOCKPILE: + case EFFECT_SPIT_UP: + case EFFECT_SWALLOW: + // Entry hazards & cleaners + case EFFECT_SPIKES: + case EFFECT_TOXIC_SPIKES: + case EFFECT_STEALTH_ROCK: + case EFFECT_STICKY_WEB: + // Inflicting sleep & related effects + case EFFECT_SLEEP: + case EFFECT_YAWN: + case EFFECT_DREAM_EATER: + case EFFECT_NIGHTMARE: + case EFFECT_REST: + case EFFECT_SLEEP_TALK: + case EFFECT_SNORE: + // Anything that ups offensive stats by more than one + case EFFECT_ATTACK_UP: + case EFFECT_ATTACK_UP_2: + case EFFECT_ATTACK_SPATK_UP: + case EFFECT_SPECIAL_ATTACK_UP: + case EFFECT_SPECIAL_ATTACK_UP_2: + case EFFECT_SPECIAL_ATTACK_UP_3: + case EFFECT_CALM_MIND: + case EFFECT_DRAGON_DANCE: + case EFFECT_BELLY_DRUM: + case EFFECT_CHARGE: + case EFFECT_BULK_UP: + case EFFECT_ATTACK_ACCURACY_UP: + // Others + case EFFECT_FOCUS_ENERGY: + case EFFECT_LOCK_ON: + case EFFECT_FLAIL: + case EFFECT_BATON_PASS: + case EFFECT_INGRAIN: + case EFFECT_AQUA_RING: + case EFFECT_LEECH_SEED: + case EFFECT_ROAR: + return TRUE; + default: + return FALSE; + } +} + // allocatedArray below needs to be large enough to hold stat totals for each mon, or totals of each type of move points #define ALLOC_ARRAY_SIZE max(NUM_STATS * FRONTIER_PARTY_SIZE, NUM_MOVE_POINT_TYPES) @@ -4456,12 +4335,65 @@ static void DisplayTrainerInfoOnCard(u8 flags, u8 trainerTourneyId) { for (k = 0; k < NUM_MOVE_POINT_TYPES; k++) { + u16 move; if (trainerId == TRAINER_FRONTIER_BRAIN) - allocatedArray[k] += sBattleStyleMovePoints[GetFrontierBrainMonMove(i, j)][k]; + move = GetFrontierBrainMonMove(i, j); else if (trainerId == TRAINER_PLAYER) - allocatedArray[k] += sBattleStyleMovePoints[gSaveBlock2Ptr->frontier.domePlayerPartyData[i].moves[j]][k]; + move = gSaveBlock2Ptr->frontier.domePlayerPartyData[i].moves[j]; else - allocatedArray[k] += sBattleStyleMovePoints[gFacilityTrainerMons[DOME_MONS[trainerTourneyId][i]].moves[j]][k]; + move = gFacilityTrainerMons[DOME_MONS[trainerTourneyId][i]].moves[j]; + + switch (k) + { + case MOVE_POINTS_COMBO: + allocatedArray[k] = IsDomeComboMoveEffect(gBattleMoves[move].effect) ? 1 : 0; + break; + case MOVE_POINTS_STAT_RAISE: + allocatedArray[k] = IsStatRaisingEffect(gBattleMoves[move].effect) ? 1 : 0; + break; + case MOVE_POINTS_STAT_LOWER: + allocatedArray[k] = IsStatLoweringEffect(gBattleMoves[move].effect) ? 1 : 0; + break; + case MOVE_POINTS_RARE: + allocatedArray[k] = IsDomeRareMove(move) ? 1 : 0; + break; + case MOVE_POINTS_HEAL: + allocatedArray[k] = IsDomeHealingMoveEffect(gBattleMoves[move].effect) ? 1 : 0; + break; + case MOVE_POINTS_RISKY: + allocatedArray[k] = IsDomeRiskyMoveEffect(gBattleMoves[move].effect) ? 1 : 0; + break; + case MOVE_POINTS_STATUS: + allocatedArray[k] = IsDomeStatusMoveEffect(gBattleMoves[move].effect) ? 1 : 0; + break; + case MOVE_POINTS_DMG: + allocatedArray[k] = (gBattleMoves[move].power != 0) ? 1 : 0; + break; + case MOVE_POINTS_DEF: + allocatedArray[k] = IsDomeDefensiveMoveEffect(gBattleMoves[move].effect) ? 1 : 0; + break; + case MOVE_POINTS_ACCURATE: + allocatedArray[k] = (gBattleMoves[move].accuracy == 0 || gBattleMoves[move].accuracy == 100) ? 1 : 0; + break; + case MOVE_POINTS_POWERFUL: + allocatedArray[k] = (gBattleMoves[move].power >= 100) ? 1 : 0; + break; + case MOVE_POINTS_POPULAR: + allocatedArray[k] = IsDomePopularMove(move) ? 1 : 0; + break; + case MOVE_POINTS_LUCK: + allocatedArray[k] = IsDomeLuckyMove(move) ? 1 : 0; + break; + case MOVE_POINTS_STRONG: + allocatedArray[k] = (gBattleMoves[move].power >= 90) ? 1 : 0; + break; + case MOVE_POINTS_LOW_PP: + allocatedArray[k] = (gBattleMoves[move].pp <= 5) ? 1 : 0; + break; + case MOVE_POINTS_EFFECT: + allocatedArray[k] = (gBattleMoves[move].secondaryEffectChance > 0) ? 1 : 0; + break; + } } } } diff --git a/src/battle_main.c b/src/battle_main.c index 28484a7061fa..d204dfa56863 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -4703,27 +4703,8 @@ s8 GetMovePriority(u32 battler, u16 move) { priority++; } - else if (ability == ABILITY_TRIAGE) - { - switch (gBattleMoves[move].effect) - { - case EFFECT_RESTORE_HP: - case EFFECT_REST: - case EFFECT_MORNING_SUN: - case EFFECT_MOONLIGHT: - case EFFECT_SYNTHESIS: - case EFFECT_HEAL_PULSE: - case EFFECT_HEALING_WISH: - case EFFECT_SWALLOW: - case EFFECT_WISH: - case EFFECT_SOFTBOILED: - case EFFECT_ABSORB: - case EFFECT_ROOST: - case EFFECT_JUNGLE_HEALING: - priority += 3; - break; - } - } + else if (ability == ABILITY_TRIAGE && IsHealingMoveEffect(gBattleMoves[move].effect)) + priority += 3; if (gProtectStructs[battler].quash) priority = -8; From 0fe203c7f57a5c140e3c095cd3fcd215871b4df4 Mon Sep 17 00:00:00 2001 From: Bassoonian Date: Sat, 25 Nov 2023 13:56:41 +0100 Subject: [PATCH 11/18] Fix fusions (#3602) --- src/party_menu.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/party_menu.c b/src/party_menu.c index 37e042a96a62..fd4bb50cb35e 100644 --- a/src/party_menu.c +++ b/src/party_menu.c @@ -5876,6 +5876,8 @@ u8 IsFusionMon(u16 species) { u16 i; const struct Fusion *itemFusion = gFusionTablePointers[species]; + if (itemFusion == NULL) + return FALSE; for (i = 0; itemFusion[i].fusionStorageIndex != FUSION_TERMINATOR; i++) { if (itemFusion[i].fusingIntoMon == species) @@ -6157,7 +6159,7 @@ void ItemUseCB_Fusion(u8 taskId, TaskFunc taskFunc) { if (gPokemonStoragePtr->fusions[itemFusion[i].fusionStorageIndex].level != 0) continue; - if (itemFusion[i].itemId == gSpecialVar_ItemId && itemFusion[i].targetSpecies1 == task->firstFusion) + if (itemFusion[i].itemId == gSpecialVar_ItemId && itemFusion[i].targetSpecies1 == task->firstFusion && itemFusion[i].targetSpecies2 == species) { task->storageIndex = itemFusion[i].fusionStorageIndex; task->fusionResult = itemFusion[i].fusingIntoMon; From 493478e94b6cc2652c66a92f66d176f4b25c04d3 Mon Sep 17 00:00:00 2001 From: psf <77138753+pkmnsnfrn@users.noreply.github.com> Date: Sun, 26 Nov 2023 07:59:44 -0800 Subject: [PATCH 12/18] Added support for XY's Sky Battles (#2950) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Original implementation from Phlayne * Moved Sky Battle Flag / Var into a config * Optimized existing code and fixed existing bugs Added error message for when sky battle var and flag are not set Merged CanDoSkyBattle and PrepareSkyBattle into one special * Added compatibility for Gen7+ * Commented out Volt Crash from banned moves * Cleaned up debug scripts from testing * Fixed bug where player did not white out even if they had no healthy Pokémon and only an egg Zeroed out both Sky Battle configs * Removed extra include from src/field_specials.c Removed extra line break in src/battle_script_commands.c * Added FLAG_DISABLED_IN_SKY_BATTLE Added FLAG_DISABLED_IN_SKY_BATTLE to appropriate moves * Changed DoesSkyBattleCancelCurrentMove to look at move flags * Fixed alignment and spacing in battle_moves.h * Added FLAG_DISABLED_IN_SKY_BATTLE to Sticky Web * Added FLAG_DISABLED_IN_SKY_BATTLE to Steel Roller * Disabled the ability to change Battle Terrain when Sky Battle is happening Stopped Ceaseless Edge from spawning Spikes when Sky Battle is happening Added B_SKY_BATTLE_STRICT_MECHANICS config * Fixed bug with SKY_BATTLE_STRICT_MECHANICS where conditions were not consistently being applied * Add rulesVariants to the BattleStruct Added skyBattle check in AllocateBattleResources * Replaced B_FLAG_SKY_BATTLE checks with rulesVariants.skyBattle checks * Fixed debug script * Reverted include/config/battle.h * Fixed spacing and placement of functions * Fixed debug script omission Fixed bug where Spikes did not set from Ceaseless Edge and Stone Axe * Added FLAG_DISABLED_IN_SKY_BATTLE to Psychic Terrain * Addressed DizzyEgg PR feedback * Forgot a file in last commit * Addressed feedback from DizzyEggg * Address Lunos' PR feedback * Update specials.inc Added an empty line at the end of data/specials.inc * Fixed spacing * Apply suggestions from code review Co-authored-by: Eduardo Quezada D'Ottone * Updated skyBattleBanned and HandleBattleVariantEndParty to use correct names * Removed STRICT_MOVES and STRICT_MECHANICS * Fixed minor spacing issues with merge * Merged in upcoming * Implemented feedback from Jasper https://github.com/rh-hideout/pokeemerald-expansion/pull/2950/files/5da6117d1bc725900c6386e62ab719f8e1695027 --------- Co-authored-by: Eduardo Quezada D'Ottone --- data/scripts/debug.inc | 13 ++++++++ data/specials.inc | 1 + include/battle.h | 1 + include/battle_script_commands.h | 2 ++ include/battle_util.h | 2 ++ include/config/battle.h | 6 ++++ include/debug.h | 1 + include/field_specials.h | 1 + include/pokemon.h | 1 + src/battle_script_commands.c | 18 ++++++++--- src/battle_setup.c | 30 +++++++++++++++++- src/battle_util.c | 53 +++++++++++++++++++++++++++++++- src/battle_util2.c | 4 +++ src/data/battle_moves.h | 47 ++++++++++++++++++++++++++++ src/debug.c | 1 + src/field_specials.c | 46 +++++++++++++++++++++++++++ src/load_save.c | 1 + 17 files changed, 222 insertions(+), 6 deletions(-) diff --git a/data/scripts/debug.inc b/data/scripts/debug.inc index 3e482c839104..573b60d457c2 100644 --- a/data/scripts/debug.inc +++ b/data/scripts/debug.inc @@ -95,6 +95,19 @@ Debug_BoxFilledMessage:: Debug_BoxFilledMessage_Text: .string "Storage boxes filled!$" +Debug_FlagsAndVarNotSetBattleConfigMessage:: + lockall + message Debug_FlagsAndVarNotSetBattleConfigMessage_Text + waitmessage + waitbuttonpress + releaseall + end + +Debug_FlagsAndVarNotSetBattleConfigMessage_Text: + .string "Feature unavailable! Please define a\n" + .string "usable flag and a usable var in:\l" + .string "'include/config/battle.h'!$" + Debug_EventScript_Script_1:: end diff --git a/data/specials.inc b/data/specials.inc index 91ba7cac13fe..34e86cad29f9 100644 --- a/data/specials.inc +++ b/data/specials.inc @@ -539,3 +539,4 @@ gSpecials:: def_special GetNumberSprayStrength def_special GetSprayId def_special GetLastUsedSprayType + def_special TrySkyBattle diff --git a/include/battle.h b/include/battle.h index 5d603399b70b..9d83e0d9f881 100644 --- a/include/battle.h +++ b/include/battle.h @@ -731,6 +731,7 @@ struct BattleStruct u32 aiDelayFrames; // Number of frames it took to choose an action. bool8 transformZeroToHero[PARTY_SIZE][NUM_BATTLE_SIDES]; u8 pledgeMove:1; + bool8 isSkyBattle:1; }; // The palaceFlags member of struct BattleStruct contains 1 flag per move to indicate which moves the AI should consider, diff --git a/include/battle_script_commands.h b/include/battle_script_commands.h index e78b57e4d1dd..a4e8a166edc8 100644 --- a/include/battle_script_commands.h +++ b/include/battle_script_commands.h @@ -27,6 +27,7 @@ s32 CalcCritChanceStage(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordA s32 GetCritHitChance(s32 critChanceIndex); u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, u32 atkAbility, u32 defAbility, u32 atkHoldEffect, u32 defHoldEffect); u8 GetBattlerTurnOrderNum(u8 battlerId); +bool32 NoAliveMonsForPlayer(void); bool32 NoAliveMonsForEitherParty(void); void SetMoveEffect(bool32 primary, u32 certain); bool32 CanBattlerSwitch(u32 battlerId); @@ -52,6 +53,7 @@ void StealTargetItem(u8 battlerStealer, u8 battlerItem); u8 GetCatchingBattler(void); u32 GetHighestStatId(u32 battlerId); bool32 ProteanTryChangeType(u32 battler, u32 ability, u32 move, u32 moveType); +bool32 IsMoveNotAllowedInSkyBattles(u32 move); bool32 DoSwitchInAbilitiesItems(u32 battlerId); u8 GetFirstFaintedPartyIndex(u8 battlerId); bool32 IsMoveAffectedByParentalBond(u32 move, u32 battler); diff --git a/include/battle_util.h b/include/battle_util.h index 20c458fc9490..f58a5a15436f 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -249,5 +249,7 @@ bool32 AreBattlersOfOppositeGender(u32 battler1, u32 battler2); bool32 AreBattlersOfSameGender(u32 battler1, u32 battler2); u32 CalcSecondaryEffectChance(u32 battler, u8 secondaryEffectChance, u16 moveEffect); u8 GetBattlerType(u32 battler, u8 typeIndex); +bool8 CanMonParticipateInSkyBattle(struct Pokemon *mon); +bool8 IsMonBannedFromSkyBattles(u16 species); #endif // GUARD_BATTLE_UTIL_H diff --git a/include/config/battle.h b/include/config/battle.h index cc82096e15a5..622054ab2854 100644 --- a/include/config/battle.h +++ b/include/config/battle.h @@ -170,6 +170,12 @@ #define VAR_TERRAIN 0 // If this var has a value, assigning a STATUS_FIELD_xx_TERRAIN to it before battle causes the battle to start with that terrain active #define B_VAR_WILD_AI_FLAGS 0 // If not 0, you can use this var to add to default wild AI flags. NOT usable with flags above (1 << 15) +// Sky Battles +#define B_FLAG_SKY_BATTLE 0 // If this flag has a value, the player will be able to engage in scripted Sky Battles. +#define B_VAR_SKY_BATTLE 0 // If this var has a value, the game will remember the positions of Pokémon used in Sky Battles. + +#define B_SKY_BATTLE_STRICT_ELIGIBILITY FALSE //If TRUE, Sky Battles will use the eligibility from Pokémon XY. If FALSE, all Flying-types or Pokémon with Levitate are allowed. + // Flag and Var settings #define B_RESET_FLAGS_VARS_AFTER_WHITEOUT TRUE // If TRUE, Overworld_ResetBattleFlagsAndVars will reset battle-related Flags and Vars when the player whites out. diff --git a/include/debug.h b/include/debug.h index d89c7994b860..e25392091b16 100644 --- a/include/debug.h +++ b/include/debug.h @@ -2,6 +2,7 @@ #define GUARD_DEBUG_H void Debug_ShowMainMenu(void); +extern const u8 Debug_FlagsAndVarNotSetBattleConfigMessage[]; extern EWRAM_DATA bool8 gIsDebugBattle; extern EWRAM_DATA u32 gDebugAIFlags; diff --git a/include/field_specials.h b/include/field_specials.h index faf71e9c0877..d36f2686c286 100644 --- a/include/field_specials.h +++ b/include/field_specials.h @@ -31,5 +31,6 @@ bool8 UsedPokemonCenterWarp(void); void ResetFanClub(void); bool8 ShouldShowBoxWasFullMessage(void); void SetPCBoxToSendMon(u8 boxId); +void PreparePartyForSkyBattle(void); #endif // GUARD_FIELD_SPECIALS_H diff --git a/include/pokemon.h b/include/pokemon.h index ff78373fa55e..5efa6a97a31d 100644 --- a/include/pokemon.h +++ b/include/pokemon.h @@ -393,6 +393,7 @@ struct BattleMove u32 instructBanned:1; u32 encoreBanned:1; u32 parentalBondBanned:1; + u32 skyBattleBanned:1; }; #define SPINDA_SPOT_WIDTH 16 diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 780f630ed82e..9fd43a3d1e5e 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1256,6 +1256,11 @@ bool32 ProteanTryChangeType(u32 battler, u32 ability, u32 move, u32 moveType) return FALSE; } +bool32 IsMoveNotAllowedInSkyBattles(u32 move) +{ + return ((gBattleStruct->isSkyBattle) && (gBattleMoves[gCurrentMove].skyBattleBanned)); +} + static void Cmd_attackcanceler(void) { CMD_ARGS(); @@ -1363,9 +1368,10 @@ static void Cmd_attackcanceler(void) } gHitMarker |= HITMARKER_OBEYS; - // Check if no available target present on the field. - if (NoTargetPresent(gBattlerAttacker, gCurrentMove) + // Check if no available target present on the field or if Sky Battles ban the move + if ((NoTargetPresent(gBattlerAttacker, gCurrentMove) && (!gBattleMoves[gCurrentMove].twoTurnMove || (gBattleMons[gBattlerAttacker].status2 & STATUS2_MULTIPLETURNS))) + || (IsMoveNotAllowedInSkyBattles(gCurrentMove))) { if (gBattleMoves[gCurrentMove].effect == EFFECT_FLING) // Edge case for removing a mon's item when there is no target available after using Fling. gBattlescriptCurrInstr = BattleScript_FlingFailConsumeItem; @@ -3595,7 +3601,11 @@ void SetMoveEffect(bool32 primary, u32 certain) { gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SPIKESSCATTERED; BattleScriptPush(gBattlescriptCurrInstr + 1); - gBattlescriptCurrInstr = BattleScript_SpikesActivates; + + if (gBattleStruct->isSkyBattle) + gBattlescriptCurrInstr++; + else + gBattlescriptCurrInstr = BattleScript_SpikesActivates; } break; case MOVE_EFFECT_TRIPLE_ARROWS: @@ -4324,7 +4334,7 @@ static bool32 NoAliveMonsForPlayerAndPartner(void) return (HP_count == 0); } -static bool32 NoAliveMonsForPlayer(void) +bool32 NoAliveMonsForPlayer(void) { u32 i; u32 HP_count = 0; diff --git a/src/battle_setup.c b/src/battle_setup.c index e8ffcd8908a5..72a41aca9ce2 100644 --- a/src/battle_setup.c +++ b/src/battle_setup.c @@ -1,5 +1,6 @@ #include "global.h" #include "battle.h" +#include "load_save.h" #include "battle_setup.h" #include "battle_transition.h" #include "main.h" @@ -81,6 +82,8 @@ static void TryUpdateGymLeaderRematchFromTrainer(void); static void CB2_GiveStarter(void); static void CB2_StartFirstBattle(void); static void CB2_EndFirstBattle(void); +static void SaveChangesToPlayerParty(void); +static void HandleBattleVariantEndParty(void); static void CB2_EndTrainerBattle(void); static bool32 IsPlayerDefeated(u32 battleOutcome); static u16 GetRematchTrainerId(u16 trainerId); @@ -1380,15 +1383,40 @@ void BattleSetup_StartTrainerBattle_Debug(void) ScriptContext_Stop(); } +static void SaveChangesToPlayerParty(void) +{ + u8 i = 0, j = 0; + u8 participatedPokemon = VarGet(B_VAR_SKY_BATTLE); + for (i = 0; i < PARTY_SIZE; i++) + { + if ((participatedPokemon >> i & 1) == 1) + { + gSaveBlock1Ptr->playerParty[i] = gPlayerParty[j]; + j++; + } + } +} + +static void HandleBattleVariantEndParty(void) +{ + if (B_FLAG_SKY_BATTLE == 0 || !FlagGet(B_FLAG_SKY_BATTLE)) + return; + SaveChangesToPlayerParty(); + LoadPlayerParty(); + FlagClear(B_FLAG_SKY_BATTLE); +} + static void CB2_EndTrainerBattle(void) { + HandleBattleVariantEndParty(); + if (gTrainerBattleOpponent_A == TRAINER_SECRET_BASE) { SetMainCallback2(CB2_ReturnToFieldContinueScriptPlayMapMusic); } else if (IsPlayerDefeated(gBattleOutcome) == TRUE) { - if (InBattlePyramid() || InTrainerHillChallenge()) + if (InBattlePyramid() || InTrainerHillChallenge() || (!NoAliveMonsForPlayer())) SetMainCallback2(CB2_ReturnToFieldContinueScriptPlayMapMusic); else SetMainCallback2(CB2_WhiteOut); diff --git a/src/battle_util.c b/src/battle_util.c index 833277b9ef19..475b38c78b4b 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -4095,7 +4095,7 @@ bool32 TryChangeBattleWeather(u32 battler, u32 weatherEnumId, bool32 viaAbility) static bool32 TryChangeBattleTerrain(u32 battler, u32 statusFlag, u8 *timer) { - if (!(gFieldStatuses & statusFlag)) + if ((!(gFieldStatuses & statusFlag) && (!gBattleStruct->isSkyBattle))) { gFieldStatuses &= ~(STATUS_FIELD_MISTY_TERRAIN | STATUS_FIELD_GRASSY_TERRAIN | STATUS_FIELD_ELECTRIC_TERRAIN | STATUS_FIELD_PSYCHIC_TERRAIN); gFieldStatuses |= statusFlag; @@ -5756,6 +5756,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 break; case ABILITY_TOXIC_DEBRIS: if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) + && (!gBattleStruct->isSkyBattle) && !gProtectStructs[gBattlerAttacker].confusionSelfDmg && IS_MOVE_PHYSICAL(gCurrentMove) && TARGET_TURN_DAMAGED @@ -11267,6 +11268,56 @@ bool32 IsGen6ExpShareEnabled(void) #endif } +bool8 CanMonParticipateInSkyBattle(struct Pokemon *mon) +{ + u16 species = GetMonData(mon, MON_DATA_SPECIES); + u16 monAbilityNum = GetMonData(mon, MON_DATA_ABILITY_NUM, NULL); + + bool8 hasLevitateAbility = gSpeciesInfo[species].abilities[monAbilityNum] == ABILITY_LEVITATE; + bool8 isFlyingType = gSpeciesInfo[species].types[0] == TYPE_FLYING || gSpeciesInfo[species].types[1] == TYPE_FLYING; + bool8 monIsValidAndNotEgg = GetMonData(mon, MON_DATA_SANITY_HAS_SPECIES) && !GetMonData(mon, MON_DATA_IS_EGG); + + if (monIsValidAndNotEgg) + { + if ((hasLevitateAbility || isFlyingType) && !IsMonBannedFromSkyBattles(species)) + return TRUE; + } + return FALSE; +} + +bool8 IsMonBannedFromSkyBattles(u16 species) +{ + switch (species) + { +#if B_SKY_BATTLE_STRICT_ELIGIBILITY == TRUE + case SPECIES_SPEAROW: + case SPECIES_FARFETCHD: + case SPECIES_DODUO: + case SPECIES_DODRIO: + case SPECIES_HOOTHOOT: + case SPECIES_NATU: + case SPECIES_MURKROW: + case SPECIES_DELIBIRD: + case SPECIES_TAILLOW: + case SPECIES_STARLY: + case SPECIES_CHATOT: + case SPECIES_SHAYMIN: + case SPECIES_PIDOVE: + case SPECIES_ARCHEN: + case SPECIES_DUCKLETT: + case SPECIES_RUFFLET: + case SPECIES_VULLABY: + case SPECIES_FLETCHLING: + case SPECIES_HAWLUCHA: + case SPECIES_ROWLET: + case SPECIES_PIKIPEK: +#endif + case SPECIES_EGG: + return TRUE; + default: + return FALSE; + } +} u8 GetBattlerType(u32 battler, u8 typeIndex) { diff --git a/src/battle_util2.c b/src/battle_util2.c index 8dda3004c057..2c333978d240 100644 --- a/src/battle_util2.c +++ b/src/battle_util2.c @@ -19,6 +19,10 @@ void AllocateBattleResources(void) gBattleStruct = AllocZeroed(sizeof(*gBattleStruct)); +#if B_FLAG_SKY_BATTLE + gBattleStruct->isSkyBattle = FlagGet(B_FLAG_SKY_BATTLE); +#endif + gBattleResources = AllocZeroed(sizeof(*gBattleResources)); gBattleResources->secretBase = AllocZeroed(sizeof(*gBattleResources->secretBase)); gBattleResources->flags = AllocZeroed(sizeof(*gBattleResources->flags)); diff --git a/src/data/battle_moves.h b/src/data/battle_moves.h index cc33db08db61..a5ef4960a710 100644 --- a/src/data/battle_moves.h +++ b/src/data/battle_moves.h @@ -376,6 +376,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .split = SPLIT_PHYSICAL, .zMoveEffect = Z_EFFECT_NONE, .makesContact = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_VINE_WHIP] = @@ -416,6 +417,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .makesContact = TRUE, .sheerForceBoost = TRUE, .minimizeDoubleDamage = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_DOUBLE_KICK] = @@ -604,6 +606,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .makesContact = TRUE, .sheerForceBoost = TRUE, .minimizeDoubleDamage = B_UPDATED_MOVE_FLAGS >= GEN_6, + .skyBattleBanned = TRUE, }, [MOVE_WRAP] = @@ -1008,6 +1011,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .split = SPLIT_SPECIAL, .zMoveEffect = Z_EFFECT_NONE, .damagesUnderwater = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_ICE_BEAM] = @@ -1216,6 +1220,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .split = SPLIT_PHYSICAL, .zMoveEffect = Z_EFFECT_NONE, .makesContact = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_STRENGTH] = @@ -1574,6 +1579,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .zMoveEffect = Z_EFFECT_NONE, .ignoresKingsRock = B_UPDATED_MOVE_FLAGS < GEN_3, .damagesUnderground = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_FISSURE] = @@ -1589,6 +1595,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .split = SPLIT_PHYSICAL, .zMoveEffect = Z_EFFECT_NONE, .damagesUnderground = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_DIG] = @@ -1612,6 +1619,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .sleepTalkBanned = TRUE, .instructBanned = TRUE, .assistBanned = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_TOXIC] = @@ -2913,6 +2921,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .snatchAffected = TRUE, .ignoresProtect = TRUE, .mirrorMoveBanned = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_STRUGGLE] = @@ -3416,6 +3425,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .mirrorMoveBanned = TRUE, .magicCoatAffected = B_UPDATED_MOVE_FLAGS >= GEN_5, .forcePressure = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_ZAP_CANNON] = @@ -3986,6 +3996,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .split = SPLIT_PHYSICAL, .zMoveEffect = Z_EFFECT_NONE, .damagesUnderground = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_DYNAMIC_PUNCH] = @@ -4952,6 +4963,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .snatchAffected = TRUE, .ignoresProtect = TRUE, .mirrorMoveBanned = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_SUPERPOWER] = @@ -5219,6 +5231,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .sleepTalkBanned = TRUE, .instructBanned = TRUE, .assistBanned = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_ARM_THRUST] = @@ -5368,6 +5381,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .zMoveEffect = Z_EFFECT_SPDEF_UP_1, .ignoresProtect = TRUE, .mirrorMoveBanned = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_ICE_BALL] = @@ -5891,6 +5905,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .split = SPLIT_SPECIAL, .zMoveEffect = Z_EFFECT_NONE, .sheerForceBoost = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_BULLET_SEED] = @@ -6028,6 +6043,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .priority = 0, .split = SPLIT_SPECIAL, .zMoveEffect = Z_EFFECT_NONE, + .skyBattleBanned = TRUE, }, [MOVE_BULK_UP] = @@ -6182,6 +6198,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .zMoveEffect = Z_EFFECT_SPDEF_UP_1, .ignoresProtect = TRUE, .mirrorMoveBanned = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_CALM_MIND] = @@ -6360,6 +6377,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .zMoveEffect = Z_EFFECT_SPATK_UP_1, .ignoresProtect = TRUE, .mirrorMoveBanned = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_MIRACLE_EYE] = @@ -6934,6 +6952,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .mirrorMoveBanned = TRUE, .magicCoatAffected = B_UPDATED_MOVE_FLAGS >= GEN_5, .forcePressure = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_HEART_SWAP] = @@ -7343,6 +7362,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .split = SPLIT_SPECIAL, .zMoveEffect = Z_EFFECT_NONE, .sheerForceBoost = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_SWITCHEROO] = @@ -7878,6 +7898,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .split = SPLIT_SPECIAL, .zMoveEffect = Z_EFFECT_NONE, .makesContact = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_CHATTER] = @@ -8428,6 +8449,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .split = SPLIT_PHYSICAL, .zMoveEffect = Z_EFFECT_NONE, .damagesAirborne = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_STORM_THROW] = @@ -8510,6 +8532,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .zMoveEffect = Z_EFFECT_NONE, .makesContact = TRUE, .minimizeDoubleDamage = B_UPDATED_MOVE_FLAGS >= GEN_7, + .skyBattleBanned = TRUE, }, [MOVE_SYNCHRONOISE] = @@ -9077,6 +9100,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .priority = 0, .split = SPLIT_SPECIAL, .zMoveEffect = Z_EFFECT_NONE, + .skyBattleBanned = TRUE, }, [MOVE_FIRE_PLEDGE] = @@ -9095,6 +9119,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .priority = 0, .split = SPLIT_SPECIAL, .zMoveEffect = Z_EFFECT_NONE, + .skyBattleBanned = TRUE, }, [MOVE_GRASS_PLEDGE] = @@ -9113,6 +9138,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .priority = 0, .split = SPLIT_SPECIAL, .zMoveEffect = Z_EFFECT_NONE, + .skyBattleBanned = TRUE, }, [MOVE_VOLT_SWITCH] = @@ -9161,6 +9187,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .split = SPLIT_PHYSICAL, .zMoveEffect = Z_EFFECT_NONE, .sheerForceBoost = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_FROST_BREATH] = @@ -9779,6 +9806,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .makesContact = TRUE, .minimizeDoubleDamage = TRUE, .gravityBanned = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_MAT_BLOCK] = @@ -9800,6 +9828,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .metronomeBanned = TRUE, .copycatBanned = TRUE, .assistBanned = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_BELCH] = @@ -9838,6 +9867,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .zMoveEffect = Z_EFFECT_ATK_UP_1, .ignoresProtect = TRUE, .mirrorMoveBanned = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_STICKY_WEB] = @@ -9855,6 +9885,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .ignoresProtect = TRUE, .mirrorMoveBanned = TRUE, .magicCoatAffected = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_FELL_STINGER] = @@ -10129,6 +10160,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .zMoveEffect = Z_EFFECT_DEF_UP_1, .ignoresProtect = TRUE, .mirrorMoveBanned = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_MISTY_TERRAIN] = @@ -10145,6 +10177,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .zMoveEffect = Z_EFFECT_SPDEF_UP_1, .ignoresProtect = TRUE, .mirrorMoveBanned = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_ELECTRIFY] = @@ -10485,6 +10518,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .twoTurnMove = TRUE, .sleepTalkBanned = TRUE, .instructBanned = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_MAGNETIC_FLUX] = @@ -10535,6 +10569,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .zMoveEffect = Z_EFFECT_SPD_UP_1, .ignoresProtect = TRUE, .mirrorMoveBanned = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_DAZZLING_GLEAM] = @@ -10704,6 +10739,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .damagesAirborne = TRUE, .ignoreTypeIfFlyingAndUngrounded = TRUE, .metronomeBanned = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_THOUSAND_WAVES] = @@ -10719,6 +10755,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .split = SPLIT_PHYSICAL, .zMoveEffect = Z_EFFECT_NONE, .metronomeBanned = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_LANDS_WRATH] = @@ -10733,6 +10770,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .priority = 0, .split = SPLIT_PHYSICAL, .zMoveEffect = Z_EFFECT_NONE, + .skyBattleBanned = TRUE, }, [MOVE_LIGHT_OF_RUIN] = @@ -11470,6 +11508,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .split = SPLIT_PHYSICAL, .zMoveEffect = Z_EFFECT_NONE, .makesContact = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_SHADOW_BONE] = @@ -12232,6 +12271,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .zMoveEffect = Z_EFFECT_NONE, .makesContact = TRUE, .metronomeBanned = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_DECORATE] = @@ -12281,6 +12321,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .zMoveEffect = Z_EFFECT_NONE, .makesContact = TRUE, .metronomeBanned = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_PYRO_BALL] = @@ -12599,6 +12640,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .zMoveEffect = Z_EFFECT_NONE, .makesContact = TRUE, .argument = ARG_TRY_REMOVE_TERRAIN_FAIL, // Remove a field terrain if there is one and hit, otherwise fail. + .skyBattleBanned = TRUE, }, [MOVE_SCALE_SHOT] = @@ -12678,6 +12720,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .split = SPLIT_PHYSICAL, .zMoveEffect = Z_EFFECT_NONE, .makesContact = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_RISING_VOLTAGE] = @@ -13199,6 +13242,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .split = SPLIT_PHYSICAL, .zMoveEffect = Z_EFFECT_NONE, .makesContact = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_CHLOROBLAST] = @@ -13644,6 +13688,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .split = SPLIT_PHYSICAL, .zMoveEffect = Z_EFFECT_NONE, .makesContact = TRUE, + .skyBattleBanned = TRUE, }, [MOVE_POPULATION_BOMB] = @@ -13678,6 +13723,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .zMoveEffect = Z_EFFECT_NONE, .makesContact = TRUE, .argument = ARG_TRY_REMOVE_TERRAIN_HIT, // Remove the active field terrain if there is one. + .skyBattleBanned = TRUE, }, [MOVE_GLAIVE_RUSH] = @@ -14479,6 +14525,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = .priority = 0, .split = SPLIT_PHYSICAL, .zMoveEffect = 0, + .skyBattleBanned = TRUE, }, [MOVE_CONTINENTAL_CRUSH] = { diff --git a/src/debug.c b/src/debug.c index 9db4862120e6..23c21f76f03b 100644 --- a/src/debug.c +++ b/src/debug.c @@ -402,6 +402,7 @@ static void DebugAction_Sound_MUS_SelectId(u8 taskId); extern const u8 Debug_FlagsNotSetOverworldConfigMessage[]; extern const u8 Debug_FlagsNotSetBattleConfigMessage[]; +extern const u8 Debug_FlagsAndVarNotSetBattleConfigMessage[]; extern const u8 Debug_EventScript_Script_1[]; extern const u8 Debug_EventScript_Script_2[]; extern const u8 Debug_EventScript_Script_3[]; diff --git a/src/field_specials.c b/src/field_specials.c index 13b8ae9daf48..c4968a6cc827 100644 --- a/src/field_specials.c +++ b/src/field_specials.c @@ -1,4 +1,5 @@ #include "global.h" +#include "debug.h" #include "malloc.h" #include "battle.h" #include "battle_tower.h" @@ -20,6 +21,7 @@ #include "international_string_util.h" #include "item_icon.h" #include "link.h" +#include "load_save.h" #include "list_menu.h" #include "main.h" #include "mystery_gift.h" @@ -4207,3 +4209,47 @@ u8 Script_TryGainNewFanFromCounter(void) { return TryGainNewFanFromCounter(gSpecialVar_0x8004); } + +void TrySkyBattle(void) +{ + int i; + + if (B_VAR_SKY_BATTLE == 0 || B_FLAG_SKY_BATTLE == 0) + { + LockPlayerFieldControls(); + ScriptContext_SetupScript(Debug_FlagsAndVarNotSetBattleConfigMessage); + return; + } + for (i = 0; i < CalculatePlayerPartyCount(); i++) + { + struct Pokemon* pokemon = &gPlayerParty[i]; + if (CanMonParticipateInSkyBattle(pokemon) && GetMonData(pokemon, MON_DATA_HP, NULL) > 0) + { + PreparePartyForSkyBattle(); + gSpecialVar_Result = TRUE; + return; + } + } + gSpecialVar_Result = FALSE; +} + +void PreparePartyForSkyBattle(void) +{ + int i, participatingPokemonSlot = 0; + u8 partyCount = CalculatePlayerPartyCount(); + + FlagSet(B_FLAG_SKY_BATTLE); + SavePlayerParty(); + + for (i = 0; i < partyCount; i++) + { + struct Pokemon* pokemon = &gPlayerParty[i]; + + if (CanMonParticipateInSkyBattle(pokemon)) + participatingPokemonSlot += 1 << i; + else + ZeroMonData(pokemon); + } + VarSet(B_VAR_SKY_BATTLE,participatingPokemonSlot); + CompactPartySlots(); +} diff --git a/src/load_save.c b/src/load_save.c index 44e08b5e9c46..4f706180c4e6 100644 --- a/src/load_save.c +++ b/src/load_save.c @@ -13,6 +13,7 @@ #include "gba/flash_internal.h" #include "decoration_inventory.h" #include "agb_flash.h" +#include "event_data.h" static void ApplyNewEncryptionKeyToAllEncryptedData(u32 encryptionKey); From fe16a2cdee053e708839e483548a1754f3be0de7 Mon Sep 17 00:00:00 2001 From: psf <77138753+pkmnsnfrn@users.noreply.github.com> Date: Sun, 26 Nov 2023 09:58:43 -0800 Subject: [PATCH 13/18] Port pokefirered's Vs. Seeker to pokeemerald (#3256) * First version of Vs. Seeker * Update movement.inc https://github.com/rh-hideout/pokeemerald-expansion/pull/3256/files/98f7e9978d8cd16083ea1bc7857f4f0235a9f1af#r1306721924 * Update field_effect_scripts.s https://github.com/rh-hideout/pokeemerald-expansion/pull/3256/files#r1306722004 * Update field_effect_scripts.s https://github.com/rh-hideout/pokeemerald-expansion/pull/3256/files#r1306722024 * Update item_use.h https://github.com/rh-hideout/pokeemerald-expansion/pull/3256/files#r1306722401 * Update movement_action_func_tables.h https://github.com/rh-hideout/pokeemerald-expansion/pull/3256/files#r1306722828 * Update event_object_movement.c https://github.com/rh-hideout/pokeemerald-expansion/pull/3256/files#r1306722887 * Update overworld.c https://github.com/rh-hideout/pokeemerald-expansion/pull/3256/files#r1306723396 * Update vs_seeker.h https://github.com/rh-hideout/pokeemerald-expansion/pull/3256/files#r1306724158 * Update vs_seeker.c Addressed some cleanup comments from SBird * Update UpdateRandomTrainerRematches Fixed typo in ClearAllTrainerRematchStates Fixed types in GetRematchableTrainerLocalId * Updated UseVsSeekerEffect_2 * Updated UseVsSeekerEffect_3 * Updated UseVsSeekerEffect_4 * Fixed bug that allowed Vs Seeker to be used indoors in correct places Moved VsSeeker function declarations into header * Refactored FieldUseFunc_VsSeeker * Added curly braces to else case in FieldUseFunc_VsSeeker * renamed data[x] in Task_ResetObjectsRematchWantedState * Refactored Task_ResetObjectsRematchWantedState * Refactored VsSeekerResetObjectMovementAfterChargeComplete * Refactored ResetMovementOfRematchableTrainers * Refactored GatherNearbyTrainerInfo * Refactored Task_VsSeeker_3 * CanUseVsSeeker * Refactored GetVsSeekerResponseInArea * GetCurVsSeekerResponse refactored * Cleaned up GetTrainerFlagFromScript * Gave sensible names to Task_VsSeeker * Fixed two bugs where player would not have the right gfx state after using VsSeeker on a Bike or Underwater * Renamed UseVsSeeker Functions * Added I_VS_SEEKER_CHARGING to make Vs. Seeker broken until flag is assigned Removed extra VsSeeker animation code * Addressed PR feedback * Fixed issue with building non-modern * Refactored GetRunningBehaviorFromGraphicsId and renamed to GetResponseMovementTypeFromTrainerGraphicsId * Addresses Lunos's PR feedback: https://github.com/rh-hideout/pokeemerald-expansion/pull/3256\#pullrequestreview-1623547850 Removed the check to see if a map was not indoors to improve readability Made IsValidLocationForVsSeeker into a static function * Added changes in response to Jasper's feedback https://github.com/rh-hideout/pokeemerald-expansion/pull/3256\#pullrequestreview-1725276522 * Updated with Edu's discord feedback https://discord.com/channels/419213663107416084/1135040810082123907/1176872015085453392 * Removed ifdef tags around the repo unless needed https://github.com/rh-hideout/pokeemerald-expansion/pull/3256/files/b5dc744cedc8e14c88d461afbbdc693c4f54e950 --- asm/macros/movement.inc | 2 + data/event_scripts.s | 7 + data/field_effect_scripts.s | 16 + data/specials.inc | 2 + data/text/trainers.inc | 15 + graphics/field_effects/pics/emote_x.png | Bin 0 -> 190 bytes .../pics/emotion_double_exclamation.png | Bin 0 -> 173 bytes include/battle_setup.h | 3 + include/config/item.h | 3 + include/constants/event_object_movement.h | 3 + include/constants/field_effects.h | 3 + include/event_object_movement.h | 3 + include/event_scripts.h | 5 + include/global.fieldmap.h | 1 + include/item_use.h | 2 + include/trainer_see.h | 2 + include/vs_seeker.h | 14 + src/battle_setup.c | 60 +- src/data/items.h | 4 + .../movement_action_func_tables.h | 14 + src/event_object_movement.c | 16 + src/field_control_avatar.c | 6 + src/field_effect.c | 72 ++ src/field_player_avatar.c | 4 +- src/item_use.c | 71 +- src/overworld.c | 9 + src/trainer_see.c | 48 +- src/vs_seeker.c | 793 ++++++++++++++++++ 28 files changed, 1155 insertions(+), 23 deletions(-) create mode 100644 graphics/field_effects/pics/emote_x.png create mode 100644 graphics/field_effects/pics/emotion_double_exclamation.png create mode 100644 include/vs_seeker.h create mode 100644 src/vs_seeker.c diff --git a/asm/macros/movement.inc b/asm/macros/movement.inc index a43dc07ac53f..b0bb1f35ee9f 100644 --- a/asm/macros/movement.inc +++ b/asm/macros/movement.inc @@ -162,5 +162,7 @@ create_movement_action figure_8, MOVEMENT_ACTION_FIGURE_8 create_movement_action fly_up, MOVEMENT_ACTION_FLY_UP create_movement_action fly_down, MOVEMENT_ACTION_FLY_DOWN + create_movement_action emote_double_exclamation_mark, MOVEMENT_ACTION_EMOTE_DOUBLE_EXCL_MARK + create_movement_action emote_x, MOVEMENT_ACTION_EMOTE_X create_movement_action step_end, MOVEMENT_ACTION_STEP_END diff --git a/data/event_scripts.s b/data/event_scripts.s index a3768661813a..7ce6f60485ae 100644 --- a/data/event_scripts.s +++ b/data/event_scripts.s @@ -1005,6 +1005,13 @@ Common_EventScript_LegendaryFlewAway:: release end +EventScript_VsSeekerChargingDone:: + special VsSeekerFreezeObjectsAfterChargeComplete + waitstate + special VsSeekerResetObjectMovementAfterChargeComplete + releaseall + end + .include "data/scripts/pc_transfer.inc" .include "data/scripts/questionnaire.inc" .include "data/scripts/abnormal_weather.inc" diff --git a/data/field_effect_scripts.s b/data/field_effect_scripts.s index 49fcf798b681..43f42225963a 100644 --- a/data/field_effect_scripts.s +++ b/data/field_effect_scripts.s @@ -1,3 +1,4 @@ +#include "config/item.h" .include "asm/macros.inc" .include "constants/constants.inc" @@ -72,6 +73,9 @@ gFieldEffectScriptPointers:: .4byte gFieldEffectScript_RayquazaSpotlight @ FLDEFF_RAYQUAZA_SPOTLIGHT .4byte gFieldEffectScript_DestroyDeoxysRock @ FLDEFF_DESTROY_DEOXYS_ROCK .4byte gFieldEffectScript_MoveDeoxysRock @ FLDEFF_MOVE_DEOXYS_ROCK + .4byte gFldEffScript_UseVsSeeker @ FLDEFF_USE_VS_SEEKER + .4byte gFldEffScript_XIcon @ FLDEFF_X_ICON + .4byte gFldEffScript_DoubleExclMarkIcon @ FLDEFF_DOUBLE_EXCL_MARK_ICON gFieldEffectScript_ExclamationMarkIcon1:: field_eff_callnative FldEff_ExclamationMarkIcon @@ -343,3 +347,15 @@ gFieldEffectScript_DestroyDeoxysRock:: gFieldEffectScript_MoveDeoxysRock:: field_eff_callnative FldEff_MoveDeoxysRock field_eff_end + +gFldEffScript_UseVsSeeker:: + field_eff_callnative FldEff_UseVsSeeker + field_eff_end + +gFldEffScript_XIcon:: + field_eff_callnative FldEff_XIcon + field_eff_end + +gFldEffScript_DoubleExclMarkIcon:: + field_eff_callnative FldEff_DoubleExclMarkIcon + field_eff_end diff --git a/data/specials.inc b/data/specials.inc index 34e86cad29f9..f05655cbd377 100644 --- a/data/specials.inc +++ b/data/specials.inc @@ -540,3 +540,5 @@ gSpecials:: def_special GetSprayId def_special GetLastUsedSprayType def_special TrySkyBattle + def_special VsSeekerResetObjectMovementAfterChargeComplete + def_special VsSeekerFreezeObjectsAfterChargeComplete \ No newline at end of file diff --git a/data/text/trainers.inc b/data/text/trainers.inc index c3e85ef504e3..ac3747f805d7 100644 --- a/data/text/trainers.inc +++ b/data/text/trainers.inc @@ -4779,3 +4779,18 @@ Route134_Text_HudsonPostBattle: .string "Our boat drifted out to sea.\p" .string "My buddy's a timid fellow, so I'm\n" .string "worried about him.$" + +VSSeeker_Text_BatteryNotChargedNeedXSteps:: + .string "The battery isn't charged enough.\p" + .string "No. of steps required to fully\n" + .string "charge the battery: {STR_VAR_1}{PAUSE_UNTIL_PRESS}$" + +VSSeeker_Text_NoTrainersWithinRange:: + .string "There are no Trainers within range\n" + .string "who can battle…\p" + .string "The VS Seeker was turned off.{PAUSE_UNTIL_PRESS}$" + +VSSeeker_Text_TrainersNotReady:: + .string "The other Trainers don't appear\n" + .string "to be ready for battle.\p" + .string "Let's wait till later.{PAUSE_UNTIL_PRESS}$" diff --git a/graphics/field_effects/pics/emote_x.png b/graphics/field_effects/pics/emote_x.png new file mode 100644 index 0000000000000000000000000000000000000000..6d0206e1d406137c517bded08a0aafc5e0fd7b4f GIT binary patch literal 190 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6T`Z5GB1G~g8-ip*W#l~ z{$Jhl|LBx^Q;Mq{9se)cay6^E+c9X$(Qi@Rdj$-Gwk%1iE_SrEl=%PuKTwlw?!4nb z%Er^hF+^f&a>44wy? literal 0 HcmV?d00001 diff --git a/include/battle_setup.h b/include/battle_setup.h index 96aeb4b761a4..a2009eea4122 100644 --- a/include/battle_setup.h +++ b/include/battle_setup.h @@ -70,5 +70,8 @@ u16 CountBattledRematchTeams(u16 trainerId); void DoStandardWildBattle_Debug(void); void BattleSetup_StartTrainerBattle_Debug(void); +s32 TrainerIdToRematchTableId(const struct RematchTrainer *table, u16 trainerId); +s32 FirstBattleTrainerIdToRematchTableId(const struct RematchTrainer *table, u16 trainerId); +u16 GetRematchTrainerIdFromTable(const struct RematchTrainer *table, u16 firstBattleTrainerId); #endif // GUARD_BATTLE_SETUP_H diff --git a/include/config/item.h b/include/config/item.h index 55de3fdb6035..868afae1a784 100644 --- a/include/config/item.h +++ b/include/config/item.h @@ -29,4 +29,7 @@ #define VAR_LAST_REPEL_LURE_USED 0 // If this var has been assigned, last Repel/Lure used will be saved and the player will get prompted with the vanilla repel YES/NO option, unless I_REPEL_LURE_MENU is set to TRUE. #define I_REPEL_LURE_MENU TRUE // If TRUE, the player is able to choose which Repel/Lure to use once the previous one runs out. Cursor position is saved by VAR_LAST_REPEL_LURE_USED if not 0. +// Vs. Seeker +#define I_VS_SEEKER_CHARGING 0 // If this flag is assigned, the Vs Seeker will functionlity will be enabled. When the player has the Vs. Seeker, Match Call rematch functions will stop working. + #endif // GUARD_CONFIG_ITEM_H diff --git a/include/constants/event_object_movement.h b/include/constants/event_object_movement.h index af5af5340354..cd971efddfd4 100755 --- a/include/constants/event_object_movement.h +++ b/include/constants/event_object_movement.h @@ -242,6 +242,8 @@ #define MOVEMENT_ACTION_FIGURE_8 0x9B #define MOVEMENT_ACTION_FLY_UP 0x9C #define MOVEMENT_ACTION_FLY_DOWN 0x9D +#define MOVEMENT_ACTION_EMOTE_X 0x9E +#define MOVEMENT_ACTION_EMOTE_DOUBLE_EXCL_MARK 0x9F #define MOVEMENT_ACTION_STEP_END 0xFE #define MOVEMENT_ACTION_NONE 0xFF @@ -300,6 +302,7 @@ #define ANIM_GET_ON_OFF_POKEMON_EAST (ANIM_STD_COUNT + 3) #define ANIM_NURSE_BOW (ANIM_STD_COUNT + 0) +#define ANIM_RAISE_HAND (ANIM_STD_COUNT + 0) #define ANIM_FIELD_MOVE 0 diff --git a/include/constants/field_effects.h b/include/constants/field_effects.h index a620409479ae..f8efeb9b81b8 100644 --- a/include/constants/field_effects.h +++ b/include/constants/field_effects.h @@ -68,6 +68,9 @@ #define FLDEFF_RAYQUAZA_SPOTLIGHT 64 #define FLDEFF_DESTROY_DEOXYS_ROCK 65 #define FLDEFF_MOVE_DEOXYS_ROCK 66 +#define FLDEFF_USE_VS_SEEKER 67 +#define FLDEFF_X_ICON 68 +#define FLDEFF_DOUBLE_EXCL_MARK_ICON 69 #define FLDEFFOBJ_SHADOW_S 0 #define FLDEFFOBJ_SHADOW_M 1 diff --git a/include/event_object_movement.h b/include/event_object_movement.h index 01269cdb5eb2..b48522ebc881 100644 --- a/include/event_object_movement.h +++ b/include/event_object_movement.h @@ -440,4 +440,7 @@ bool32 IsVirtualObjectInvisible(u8 virtualObjId); void SetVirtualObjectSpriteAnim(u8 virtualObjId, u8 animNum); bool32 IsVirtualObjectAnimating(u8 virtualObjId); +bool8 MovementAction_EmoteX_Step0(struct ObjectEvent *, struct Sprite *); +bool8 MovementAction_EmoteDoubleExclamationMark_Step0(struct ObjectEvent *, struct Sprite *); + #endif //GUARD_EVENT_OBJECT_MOVEMENT_H diff --git a/include/event_scripts.h b/include/event_scripts.h index 4e0a88f82ac1..00b06165cf6b 100644 --- a/include/event_scripts.h +++ b/include/event_scripts.h @@ -612,4 +612,9 @@ extern const u8 EventScript_TradeCenter_Chair0[]; extern const u8 EventScript_ConfirmLeaveCableClubRoom[]; extern const u8 EventScript_TerminateLink[]; +extern const u8 VSSeeker_Text_BatteryNotChargedNeedXSteps[]; +extern const u8 VSSeeker_Text_NoTrainersWithinRange[]; +extern const u8 VSSeeker_Text_TrainersNotReady[]; +extern const u8 EventScript_VsSeekerChargingDone[]; + #endif // GUARD_EVENT_SCRIPTS_H diff --git a/include/global.fieldmap.h b/include/global.fieldmap.h index 7461929c3a5c..b5bf29ca88c8 100644 --- a/include/global.fieldmap.h +++ b/include/global.fieldmap.h @@ -254,6 +254,7 @@ enum { PLAYER_AVATAR_STATE_FIELD_MOVE, PLAYER_AVATAR_STATE_FISHING, PLAYER_AVATAR_STATE_WATERING, + PLAYER_AVATAR_STATE_VSSEEKER, }; #define PLAYER_AVATAR_FLAG_ON_FOOT (1 << 0) diff --git a/include/item_use.h b/include/item_use.h index 5abe87f0573b..90d671bd42c3 100644 --- a/include/item_use.h +++ b/include/item_use.h @@ -41,6 +41,8 @@ void ItemUseInBattle_PartyMenuChooseMove(u8 taskId); void Task_UseDigEscapeRopeOnField(u8 taskId); u8 CanUseDigOrEscapeRopeOnCurMap(void); u8 CheckIfItemIsTMHMOrEvolutionStone(u16 itemId); +void FieldUseFunc_VsSeeker(u8 taskId); +void Task_ItemUse_CloseMessageBoxAndReturnToField_VsSeeker(u8 taskId); enum { BALL_THROW_UNABLE_TWO_MONS, diff --git a/include/trainer_see.h b/include/trainer_see.h index ab808c720c7f..5518a91761c2 100644 --- a/include/trainer_see.h +++ b/include/trainer_see.h @@ -26,5 +26,7 @@ u8 FldEff_HeartIcon(void); u8 GetCurrentApproachingTrainerObjectEventId(void); u8 GetChosenApproachingTrainerObjectEventId(u8 arrayId); void PlayerFaceTrainerAfterBattle(void); +u8 FldEff_DoubleExclMarkIcon(void); +u8 FldEff_XIcon(void); #endif // GUARD_TRAINER_SEE_H diff --git a/include/vs_seeker.h b/include/vs_seeker.h new file mode 100644 index 000000000000..723e73bf372d --- /dev/null +++ b/include/vs_seeker.h @@ -0,0 +1,14 @@ +#ifndef GUARD_VS_SEEKER_H +#define GUARD_VS_SEEKER_H + +#include "global.h" + +void Task_InitVsSeekerAndCheckForTrainersOnScreen(u8 taskId); +bool8 UpdateVsSeekerStepCounter(void); +void MapResetTrainerRematches(u16 mapGroup, u16 mapNum); +void ClearRematchMovementByTrainerId(void); +u16 GetRematchTrainerIdVSSeeker(u16 trainerId); + +#define VSSEEKER_RECHARGE_STEPS 100 + +#endif //GUARD_VS_SEEKER_H diff --git a/src/battle_setup.c b/src/battle_setup.c index 72a41aca9ce2..5459c0492511 100644 --- a/src/battle_setup.c +++ b/src/battle_setup.c @@ -38,6 +38,8 @@ #include "mirage_tower.h" #include "field_screen_effect.h" #include "data.h" +#include "vs_seeker.h" +#include "item.h" #include "constants/battle_frontier.h" #include "constants/battle_setup.h" #include "constants/game_stat.h" @@ -1627,7 +1629,7 @@ static const u8 *GetTrainerCantBattleSpeech(void) return ReturnEmptyStringIfNull(sTrainerCannotBattleSpeech); } -static s32 FirstBattleTrainerIdToRematchTableId(const struct RematchTrainer *table, u16 trainerId) +s32 FirstBattleTrainerIdToRematchTableId(const struct RematchTrainer *table, u16 trainerId) { s32 i; @@ -1640,7 +1642,7 @@ static s32 FirstBattleTrainerIdToRematchTableId(const struct RematchTrainer *tab return -1; } -static s32 TrainerIdToRematchTableId(const struct RematchTrainer *table, u16 trainerId) +s32 TrainerIdToRematchTableId(const struct RematchTrainer *table, u16 trainerId) { s32 i, j; @@ -1686,30 +1688,42 @@ static void SetRematchIdForTrainer(const struct RematchTrainer *table, u32 table gSaveBlock1Ptr->trainerRematches[tableId] = i; } +static bool32 DoesCurrentMapMatchRematchTrainerMap(s32 i, const struct RematchTrainer *table, u16 mapGroup, u16 mapNum) +{ + return table[i].mapGroup == mapGroup && table[i].mapNum == mapNum; +} + +bool32 TrainerIsMatchCallRegistered(s32 i) +{ + return FlagGet(FLAG_MATCH_CALL_REGISTERED + i); +} + static bool32 UpdateRandomTrainerRematches(const struct RematchTrainer *table, u16 mapGroup, u16 mapNum) { s32 i; - bool32 ret = FALSE; + + if (CheckBagHasItem(ITEM_VS_SEEKER, 1) && I_VS_SEEKER_CHARGING != 0) + return FALSE; for (i = 0; i <= REMATCH_SPECIAL_TRAINER_START; i++) { - if (table[i].mapGroup == mapGroup && table[i].mapNum == mapNum && !IsRematchForbidden(i)) + if (DoesCurrentMapMatchRematchTrainerMap(i,table,mapGroup,mapNum) && !IsRematchForbidden(i)) + continue; + + if (gSaveBlock1Ptr->trainerRematches[i] != 0) + { + // Trainer already wants a rematch. Don't bother updating it. + return TRUE; + } + else if (TrainerIsMatchCallRegistered(i) && ((Random() % 100) <= 30)) + // 31% chance of getting a rematch. { - if (gSaveBlock1Ptr->trainerRematches[i] != 0) - { - // Trainer already wants a rematch. Don't bother updating it. - ret = TRUE; - } - else if (FlagGet(FLAG_MATCH_CALL_REGISTERED + i) - && (Random() % 100) <= 30) // 31% chance of getting a rematch. - { - SetRematchIdForTrainer(table, i); - ret = TRUE; - } + SetRematchIdForTrainer(table, i); + return TRUE; } } - return ret; + return FALSE; } void UpdateRematchIfDefeated(s32 rematchTableId) @@ -1772,7 +1786,7 @@ static bool8 IsTrainerReadyForRematch_(const struct RematchTrainer *table, u16 t return TRUE; } -static u16 GetRematchTrainerIdFromTable(const struct RematchTrainer *table, u16 firstBattleTrainerId) +u16 GetRematchTrainerIdFromTable(const struct RematchTrainer *table, u16 firstBattleTrainerId) { const struct RematchTrainer *trainerEntry; s32 i; @@ -1877,7 +1891,9 @@ static bool32 HasAtLeastFiveBadges(void) void IncrementRematchStepCounter(void) { - if (HasAtLeastFiveBadges()) + if (HasAtLeastFiveBadges() + && (I_VS_SEEKER_CHARGING != 0) + && (!CheckBagHasItem(ITEM_VS_SEEKER, 1))) { if (gSaveBlock1Ptr->trainerRematchStepCounter >= STEP_COUNTER_MAX) gSaveBlock1Ptr->trainerRematchStepCounter = STEP_COUNTER_MAX; @@ -1912,7 +1928,10 @@ bool32 IsRematchTrainerIn(u16 mapGroup, u16 mapNum) static u16 GetRematchTrainerId(u16 trainerId) { - return GetRematchTrainerIdFromTable(gRematchTable, trainerId); + if (FlagGet(I_VS_SEEKER_CHARGING) && (I_VS_SEEKER_CHARGING != 0)) + return GetRematchTrainerIdVSSeeker(trainerId); + else + return GetRematchTrainerIdFromTable(gRematchTable, trainerId); } u16 GetLastBeatenRematchTrainerId(u16 trainerId) @@ -1935,6 +1954,9 @@ bool8 IsTrainerReadyForRematch(void) static void HandleRematchVarsOnBattleEnd(void) { + if ((gBattleTypeFlags & BATTLE_TYPE_TRAINER) && (I_VS_SEEKER_CHARGING != 0)) + ClearRematchMovementByTrainerId(); + ClearTrainerWantRematchState(gRematchTable, gTrainerBattleOpponent_A); SetBattledTrainersFlags(); } diff --git a/src/data/items.h b/src/data/items.h index 670d7dd2e5f4..85fa2dc8939b 100644 --- a/src/data/items.h +++ b/src/data/items.h @@ -8723,7 +8723,11 @@ const struct Item gItems[] = .importance = 1, .pocket = POCKET_KEY_ITEMS, .type = ITEM_USE_FIELD, +#if I_VS_SEEKER_CHARGING != 0 + .fieldUseFunc = FieldUseFunc_VsSeeker, +#else .fieldUseFunc = ItemUseOutOfBattle_CannotUse, +#endif }, [ITEM_TM_CASE] = diff --git a/src/data/object_events/movement_action_func_tables.h b/src/data/object_events/movement_action_func_tables.h index 2e1b6b3b31a8..e16cae74fcdc 100755 --- a/src/data/object_events/movement_action_func_tables.h +++ b/src/data/object_events/movement_action_func_tables.h @@ -420,6 +420,8 @@ u8 (*const gMovementActionFuncs_StopLevitateAtTop[])(struct ObjectEvent *, struc u8 (*const gMovementActionFuncs_Figure8[])(struct ObjectEvent *, struct Sprite *); u8 (*const gMovementActionFuncs_FlyUp[])(struct ObjectEvent *, struct Sprite *); u8 (*const gMovementActionFuncs_FlyDown[])(struct ObjectEvent *, struct Sprite *); +u8 (*const gMovementActionFuncs_EmoteX[])(struct ObjectEvent *, struct Sprite *); +u8 (*const gMovementActionFuncs_EmoteDoubleExclMark[])(struct ObjectEvent *, struct Sprite *); u8 (*const *const gMovementActionFuncs[])(struct ObjectEvent *, struct Sprite *) = { [MOVEMENT_ACTION_FACE_DOWN] = gMovementActionFuncs_FaceDown, @@ -580,6 +582,8 @@ u8 (*const *const gMovementActionFuncs[])(struct ObjectEvent *, struct Sprite *) [MOVEMENT_ACTION_FIGURE_8] = gMovementActionFuncs_Figure8, [MOVEMENT_ACTION_FLY_UP] = gMovementActionFuncs_FlyUp, [MOVEMENT_ACTION_FLY_DOWN] = gMovementActionFuncs_FlyDown, + [MOVEMENT_ACTION_EMOTE_X] = gMovementActionFuncs_EmoteX, + [MOVEMENT_ACTION_EMOTE_DOUBLE_EXCL_MARK] = gMovementActionFuncs_EmoteDoubleExclMark, }; u8 (*const gMovementActionFuncs_FaceDown[])(struct ObjectEvent *, struct Sprite *) = { @@ -1519,3 +1523,13 @@ u8 (*const gMovementActionFuncs_StopLevitateAtTop[])(struct ObjectEvent *, struc MovementAction_StopLevitateAtTop_Step0, MovementAction_Finish, }; + +u8 (*const gMovementActionFuncs_EmoteX[])(struct ObjectEvent *, struct Sprite *) = { + MovementAction_EmoteX_Step0, + MovementAction_Finish, +}; + +u8 (*const gMovementActionFuncs_EmoteDoubleExclMark[])(struct ObjectEvent *, struct Sprite *) = { + MovementAction_EmoteDoubleExclamationMark_Step0, + MovementAction_Finish, +}; diff --git a/src/event_object_movement.c b/src/event_object_movement.c index cdd72d668c3e..6cd6955031c6 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -8965,3 +8965,19 @@ u8 MovementAction_Fly_Finish(struct ObjectEvent *objectEvent, struct Sprite *spr { return TRUE; } + +bool8 MovementAction_EmoteX_Step0(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + ObjectEventGetLocalIdAndMap(objectEvent, &gFieldEffectArguments[0], &gFieldEffectArguments[1], &gFieldEffectArguments[2]); + FieldEffectStart(FLDEFF_X_ICON); + sprite->sActionFuncId = 1; + return TRUE; +} + +bool8 MovementAction_EmoteDoubleExclamationMark_Step0(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + ObjectEventGetLocalIdAndMap(objectEvent, &gFieldEffectArguments[0], &gFieldEffectArguments[1], &gFieldEffectArguments[2]); + FieldEffectStart(FLDEFF_DOUBLE_EXCL_MARK_ICON); + sprite->sActionFuncId = 1; + return TRUE; +} diff --git a/src/field_control_avatar.c b/src/field_control_avatar.c index 6a5422046eca..d5c252415df9 100644 --- a/src/field_control_avatar.c +++ b/src/field_control_avatar.c @@ -28,6 +28,7 @@ #include "start_menu.h" #include "trainer_see.h" #include "trainer_hill.h" +#include "vs_seeker.h" #include "wild_encounter.h" #include "constants/event_bg.h" #include "constants/event_objects.h" @@ -615,6 +616,11 @@ static bool8 TryStartStepCountScript(u16 metatileBehavior) ScriptContext_SetupScript(MossdeepCity_SpaceCenter_2F_EventScript_RivalRayquazaCall); return TRUE; } + if (UpdateVsSeekerStepCounter()) + { + ScriptContext_SetupScript(EventScript_VsSeekerChargingDone); + return TRUE; + } } if (SafariZoneTakeStep() == TRUE) diff --git a/src/field_effect.c b/src/field_effect.c index cfb502155038..2d2aa174ffd9 100644 --- a/src/field_effect.c +++ b/src/field_effect.c @@ -31,6 +31,7 @@ #include "constants/metatile_behaviors.h" #include "constants/rgb.h" #include "constants/songs.h" +#include "constants/map_types.h" #define subsprite_table(ptr) {.subsprites = ptr, .subspriteCount = (sizeof ptr) / (sizeof(struct Subsprite))} @@ -231,6 +232,12 @@ static void SpriteCB_DeoxysRockFragment(struct Sprite *sprite); static void Task_MoveDeoxysRock(u8 taskId); +static void Task_FldEffUseVsSeeker(u8 taskId); +static void UseVsSeeker_StopPlayerMovement(struct Task *task); +static void UseVsSeeker_DoPlayerAnimation(struct Task *task); +static void UseVsSeeker_ResetPlayerGraphics(struct Task *task); +static void UseVsSeeker_CleanUpFieldEffect(struct Task *task); + // Static RAM declarations static u8 sActiveList[32]; @@ -3911,3 +3918,68 @@ static void Task_MoveDeoxysRock(u8 taskId) #undef tVelocityY #undef tMoveSteps #undef tObjEventId + +static void (*const sUseVsSeekerEffectFuncs[])(struct Task *task) = { + UseVsSeeker_StopPlayerMovement, + UseVsSeeker_DoPlayerAnimation, + UseVsSeeker_ResetPlayerGraphics, + UseVsSeeker_CleanUpFieldEffect +}; + +u32 FldEff_UseVsSeeker(void) +{ + CreateTask(Task_FldEffUseVsSeeker, 0xFF); + return 0; +} + +static void Task_FldEffUseVsSeeker(u8 taskId) +{ + sUseVsSeekerEffectFuncs[gTasks[taskId].data[0]](&gTasks[taskId]); +} + +static void UseVsSeeker_StopPlayerMovement(struct Task *task) +{ + LockPlayerFieldControls(); + FreezeObjectEvents(); + gPlayerAvatar.preventStep = TRUE; + task->data[0]++; +} + +static void UseVsSeeker_DoPlayerAnimation(struct Task *task) +{ + struct ObjectEvent * playerObj = &gObjectEvents[gPlayerAvatar.objectEventId]; + if ((ObjectEventIsMovementOverridden(playerObj) && (!(ObjectEventClearHeldMovementIfFinished(playerObj))))) + return; + + if (gMapHeader.mapType != MAP_TYPE_UNDERWATER) + { + SetPlayerAvatarFieldMove(); + ObjectEventSetHeldMovement(playerObj, MOVEMENT_ACTION_START_ANIM_IN_DIRECTION); + } + task->data[0]++; +} + +static void UseVsSeeker_ResetPlayerGraphics(struct Task *task) { + struct ObjectEvent* playerObj = &gObjectEvents[gPlayerAvatar.objectEventId]; + + if (!ObjectEventClearHeldMovementIfFinished(playerObj)) + return; + + if (gMapHeader.mapType != MAP_TYPE_UNDERWATER) + { + ObjectEventSetGraphicsId(&gObjectEvents[gPlayerAvatar.objectEventId], GetPlayerAvatarGraphicsIdByCurrentState()); + ObjectEventForceSetHeldMovement(playerObj, GetFaceDirectionMovementAction(playerObj->facingDirection)); + } + task->data[0]++; +} + +static void UseVsSeeker_CleanUpFieldEffect(struct Task *task) +{ + struct ObjectEvent * playerObj = &gObjectEvents[gPlayerAvatar.objectEventId]; + if (!ObjectEventClearHeldMovementIfFinished(playerObj)) + return; + + gPlayerAvatar.preventStep = FALSE; + FieldEffectActiveListRemove(FLDEFF_USE_VS_SEEKER); + DestroyTask(FindTaskIdByFunc(Task_FldEffUseVsSeeker)); +} diff --git a/src/field_player_avatar.c b/src/field_player_avatar.c index 0f087799e3fa..99cab7fd7714 100644 --- a/src/field_player_avatar.c +++ b/src/field_player_avatar.c @@ -241,7 +241,8 @@ static const u8 sRivalAvatarGfxIds[][2] = [PLAYER_AVATAR_STATE_UNDERWATER] = {OBJ_EVENT_GFX_BRENDAN_UNDERWATER, OBJ_EVENT_GFX_MAY_UNDERWATER}, [PLAYER_AVATAR_STATE_FIELD_MOVE] = {OBJ_EVENT_GFX_RIVAL_BRENDAN_FIELD_MOVE, OBJ_EVENT_GFX_RIVAL_MAY_FIELD_MOVE}, [PLAYER_AVATAR_STATE_FISHING] = {OBJ_EVENT_GFX_BRENDAN_FISHING, OBJ_EVENT_GFX_MAY_FISHING}, - [PLAYER_AVATAR_STATE_WATERING] = {OBJ_EVENT_GFX_BRENDAN_WATERING, OBJ_EVENT_GFX_MAY_WATERING} + [PLAYER_AVATAR_STATE_WATERING] = {OBJ_EVENT_GFX_BRENDAN_WATERING, OBJ_EVENT_GFX_MAY_WATERING}, + [PLAYER_AVATAR_STATE_VSSEEKER] = {OBJ_EVENT_GFX_RIVAL_BRENDAN_FIELD_MOVE, OBJ_EVENT_GFX_RIVAL_MAY_FIELD_MOVE}, }; static const u8 sPlayerAvatarGfxIds[][2] = @@ -254,6 +255,7 @@ static const u8 sPlayerAvatarGfxIds[][2] = [PLAYER_AVATAR_STATE_FIELD_MOVE] = {OBJ_EVENT_GFX_BRENDAN_FIELD_MOVE, OBJ_EVENT_GFX_MAY_FIELD_MOVE}, [PLAYER_AVATAR_STATE_FISHING] = {OBJ_EVENT_GFX_BRENDAN_FISHING, OBJ_EVENT_GFX_MAY_FISHING}, [PLAYER_AVATAR_STATE_WATERING] = {OBJ_EVENT_GFX_BRENDAN_WATERING, OBJ_EVENT_GFX_MAY_WATERING}, + [PLAYER_AVATAR_STATE_VSSEEKER] = {OBJ_EVENT_GFX_BRENDAN_FIELD_MOVE, OBJ_EVENT_GFX_MAY_FIELD_MOVE}, }; static const u8 sFRLGAvatarGfxIds[GENDER_COUNT] = diff --git a/src/item_use.c b/src/item_use.c index 11d92e7427d9..1b87cc0e3672 100644 --- a/src/item_use.c +++ b/src/item_use.c @@ -38,11 +38,13 @@ #include "string_util.h" #include "task.h" #include "text.h" +#include "vs_seeker.h" #include "constants/event_bg.h" #include "constants/event_objects.h" #include "constants/item_effects.h" #include "constants/items.h" #include "constants/songs.h" +#include "constants/map_types.h" static void SetUpItemUseCallback(u8); static void FieldCB_UseItemOnField(void); @@ -75,6 +77,7 @@ static void Task_CloseCantUseKeyItemMessage(u8); static void SetDistanceOfClosestHiddenItem(u8, s16, s16); static void CB2_OpenPokeblockFromBag(void); static void ItemUseOnFieldCB_Honey(u8 taskId); +static bool32 IsValidLocationForVsSeeker(void); static bool32 CannotUseBagBattleItem(u16 itemId); // EWRAM variables @@ -1315,7 +1318,7 @@ void ItemUseOutOfBattle_FormChange_ConsumedOnUse(u8 taskId) } void ItemUseOutOfBattle_RotomCatalog(u8 taskId) -{ +{ if (!gTasks[taskId].tUsingRegisteredKeyItem) { gItemUseCB = ItemUseCB_RotomCatalog; @@ -1330,7 +1333,7 @@ void ItemUseOutOfBattle_RotomCatalog(u8 taskId) } void ItemUseOutOfBattle_ZygardeCube(u8 taskId) -{ +{ if (!gTasks[taskId].tUsingRegisteredKeyItem) { gItemUseCB = ItemUseCB_ZygardeCube; @@ -1379,4 +1382,68 @@ void ItemUseOutOfBattle_CannotUse(u8 taskId) DisplayDadsAdviceCannotUseItemMessage(taskId, gTasks[taskId].tUsingRegisteredKeyItem); } +static bool32 IsValidLocationForVsSeeker(void) +{ + u16 mapGroup = gSaveBlock1Ptr->location.mapGroup; + u16 mapNum = gSaveBlock1Ptr->location.mapNum; + u16 mapType = gMapHeader.mapType; + + typedef struct { + u16 mapGroup; + u16 mapNum; + } Location; + + u32 i; + Location validIndoorLocations[] = + { + { MAP_GROUP(MT_PYRE_SUMMIT), MAP_NUM(MT_PYRE_SUMMIT) }, + { MAP_GROUP(SAFARI_ZONE_NORTH), MAP_NUM(SAFARI_ZONE_NORTH) }, + { MAP_GROUP(SAFARI_ZONE_NORTHEAST), MAP_NUM(SAFARI_ZONE_NORTHEAST) }, + { MAP_GROUP(SAFARI_ZONE_NORTHWEST), MAP_NUM(SAFARI_ZONE_NORTHWEST) }, + { MAP_GROUP(SAFARI_ZONE_SOUTH), MAP_NUM(SAFARI_ZONE_SOUTH) }, + { MAP_GROUP(SAFARI_ZONE_SOUTHEAST), MAP_NUM(SAFARI_ZONE_SOUTHEAST) }, + { MAP_GROUP(SAFARI_ZONE_SOUTHWEST), MAP_NUM(SAFARI_ZONE_SOUTHWEST) }, + { MAP_GROUP(SKY_PILLAR_TOP), MAP_NUM(SKY_PILLAR_TOP) }, + { MAP_GROUP(SOUTHERN_ISLAND_EXTERIOR), MAP_NUM(SOUTHERN_ISLAND_EXTERIOR) }, + { MAP_GROUP(SOUTHERN_ISLAND_INTERIOR), MAP_NUM(SOUTHERN_ISLAND_INTERIOR) }, + { MAP_GROUP(RUSTBORO_CITY_GYM), MAP_NUM(RUSTBORO_CITY_GYM) }, + { MAP_GROUP(DEWFORD_TOWN_GYM), MAP_NUM(DEWFORD_TOWN_GYM) }, + { MAP_GROUP(MAUVILLE_CITY_GYM), MAP_NUM(MAUVILLE_CITY_GYM) }, + { MAP_GROUP(LAVARIDGE_TOWN_GYM_1F), MAP_NUM(LAVARIDGE_TOWN_GYM_1F) }, + { MAP_GROUP(LAVARIDGE_TOWN_GYM_B1F), MAP_NUM(LAVARIDGE_TOWN_GYM_B1F) }, + { MAP_GROUP(PETALBURG_CITY_GYM), MAP_NUM(PETALBURG_CITY_GYM) }, + { MAP_GROUP(FORTREE_CITY_GYM), MAP_NUM(FORTREE_CITY_GYM) }, + { MAP_GROUP(MOSSDEEP_CITY_GYM), MAP_NUM(MOSSDEEP_CITY_GYM) }, + { MAP_GROUP(SOOTOPOLIS_CITY_GYM_1F), MAP_NUM(SOOTOPOLIS_CITY_GYM_1F) }, + { MAP_GROUP(SOOTOPOLIS_CITY_GYM_B1F), MAP_NUM(SOOTOPOLIS_CITY_GYM_B1F) }, + }; + + if (IsMapTypeOutdoors(mapType)) + return TRUE; + + for (i = 0; i < ARRAY_COUNT(validIndoorLocations); i++) + { + if (mapNum == validIndoorLocations[i].mapNum && mapGroup == validIndoorLocations[i].mapGroup) + return TRUE; + } + + return FALSE; +} + +void FieldUseFunc_VsSeeker(u8 taskId) +{ + if (IsValidLocationForVsSeeker()) + { + sItemUseOnFieldCB = Task_InitVsSeekerAndCheckForTrainersOnScreen; + SetUpItemUseOnFieldCallback(taskId); + } + else + DisplayDadsAdviceCannotUseItemMessage(taskId, gTasks[taskId].data[3]); +} + +void Task_ItemUse_CloseMessageBoxAndReturnToField_VsSeeker(u8 taskId) +{ + Task_CloseCantUseKeyItemMessage(taskId); +} + #undef tUsingRegisteredKeyItem diff --git a/src/overworld.c b/src/overworld.c index bf2bcf5acef8..09e242b21796 100644 --- a/src/overworld.c +++ b/src/overworld.c @@ -58,6 +58,7 @@ #include "tv.h" #include "scanline_effect.h" #include "wild_encounter.h" +#include "vs_seeker.h" #include "frontier_util.h" #include "constants/abilities.h" #include "constants/layouts.h" @@ -818,6 +819,10 @@ void LoadMapFromCameraTransition(u8 mapGroup, u8 mapNum) ResetCyclingRoadChallengeData(); RestartWildEncounterImmunitySteps(); TryUpdateRandomTrainerRematches(mapGroup, mapNum); + +if (I_VS_SEEKER_CHARGING != 0) + MapResetTrainerRematches(mapGroup, mapNum); + DoTimeBasedEvents(); SetSavedWeatherFromCurrMapHeader(); ChooseAmbientCrySpecies(); @@ -868,6 +873,10 @@ static void LoadMapFromWarp(bool32 a1) ResetCyclingRoadChallengeData(); RestartWildEncounterImmunitySteps(); TryUpdateRandomTrainerRematches(gSaveBlock1Ptr->location.mapGroup, gSaveBlock1Ptr->location.mapNum); + +if (I_VS_SEEKER_CHARGING != 0) + MapResetTrainerRematches(gSaveBlock1Ptr->location.mapGroup, gSaveBlock1Ptr->location.mapNum); + if (a1 != TRUE) DoTimeBasedEvents(); SetSavedWeatherFromCurrMapHeader(); diff --git a/src/trainer_see.c b/src/trainer_see.c index 718afb19d1b0..ee10747cdf50 100644 --- a/src/trainer_see.c +++ b/src/trainer_see.c @@ -62,6 +62,8 @@ EWRAM_DATA u8 gApproachingTrainerId = 0; static const u8 sEmotion_ExclamationMarkGfx[] = INCBIN_U8("graphics/field_effects/pics/emotion_exclamation.4bpp"); static const u8 sEmotion_QuestionMarkGfx[] = INCBIN_U8("graphics/field_effects/pics/emotion_question.4bpp"); static const u8 sEmotion_HeartGfx[] = INCBIN_U8("graphics/field_effects/pics/emotion_heart.4bpp"); +static const u8 sEmotion_DoubleExclamationMarkGfx[] = INCBIN_U8("graphics/field_effects/pics/emotion_double_exclamation.4bpp"); +static const u8 sEmotion_XGfx[] = INCBIN_U8("graphics/field_effects/pics/emote_x.4bpp"); static u8 (*const sDirectionalApproachDistanceFuncs[])(struct ObjectEvent *trainerObj, s16 range, s16 x, s16 y) = { @@ -136,6 +138,14 @@ static const struct SpriteFrameImage sSpriteImageTable_ExclamationQuestionMark[] { .data = sEmotion_QuestionMarkGfx, .size = sizeof(sEmotion_QuestionMarkGfx) + }, + { + .data = sEmotion_DoubleExclamationMarkGfx, + .size = sizeof(sEmotion_DoubleExclamationMarkGfx) + }, + { + .data = sEmotion_XGfx, + .size = sizeof(sEmotion_XGfx) } }; @@ -159,10 +169,25 @@ static const union AnimCmd sSpriteAnim_Icons2[] = ANIMCMD_END }; + +static const union AnimCmd sSpriteAnim_Icons3[] = +{ + ANIMCMD_FRAME(2, 60), + ANIMCMD_END +}; + +static const union AnimCmd sSpriteAnim_Icons4[] = +{ + ANIMCMD_FRAME(3, 60), + ANIMCMD_END +}; + static const union AnimCmd *const sSpriteAnimTable_Icons[] = { sSpriteAnim_Icons1, - sSpriteAnim_Icons2 + sSpriteAnim_Icons2, + sSpriteAnim_Icons3, + sSpriteAnim_Icons4 }; static const struct SpriteTemplate sSpriteTemplate_ExclamationQuestionMark = @@ -731,6 +756,27 @@ u8 FldEff_HeartIcon(void) return 0; } + +u8 FldEff_DoubleExclMarkIcon(void) +{ + u8 spriteId = CreateSpriteAtEnd(&sSpriteTemplate_ExclamationQuestionMark, 0, 0, 0x53); + + if (spriteId != MAX_SPRITES) + SetIconSpriteData(&gSprites[spriteId], FLDEFF_EXCLAMATION_MARK_ICON, 2); + + return 0; +} + +u8 FldEff_XIcon(void) +{ + u8 spriteId = CreateSpriteAtEnd(&sSpriteTemplate_ExclamationQuestionMark, 0, 0, 0x53); + + if (spriteId != MAX_SPRITES) + SetIconSpriteData(&gSprites[spriteId], FLDEFF_EXCLAMATION_MARK_ICON, 3); + + return 0; +} + static void SetIconSpriteData(struct Sprite *sprite, u16 fldEffId, u8 spriteAnimNum) { sprite->oam.priority = 1; diff --git a/src/vs_seeker.c b/src/vs_seeker.c new file mode 100644 index 000000000000..0b58b9bac321 --- /dev/null +++ b/src/vs_seeker.c @@ -0,0 +1,793 @@ +#include "global.h" +#include "task.h" +#include "event_object_movement.h" +#include "item_use.h" +#include "event_scripts.h" +#include "event_data.h" +#include "script.h" +#include "event_object_lock.h" +#include "field_specials.h" +#include "item.h" +#include "item_menu.h" +#include "field_effect.h" +#include "script_movement.h" +#include "battle.h" +#include "battle_setup.h" +#include "random.h" +#include "field_player_avatar.h" +#include "vs_seeker.h" +#include "menu.h" +#include "string_util.h" +#include "tv.h" +#include "malloc.h" +#include "field_screen_effect.h" +#include "gym_leader_rematch.h" +#include "sound.h" +#include "constants/event_object_movement.h" +#include "constants/event_objects.h" +#include "constants/items.h" +#include "constants/maps.h" +#include "constants/songs.h" +#include "constants/trainer_types.h" +#include "constants/field_effects.h" + +enum +{ + VSSEEKER_NOT_CHARGED, + VSSEEKER_NO_ONE_IN_RANGE, + VSSEEKER_CAN_USE, +}; + +typedef enum +{ + VSSEEKER_SINGLE_RESP_RAND, + VSSEEKER_SINGLE_RESP_NO, + VSSEEKER_SINGLE_RESP_YES +} VsSeekerSingleRespCode; + +typedef enum +{ + VSSEEKER_RESPONSE_NO_RESPONSE, + VSSEEKER_RESPONSE_UNFOUGHT_TRAINERS, + VSSEEKER_RESPONSE_FOUND_REMATCHES +} VsSeekerResponseCode; + +struct VsSeekerTrainerInfo +{ + const u8 *script; + u16 trainerIdx; + u8 localId; + u8 objectEventId; + s16 xCoord; + s16 yCoord; + u8 graphicsId; +}; + +struct VsSeekerStruct +{ + struct VsSeekerTrainerInfo trainerInfo[OBJECT_EVENTS_COUNT]; + u16 trainerIdxArray[OBJECT_EVENTS_COUNT]; + u8 runningBehaviourEtcArray[OBJECT_EVENTS_COUNT]; + u8 numRematchableTrainers; + u8 trainerHasNotYetBeenFought:1; + u8 trainerDoesNotWantRematch:1; + u8 trainerWantsRematch:1; + u8 responseCode:5; +}; + +// static declarations +static EWRAM_DATA struct VsSeekerStruct *sVsSeeker = NULL; + +static void VsSeekerResetInBagStepCounter(void); +static void VsSeekerResetChargingStepCounter(void); +static void Task_ResetObjectsRematchWantedState(u8 taskId); +static void ResetMovementOfRematchableTrainers(void); +static void Task_VsSeekerFrameCountdown(u8 taskId); +static void Task_VsSeeker_PlaySoundAndGetResponseCode(u8 taskId); +static void GatherNearbyTrainerInfo(void); +static void Task_VsSeeker_ShowResponseToPlayer(u8 taskId); +static bool8 CanUseVsSeeker(void); +static u8 GetVsSeekerResponseInArea(void); +static u8 GetResponseMovementTypeFromTrainerGraphicsId(u8 graphicsId); +static u16 GetTrainerFlagFromScript(const u8 * script); +static void ClearAllTrainerRematchStates(void); +static bool8 IsTrainerVisibleOnScreen(struct VsSeekerTrainerInfo * trainerInfo); +static u32 GetRematchableTrainerLocalId(void); +static void StartTrainerObjectMovementScript(struct VsSeekerTrainerInfo * trainerInfo, const u8 * script); +static u8 GetCurVsSeekerResponse(s32 vsSeekerIdx, u16 trainerIdx); +static void StartAllRespondantIdleMovements(void); +static bool8 ObjectEventIdIsSane(u8 objectEventId); +static u8 GetRandomFaceDirectionMovementType(); + +static const u8 sMovementScript_Wait48[] = { + MOVEMENT_ACTION_DELAY_16, + MOVEMENT_ACTION_DELAY_16, + MOVEMENT_ACTION_DELAY_16, + MOVEMENT_ACTION_STEP_END +}; + +static const u8 sMovementScript_TrainerUnfought[] = { + MOVEMENT_ACTION_EMOTE_EXCLAMATION_MARK, + MOVEMENT_ACTION_STEP_END +}; + +static const u8 sMovementScript_TrainerNoRematch[] = { + MOVEMENT_ACTION_EMOTE_X, + MOVEMENT_ACTION_STEP_END +}; + +static const u8 sMovementScript_TrainerRematch[] = { + MOVEMENT_ACTION_WALK_IN_PLACE_FASTER_DOWN, + MOVEMENT_ACTION_EMOTE_DOUBLE_EXCL_MARK, + MOVEMENT_ACTION_STEP_END +}; + +static const u8 sFaceDirectionMovementTypeByFacingDirection[] = { + MOVEMENT_TYPE_FACE_DOWN, + MOVEMENT_TYPE_FACE_DOWN, + MOVEMENT_TYPE_FACE_UP, + MOVEMENT_TYPE_FACE_LEFT, + MOVEMENT_TYPE_FACE_RIGHT +}; + +void VsSeekerFreezeObjectsAfterChargeComplete(void) +{ + CreateTask(Task_ResetObjectsRematchWantedState, 80); +} + +#define tIsPlayerFrozen data[0] +#define tAreObjectsFrozen data[1] + +static void Task_ResetObjectsRematchWantedState(u8 taskId) +{ + struct Task *task = &gTasks[taskId]; + u32 i; + + if ((!task->tIsPlayerFrozen) && IsPlayerStandingStill()) + { + PlayerFreeze(); + task->tIsPlayerFrozen = TRUE; + } + + if (!task->tAreObjectsFrozen) + { + for (i = 0; i < OBJECT_EVENTS_COUNT; i++) + { + if (!ObjectEventIdIsSane(i)) + continue; + + if (gObjectEvents[i].singleMovementActive) + return; + + FreezeObjectEvent(&gObjectEvents[i]); + } + } + + task->tAreObjectsFrozen = TRUE; + if (task->tIsPlayerFrozen) + { + DestroyTask(taskId); + StopPlayerAvatar(); + ScriptContext_Enable(); + } +} +#undef tIsPlayerFrozen +#undef tAreObjectsFrozen + +u16 VsSeekerConvertLocalIdToTableId(u16 localId) +{ + u32 localIdIndex = 0; + u32 trainerId = 0; + + for (localIdIndex = 0; localIdIndex < OBJECT_EVENTS_COUNT ; localIdIndex++) + { + if (sVsSeeker->trainerInfo[localIdIndex].localId == localId) + { + trainerId = sVsSeeker->trainerInfo[localIdIndex].trainerIdx; + return TrainerIdToRematchTableId(gRematchTable,trainerId); + } + } + return -1; +} + +void VsSeekerResetObjectMovementAfterChargeComplete(void) +{ + struct ObjectEventTemplate * templates = gSaveBlock1Ptr->objectEventTemplates; + u32 i; + u32 movementType; + u8 objEventId; + struct ObjectEvent * objectEvent; + + for (i = 0; i < gMapHeader.events->objectEventCount; i++) + { + if (templates[i].trainerType != TRAINER_TYPE_NORMAL + && templates[i].trainerType != TRAINER_TYPE_BURIED) + continue; + + if (templates[i].movementType != MOVEMENT_TYPE_ROTATE_CLOCKWISE) + continue; + + movementType = GetRandomFaceDirectionMovementType(); + TryGetObjectEventIdByLocalIdAndMap(templates[i].localId, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, &objEventId); + objectEvent = &gObjectEvents[objEventId]; + + if (!ObjectEventIdIsSane(objEventId)) + continue; + + SetTrainerMovementType(objectEvent, movementType); + templates[i].movementType = movementType; + } +} + +bool8 UpdateVsSeekerStepCounter(void) +{ + u8 x = 0; + + if (!I_VS_SEEKER_CHARGING) return FALSE; + + if (CheckBagHasItem(ITEM_VS_SEEKER, 1)) + { + if ((gSaveBlock1Ptr->trainerRematchStepCounter & 0xFF) < VSSEEKER_RECHARGE_STEPS) + gSaveBlock1Ptr->trainerRematchStepCounter++; + } + + if (FlagGet(I_VS_SEEKER_CHARGING)) + { + if (((gSaveBlock1Ptr->trainerRematchStepCounter >> 8) & 0xFF) < VSSEEKER_RECHARGE_STEPS) + { + x = (((gSaveBlock1Ptr->trainerRematchStepCounter >> 8) & 0xFF) + 1); + gSaveBlock1Ptr->trainerRematchStepCounter = (gSaveBlock1Ptr->trainerRematchStepCounter & 0xFF) | (x << 8); + } + if (((gSaveBlock1Ptr->trainerRematchStepCounter >> 8) & 0xFF) == VSSEEKER_RECHARGE_STEPS) + { + FlagClear(I_VS_SEEKER_CHARGING); + VsSeekerResetChargingStepCounter(); + ClearAllTrainerRematchStates(); + return TRUE; + } + } + + return FALSE; +} + +void MapResetTrainerRematches(u16 mapGroup, u16 mapNum) +{ + if (!I_VS_SEEKER_CHARGING) return; + + FlagClear(I_VS_SEEKER_CHARGING); + VsSeekerResetChargingStepCounter(); + ClearAllTrainerRematchStates(); + ResetMovementOfRematchableTrainers(); +} + +static void ResetMovementOfRematchableTrainers(void) +{ + u32 i; + u8 movementType = 0; + + for (i = 0; i < OBJECT_EVENTS_COUNT; i++) + { + struct ObjectEvent * objectEvent = &gObjectEvents[i]; + if (objectEvent->movementType != MOVEMENT_TYPE_ROTATE_CLOCKWISE) + continue; + + movementType = GetRandomFaceDirectionMovementType(); + + if (!objectEvent->active || gSprites[objectEvent->spriteId].data[0] != i) + continue; + + gSprites[objectEvent->spriteId].x2 = 0; + gSprites[objectEvent->spriteId].y2 = 0; + SetTrainerMovementType(objectEvent, movementType); + } +} + +static void VsSeekerResetInBagStepCounter(void) +{ + gSaveBlock1Ptr->trainerRematchStepCounter &= 0xFF00; +} + +static void VsSeekerResetChargingStepCounter(void) +{ + gSaveBlock1Ptr->trainerRematchStepCounter &= 0x00FF; +} + +void Task_InitVsSeekerAndCheckForTrainersOnScreen(u8 taskId) +{ + u32 i; + u32 respval; + + if (!I_VS_SEEKER_CHARGING) return; + + for (i = 0; i < 16; i++) + gTasks[taskId].data[i] = 0; + + sVsSeeker = AllocZeroed(sizeof(struct VsSeekerStruct)); + GatherNearbyTrainerInfo(); + respval = CanUseVsSeeker(); + if (respval == VSSEEKER_NOT_CHARGED) + { + Free(sVsSeeker); + DisplayItemMessageOnField(taskId, VSSeeker_Text_BatteryNotChargedNeedXSteps, Task_ItemUse_CloseMessageBoxAndReturnToField_VsSeeker); + } + else if (respval == VSSEEKER_NO_ONE_IN_RANGE) + { + Free(sVsSeeker); + DisplayItemMessageOnField(taskId, VSSeeker_Text_NoTrainersWithinRange, Task_ItemUse_CloseMessageBoxAndReturnToField_VsSeeker); + } + else if (respval == VSSEEKER_CAN_USE) + { + FieldEffectStart(FLDEFF_USE_VS_SEEKER); + gTasks[taskId].func = Task_VsSeekerFrameCountdown; + gTasks[taskId].data[0] = 15; + } +} + +static void Task_VsSeekerFrameCountdown(u8 taskId) +{ + if (--gTasks[taskId].data[0] == 0) + { + gTasks[taskId].func = Task_VsSeeker_PlaySoundAndGetResponseCode; + gTasks[taskId].data[1] = 16; + } +} + +static void Task_VsSeeker_PlaySoundAndGetResponseCode(u8 taskId) +{ + s16 * data = gTasks[taskId].data; + + if (data[2] != 2 && --data[1] == 0) + { + PlaySE(SE_CONTEST_MONS_TURN); + data[1] = 11; + data[2]++; + } + + if (!FieldEffectActiveListContains(FLDEFF_USE_VS_SEEKER)) + { + data[1] = 0; + data[2] = 0; + VsSeekerResetInBagStepCounter(); + sVsSeeker->responseCode = GetVsSeekerResponseInArea(); + ScriptMovement_StartObjectMovementScript(0xFF, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, sMovementScript_Wait48); + gTasks[taskId].func = Task_VsSeeker_ShowResponseToPlayer; + } +} + +static void GatherNearbyTrainerInfo(void) +{ + struct ObjectEventTemplate *templates = gSaveBlock1Ptr->objectEventTemplates; + u8 objectEventId = 0; + u8 vsSeekerObjectIdx = 0; + s32 objectEventIdx; + + for (objectEventIdx = 0; objectEventIdx < gMapHeader.events->objectEventCount; objectEventIdx++) + { + if (templates[objectEventIdx].trainerType != TRAINER_TYPE_NORMAL && templates[objectEventIdx].trainerType != TRAINER_TYPE_BURIED) + continue; + + sVsSeeker->trainerInfo[vsSeekerObjectIdx].script = templates[objectEventIdx].script; + sVsSeeker->trainerInfo[vsSeekerObjectIdx].trainerIdx = GetTrainerFlagFromScript(templates[objectEventIdx].script); + sVsSeeker->trainerInfo[vsSeekerObjectIdx].localId = templates[objectEventIdx].localId; + TryGetObjectEventIdByLocalIdAndMap(templates[objectEventIdx].localId, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, &objectEventId); + sVsSeeker->trainerInfo[vsSeekerObjectIdx].objectEventId = objectEventId; + sVsSeeker->trainerInfo[vsSeekerObjectIdx].xCoord = gObjectEvents[objectEventId].currentCoords.x - 7; + sVsSeeker->trainerInfo[vsSeekerObjectIdx].yCoord = gObjectEvents[objectEventId].currentCoords.y - 7; + sVsSeeker->trainerInfo[vsSeekerObjectIdx].graphicsId = templates[objectEventIdx].graphicsId; + vsSeekerObjectIdx++; + } + sVsSeeker->trainerInfo[vsSeekerObjectIdx].localId = 0xFF; +} + +static void Task_VsSeeker_ShowResponseToPlayer(u8 taskId) +{ + if (!ScriptMovement_IsObjectMovementFinished(0xFF, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup)) + return; + + if (sVsSeeker->responseCode == VSSEEKER_RESPONSE_NO_RESPONSE) + { + DisplayItemMessageOnField(taskId, VSSeeker_Text_TrainersNotReady, Task_ItemUse_CloseMessageBoxAndReturnToField_VsSeeker); + } + else + { + if (sVsSeeker->responseCode == VSSEEKER_RESPONSE_FOUND_REMATCHES) + StartAllRespondantIdleMovements(); + + ClearDialogWindowAndFrame(0, TRUE); + ScriptUnfreezeObjectEvents(); + UnlockPlayerFieldControls(); + DestroyTask(taskId); + } + Free(sVsSeeker); +} + +static u8 CanUseVsSeeker(void) +{ + u8 vsSeekerChargeSteps = gSaveBlock1Ptr->trainerRematchStepCounter; + + if ((vsSeekerChargeSteps == VSSEEKER_RECHARGE_STEPS) && (GetRematchableTrainerLocalId() == 0xFF)) + return VSSEEKER_NO_ONE_IN_RANGE; + + if (vsSeekerChargeSteps == VSSEEKER_RECHARGE_STEPS) + return VSSEEKER_CAN_USE; + + ConvertIntToDecimalStringN(gStringVar1, (VSSEEKER_RECHARGE_STEPS - vsSeekerChargeSteps), STR_CONV_MODE_LEFT_ALIGN, 3); + return VSSEEKER_NOT_CHARGED; +} + +static u8 GetVsSeekerResponseInArea(void) +{ + u16 trainerIdx = 0; + u8 response = 0, rematchTrainerIdx; + s32 vsSeekerIdx = 0, randomValue = 0; + + while (sVsSeeker->trainerInfo[vsSeekerIdx].localId != 0xFF) + { + if (!IsTrainerVisibleOnScreen(&sVsSeeker->trainerInfo[vsSeekerIdx])) + { + vsSeekerIdx++; + continue; + } + + trainerIdx = sVsSeeker->trainerInfo[vsSeekerIdx].trainerIdx; + if (!HasTrainerBeenFought(trainerIdx)) + { + StartTrainerObjectMovementScript(&sVsSeeker->trainerInfo[vsSeekerIdx], sMovementScript_TrainerUnfought); + sVsSeeker->trainerHasNotYetBeenFought = 1; + vsSeekerIdx++; + continue; + } + + rematchTrainerIdx = GetRematchTrainerIdFromTable(gRematchTable, trainerIdx); + if (rematchTrainerIdx == 0) + { + StartTrainerObjectMovementScript(&sVsSeeker->trainerInfo[vsSeekerIdx], sMovementScript_TrainerNoRematch); + sVsSeeker->trainerDoesNotWantRematch = 1; + } + else + { + randomValue = Random() % 100; // Even if it's overwritten below, it progresses the RNG. + response = GetCurVsSeekerResponse(vsSeekerIdx, trainerIdx); + + if (response == VSSEEKER_SINGLE_RESP_YES) + { + randomValue = 100; // Definitely yes + } + else if (response == VSSEEKER_SINGLE_RESP_NO) + { + randomValue = 0; // Definitely no + } + else if (randomValue < 30) + { + StartTrainerObjectMovementScript(&sVsSeeker->trainerInfo[vsSeekerIdx], sMovementScript_TrainerNoRematch); + sVsSeeker->trainerDoesNotWantRematch = 1; + } + else + { + gSaveBlock1Ptr->trainerRematches[VsSeekerConvertLocalIdToTableId(sVsSeeker->trainerInfo[vsSeekerIdx].localId)] = rematchTrainerIdx; + ShiftStillObjectEventCoords(&gObjectEvents[sVsSeeker->trainerInfo[vsSeekerIdx].objectEventId]); + StartTrainerObjectMovementScript(&sVsSeeker->trainerInfo[vsSeekerIdx], sMovementScript_TrainerRematch); + sVsSeeker->trainerIdxArray[sVsSeeker->numRematchableTrainers] = trainerIdx; + sVsSeeker->runningBehaviourEtcArray[sVsSeeker->numRematchableTrainers] = GetResponseMovementTypeFromTrainerGraphicsId(sVsSeeker->trainerInfo[vsSeekerIdx].graphicsId); + sVsSeeker->numRematchableTrainers++; + sVsSeeker->trainerWantsRematch = 1; + } + } + vsSeekerIdx++; + } + + if (sVsSeeker->trainerWantsRematch) + { + PlaySE(SE_PIN); + FlagSet(I_VS_SEEKER_CHARGING); + VsSeekerResetChargingStepCounter(); + return VSSEEKER_RESPONSE_FOUND_REMATCHES; + } + + if (sVsSeeker->trainerHasNotYetBeenFought) + return VSSEEKER_RESPONSE_UNFOUGHT_TRAINERS; + + return VSSEEKER_RESPONSE_NO_RESPONSE; +} + +void ClearRematchMovementByTrainerId(void) +{ + s32 i; + u8 objEventId = 0; + struct ObjectEventTemplate *objectEventTemplates = gSaveBlock1Ptr->objectEventTemplates; + struct ObjectEvent *objectEvent; + + int vsSeekerDataIdx = TrainerIdToRematchTableId(gRematchTable, gTrainerBattleOpponent_A); + + if (!I_VS_SEEKER_CHARGING) return; + + if (vsSeekerDataIdx == -1) + return; + + for (i = 0; i < gMapHeader.events->objectEventCount; i++) + { + if ((objectEventTemplates[i].trainerType != TRAINER_TYPE_NORMAL + && objectEventTemplates[i].trainerType != TRAINER_TYPE_BURIED) + || vsSeekerDataIdx != TrainerIdToRematchTableId(gRematchTable, GetTrainerFlagFromScript(objectEventTemplates[i].script))) + continue; + + TryGetObjectEventIdByLocalIdAndMap(objectEventTemplates[i].localId, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, &objEventId); + objectEvent = &gObjectEvents[objEventId]; + GetRandomFaceDirectionMovementType(&objectEventTemplates[i]); + TryOverrideTemplateCoordsForObjectEvent(objectEvent, sFaceDirectionMovementTypeByFacingDirection[objectEvent->facingDirection]); + + if (gSelectedObjectEvent == objEventId) + objectEvent->movementType = sFaceDirectionMovementTypeByFacingDirection[objectEvent->facingDirection]; + else + objectEvent->movementType = MOVEMENT_TYPE_FACE_DOWN; + } +} + +static u32 GetGameProgressFlags() +{ + const u32 gameProgressFlags[] = { + FLAG_VISITED_LAVARIDGE_TOWN, + FLAG_VISITED_FORTREE_CITY, + FLAG_SYS_GAME_CLEAR, + FLAG_DEFEATED_METEOR_FALLS_STEVEN + }; + u32 i = 0, numGameProgressFlags = 0; + u32 maxGameProgressFlags = ARRAY_COUNT(gameProgressFlags); + + for (i = 0; i < maxGameProgressFlags; i++) + { + if (FlagGet(gameProgressFlags[i])) + numGameProgressFlags++; + } + + return numGameProgressFlags; +} + +u16 GetRematchTrainerIdVSSeeker(u16 trainerId) +{ + u32 tableId = FirstBattleTrainerIdToRematchTableId(gRematchTable, trainerId); + u32 rematchTrainerIdx = GetGameProgressFlags(); + + if (!I_VS_SEEKER_CHARGING) return 0; + + while (!HasTrainerBeenFought(gRematchTable[tableId].trainerIds[rematchTrainerIdx-1])) + { + if (rematchTrainerIdx== 0) + break; + + rematchTrainerIdx--; + } + + return gRematchTable[tableId].trainerIds[rematchTrainerIdx]; +} + +static bool8 ObjectEventIdIsSane(u8 objectEventId) +{ + struct ObjectEvent *objectEvent = &gObjectEvents[objectEventId]; + + if (objectEvent->active && gMapHeader.events->objectEventCount >= objectEvent->localId && gSprites[objectEvent->spriteId].data[0] == objectEventId) + return TRUE; + return FALSE; +} + +static u8 GetRandomFaceDirectionMovementType() +{ + u16 randomFacingDirection = Random() % 4; + + switch (randomFacingDirection) + { + case 0: + return MOVEMENT_TYPE_FACE_UP; + case 1: + return MOVEMENT_TYPE_FACE_DOWN; + case 2: + return MOVEMENT_TYPE_FACE_LEFT; + case 3: + return MOVEMENT_TYPE_FACE_RIGHT; + default: + return MOVEMENT_TYPE_FACE_DOWN; + } +} + +static bool32 IsRegularLandTrainer(u8 graphicsId) +{ + u32 i; + u16 regularTrainersOnLand[] = + { + OBJ_EVENT_GFX_AQUA_MEMBER_F, + OBJ_EVENT_GFX_AQUA_MEMBER_M, + OBJ_EVENT_GFX_BEAUTY, + OBJ_EVENT_GFX_BLACK_BELT, + OBJ_EVENT_GFX_BOY_1, + OBJ_EVENT_GFX_BOY_2, + OBJ_EVENT_GFX_BOY_3, + OBJ_EVENT_GFX_BUG_CATCHER, + OBJ_EVENT_GFX_CAMPER, + OBJ_EVENT_GFX_CYCLING_TRIATHLETE_F, + OBJ_EVENT_GFX_CYCLING_TRIATHLETE_M, + OBJ_EVENT_GFX_EXPERT_F, + OBJ_EVENT_GFX_EXPERT_M, + OBJ_EVENT_GFX_FAT_MAN, + OBJ_EVENT_GFX_FISHERMAN, + OBJ_EVENT_GFX_GENTLEMAN, + OBJ_EVENT_GFX_GIRL_1, + OBJ_EVENT_GFX_GIRL_2, + OBJ_EVENT_GFX_GIRL_3, + OBJ_EVENT_GFX_HEX_MANIAC, + OBJ_EVENT_GFX_HIKER, + OBJ_EVENT_GFX_LASS, + OBJ_EVENT_GFX_LITTLE_BOY, + OBJ_EVENT_GFX_LITTLE_GIRL, + OBJ_EVENT_GFX_MAGMA_MEMBER_F, + OBJ_EVENT_GFX_MAGMA_MEMBER_M, + OBJ_EVENT_GFX_MAN_3, + OBJ_EVENT_GFX_MAN_4, + OBJ_EVENT_GFX_MAN_5, + OBJ_EVENT_GFX_MANIAC, + OBJ_EVENT_GFX_NINJA_BOY, + OBJ_EVENT_GFX_PICNICKER, + OBJ_EVENT_GFX_POKEFAN_F, + OBJ_EVENT_GFX_POKEFAN_M, + OBJ_EVENT_GFX_PSYCHIC_M, + OBJ_EVENT_GFX_RICH_BOY, + OBJ_EVENT_GFX_RUNNING_TRIATHLETE_F, + OBJ_EVENT_GFX_RUNNING_TRIATHLETE_M, + OBJ_EVENT_GFX_SAILOR, + OBJ_EVENT_GFX_SCHOOL_KID_M, + OBJ_EVENT_GFX_TUBER_F, + OBJ_EVENT_GFX_TUBER_M, + OBJ_EVENT_GFX_TWIN, + OBJ_EVENT_GFX_WOMAN_1, + OBJ_EVENT_GFX_WOMAN_2, + OBJ_EVENT_GFX_WOMAN_4, + OBJ_EVENT_GFX_WOMAN_5, + OBJ_EVENT_GFX_YOUNGSTER + }; + + for (i = 0; i < ARRAY_COUNT(regularTrainersOnLand); i++) + { + if (graphicsId == regularTrainersOnLand[i]) + return TRUE; + } + return FALSE; +} + +static bool32 IsRegularWaterTrainer(u8 graphicsId) +{ + u32 i; + u16 regularTrainersInWater[] = + { + OBJ_EVENT_GFX_SWIMMER_F, + OBJ_EVENT_GFX_SWIMMER_M, + OBJ_EVENT_GFX_TUBER_M_SWIMMING + }; + + for (i = 0; i < ARRAY_COUNT(regularTrainersInWater); i++) + { + if (graphicsId == regularTrainersInWater[i]) + return TRUE; + } + return FALSE; +} + +static u8 GetResponseMovementTypeFromTrainerGraphicsId(u8 graphicsId) +{ + if (IsRegularLandTrainer(graphicsId) || IsRegularWaterTrainer(graphicsId)) + return MOVEMENT_TYPE_ROTATE_CLOCKWISE; + + return MOVEMENT_TYPE_FACE_DOWN; +} + +static u16 GetTrainerFlagFromScript(const u8 *script) + /* + * The trainer flag is a little-endian short located +2 from + * the script pointer, assuming the trainerbattle command is + * first in the script. Because scripts are unaligned, and + * because the ARM processor requires shorts to be 16-bit + * aligned, this function needs to perform explicit bitwise + * operations to get the correct flag. + * + * 5c XX YY ZZ ... + * -- -- + */ +{ + u16 trainerFlag; + + script += 2; + trainerFlag = script[0]; + trainerFlag |= script[1] << 8; + return trainerFlag; +} + +static void ClearAllTrainerRematchStates(void) +{ + u32 i; + + if (!CheckBagHasItem(ITEM_VS_SEEKER, 1)) + return; + + for (i = 0; i < ARRAY_COUNT(gSaveBlock1Ptr->trainerRematches); i++) + gSaveBlock1Ptr->trainerRematches[i] = 0; +} + +static bool8 IsTrainerVisibleOnScreen(struct VsSeekerTrainerInfo * trainerInfo) +{ + s16 x; + s16 y; + + PlayerGetDestCoords(&x, &y); + x -= 7; + y -= 7; + + if ( x - 7 <= trainerInfo->xCoord + && x + 7 >= trainerInfo->xCoord + && y - 5 <= trainerInfo->yCoord + && y + 5 >= trainerInfo->yCoord + && ObjectEventIdIsSane(trainerInfo->objectEventId) == 1) + return TRUE; + return FALSE; +} + +static u32 GetRematchableTrainerLocalId(void) +{ + u32 i; + + for (i = 0; sVsSeeker->trainerInfo[i].localId != 0xFF; i++) + { + if (IsTrainerVisibleOnScreen(&sVsSeeker->trainerInfo[i]) == 1) + { + if (HasTrainerBeenFought(sVsSeeker->trainerInfo[i].trainerIdx) != 1 || GetRematchTrainerIdFromTable(gRematchTable, sVsSeeker->trainerInfo[i].trainerIdx)) + return sVsSeeker->trainerInfo[i].localId; + } + } + + return 0xFF; +} + +static void StartTrainerObjectMovementScript(struct VsSeekerTrainerInfo * trainerInfo, const u8 * script) +{ + UnfreezeObjectEvent(&gObjectEvents[trainerInfo->objectEventId]); + ScriptMovement_StartObjectMovementScript(trainerInfo->localId, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, script); +} + +static u8 GetCurVsSeekerResponse(s32 vsSeekerIdx, u16 trainerIdx) +{ + s32 i; + s32 j; + + for (i = 0; i < vsSeekerIdx; i++) + { + if (IsTrainerVisibleOnScreen(&sVsSeeker->trainerInfo[i]) != 1 || sVsSeeker->trainerInfo[i].trainerIdx != trainerIdx) + continue; + + for (j = 0; j < sVsSeeker->numRematchableTrainers; j++) + { + if (sVsSeeker->trainerIdxArray[j] == sVsSeeker->trainerInfo[i].trainerIdx) + return VSSEEKER_SINGLE_RESP_YES; + } + return VSSEEKER_SINGLE_RESP_NO; + } + return VSSEEKER_SINGLE_RESP_RAND; +} + +static void StartAllRespondantIdleMovements(void) +{ + s32 i; + s32 j; + + for (i = 0; i < sVsSeeker->numRematchableTrainers; i++) + { + for (j = 0; sVsSeeker->trainerInfo[j].localId != 0xFF; j++) + { + if (sVsSeeker->trainerInfo[j].trainerIdx == sVsSeeker->trainerIdxArray[i]) + { + struct ObjectEvent *objectEvent = &gObjectEvents[sVsSeeker->trainerInfo[j].objectEventId]; + + if (ObjectEventIdIsSane(sVsSeeker->trainerInfo[j].objectEventId) == 1) + SetTrainerMovementType(objectEvent, sVsSeeker->runningBehaviourEtcArray[i]); + TryOverrideTemplateCoordsForObjectEvent(objectEvent, sVsSeeker->runningBehaviourEtcArray[i]); + gSaveBlock1Ptr->trainerRematches[VsSeekerConvertLocalIdToTableId(sVsSeeker->trainerInfo[j].localId)] = GetRematchTrainerIdFromTable(gRematchTable, sVsSeeker->trainerInfo[j].trainerIdx); + } + } + } +} From b72581985cc3d62165dab03b86271a84592e9a43 Mon Sep 17 00:00:00 2001 From: Bassoonian Date: Mon, 27 Nov 2023 12:42:53 +0100 Subject: [PATCH 14/18] Add bad poison downgrade config (#3605) Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- include/config/battle.h | 1 + src/battle_setup.c | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/include/config/battle.h b/include/config/battle.h index 622054ab2854..503ce9328e74 100644 --- a/include/config/battle.h +++ b/include/config/battle.h @@ -219,6 +219,7 @@ #define B_OBEDIENCE_MECHANICS GEN_LATEST // In PLA+ (here Gen8+), obedience restrictions also apply to non-outsider Pokémon, albeit based on their level met rather than actual level #define B_USE_FROSTBITE FALSE // In PLA, Frostbite replaces Freeze. Enabling this flag does the same here. Moves can still be cherry-picked to either Freeze or Frostbite. Freeze-Dry, Secret Power & Tri Attack depend on this config. #define B_OVERWORLD_SNOW GEN_LATEST // In Gen9+, overworld Snow will summon snow instead of hail. +#define B_TOXIC_REVERSAL GEN_LATEST // In Gen5+, bad poison will change to regular poison at the end of battles. // Animation Settings #define B_NEW_SWORD_PARTICLE TRUE // If set to TRUE, it updates Swords Dance's particle. diff --git a/src/battle_setup.c b/src/battle_setup.c index 5459c0492511..750191d74ff8 100644 --- a/src/battle_setup.c +++ b/src/battle_setup.c @@ -677,6 +677,19 @@ void StartRegiBattle(void) TryUpdateGymLeaderRematchFromWild(); } +static void DowngradeBadPoison(void) +{ + u8 i; + u32 status = STATUS1_POISON; + if (B_TOXIC_REVERSAL < GEN_5) + return; + for(i = 0; i < PARTY_SIZE; i++) + { + if (GetMonData(&gPlayerParty[i], MON_DATA_SANITY_HAS_SPECIES) && GetMonData(&gPlayerParty[i], MON_DATA_STATUS) == STATUS1_TOXIC_POISON) + SetMonData(&gPlayerParty[i], MON_DATA_STATUS, &status); + } +} + static void CB2_EndWildBattle(void) { CpuFill16(0, (void *)(BG_PLTT), BG_PLTT_SIZE); @@ -689,6 +702,7 @@ static void CB2_EndWildBattle(void) else { SetMainCallback2(CB2_ReturnToField); + DowngradeBadPoison(); gFieldCallback = FieldCB_ReturnToFieldNoScriptCheckMusic; } } @@ -707,6 +721,7 @@ static void CB2_EndScriptedWildBattle(void) } else { + DowngradeBadPoison(); SetMainCallback2(CB2_ReturnToFieldContinueScriptPlayMapMusic); } } @@ -997,6 +1012,7 @@ static void CB2_StartFirstBattle(void) static void CB2_EndFirstBattle(void) { Overworld_ClearSavedMusic(); + DowngradeBadPoison(); SetMainCallback2(CB2_ReturnToFieldContinueScriptPlayMapMusic); } @@ -1414,6 +1430,7 @@ static void CB2_EndTrainerBattle(void) if (gTrainerBattleOpponent_A == TRAINER_SECRET_BASE) { + DowngradeBadPoison(); SetMainCallback2(CB2_ReturnToFieldContinueScriptPlayMapMusic); } else if (IsPlayerDefeated(gBattleOutcome) == TRUE) @@ -1426,6 +1443,7 @@ static void CB2_EndTrainerBattle(void) else { SetMainCallback2(CB2_ReturnToFieldContinueScriptPlayMapMusic); + DowngradeBadPoison(); if (!InBattlePyramid() && !InTrainerHillChallenge()) { RegisterTrainerInMatchCall(); @@ -1438,6 +1456,7 @@ static void CB2_EndRematchBattle(void) { if (gTrainerBattleOpponent_A == TRAINER_SECRET_BASE) { + DowngradeBadPoison(); SetMainCallback2(CB2_ReturnToFieldContinueScriptPlayMapMusic); } else if (IsPlayerDefeated(gBattleOutcome) == TRUE) @@ -1450,6 +1469,7 @@ static void CB2_EndRematchBattle(void) RegisterTrainerInMatchCall(); SetBattledTrainersFlags(); HandleRematchVarsOnBattleEnd(); + DowngradeBadPoison(); } } From 8c4058c7a85345a9c580b706e32b12765fd5286d Mon Sep 17 00:00:00 2001 From: ghoulslash <41651341+ghoulslash@users.noreply.github.com> Date: Tue, 28 Nov 2023 12:57:11 -0500 Subject: [PATCH 15/18] add HITMARKER_DISABLE_ANIMATION for bug bite consumeberry (#3607) Co-authored-by: ghoulslash Co-authored-by: Bassoonian --- data/battle_scripts_1.s | 16 ++++++++-------- include/constants/battle.h | 3 ++- src/battle_script_commands.c | 12 ++++++------ 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 355c7ad53e28..79c363ec4c72 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -749,10 +749,10 @@ BattleScript_TeatimeLoop: jumpifabsorbaffected BS_TARGET, BattleScript_Teatimesorb jumpifmotoraffected BS_TARGET, BattleScript_Teatimemotor jumpifteainvulnerable BS_TARGET, BattleScript_Teatimevul @ in semi-invulnerable state OR held item is not a Berry - orword gHitMarker, HITMARKER_NO_ANIMATIONS | HITMARKER_IGNORE_SUBSTITUTE | HITMARKER_IGNORE_DISGUISE + orword gHitMarker, HITMARKER_DISABLE_ANIMATION | HITMARKER_IGNORE_SUBSTITUTE | HITMARKER_IGNORE_DISGUISE setbyte sBERRY_OVERRIDE, TRUE @ override the requirements for eating berries consumeberry BS_TARGET, TRUE @ consume the berry, then restore the item from changedItems - bicword gHitMarker, HITMARKER_NO_ANIMATIONS | HITMARKER_IGNORE_SUBSTITUTE | HITMARKER_IGNORE_DISGUISE + bicword gHitMarker, HITMARKER_DISABLE_ANIMATION | HITMARKER_IGNORE_SUBSTITUTE | HITMARKER_IGNORE_DISGUISE setbyte sBERRY_OVERRIDE, FALSE removeitem BS_TARGET moveendto MOVEEND_NEXT_TARGET @@ -1052,9 +1052,9 @@ BattleScript_EffectFlingConsumeBerry: savebattleritem BS_TARGET battleritemtolastuseditem BS_TARGET setbyte sBERRY_OVERRIDE, 1 @ override the requirements for eating berries - orword gHitMarker, HITMARKER_NO_ANIMATIONS + orword gHitMarker, HITMARKER_DISABLE_ANIMATION consumeberry BS_TARGET, TRUE - bicword gHitMarker, HITMARKER_NO_ANIMATIONS + bicword gHitMarker, HITMARKER_DISABLE_ANIMATION setbyte sBERRY_OVERRIDE, 0 restorebattleritem BS_TARGET BattleScript_FlingEnd: @@ -1327,9 +1327,9 @@ BattleScript_EffectStuffCheeks:: waitanimation BattleScript_StuffCheeksEatBerry: setbyte sBERRY_OVERRIDE, 1 - orword gHitMarker, HITMARKER_NO_ANIMATIONS + orword gHitMarker, HITMARKER_DISABLE_ANIMATION consumeberry BS_ATTACKER, TRUE - bicword gHitMarker, HITMARKER_NO_ANIMATIONS + bicword gHitMarker, HITMARKER_DISABLE_ANIMATION setbyte sBERRY_OVERRIDE, 0 removeitem BS_ATTACKER setstatchanger STAT_DEF, 2, FALSE @@ -1593,11 +1593,11 @@ BattleScript_MoveEffectIncinerate:: BattleScript_MoveEffectBugBite:: printstring STRINGID_BUGBITE waitmessage B_WAIT_TIME_LONG - orword gHitMarker, HITMARKER_NO_ANIMATIONS + orword gHitMarker, HITMARKER_DISABLE_ANIMATION setbyte sBERRY_OVERRIDE, 1 @ override the requirements for eating berries savetarget consumeberry BS_ATTACKER, FALSE - bicword gHitMarker, HITMARKER_NO_ANIMATIONS + bicword gHitMarker, HITMARKER_DISABLE_ANIMATION setbyte sBERRY_OVERRIDE, 0 trysymbiosis restoretarget diff --git a/include/constants/battle.h b/include/constants/battle.h index ecc823ee8bc1..18977637993a 100644 --- a/include/constants/battle.h +++ b/include/constants/battle.h @@ -193,7 +193,7 @@ #define HITMARKER_WAKE_UP_CLEAR (1 << 4) // Cleared when waking up. Never set or checked. #define HITMARKER_SKIP_DMG_TRACK (1 << 5) #define HITMARKER_DESTINYBOND (1 << 6) -#define HITMARKER_NO_ANIMATIONS (1 << 7) +#define HITMARKER_NO_ANIMATIONS (1 << 7) // set from battleSceneOff. Never changed during battle #define HITMARKER_IGNORE_SUBSTITUTE (1 << 8) #define HITMARKER_NO_ATTACKSTRING (1 << 9) #define HITMARKER_ATTACKSTRING_PRINTED (1 << 10) @@ -203,6 +203,7 @@ #define HITMARKER_SYNCHRONISE_EFFECT (1 << 14) #define HITMARKER_RUN (1 << 15) #define HITMARKER_IGNORE_DISGUISE (1 << 16) +#define HITMARKER_DISABLE_ANIMATION (1 << 17) // disable animations during battle scripts, e.g. for Bug Bite // 3 free spots because of change in handling of UNDERGROUND/UNDERWATER/ON AIR #define HITMARKER_UNABLE_TO_USE_MOVE (1 << 19) #define HITMARKER_PASSIVE_DAMAGE (1 << 20) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 9fd43a3d1e5e..6d8769f747f7 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2159,7 +2159,7 @@ static void Cmd_attackanimation(void) if (gBattleControllerExecFlags) return; - if ((gHitMarker & HITMARKER_NO_ANIMATIONS) + if ((gHitMarker & (HITMARKER_NO_ANIMATIONS | HITMARKER_DISABLE_ANIMATION)) && gCurrentMove != MOVE_TRANSFORM && gCurrentMove != MOVE_SUBSTITUTE // In a wild double battle gotta use the teleport animation if two wild pokemon are alive. @@ -4920,7 +4920,7 @@ static void PlayAnimation(u32 battler, u8 animId, const u16 *argPtr, const u8 *n MarkBattlerForControllerExec(battler); gBattlescriptCurrInstr = nextInstr; } - else if (gHitMarker & HITMARKER_NO_ANIMATIONS && animId != B_ANIM_RESTORE_BG) + else if (gHitMarker & (HITMARKER_NO_ANIMATIONS | HITMARKER_DISABLE_ANIMATION) && animId != B_ANIM_RESTORE_BG) { BattleScriptPush(nextInstr); gBattlescriptCurrInstr = BattleScript_Pausex20; @@ -5502,7 +5502,7 @@ static void Cmd_moveend(void) break; case MOVEEND_ATTACKER_INVISIBLE: // make attacker sprite invisible if (gStatuses3[gBattlerAttacker] & (STATUS3_SEMI_INVULNERABLE) - && gHitMarker & HITMARKER_NO_ANIMATIONS) + && gHitMarker & (HITMARKER_NO_ANIMATIONS | HITMARKER_DISABLE_ANIMATION)) { BtlController_EmitSpriteInvisibility(gBattlerAttacker, BUFFER_A, TRUE); MarkBattlerForControllerExec(gBattlerAttacker); @@ -7446,7 +7446,7 @@ static void Cmd_statusanimation(void) u32 battler = GetBattlerForBattleScript(cmd->battler); if (!(gStatuses3[battler] & STATUS3_SEMI_INVULNERABLE) && gDisableStructs[battler].substituteHP == 0 - && !(gHitMarker & HITMARKER_NO_ANIMATIONS)) + && !(gHitMarker & (HITMARKER_NO_ANIMATIONS | HITMARKER_DISABLE_ANIMATION))) { BtlController_EmitStatusAnimation(battler, BUFFER_A, FALSE, gBattleMons[battler].status1); MarkBattlerForControllerExec(battler); @@ -7465,7 +7465,7 @@ static void Cmd_status2animation(void) u32 status2ToAnim = cmd->status2; if (!(gStatuses3[battler] & STATUS3_SEMI_INVULNERABLE) && gDisableStructs[battler].substituteHP == 0 - && !(gHitMarker & HITMARKER_NO_ANIMATIONS)) + && !(gHitMarker & (HITMARKER_NO_ANIMATIONS | HITMARKER_DISABLE_ANIMATION))) { BtlController_EmitStatusAnimation(battler, BUFFER_A, TRUE, gBattleMons[battler].status2 & status2ToAnim); MarkBattlerForControllerExec(battler); @@ -7484,7 +7484,7 @@ static void Cmd_chosenstatusanimation(void) u32 wantedStatus = cmd->status; if (!(gStatuses3[battler] & STATUS3_SEMI_INVULNERABLE) && gDisableStructs[battler].substituteHP == 0 - && !(gHitMarker & HITMARKER_NO_ANIMATIONS)) + && !(gHitMarker & (HITMARKER_NO_ANIMATIONS | HITMARKER_DISABLE_ANIMATION))) { BtlController_EmitStatusAnimation(battler, BUFFER_A, cmd->isStatus2, wantedStatus); MarkBattlerForControllerExec(battler); From 2b1d36db46ec59ab3934580be295db91870c5603 Mon Sep 17 00:00:00 2001 From: psf <77138753+pkmnsnfrn@users.noreply.github.com> Date: Wed, 29 Nov 2023 14:08:36 -0800 Subject: [PATCH 16/18] Fix issue where Sky Battles cause upcoming not to compile when debug menu is turned off (#3617) * Moved Sky Battle error message outside of debug.inc https://github.com/rh-hideout/pokeemerald-expansion/issues/3615\#issue-2015546179 * Update data/scripts/debug.inc Made changes from Jasper's review https://github.com/rh-hideout/pokeemerald-expansion/pull/3617/files/625f0077416c5fa919b4999fc4be9c715d031875 Co-authored-by: Bassoonian --------- Co-authored-by: Bassoonian --- data/scripts/debug.inc | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/data/scripts/debug.inc b/data/scripts/debug.inc index 573b60d457c2..4d14a2a2a65b 100644 --- a/data/scripts/debug.inc +++ b/data/scripts/debug.inc @@ -95,19 +95,6 @@ Debug_BoxFilledMessage:: Debug_BoxFilledMessage_Text: .string "Storage boxes filled!$" -Debug_FlagsAndVarNotSetBattleConfigMessage:: - lockall - message Debug_FlagsAndVarNotSetBattleConfigMessage_Text - waitmessage - waitbuttonpress - releaseall - end - -Debug_FlagsAndVarNotSetBattleConfigMessage_Text: - .string "Feature unavailable! Please define a\n" - .string "usable flag and a usable var in:\l" - .string "'include/config/battle.h'!$" - Debug_EventScript_Script_1:: end @@ -229,3 +216,17 @@ Debug_ExpansionVersion: .string "pokeemerald-expansion {STR_VAR_1}$" .endif + +Debug_FlagsAndVarNotSetBattleConfigMessage:: + lockall + message Debug_FlagsAndVarNotSetBattleConfigMessage_Text + waitmessage + waitbuttonpress + releaseall + end + +Debug_FlagsAndVarNotSetBattleConfigMessage_Text: + .string "Feature unavailable! Please define a\n" + .string "usable flag and a usable var in:\l" + .string "'include/config/battle.h'!$" + From 131bb94d7385e71838171ef3513782c61882bfe6 Mon Sep 17 00:00:00 2001 From: Pawkkie <61265402+Pawkkie@users.noreply.github.com> Date: Thu, 30 Nov 2023 06:37:19 -0500 Subject: [PATCH 17/18] Extend AI_FLAG_SMART_SWITCHING for Encore / hazards / lowered attacking stats (#3557) * Consider Encore / lowered stats / hazards * Fix poorly thought out Return FALSE * Incorporate Egg's feedback * Tests for hazards and stats, and some fixes --------- Co-authored-by: Bassoonian --- src/battle_ai_switch_items.c | 156 ++++++++++++++++++++++++++++++++++- test/battle/ai.c | 39 +++++++++ 2 files changed, 194 insertions(+), 1 deletion(-) diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 4a35e824c07e..f4ea29176707 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -28,6 +28,7 @@ static bool8 ShouldUseItem(u32 battler); static bool32 AiExpectsToFaintPlayer(u32 battler); static bool32 AI_ShouldHeal(u32 battler, u32 healAmount); static bool32 AI_OpponentCanFaintAiWithMod(u32 battler, u32 healAmount); +static u32 GetSwitchinHazardsDamage(u32 battler, struct BattlePokemon *battleMon); static void InitializeSwitchinCandidate(struct Pokemon *mon) { @@ -774,6 +775,153 @@ static bool8 FindMonWithFlagsAndSuperEffective(u32 battler, u16 flags, u8 modulo return FALSE; } +static bool32 CanMonSurviveHazardSwitchin(u32 battler) +{ + u32 battlerIn1, battlerIn2; + u32 hazardDamage = 0, battlerHp = gBattleMons[battler].hp; + u32 ability = GetBattlerAbility(battler), aiMove; + s32 firstId, lastId, i, j; + struct Pokemon *party; + + if (ability == ABILITY_REGENERATOR) + battlerHp = (battlerHp * 133) / 100; // Account for Regenerator healing + + hazardDamage = GetSwitchinHazardsDamage(battler, &gBattleMons[battler]); + + // Battler will faint to hazards, check to see if another mon can clear them + if (hazardDamage > battlerHp) + { + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + { + battlerIn1 = battler; + if (gAbsentBattlerFlags & gBitTable[GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler)))]) + battlerIn2 = battler; + else + battlerIn2 = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler))); + } + else + { + battlerIn1 = battler; + battlerIn2 = battler; + } + + GetAIPartyIndexes(battler, &firstId, &lastId); + party = GetBattlerParty(battler); + + for (i = firstId; i < lastId; i++) + { + if (!IsValidForBattle(&party[i])) + continue; + if (i == gBattlerPartyIndexes[battlerIn1]) + continue; + if (i == gBattlerPartyIndexes[battlerIn2]) + continue; + if (i == *(gBattleStruct->monToSwitchIntoId + battlerIn1)) + continue; + if (i == *(gBattleStruct->monToSwitchIntoId + battlerIn2)) + continue; + if (IsAceMon(battler, i)) + continue; + + for (j = 0; j < MAX_MON_MOVES; j++) + { + aiMove = GetMonData(&party[i], MON_DATA_MOVE1 + j, NULL); + if (aiMove == MOVE_RAPID_SPIN || aiMove == MOVE_DEFOG || aiMove == MOVE_MORTAL_SPIN || aiMove == MOVE_TIDY_UP) + { + // Have a mon that can clear the hazards, so switching out is okay + return TRUE; + } + } + } + // Faints to hazards and party can't clear them, don't switch out + return FALSE; + } + return TRUE; +} + +static bool32 ShouldSwitchIfEncored(u32 battler) +{ + // Only use this if AI_FLAG_SMART_SWITCHING is set for the trainer + if (!(AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_SWITCHING)) + return FALSE; + + // If not Encored or if no good switchin, don't switch + if (gDisableStructs[battler].encoredMove == MOVE_NONE || AI_DATA->mostSuitableMonId == PARTY_SIZE) + return FALSE; + + // Otherwise 50% chance to switch out + if (Random() & 1) + { + *(gBattleStruct->AI_monToSwitchIntoId + battler) = PARTY_SIZE; + BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); + return TRUE; + } + + return FALSE; +} + +// AI should switch if it's become setup fodder and has something better to switch to +static bool8 AreAttackingStatsLowered(u32 battler) +{ + s8 attackingStage = gBattleMons[battler].statStages[STAT_ATK]; + s8 spAttackingStage = gBattleMons[battler].statStages[STAT_SPATK]; + + // Only use this if AI_FLAG_SMART_SWITCHING is set for the trainer + if (!(AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_SWITCHING)) + return FALSE; + + // Physical attacker + if (gBattleMons[battler].attack > gBattleMons[battler].spAttack) + { + // Don't switch if attack isn't below -1 + if (attackingStage > DEFAULT_STAT_STAGE - 2) + return FALSE; + // 50% chance if attack at -2 and have a good candidate mon + else if (attackingStage == DEFAULT_STAT_STAGE - 2) + { + if (AI_DATA->mostSuitableMonId != PARTY_SIZE && (Random() & 1)) + { + *(gBattleStruct->AI_monToSwitchIntoId + battler) = PARTY_SIZE; + BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); + return TRUE; + } + } + // If at -3 or worse, switch out regardless + else if (attackingStage < DEFAULT_STAT_STAGE - 2) + { + *(gBattleStruct->AI_monToSwitchIntoId + battler) = PARTY_SIZE; + BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); + return TRUE; + } + } + + // Special attacker + else + { + // Don't switch if attack isn't below -1 + if (spAttackingStage > DEFAULT_STAT_STAGE - 2) + return FALSE; + // 50% chance if attack at -2 and have a good candidate mon + else if (spAttackingStage == DEFAULT_STAT_STAGE - 2) + { + if (AI_DATA->mostSuitableMonId != PARTY_SIZE && (Random() & 1)) + { + *(gBattleStruct->AI_monToSwitchIntoId + battler) = PARTY_SIZE; + BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); + return TRUE; + } + } + // If at -3 or worse, switch out regardless + else if (spAttackingStage < DEFAULT_STAT_STAGE - 2) + { + *(gBattleStruct->AI_monToSwitchIntoId + battler) = PARTY_SIZE; + BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); + return TRUE; + } + } + return FALSE; +} + bool32 ShouldSwitch(u32 battler) { u8 battlerIn1, battlerIn2; @@ -857,12 +1005,18 @@ bool32 ShouldSwitch(u32 battler) return TRUE; //These Functions can prompt switch to generic pary members + if ((AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_SWITCHING) && (CanMonSurviveHazardSwitchin(battler) == FALSE)) + return FALSE; if (ShouldSwitchIfAllBadMoves(battler)) return TRUE; if (ShouldSwitchIfAbilityBenefit(battler)) return TRUE; if (HasBadOdds(battler)) return TRUE; + if (ShouldSwitchIfEncored(battler)) + return TRUE; + if (AreAttackingStatsLowered(battler)) + return TRUE; //Removing switch capabilites under specific conditions //These Functions prevent the "FindMonWithFlagsAndSuperEffective" from getting out of hand. @@ -1108,7 +1262,7 @@ static u32 GetSwitchinHazardsDamage(u32 battler, struct BattlePokemon *battleMon { // Stealth Rock if ((hazardFlags & SIDE_STATUS_STEALTH_ROCK) && heldItemEffect != HOLD_EFFECT_HEAVY_DUTY_BOOTS) - hazardDamage += GetStealthHazardDamageByTypesAndHP(gBattleMoves[MOVE_STEALTH_ROCK].type, defType1, defType2, battleMon->hp); + hazardDamage += GetStealthHazardDamageByTypesAndHP(gBattleMoves[MOVE_STEALTH_ROCK].type, defType1, defType2, battleMon->maxHP); // Spikes if ((hazardFlags & SIDE_STATUS_SPIKES) && IsMonGrounded(heldItemEffect, ability, defType1, defType2)) { diff --git a/test/battle/ai.c b/test/battle/ai.c index 42fa32760faa..d8f382961ac2 100644 --- a/test/battle/ai.c +++ b/test/battle/ai.c @@ -608,3 +608,42 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Post-KO switches prioritize of TURN { MOVE(player, MOVE_WING_ATTACK) ; EXPECT_SEND_OUT(opponent, 2); } } } + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI switches out after sufficient stat drops") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_HITMONTOP) { Level(30); Moves(MOVE_CHARM, MOVE_TACKLE); Ability(ABILITY_INTIMIDATE); Speed(5); } + OPPONENT(SPECIES_GRIMER) { Level(30); Moves(MOVE_TACKLE); Speed(4); } + OPPONENT(SPECIES_PONYTA) { Level(30); Moves(MOVE_HEADBUTT); Speed(4); } + } WHEN { + TURN { MOVE(player, MOVE_CHARM) ;} + TURN { MOVE(player, MOVE_TACKLE) ; EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will not switch out if Pokemon would faint to hazards unless party member can clear them") +{ + u32 move1; + + PARAMETRIZE{move1 = MOVE_TACKLE; } + PARAMETRIZE{move1 = MOVE_RAPID_SPIN; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_HITMONTOP) { Level(30); Moves(MOVE_CHARM, MOVE_TACKLE, MOVE_STEALTH_ROCK, MOVE_EARTHQUAKE); Ability(ABILITY_INTIMIDATE); Speed(5); } + OPPONENT(SPECIES_GRIMER) { Level(30); Moves(MOVE_TACKLE); Item(ITEM_FOCUS_SASH); Speed(4); } + OPPONENT(SPECIES_PONYTA) { Level(30); Moves(MOVE_HEADBUTT, move1); Speed(4); } + } WHEN { + TURN { MOVE(player, MOVE_STEALTH_ROCK) ;} + TURN { MOVE(player, MOVE_EARTHQUAKE) ;} + TURN { MOVE(player, MOVE_CHARM) ;} + TURN { // If the AI has a mon that can remove hazards, don't prevent them switching out + MOVE(player, MOVE_CHARM); + if (move1 == MOVE_RAPID_SPIN) + EXPECT_SWITCH(opponent, 1); + else if (move1 == MOVE_TACKLE) + EXPECT_MOVE(opponent, MOVE_TACKLE); + } + } +} From d1a69d7e044af3feb79199690bc04479ac2b31f7 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Fri, 1 Dec 2023 00:01:21 +0100 Subject: [PATCH 18/18] fix new line (#3621) --- data/specials.inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/specials.inc b/data/specials.inc index f05655cbd377..152210c176d9 100644 --- a/data/specials.inc +++ b/data/specials.inc @@ -540,5 +540,5 @@ gSpecials:: def_special GetSprayId def_special GetLastUsedSprayType def_special TrySkyBattle - def_special VsSeekerResetObjectMovementAfterChargeComplete - def_special VsSeekerFreezeObjectsAfterChargeComplete \ No newline at end of file + def_special VsSeekerResetObjectMovementAfterChargeComplete + def_special VsSeekerFreezeObjectsAfterChargeComplete