diff --git a/README.md b/README.md index fc2a012780..0cb95f3810 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,10 @@ ## 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? -- 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) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 50d10e81ab..355c7ad53e 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -713,6 +713,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 @@ -727,6 +728,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/include/battle.h b/include/battle.h index f34db84202..5d603399b7 100644 --- a/include/battle.h +++ b/include/battle.h @@ -108,6 +108,8 @@ struct DisableStruct u8 syrupBombTimer; u8 syrupBombIsShiny:1; u8 steelSurgeDone:1; + u8 weatherAbilityDone:1; + u8 terrainAbilityDone:1; }; struct ProtectStruct @@ -199,8 +201,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 c4fb6da0a0..833277b9ef 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -1884,12 +1884,13 @@ u32 GetBattlerAffectionHearts(u32 battler) return GetMonAffectionHearts(&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); } @@ -1942,7 +1943,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; @@ -2242,6 +2243,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 @@ -4096,6 +4099,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; @@ -6122,10 +6126,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++; } @@ -6137,9 +6142,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); @@ -6147,11 +6152,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++; } @@ -6160,7 +6165,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; diff --git a/test/battle/ability/protosynthesis.c b/test/battle/ability/protosynthesis.c index 0d53141c34..93ed9630e9 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 d9c7cf7307..f4448e1439 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!"); + } +} diff --git a/test/battle/move_effect/attack_up_user_ally.c b/test/battle/move_effect/attack_up_user_ally.c index 6691b49ed8..4b7fdae084 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]); + } +}