Adds hazard queue (#7295)

This commit is contained in:
Alex 2025-07-07 21:30:35 +02:00 committed by GitHub
parent 7ae97ab6e9
commit 31a561201b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 666 additions and 386 deletions

View File

@ -6113,53 +6113,13 @@ BattleScript_LeechSeedFree::
waitmessage B_WAIT_TIME_LONG
return
BattleScript_SpikesFree::
printstring STRINGID_PKMNBLEWAWAYSPIKES
BattleScript_SpinHazardsAway::
printfromtable gSpinHazardsStringIds
waitmessage B_WAIT_TIME_LONG
return
BattleScript_ToxicSpikesFree::
printstring STRINGID_PKMNBLEWAWAYTOXICSPIKES
waitmessage B_WAIT_TIME_LONG
return
BattleScript_StickyWebFree::
printstring STRINGID_PKMNBLEWAWAYSTICKYWEB
waitmessage B_WAIT_TIME_LONG
return
BattleScript_StealthRockFree::
printstring STRINGID_PKMNBLEWAWAYSTEALTHROCK
waitmessage B_WAIT_TIME_LONG
return
BattleScript_SteelsurgeFree::
printstring STRINGID_PKMNBLEWAWAYSHARPSTEEL
waitmessage B_WAIT_TIME_LONG
return
BattleScript_SpikesDefog::
printstring STRINGID_SPIKESDISAPPEAREDFROMTEAM
waitmessage B_WAIT_TIME_LONG
return
BattleScript_ToxicSpikesDefog::
printstring STRINGID_TOXICSPIKESDISAPPEAREDFROMTEAM
waitmessage B_WAIT_TIME_LONG
return
BattleScript_StickyWebDefog::
printstring STRINGID_STICKYWEBDISAPPEAREDFROMTEAM
waitmessage B_WAIT_TIME_LONG
return
BattleScript_StealthRockDefog::
printstring STRINGID_STEALTHROCKDISAPPEAREDFROMTEAM
waitmessage B_WAIT_TIME_LONG
return
BattleScript_SteelsurgeDefog::
printstring STRINGID_SHARPSTEELDISAPPEAREDFROMTEAM
BattleScript_DefogClearHazards::
printfromtable gDefogHazardsStringIds
waitmessage B_WAIT_TIME_LONG
return

View File

@ -117,24 +117,20 @@ struct DisableStruct
u8 tarShot:1;
u8 octolock:1;
u8 cudChew:1;
u8 spikesDone:1;
u8 toxicSpikesDone:1;
u8 stickyWebDone:1;
u8 stealthRockDone:1;
u8 weatherAbilityDone:1;
u8 terrainAbilityDone:1;
u8 syrupBombIsShiny:1;
u8 steelSurgeDone:1;
u8 usedProteanLibero:1;
u8 flashFireBoosted:1;
u16 overwrittenAbility; // abilities overwritten during battle (keep separate from battle history in case of switching)
u8 boosterEnergyActivated:1;
u16 overwrittenAbility; // abilities overwritten during battle (keep separate from battle history in case of switching)
u8 roostActive:1;
u8 unburdenActive:1;
u8 neutralizingGas:1;
u8 iceFaceActivationPrevention:1; // fixes hit escape move edge case
u8 unnerveActivated:1; // Unnerve and As One (Unnerve part) activate only once per switch in
u8 padding:3;
u8 hazardsDone:1;
u8 padding:1;
};
// Fully Cleared each turn after end turn effects are done. A few things are cleared before end turn effects
@ -222,21 +218,18 @@ struct SideTimer
u16 lightscreenTimer;
u16 mistTimer;
u16 safeguardTimer;
u16 spikesAmount; // debug menu complains. might be better to solve there instead if possible
u16 toxicSpikesAmount;
u16 stealthRockAmount;
u16 stickyWebAmount;
u8 spikesAmount:4;
u8 toxicSpikesAmount:4;
u8 stickyWebBattlerId;
u8 stickyWebBattlerSide; // Used for Court Change
u16 auroraVeilTimer;
u16 tailwindTimer;
u16 luckyChantTimer;
u16 steelsurgeAmount;
// Timers below this point are not swapped by Court Change
u16 followmeTimer;
u8 followmeTimer:4;
u8 followmeTarget:3;
u8 followmePowder:1; // Rage powder, does not affect grass type pokemon.
u16 retaliateTimer;
u8 retaliateTimer;
u16 damageNonTypesTimer;
u8 damageNonTypesType;
u16 rainbowTimer;
@ -780,6 +773,10 @@ struct BattleStruct
s16 savedcheekPouchDamage; // Cheek Pouch can happen in the middle of an attack execution so we need to store the current dmg
struct MessageStatus slideMessageStatus;
u8 trainerSlideSpriteIds[MAX_BATTLERS_COUNT];
u8 hazardsQueue[NUM_BATTLE_SIDES][HAZARDS_MAX_COUNT];
u8 numHazards[NUM_BATTLE_SIDES];
u8 hazardsCounter:4; // Counter for applying hazard on switch in
u8 padding2:4;
};
struct AiBattleData

View File

@ -92,7 +92,7 @@ extern const u8 BattleScript_AllStatsUp[];
extern const u8 BattleScript_RapidSpinAway[];
extern const u8 BattleScript_WrapFree[];
extern const u8 BattleScript_LeechSeedFree[];
extern const u8 BattleScript_SpikesFree[];
extern const u8 BattleScript_SpinHazardsAway[];
extern const u8 BattleScript_MonTookFutureAttack[];
extern const u8 BattleScript_NoMovesLeft[];
extern const u8 BattleScript_SelectingMoveWithNoPP[];
@ -312,13 +312,7 @@ extern const u8 BattleScript_SelectingNotAllowedMoveGravityInPalace[];
extern const u8 BattleScript_SelectingNotAllowedMoveHealBlock[];
extern const u8 BattleScript_MoveUsedHealBlockPrevents[];
extern const u8 BattleScript_SelectingNotAllowedMoveHealBlockInPalace[];
extern const u8 BattleScript_ToxicSpikesFree[];
extern const u8 BattleScript_StickyWebFree[];
extern const u8 BattleScript_StealthRockFree[];
extern const u8 BattleScript_SpikesDefog[];
extern const u8 BattleScript_ToxicSpikesDefog[];
extern const u8 BattleScript_StickyWebDefog[];
extern const u8 BattleScript_StealthRockDefog[];
extern const u8 BattleScript_DefogClearHazards[];
extern const u8 BattleScript_MegaEvolution[];
extern const u8 BattleScript_WishMegaEvolution[];
extern const u8 BattleScript_MoveEffectClearSmog[];
@ -555,8 +549,6 @@ extern const u8 BattleScript_EffectSetWeather[];
extern const u8 BattleScript_EffectSetTerrain[];
extern const u8 BattleScript_EffectStonesurge[];
extern const u8 BattleScript_EffectSteelsurge[];
extern const u8 BattleScript_SteelsurgeFree[];
extern const u8 BattleScript_SteelsurgeDefog[];
extern const u8 BattleScript_DamageNonTypesStarts[];
extern const u8 BattleScript_DamageNonTypesContinues[];
extern const u8 BattleScript_DefogTryHazards[];

View File

@ -400,5 +400,11 @@ bool32 TrySwitchInEjectPack(enum ItemCaseId caseID);
u32 GetMonVolatile(u32 battler, enum Volatile volatile);
void SetMonVolatile(u32 battler, enum Volatile volatile, u32 newValue);
u32 TryBoosterEnergy(u32 battler, u32 ability, enum ItemCaseId caseID);
void PushHazardTypeToQueue(u32 side, enum Hazards hazardType);
bool32 IsHazardOnSide(u32 side, enum Hazards hazardType);
bool32 AreAnyHazardsOnSide(u32 side);
void RemoveAllHazardsFromField(u32 side);
bool32 IsHazardOnSideAndClear(u32 side, enum Hazards hazardType);
void RemoveHazardFromField(u32 side, enum Hazards hazardType);
#endif // GUARD_BATTLE_UTIL_H

View File

@ -289,28 +289,31 @@ enum Volatile
// Per-side statuses that affect an entire party
#define SIDE_STATUS_REFLECT (1 << 0)
#define SIDE_STATUS_LIGHTSCREEN (1 << 1)
#define SIDE_STATUS_STICKY_WEB (1 << 2)
#define SIDE_STATUS_SPIKES (1 << 4)
#define SIDE_STATUS_SAFEGUARD (1 << 5)
#define SIDE_STATUS_FUTUREATTACK (1 << 6)
#define SIDE_STATUS_MIST (1 << 8)
// (1 << 9) previously was SIDE_STATUS_SPIKES_DAMAGED
#define SIDE_STATUS_TAILWIND (1 << 10)
#define SIDE_STATUS_AURORA_VEIL (1 << 11)
#define SIDE_STATUS_LUCKY_CHANT (1 << 12)
#define SIDE_STATUS_TOXIC_SPIKES (1 << 13)
#define SIDE_STATUS_STEALTH_ROCK (1 << 14)
// Missing flags previously were SIDE_STATUS_TOXIC_SPIKES_DAMAGED, SIDE_STATUS_STEALTH_ROCK_DAMAGED, SIDE_STATUS_STICKY_WEB_DAMAGED
#define SIDE_STATUS_STEELSURGE (1 << 18)
#define SIDE_STATUS_DAMAGE_NON_TYPES (1 << 19)
#define SIDE_STATUS_RAINBOW (1 << 20)
#define SIDE_STATUS_SEA_OF_FIRE (1 << 21)
#define SIDE_STATUS_SWAMP (1 << 22)
#define SIDE_STATUS_SAFEGUARD (1 << 2)
#define SIDE_STATUS_FUTUREATTACK (1 << 3)
#define SIDE_STATUS_MIST (1 << 4)
#define SIDE_STATUS_TAILWIND (1 << 5)
#define SIDE_STATUS_AURORA_VEIL (1 << 6)
#define SIDE_STATUS_LUCKY_CHANT (1 << 7)
#define SIDE_STATUS_DAMAGE_NON_TYPES (1 << 8)
#define SIDE_STATUS_RAINBOW (1 << 9)
#define SIDE_STATUS_SEA_OF_FIRE (1 << 10)
#define SIDE_STATUS_SWAMP (1 << 11)
#define SIDE_STATUS_HAZARDS_ANY (SIDE_STATUS_SPIKES | SIDE_STATUS_STICKY_WEB | SIDE_STATUS_TOXIC_SPIKES | SIDE_STATUS_STEALTH_ROCK | SIDE_STATUS_STEELSURGE)
#define SIDE_STATUS_SCREEN_ANY (SIDE_STATUS_REFLECT | SIDE_STATUS_LIGHTSCREEN | SIDE_STATUS_AURORA_VEIL)
#define SIDE_STATUS_PLEDGE_ANY (SIDE_STATUS_RAINBOW | SIDE_STATUS_SEA_OF_FIRE | SIDE_STATUS_SWAMP)
enum Hazards
{
HAZARDS_NONE,
HAZARDS_SPIKES,
HAZARDS_STICKY_WEB,
HAZARDS_TOXIC_SPIKES,
HAZARDS_STEALTH_ROCK,
HAZARDS_STEELSURGE,
HAZARDS_MAX_COUNT,
};
// Used for damaging entry hazards based on type
enum TypeSideHazard
{

View File

@ -1817,7 +1817,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(-10); // only one mon needs to set up the last layer of Spikes
break;
case EFFECT_STEALTH_ROCK:
if (gSideTimers[GetBattlerSide(battlerDef)].stealthRockAmount > 0
if (IsHazardOnSide(GetBattlerSide(battlerDef), HAZARDS_STEALTH_ROCK)
|| PartnerMoveIsSameNoTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) //Only one mon needs to set up Stealth Rocks
ADJUST_SCORE(-10);
break;
@ -1828,9 +1828,9 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(-10); // only one mon needs to set up the last layer of Toxic Spikes
break;
case EFFECT_STICKY_WEB:
if (gSideTimers[GetBattlerSide(battlerDef)].stickyWebAmount)
if (IsHazardOnSide(GetBattlerSide(battlerDef), HAZARDS_STICKY_WEB))
ADJUST_SCORE(-10);
else if (PartnerMoveIsSameNoTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove) && gSideTimers[GetBattlerSide(battlerDef)].stickyWebAmount)
else if (PartnerMoveIsSameNoTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove) && IsHazardOnSide(GetBattlerSide(battlerDef), HAZARDS_STICKY_WEB))
ADJUST_SCORE(-10); // only one mon needs to set up Sticky Web
break;
case EFFECT_FORESIGHT:
@ -2283,9 +2283,8 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(-9);
break;
case EFFECT_DEFOG:
if (gSideStatuses[GetBattlerSide(battlerDef)]
& (SIDE_STATUS_SCREEN_ANY | SIDE_STATUS_SAFEGUARD | SIDE_STATUS_MIST)
|| gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_HAZARDS_ANY)
if (gSideStatuses[GetBattlerSide(battlerDef)] & (SIDE_STATUS_SCREEN_ANY | SIDE_STATUS_SAFEGUARD | SIDE_STATUS_MIST)
|| AreAnyHazardsOnSide(GetBattlerSide(battlerAtk)))
{
if (PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove))
{
@ -2294,7 +2293,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
}
}
if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_HAZARDS_ANY)
if (AreAnyHazardsOnSide(GetBattlerSide(battlerDef)))
{
ADJUST_SCORE(-10); //Don't blow away opposing hazards
break;
@ -4390,12 +4389,12 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
//ADJUST_SCORE(8);
break;
case EFFECT_DEFOG:
if ((gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_HAZARDS_ANY && CountUsablePartyMons(battlerAtk) != 0)
if ((AreAnyHazardsOnSide(GetBattlerSide(battlerAtk)) && CountUsablePartyMons(battlerAtk) != 0)
|| (gSideStatuses[GetBattlerSide(battlerDef)] & (SIDE_STATUS_SCREEN_ANY | SIDE_STATUS_SAFEGUARD | SIDE_STATUS_MIST)))
{
ADJUST_SCORE(GOOD_EFFECT);
}
else if (!(gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_SPIKES)) //Don't blow away hazards if you set them up
else if (!IsHazardOnSide(GetBattlerSide(battlerDef), HAZARDS_SPIKES)) //Don't blow away hazards if you set them up
{
if (isDoubleBattle)
{
@ -4939,7 +4938,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
ADJUST_SCORE(GOOD_EFFECT);
break;
case EFFECT_RAPID_SPIN:
if ((gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_HAZARDS_ANY && CountUsablePartyMons(battlerAtk) != 0)
if ((AreAnyHazardsOnSide(GetBattlerSide(battlerAtk)) && CountUsablePartyMons(battlerAtk) != 0)
|| (gStatuses3[battlerAtk] & STATUS3_LEECHSEED || gBattleMons[battlerAtk].status2 & STATUS2_WRAPPED))
ADJUST_SCORE(GOOD_EFFECT);
case EFFECT_SPECTRAL_THIEF:
@ -5789,13 +5788,31 @@ static s32 AI_PowerfulStatus(u32 battlerAtk, u32 battlerDef, u32 move, s32 score
return score;
}
bool32 DoesSideHaveDamagingHazards(u32 side)
{
for (u32 counter = 0; counter < HAZARDS_MAX_COUNT; counter++)
{
switch (gBattleStruct->hazardsQueue[side][counter])
{
case HAZARDS_SPIKES:
case HAZARDS_TOXIC_SPIKES:
case HAZARDS_STEALTH_ROCK:
case HAZARDS_STEELSURGE:
return TRUE;
default:
return FALSE;
}
}
return FALSE;
}
static s32 AI_PredictSwitch(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
{
u32 i;
u32 unmodifiedScore = score;
u32 ability = gBattleMons[battlerAtk].ability;
u32 opposingHazardFlags = gSideStatuses[GetBattlerSide(battlerDef)] & (SIDE_STATUS_SPIKES | SIDE_STATUS_STEALTH_ROCK | SIDE_STATUS_TOXIC_SPIKES);
u32 aiHazardFlags = gSideStatuses[GetBattlerSide(battlerAtk)] & (SIDE_STATUS_HAZARDS_ANY);
bool32 opposingHazardFlags = DoesSideHaveDamagingHazards(GetBattlerSide(battlerDef));
bool32 aiHazardFlags = AreAnyHazardsOnSide(GetBattlerSide(battlerAtk));
enum BattleMoveEffects moveEffect = GetMoveEffect(move);
struct AiLogicData *aiData = gAiLogicData;
uq4_12_t effectiveness = aiData->effectiveness[battlerAtk][battlerDef][gAiThinkingStruct->movesetIndex];
@ -5856,21 +5873,21 @@ static s32 AI_PredictSwitch(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(GOOD_EFFECT);
break;
case EFFECT_HIT_SWITCH_TARGET:
if (opposingHazardFlags != 0)
if (opposingHazardFlags)
ADJUST_SCORE(BEST_EFFECT);
else
ADJUST_SCORE(GOOD_EFFECT);
break;
case EFFECT_ROAR:
if (opposingHazardFlags != 0)
if (opposingHazardFlags)
ADJUST_SCORE(GOOD_EFFECT);
break;
case EFFECT_RAPID_SPIN:
if (aiHazardFlags != 0)
if (aiHazardFlags)
ADJUST_SCORE(BEST_EFFECT);
break;
case EFFECT_DEFOG:
if (aiHazardFlags != 0)
if (aiHazardFlags)
ADJUST_SCORE(GOOD_EFFECT);
break;
case EFFECT_WISH:

View File

@ -1529,20 +1529,20 @@ static u32 GetSwitchinHazardsDamage(u32 battler, struct BattlePokemon *battleMon
u16 heldItemEffect = GetItemHoldEffect(battleMon->item);
u32 maxHP = battleMon->maxHP, ability = battleMon->ability, status = battleMon->status1;
u32 spikesDamage = 0, tSpikesDamage = 0, hazardDamage = 0;
u32 hazardFlags = gSideStatuses[GetBattlerSide(battler)] & (SIDE_STATUS_SPIKES | SIDE_STATUS_STEALTH_ROCK | SIDE_STATUS_STICKY_WEB | SIDE_STATUS_TOXIC_SPIKES | SIDE_STATUS_SAFEGUARD);
u32 side = GetBattlerSide(battler);
// Check ways mon might avoid all hazards
if (ability != ABILITY_MAGIC_GUARD || (heldItemEffect == HOLD_EFFECT_HEAVY_DUTY_BOOTS &&
!((gFieldStatuses & STATUS_FIELD_MAGIC_ROOM) || ability == ABILITY_KLUTZ)))
{
// Stealth Rock
if ((hazardFlags & SIDE_STATUS_STEALTH_ROCK) && heldItemEffect != HOLD_EFFECT_HEAVY_DUTY_BOOTS)
if (IsHazardOnSide(side, HAZARDS_STEALTH_ROCK) && heldItemEffect != HOLD_EFFECT_HEAVY_DUTY_BOOTS)
hazardDamage += GetStealthHazardDamageByTypesAndHP(TYPE_SIDE_HAZARD_POINTED_STONES, defType1, defType2, battleMon->maxHP);
// G-Max Steelsurge
if ((hazardFlags & SIDE_STATUS_STEELSURGE) && heldItemEffect != HOLD_EFFECT_HEAVY_DUTY_BOOTS)
if (IsHazardOnSide(side, HAZARDS_STEELSURGE) && heldItemEffect != HOLD_EFFECT_HEAVY_DUTY_BOOTS)
hazardDamage += GetStealthHazardDamageByTypesAndHP(TYPE_SIDE_HAZARD_SHARP_STEEL, defType1, defType2, battleMon->maxHP);
// Spikes
if ((hazardFlags & SIDE_STATUS_SPIKES) && IsMonGrounded(heldItemEffect, ability, defType1, defType2))
if (IsHazardOnSide(side, HAZARDS_TOXIC_SPIKES) && IsMonGrounded(heldItemEffect, ability, defType1, defType2))
{
spikesDamage = maxHP / ((5 - gSideTimers[GetBattlerSide(battler)].spikesAmount) * 2);
if (spikesDamage == 0)
@ -1550,11 +1550,11 @@ static u32 GetSwitchinHazardsDamage(u32 battler, struct BattlePokemon *battleMon
hazardDamage += spikesDamage;
}
if ((hazardFlags & SIDE_STATUS_TOXIC_SPIKES) && (defType1 != TYPE_POISON && defType2 != TYPE_POISON
if (IsHazardOnSide(side, HAZARDS_SPIKES) && (defType1 != TYPE_POISON && defType2 != TYPE_POISON
&& defType1 != TYPE_STEEL && defType2 != TYPE_STEEL
&& ability != ABILITY_IMMUNITY && ability != ABILITY_POISON_HEAL && ability != ABILITY_COMATOSE
&& status == 0
&& !(hazardFlags & SIDE_STATUS_SAFEGUARD)
&& !(gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SAFEGUARD)
&& !IsAbilityOnSide(battler, ABILITY_PASTEL_VEIL)
&& !IsBattlerTerrainAffected(battler, STATUS_FIELD_MISTY_TERRAIN)
&& !IsAbilityStatusProtected(battler, ability)

View File

@ -1982,7 +1982,7 @@ u32 IncreaseStatDownScore(u32 battlerAtk, u32 battlerDef, u32 stat)
// Don't decrese stat if opposing battler has Encore
if (HasBattlerSideMoveWithEffect(battlerDef, EFFECT_ENCORE))
return NO_INCREASE;
if (DoesAbilityRaiseStatsWhenLowered(gAiLogicData->abilities[battlerDef]))
return NO_INCREASE;
@ -2979,13 +2979,13 @@ static bool32 PartyBattlerShouldAvoidHazards(u32 currBattler, u32 switchBattler)
u32 ability = GetMonAbility(mon); // we know our own party data
enum ItemHoldEffect holdEffect;
u32 species = GetMonData(mon, MON_DATA_SPECIES);
u32 flags = gSideStatuses[GetBattlerSide(currBattler)] & (SIDE_STATUS_SPIKES | SIDE_STATUS_STEALTH_ROCK | SIDE_STATUS_STEELSURGE | SIDE_STATUS_STICKY_WEB | SIDE_STATUS_TOXIC_SPIKES);
s32 hazardDamage = 0;
u32 type1 = GetSpeciesType(species, 0);
u32 type2 = GetSpeciesType(species, 1);
u32 maxHp = GetMonData(mon, MON_DATA_MAX_HP);
u32 side = GetBattlerSide(currBattler);
if (flags == 0)
if (!AreAnyHazardsOnSide(side))
return FALSE;
if (ability == ABILITY_MAGIC_GUARD)
@ -2997,12 +2997,12 @@ static bool32 PartyBattlerShouldAvoidHazards(u32 currBattler, u32 switchBattler)
if (holdEffect == HOLD_EFFECT_HEAVY_DUTY_BOOTS)
return FALSE;
if (flags & SIDE_STATUS_STEALTH_ROCK)
if (IsHazardOnSide(side, HAZARDS_STEALTH_ROCK))
hazardDamage += GetStealthHazardDamageByTypesAndHP(TYPE_SIDE_HAZARD_POINTED_STONES, type1, type2, maxHp);
if ((flags & SIDE_STATUS_STEELSURGE))
if (IsHazardOnSide(side, HAZARDS_STEELSURGE))
hazardDamage += GetStealthHazardDamageByTypesAndHP(TYPE_SIDE_HAZARD_SHARP_STEEL, type1, type2, maxHp);
if (flags & SIDE_STATUS_SPIKES && ((type1 != TYPE_FLYING && type2 != TYPE_FLYING
if (IsHazardOnSide(side, HAZARDS_SPIKES) && ((type1 != TYPE_FLYING && type2 != TYPE_FLYING
&& ability != ABILITY_LEVITATE && holdEffect != HOLD_EFFECT_AIR_BALLOON)
|| holdEffect == HOLD_EFFECT_IRON_BALL || gFieldStatuses & STATUS_FIELD_GRAVITY))
{
@ -3070,7 +3070,7 @@ enum AIPivot ShouldPivot(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 mov
return SHOULD_PIVOT;
/* TODO - check if switchable mon unafffected by/will remove hazards
if (gSideStatuses[battlerAtk] & SIDE_STATUS_SPIKES && switchScore >= SWITCHING_INCREASE_CAN_REMOVE_HAZARDS)
if (IsHazardOnSide(GetBattlerSide(battlerAtk, HAZARDS_SPIKES) && switchScore >= SWITCHING_INCREASE_CAN_REMOVE_HAZARDS)
return SHOULD_PIVOT;*/
/*if (BattlerWillFaintFromSecondaryDamage(battlerAtk, gAiLogicData->abilities[battlerAtk]) && switchScore >= SWITCHING_INCREASE_WALLS_FOE)
@ -3152,7 +3152,7 @@ enum AIPivot ShouldPivot(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 mov
if (!hasStatBoost)
{
// TODO - check if switching prevents/removes hazards
//if (gSideStatuses[battlerAtk] & SIDE_STATUS_SPIKES && switchScore >= SWITCHING_INCREASE_CAN_REMOVE_HAZARDS)
//if (IsHazardOnSide(GetBattlerSide(battlerAtk, HAZARDS_SPIKES) && switchScore >= SWITCHING_INCREASE_CAN_REMOVE_HAZARDS)
//return SHOULD_PIVOT;
// TODO - not always a good idea
@ -4787,9 +4787,9 @@ bool32 AI_ShouldSetUpHazards(u32 battlerAtk, u32 battlerDef, struct AiLogicData
void IncreaseTidyUpScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score)
{
if (gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_HAZARDS_ANY && CountUsablePartyMons(battlerAtk) != 0)
if (AreAnyHazardsOnSide(GetBattlerSide(battlerAtk)) && CountUsablePartyMons(battlerAtk) != 0)
ADJUST_SCORE_PTR(GOOD_EFFECT);
if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_HAZARDS_ANY && CountUsablePartyMons(battlerDef) != 0)
if (AreAnyHazardsOnSide(GetBattlerSide(battlerDef)) && CountUsablePartyMons(battlerDef) != 0)
ADJUST_SCORE_PTR(-2);
if (gBattleMons[battlerAtk].status2 & STATUS2_SUBSTITUTE && AI_IsFaster(battlerAtk, battlerDef, move))
@ -5029,7 +5029,7 @@ bool32 ShouldTriggerAbility(u32 battlerAtk, u32 battlerDef, u32 ability)
// At the moment, the parts about Mummy and Wandering Spirit are not actually used.
bool32 CanEffectChangeAbility(u32 battlerAtk, u32 battlerDef, u32 effect, struct AiLogicData *aiData)
{
// Dynamaxed Pokemon are immune to some ability-changing effects.
// Dynamaxed Pokemon are immune to some ability-changing effects.
if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX)
{
switch (effect)
@ -5044,7 +5044,7 @@ bool32 CanEffectChangeAbility(u32 battlerAtk, u32 battlerDef, u32 effect, struct
if (gStatuses3[battlerDef] & STATUS3_GASTRO_ACID)
return FALSE;
u32 atkAbility = aiData->abilities[battlerAtk];
u32 defAbility = aiData->abilities[battlerDef];
bool32 hasSameAbility = (atkAbility == defAbility);
@ -5078,7 +5078,7 @@ bool32 CanEffectChangeAbility(u32 battlerAtk, u32 battlerDef, u32 effect, struct
{
u32 partnerAbility = aiData->abilities[BATTLE_PARTNER(battlerAtk)];
if (gAbilitiesInfo[partnerAbility].cantBeSuppressed)
return FALSE;
return FALSE;
if (partnerAbility == defAbility)
return FALSE;
}
@ -5113,7 +5113,7 @@ bool32 CanEffectChangeAbility(u32 battlerAtk, u32 battlerDef, u32 effect, struct
if (defAbility == ABILITY_INSOMNIA || gAbilitiesInfo[defAbility].cantBeOverwritten)
return FALSE;
break;
default:
return FALSE;
}
@ -5217,14 +5217,14 @@ void AbilityChangeScore(u32 battlerAtk, u32 battlerDef, u32 effect, s32 *score,
{
ADJUST_SCORE_PTR(-20);
}
// Trigger Plus or Minus in modern gens. This is not in the overarching function because Skill Swap is rarely beneficial here.
if (B_PLUS_MINUS_INTERACTION >= GEN_5)
{
if (((effect == EFFECT_ENTRAINMENT) && (abilityAtk == ABILITY_PLUS || abilityAtk == ABILITY_MINUS)) || ((effect == EFFECT_ROLE_PLAY) && (abilityDef == ABILITY_PLUS || abilityDef == ABILITY_MINUS)))
ADJUST_SCORE_PTR(DECENT_EFFECT);
}
}
// Targeting an opponent.
else

View File

@ -107,6 +107,7 @@ enum
LIST_ITEM_VOLATILE,
LIST_ITEM_STATUS3,
LIST_ITEM_STATUS4,
LIST_ITEM_HAZARDS,
LIST_ITEM_SIDE_STATUS,
LIST_ITEM_AI,
LIST_ITEM_AI_MOVES_PTS,
@ -176,18 +177,22 @@ enum
enum
{
LIST_SIDE_REFLECT,
LIST_SIDE_LIGHTSCREEN,
LIST_SIDE_STICKY_WEB,
LIST_SIDE_SPIKES,
LIST_SIDE_TOXIC_SPIKES,
LIST_SIDE_STEALTH_ROCK,
LIST_SIDE_STEELSURGE,
};
enum
{
LIST_SIDE_REFLECT,
LIST_SIDE_LIGHTSCREEN,
LIST_SIDE_SAFEGUARD,
LIST_SIDE_MIST,
LIST_SIDE_TAILWIND,
LIST_SIDE_AURORA_VEIL,
LIST_SIDE_LUCKY_CHANT,
LIST_SIDE_TOXIC_SPIKES,
LIST_SIDE_STEALTH_ROCK,
LIST_SIDE_STEELSURGE,
LIST_SIDE_DAMAGE_NON_TYPES,
LIST_SIDE_RAINBOW,
LIST_SIDE_SEA_OF_FIRE,
@ -250,6 +255,7 @@ enum
VAL_BITFIELD_16,
VAL_BITFIELD_32,
VAL_VOLATILE,
VAL_HAZARDS,
VAR_SIDE_STATUS,
VAR_SHOW_HP,
VAR_SUBSTITUTE,
@ -352,23 +358,24 @@ static const struct BitfieldInfo sAIBitfield[] =
static const struct ListMenuItem sMainListItems[] =
{
{COMPOUND_STRING("Moves"), LIST_ITEM_MOVES},
{sText_Ability, LIST_ITEM_ABILITY},
{sText_HeldItem, LIST_ITEM_HELD_ITEM},
{COMPOUND_STRING("PP"), LIST_ITEM_PP},
{COMPOUND_STRING("Types"), LIST_ITEM_TYPES},
{COMPOUND_STRING("Stats"), LIST_ITEM_STATS},
{COMPOUND_STRING("Stat Stages"), LIST_ITEM_STAT_STAGES},
{COMPOUND_STRING("Status1"), LIST_ITEM_STATUS1},
{COMPOUND_STRING("Volatiles"), LIST_ITEM_VOLATILE},
{COMPOUND_STRING("Status3"), LIST_ITEM_STATUS3},
{COMPOUND_STRING("Status4"), LIST_ITEM_STATUS4},
{COMPOUND_STRING("Side Status"), LIST_ITEM_SIDE_STATUS},
{COMPOUND_STRING("AI"), LIST_ITEM_AI},
{COMPOUND_STRING("AI Pts/Dmg"), LIST_ITEM_AI_MOVES_PTS},
{COMPOUND_STRING("AI Info"), LIST_ITEM_AI_INFO},
{COMPOUND_STRING("AI Party"), LIST_ITEM_AI_PARTY},
{COMPOUND_STRING("Various"), LIST_ITEM_VARIOUS},
{COMPOUND_STRING("Moves"), LIST_ITEM_MOVES},
{sText_Ability, LIST_ITEM_ABILITY},
{sText_HeldItem, LIST_ITEM_HELD_ITEM},
{COMPOUND_STRING("PP"), LIST_ITEM_PP},
{COMPOUND_STRING("Types"), LIST_ITEM_TYPES},
{COMPOUND_STRING("Stats"), LIST_ITEM_STATS},
{COMPOUND_STRING("Stat Stages"), LIST_ITEM_STAT_STAGES},
{COMPOUND_STRING("Status1"), LIST_ITEM_STATUS1},
{COMPOUND_STRING("Volatiles"), LIST_ITEM_VOLATILE},
{COMPOUND_STRING("Status3"), LIST_ITEM_STATUS3},
{COMPOUND_STRING("Status4"), LIST_ITEM_STATUS4},
{COMPOUND_STRING("Hazards"), LIST_ITEM_HAZARDS},
{COMPOUND_STRING("Side Status"), LIST_ITEM_SIDE_STATUS},
{COMPOUND_STRING("AI"), LIST_ITEM_AI},
{COMPOUND_STRING("AI Pts/Dmg"), LIST_ITEM_AI_MOVES_PTS},
{COMPOUND_STRING("AI Info"), LIST_ITEM_AI_INFO},
{COMPOUND_STRING("AI Party"), LIST_ITEM_AI_PARTY},
{COMPOUND_STRING("Various"), LIST_ITEM_VARIOUS},
};
static const struct ListMenuItem sStatsListItems[] =
@ -448,20 +455,24 @@ static const struct ListMenuItem sStatus4ListItems[] =
{COMPOUND_STRING("Glaive Rush"), LIST_STATUS4_GLAIVE_RUSH},
};
static const struct ListMenuItem sHazardsListItems[] =
{
{COMPOUND_STRING("Spikes"), LIST_SIDE_SPIKES},
{COMPOUND_STRING("Sticky Web"), LIST_SIDE_STICKY_WEB},
{COMPOUND_STRING("Toxic Spikes"), LIST_SIDE_TOXIC_SPIKES},
{COMPOUND_STRING("Stealth Rock"), LIST_SIDE_STEALTH_ROCK},
{COMPOUND_STRING("Steelsurge"), LIST_SIDE_STEELSURGE},
};
static const struct ListMenuItem sSideStatusListItems[] =
{
{COMPOUND_STRING("Reflect"), LIST_SIDE_REFLECT},
{COMPOUND_STRING("Light Screen"), LIST_SIDE_LIGHTSCREEN},
{COMPOUND_STRING("Sticky Web"), LIST_SIDE_STICKY_WEB},
{COMPOUND_STRING("Spikes"), LIST_SIDE_SPIKES},
{COMPOUND_STRING("Safeguard"), LIST_SIDE_SAFEGUARD},
{COMPOUND_STRING("Mist"), LIST_SIDE_MIST},
{COMPOUND_STRING("Tailwind"), LIST_SIDE_TAILWIND},
{COMPOUND_STRING("Aurora Veil"), LIST_SIDE_AURORA_VEIL},
{COMPOUND_STRING("Lucky Chant"), LIST_SIDE_LUCKY_CHANT},
{COMPOUND_STRING("Toxic Spikes"), LIST_SIDE_TOXIC_SPIKES},
{COMPOUND_STRING("Stealth Rock"), LIST_SIDE_STEALTH_ROCK},
{COMPOUND_STRING("Steelsurge"), LIST_SIDE_STEELSURGE},
{COMPOUND_STRING("Damage Non-Types"), LIST_SIDE_DAMAGE_NON_TYPES},
{COMPOUND_STRING("Rainbow"), LIST_SIDE_RAINBOW},
{COMPOUND_STRING("Sea of Fire"), LIST_SIDE_SEA_OF_FIRE},
@ -659,6 +670,8 @@ static void PrintDigitChars(struct BattleDebugMenu *data);
static void SetUpModifyArrows(struct BattleDebugMenu *data);
static void UpdateBattlerValue(struct BattleDebugMenu *data);
static void UpdateMonData(struct BattleDebugMenu *data);
static void ChangeHazardsValue(struct BattleDebugMenu *data);
static u32 GetHazardsValue(struct BattleDebugMenu *data);
static u16 *GetSideStatusValue(struct BattleDebugMenu *data, bool32 changeStatus, bool32 statusTrue);
static bool32 TryMoveDigit(struct BattleDebugModifyArrows *modArrows, bool32 moveUp);
static void SwitchToDebugView(u8 taskId);
@ -1424,6 +1437,10 @@ static void CreateSecondaryListMenu(struct BattleDebugMenu *data)
listTemplate.items = sVariousListItems;
itemsCount = ARRAY_COUNT(sVariousListItems);
break;
case LIST_ITEM_HAZARDS:
listTemplate.items = sHazardsListItems;
itemsCount = ARRAY_COUNT(sHazardsListItems);
break;
case LIST_ITEM_SIDE_STATUS:
listTemplate.items = sSideStatusListItems;
itemsCount = ARRAY_COUNT(sSideStatusListItems);
@ -1625,6 +1642,9 @@ static void UpdateBattlerValue(struct BattleDebugMenu *data)
case VAL_VOLATILE:
SetMonVolatile(data->battlerId, data->currentSecondaryListItemId, data->modifyArrows.currValue);
break;
case VAL_HAZARDS:
ChangeHazardsValue(data);
break;
case VAR_SIDE_STATUS:
*GetSideStatusValue(data, TRUE, data->modifyArrows.currValue != 0) = data->modifyArrows.currValue;
break;
@ -1702,6 +1722,83 @@ static void ValueToCharDigits(u8 *charDigits, u32 newValue, u8 maxDigits)
charDigits[i] = valueDigits[i] + CHAR_0;
}
static void ChangeHazardsValue(struct BattleDebugMenu *data)
{
u32 side = GetBattlerSide(data->battlerId);
switch (data->currentSecondaryListItemId)
{
case LIST_SIDE_SPIKES:
if (data->modifyArrows.currValue > 0)
{
if (gSideTimers[side].spikesAmount == 0)
PushHazardTypeToQueue(side, HAZARDS_SPIKES);
gSideTimers[side].spikesAmount = data->modifyArrows.currValue;
}
else if (data->modifyArrows.currValue == 0)
{
gSideTimers[side].spikesAmount = 0;
RemoveHazardFromField(side, HAZARDS_SPIKES);
}
break;
case LIST_SIDE_TOXIC_SPIKES:
if (data->modifyArrows.currValue > 0)
{
if (gSideTimers[side].toxicSpikesAmount == 0)
PushHazardTypeToQueue(side, HAZARDS_TOXIC_SPIKES);
gSideTimers[side].toxicSpikesAmount = data->modifyArrows.currValue;
}
else if (data->modifyArrows.currValue == 0)
{
gSideTimers[side].toxicSpikesAmount = 0;
RemoveHazardFromField(side, HAZARDS_TOXIC_SPIKES);
}
break;
case LIST_SIDE_STICKY_WEB:
if (data->modifyArrows.currValue > 0)
PushHazardTypeToQueue(side, HAZARDS_STICKY_WEB);
else if (data->modifyArrows.currValue == 0)
RemoveHazardFromField(side, HAZARDS_STICKY_WEB);
break;
case LIST_SIDE_STEALTH_ROCK:
if (data->modifyArrows.currValue > 0)
PushHazardTypeToQueue(side, HAZARDS_STEALTH_ROCK);
else if (data->modifyArrows.currValue == 0)
RemoveHazardFromField(side, HAZARDS_STEALTH_ROCK);
break;
case LIST_SIDE_STEELSURGE:
if (data->modifyArrows.currValue > 0)
PushHazardTypeToQueue(side, HAZARDS_STEELSURGE);
else if (data->modifyArrows.currValue == 0)
RemoveHazardFromField(side, HAZARDS_STEELSURGE);
break;
}
}
static u32 GetHazardsValue(struct BattleDebugMenu *data)
{
u32 hazardsLayers = 0;
switch (data->currentSecondaryListItemId)
{
case LIST_SIDE_SPIKES:
hazardsLayers = gSideTimers[GetBattlerSide(data->battlerId)].spikesAmount;
break;
case LIST_SIDE_TOXIC_SPIKES:
hazardsLayers = gSideTimers[GetBattlerSide(data->battlerId)].toxicSpikesAmount;
break;
case LIST_SIDE_STICKY_WEB:
hazardsLayers = IsHazardOnSide(GetBattlerSide(data->battlerId), HAZARDS_STICKY_WEB);
break;
case LIST_SIDE_STEALTH_ROCK:
hazardsLayers = IsHazardOnSide(GetBattlerSide(data->battlerId), HAZARDS_STEALTH_ROCK);
break;
case LIST_SIDE_STEELSURGE:
hazardsLayers = IsHazardOnSide(GetBattlerSide(data->battlerId), HAZARDS_STEELSURGE);
break;
}
return hazardsLayers;
}
static u16 *GetSideStatusValue(struct BattleDebugMenu *data, bool32 changeStatus, bool32 statusTrue)
{
struct SideTimer *sideTimer = &gSideTimers[GetBattlerSide(data->battlerId)];
@ -1726,26 +1823,6 @@ static u16 *GetSideStatusValue(struct BattleDebugMenu *data, bool32 changeStatus
*(u32 *)(data->modifyArrows.modifiedValPtr) &= ~SIDE_STATUS_LIGHTSCREEN;
}
return &sideTimer->lightscreenTimer;
case LIST_SIDE_STICKY_WEB:
if (changeStatus)
{
if (statusTrue)
*(u32 *)(data->modifyArrows.modifiedValPtr) |= SIDE_STATUS_STICKY_WEB;
else
*(u32 *)(data->modifyArrows.modifiedValPtr) &= ~SIDE_STATUS_STICKY_WEB;
sideTimer->stickyWebBattlerId = data->battlerId;
sideTimer->stickyWebBattlerSide = GetBattlerSide(data->battlerId);
}
return &sideTimer->stickyWebAmount;
case LIST_SIDE_SPIKES:
if (changeStatus)
{
if (statusTrue)
*(u32 *)(data->modifyArrows.modifiedValPtr) |= SIDE_STATUS_SPIKES;
else
*(u32 *)(data->modifyArrows.modifiedValPtr) &= ~SIDE_STATUS_SPIKES;
}
return &sideTimer->spikesAmount;
case LIST_SIDE_SAFEGUARD:
if (changeStatus)
{
@ -1791,33 +1868,6 @@ static u16 *GetSideStatusValue(struct BattleDebugMenu *data, bool32 changeStatus
*(u32 *)(data->modifyArrows.modifiedValPtr) &= ~SIDE_STATUS_LUCKY_CHANT;
}
return &sideTimer->luckyChantTimer;
case LIST_SIDE_TOXIC_SPIKES:
if (changeStatus)
{
if (statusTrue)
*(u32 *)(data->modifyArrows.modifiedValPtr) |= SIDE_STATUS_TOXIC_SPIKES;
else
*(u32 *)(data->modifyArrows.modifiedValPtr) &= ~SIDE_STATUS_TOXIC_SPIKES;
}
return &sideTimer->toxicSpikesAmount;
case LIST_SIDE_STEALTH_ROCK:
if (changeStatus)
{
if (statusTrue)
*(u32 *)(data->modifyArrows.modifiedValPtr) |= SIDE_STATUS_STEALTH_ROCK;
else
*(u32 *)(data->modifyArrows.modifiedValPtr) &= ~SIDE_STATUS_STEALTH_ROCK;
}
return &sideTimer->stealthRockAmount;
case LIST_SIDE_STEELSURGE:
if (changeStatus)
{
if (statusTrue)
*(u32 *)(data->modifyArrows.modifiedValPtr) |= SIDE_STATUS_STEELSURGE;
else
*(u32 *)(data->modifyArrows.modifiedValPtr) &= ~SIDE_STATUS_STEELSURGE;
}
return &sideTimer->steelsurgeAmount;
case LIST_SIDE_DAMAGE_NON_TYPES:
if (changeStatus)
{
@ -2034,16 +2084,29 @@ static void SetUpModifyArrows(struct BattleDebugMenu *data)
data->modifyArrows.maxValue = (1 << data->bitfield[data->currentSecondaryListItemId].bitsCount) - 1;
data->modifyArrows.maxDigits = MAX_DIGITS(data->modifyArrows.maxValue);
break;
case LIST_ITEM_HAZARDS:
data->modifyArrows.minValue = 0;
switch (data->currentSecondaryListItemId)
{
case LIST_SIDE_SPIKES:
data->modifyArrows.maxValue = 3;
break;
case LIST_SIDE_TOXIC_SPIKES:
data->modifyArrows.maxValue = 2;
break;
case LIST_SIDE_STICKY_WEB:
case LIST_SIDE_STEALTH_ROCK:
case LIST_SIDE_STEELSURGE:
data->modifyArrows.maxValue = 1;
break;
}
data->modifyArrows.maxDigits = 2;
data->modifyArrows.typeOfVal = VAL_HAZARDS;
data->modifyArrows.currValue = GetHazardsValue(data);
break;
case LIST_ITEM_SIDE_STATUS:
data->modifyArrows.minValue = 0;
if (data->currentSecondaryListItemId == LIST_SIDE_SPIKES)
data->modifyArrows.maxValue = 3;
else if (data->currentSecondaryListItemId == LIST_SIDE_STEALTH_ROCK || data->currentSecondaryListItemId == LIST_SIDE_STICKY_WEB)
data->modifyArrows.maxValue = 1;
else
data->modifyArrows.maxValue = 9;
data->modifyArrows.maxValue = 9;
data->modifyArrows.maxDigits = 2;
data->modifyArrows.modifiedValPtr = &gSideStatuses[GetBattlerSide(data->battlerId)];
data->modifyArrows.typeOfVal = VAR_SIDE_STATUS;

View File

@ -1400,6 +1400,24 @@ const u16 gDamageNonTypesDmgStringIds[] =
[B_MSG_HURT_BY_ROCKS_THROWN] = STRINGID_PKMNHURTBYROCKSTHROWN,
};
const u16 gDefogHazardsStringIds[] =
{
[HAZARDS_SPIKES] = STRINGID_SPIKESDISAPPEAREDFROMTEAM,
[HAZARDS_STICKY_WEB] = STRINGID_STICKYWEBDISAPPEAREDFROMTEAM,
[HAZARDS_TOXIC_SPIKES] = STRINGID_TOXICSPIKESDISAPPEAREDFROMTEAM,
[HAZARDS_STEALTH_ROCK] = STRINGID_STEALTHROCKDISAPPEAREDFROMTEAM,
[HAZARDS_STEELSURGE] = STRINGID_SHARPSTEELDISAPPEAREDFROMTEAM,
};
const u16 gSpinHazardsStringIds[] =
{
[HAZARDS_SPIKES] = STRINGID_PKMNBLEWAWAYSPIKES,
[HAZARDS_STICKY_WEB] = STRINGID_PKMNBLEWAWAYSTICKYWEB,
[HAZARDS_TOXIC_SPIKES] = STRINGID_PKMNBLEWAWAYTOXICSPIKES,
[HAZARDS_STEALTH_ROCK] = STRINGID_PKMNBLEWAWAYSTEALTHROCK,
[HAZARDS_STEELSURGE] = STRINGID_PKMNBLEWAWAYSHARPSTEEL,
};
const u8 gText_PkmnIsEvolving[] = _("What?\n{STR_VAR_1} is evolving!");
const u8 gText_CongratsPkmnEvolved[] = _("Congratulations! Your {STR_VAR_1}\nevolved into {STR_VAR_2}!{WAIT_SE}\p");
const u8 gText_PkmnStoppedEvolving[] = _("Huh? {STR_VAR_1}\nstopped evolving!\p");

View File

@ -3848,7 +3848,7 @@ void SetMoveEffect(u32 battler, u32 effectBattler, bool32 primary, bool32 certai
}
break;
case MOVE_EFFECT_STEALTH_ROCK:
if (!(gSideStatuses[GetBattlerSide(gEffectBattler)] & SIDE_STATUS_STEALTH_ROCK))
if (!IsHazardOnSide(GetBattlerSide(gEffectBattler), HAZARDS_STEALTH_ROCK))
{
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_POINTEDSTONESFLOAT;
BattleScriptPush(gBattlescriptCurrInstr + 1);
@ -4284,7 +4284,7 @@ void SetMoveEffect(u32 battler, u32 effectBattler, bool32 primary, bool32 certai
break;
}
case MOVE_EFFECT_STEELSURGE:
if (!(gSideStatuses[GetBattlerSide(gBattlerTarget)] & SIDE_STATUS_STEELSURGE))
if (!IsHazardOnSide(GetBattlerSide(gBattlerTarget), HAZARDS_STEELSURGE))
{
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SHARPSTEELFLOATS;
BattleScriptPush(gBattlescriptCurrInstr + 1);
@ -4293,8 +4293,8 @@ void SetMoveEffect(u32 battler, u32 effectBattler, bool32 primary, bool32 certai
break;
case MOVE_EFFECT_DEFOG:
if (gSideStatuses[GetBattlerSide(gBattlerTarget)] & SIDE_STATUS_SCREEN_ANY
|| gSideStatuses[GetBattlerSide(gBattlerTarget)] & SIDE_STATUS_HAZARDS_ANY
|| gSideStatuses[GetBattlerSide(gBattlerAttacker)] & SIDE_STATUS_HAZARDS_ANY
|| AreAnyHazardsOnSide(GetBattlerSide(gBattlerTarget))
|| AreAnyHazardsOnSide(GetBattlerSide(gBattlerAttacker))
|| gFieldStatuses & STATUS_FIELD_TERRAIN_ANY)
{
BattleScriptPush(gBattlescriptCurrInstr + 1);
@ -8026,23 +8026,6 @@ static void Cmd_switchhandleorder(void)
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void SetDmgHazardsBattlescript(u8 battler, u8 multistringId)
{
gBattleMons[battler].status2 &= ~STATUS2_DESTINY_BOND;
gHitMarker &= ~HITMARKER_DESTINYBOND;
gBattleScripting.battler = battler;
gBattleCommunication[MULTISTRING_CHOOSER] = multistringId;
if (gBattlescriptCurrInstr[1] == BS_TARGET)
BattleScriptCall(BattleScript_DmgHazardsOnTarget);
else if (gBattlescriptCurrInstr[1] == BS_ATTACKER)
BattleScriptCall(BattleScript_DmgHazardsOnAttacker);
else if (gBattlescriptCurrInstr[1] == BS_SCRIPTING)
BattleScriptCall(BattleScript_DmgHazardsOnBattlerScripting);
else
BattleScriptCall(BattleScript_DmgHazardsOnFaintedBattler);
}
bool32 DoSwitchInAbilities(u32 battler)
{
return (TryPrimalReversion(battler)
@ -8062,9 +8045,106 @@ static void UpdateSentMonFlags(u32 battler)
gBattleStruct->appearedInBattle |= 1u << gBattlerPartyIndexes[battler];
}
static void SetDmgHazardsBattlescript(u8 battler, u8 multistringId)
{
gBattleMons[battler].status2 &= ~STATUS2_DESTINY_BOND;
gHitMarker &= ~HITMARKER_DESTINYBOND;
gBattleScripting.battler = battler;
gBattleCommunication[MULTISTRING_CHOOSER] = multistringId;
if (gBattlescriptCurrInstr[1] == BS_TARGET)
BattleScriptCall(BattleScript_DmgHazardsOnTarget);
else if (gBattlescriptCurrInstr[1] == BS_ATTACKER)
BattleScriptCall(BattleScript_DmgHazardsOnAttacker);
else if (gBattlescriptCurrInstr[1] == BS_SCRIPTING)
BattleScriptCall(BattleScript_DmgHazardsOnBattlerScripting);
else
BattleScriptCall(BattleScript_DmgHazardsOnFaintedBattler);
}
void TryHazardsOnSwitchIn(u32 battler, u32 side, enum Hazards hazardType)
{
switch (hazardType)
{
case HAZARDS_NONE:
break;
case HAZARDS_SPIKES:
if (GetBattlerAbility(battler) != ABILITY_MAGIC_GUARD
&& IsBattlerAffectedByHazards(battler, FALSE)
&& IsBattlerGrounded(battler))
{
u8 spikesDmg = (5 - gSideTimers[side].spikesAmount) * 2;
gBattleStruct->moveDamage[battler] = GetNonDynamaxMaxHP(battler) / (spikesDmg);
if (gBattleStruct->moveDamage[battler] == 0)
gBattleStruct->moveDamage[battler] = 1;
SetDmgHazardsBattlescript(battler, B_MSG_PKMNHURTBYSPIKES);
}
break;
case HAZARDS_STICKY_WEB:
if (IsBattlerAffectedByHazards(battler, FALSE) && IsBattlerGrounded(battler))
{
gBattleScripting.battler = battler;
SET_STATCHANGER(STAT_SPEED, 1, TRUE);
BattleScriptCall(BattleScript_StickyWebOnSwitchIn);
}
break;
case HAZARDS_TOXIC_SPIKES:
if (!IsBattlerGrounded(battler))
break;
if (IS_BATTLER_OF_TYPE(battler, TYPE_POISON)) // Absorb the toxic spikes.
{
gBattleStruct->hazardsCounter--; // reduce counter so the next hazard can be applied
gSideTimers[GetBattlerSide(battler)].toxicSpikesAmount = 0;
RemoveHazardFromField(side, HAZARDS_TOXIC_SPIKES);
gBattleScripting.battler = battler;
BattleScriptCall(BattleScript_ToxicSpikesAbsorbed);
}
else if (IsBattlerAffectedByHazards(battler, TRUE)
&& CanBePoisoned(battler, battler, GetBattlerAbility(battler), GetBattlerAbility(battler)))
{
gBattleScripting.battler = battler;
BattleScriptPushCursor();
if (gSideTimers[GetBattlerSide(battler)].toxicSpikesAmount >= 2)
{
gBattlescriptCurrInstr = BattleScript_ToxicSpikesBadlyPoisoned;
gBattleMons[battler].status1 |= STATUS1_TOXIC_POISON;
}
else
{
gBattlescriptCurrInstr = BattleScript_ToxicSpikesPoisoned;
gBattleMons[battler].status1 |= STATUS1_POISON;
}
BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[battler].status1), &gBattleMons[battler].status1);
MarkBattlerForControllerExec(battler);
}
break;
case HAZARDS_STEALTH_ROCK:
if (IsBattlerAffectedByHazards(battler, FALSE) && GetBattlerAbility(battler) != ABILITY_MAGIC_GUARD)
{
gBattleStruct->moveDamage[battler] = GetStealthHazardDamage(TYPE_SIDE_HAZARD_POINTED_STONES, battler);
if (gBattleStruct->moveDamage[battler] != 0)
SetDmgHazardsBattlescript(battler, B_MSG_STEALTHROCKDMG);
}
break;
case HAZARDS_STEELSURGE:
if (IsBattlerAffectedByHazards(battler, FALSE) && GetBattlerAbility(battler) != ABILITY_MAGIC_GUARD)
{
gBattleStruct->moveDamage[battler] = GetStealthHazardDamage(TYPE_SIDE_HAZARD_SHARP_STEEL, battler);
if (gBattleStruct->moveDamage[battler] != 0)
SetDmgHazardsBattlescript(battler, B_MSG_SHARPSTEELDMG);
}
break;
case HAZARDS_MAX_COUNT:
break;
}
}
static bool32 DoSwitchInEffectsForBattler(u32 battler)
{
u32 i = 0;
u32 side = GetBattlerSide(battler);
// Neutralizing Gas announces itself before hazards
if (gBattleMons[battler].ability == ABILITY_NEUTRALIZING_GAS && gSpecialStatuses[battler].announceNeutralizingGas == 0)
{
@ -8091,85 +8171,17 @@ static bool32 DoSwitchInEffectsForBattler(u32 battler)
gBattleStruct->battlerState[battler].storedLunarDance = FALSE;
}
}
else if (!(gDisableStructs[battler].spikesDone)
&& (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SPIKES)
&& GetBattlerAbility(battler) != ABILITY_MAGIC_GUARD
&& IsBattlerAffectedByHazards(battler, FALSE)
&& IsBattlerGrounded(battler))
else if (!gDisableStructs[battler].hazardsDone)
{
u8 spikesDmg = (5 - gSideTimers[GetBattlerSide(battler)].spikesAmount) * 2;
gBattleStruct->moveDamage[battler] = GetNonDynamaxMaxHP(battler) / (spikesDmg);
if (gBattleStruct->moveDamage[battler] == 0)
gBattleStruct->moveDamage[battler] = 1;
gDisableStructs[battler].spikesDone = TRUE;
SetDmgHazardsBattlescript(battler, B_MSG_PKMNHURTBYSPIKES);
}
else if (!(gDisableStructs[battler].stealthRockDone)
&& (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_STEALTH_ROCK)
&& IsBattlerAffectedByHazards(battler, FALSE)
&& GetBattlerAbility(battler) != ABILITY_MAGIC_GUARD)
{
gDisableStructs[battler].stealthRockDone = TRUE;
gBattleStruct->moveDamage[battler] = GetStealthHazardDamage(TYPE_SIDE_HAZARD_POINTED_STONES, battler);
if (gBattleStruct->moveDamage[battler] != 0)
SetDmgHazardsBattlescript(battler, B_MSG_STEALTHROCKDMG);
}
else if (!(gDisableStructs[battler].toxicSpikesDone)
&& (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_TOXIC_SPIKES)
&& IsBattlerGrounded(battler))
{
gDisableStructs[battler].toxicSpikesDone = TRUE;
if (IS_BATTLER_OF_TYPE(battler, TYPE_POISON)) // Absorb the toxic spikes.
TryHazardsOnSwitchIn(battler, side, gBattleStruct->hazardsQueue[side][gBattleStruct->hazardsCounter]);
gBattleStruct->hazardsCounter++;
// Done once we reach the first element without any hazard type or the array is full
if (gBattleStruct->hazardsQueue[side][gBattleStruct->hazardsCounter] == HAZARDS_NONE
|| gBattleStruct->hazardsCounter == HAZARDS_MAX_COUNT)
{
gSideStatuses[GetBattlerSide(battler)] &= ~SIDE_STATUS_TOXIC_SPIKES;
gSideTimers[GetBattlerSide(battler)].toxicSpikesAmount = 0;
gBattleScripting.battler = battler;
BattleScriptCall(BattleScript_ToxicSpikesAbsorbed);
gDisableStructs[battler].hazardsDone = TRUE;
gBattleStruct->hazardsCounter = 0;
}
else if (IsBattlerAffectedByHazards(battler, TRUE))
{
if (CanBePoisoned(gBattlerAttacker, battler, GetBattlerAbility(gBattlerAttacker), GetBattlerAbility(battler)))
{
gBattleScripting.battler = battler;
BattleScriptPushCursor();
if (gSideTimers[GetBattlerSide(battler)].toxicSpikesAmount >= 2)
{
gBattlescriptCurrInstr = BattleScript_ToxicSpikesBadlyPoisoned;
gBattleMons[battler].status1 |= STATUS1_TOXIC_POISON;
}
else
{
gBattlescriptCurrInstr = BattleScript_ToxicSpikesPoisoned;
gBattleMons[battler].status1 |= STATUS1_POISON;
}
BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[battler].status1), &gBattleMons[battler].status1);
MarkBattlerForControllerExec(battler);
}
}
}
else if (!(gDisableStructs[battler].stickyWebDone)
&& (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_STICKY_WEB)
&& IsBattlerAffectedByHazards(battler, FALSE)
&& IsBattlerGrounded(battler))
{
gDisableStructs[battler].stickyWebDone = TRUE;
gBattleScripting.battler = battler;
SET_STATCHANGER(STAT_SPEED, 1, TRUE);
BattleScriptCall(BattleScript_StickyWebOnSwitchIn);
}
else if (!(gDisableStructs[battler].steelSurgeDone)
&& (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_STEELSURGE)
&& IsBattlerAffectedByHazards(battler, FALSE)
&& GetBattlerAbility(battler) != ABILITY_MAGIC_GUARD)
{
gDisableStructs[battler].steelSurgeDone = TRUE;
gBattleStruct->moveDamage[battler] = GetStealthHazardDamage(TYPE_SIDE_HAZARD_SHARP_STEEL, battler);
if (gBattleStruct->moveDamage[battler] != 0)
SetDmgHazardsBattlescript(battler, B_MSG_SHARPSTEELDMG);
}
else if (gBattleMons[battler].hp != gBattleMons[battler].maxHP && gBattleStruct->zmove.healReplacement)
{
@ -8220,12 +8232,6 @@ static bool32 DoSwitchInEffectsForBattler(u32 battler)
return TRUE;
}
gDisableStructs[battler].stickyWebDone = FALSE;
gDisableStructs[battler].spikesDone = FALSE;
gDisableStructs[battler].toxicSpikesDone = FALSE;
gDisableStructs[battler].stealthRockDone = FALSE;
gDisableStructs[battler].steelSurgeDone = FALSE;
for (i = 0; i < gBattlersCount; i++)
{
if (gBattlerByTurnOrder[i] == battler)
@ -8234,6 +8240,7 @@ static bool32 DoSwitchInEffectsForBattler(u32 battler)
gBattleStruct->hpOnSwitchout[GetBattlerSide(i)] = gBattleMons[i].hp;
}
gDisableStructs[battler].hazardsDone = FALSE;
gBattleStruct->battlerState[battler].forcedSwitch = FALSE;
return FALSE;
}
@ -9567,6 +9574,32 @@ static void RemoveAllTerrains(void)
} \
}
static bool32 DefogClearHazards(u32 saveBattler, u32 side, bool32 clear)
{
if (!AreAnyHazardsOnSide(side))
return FALSE;
for (u32 hazardType = HAZARDS_NONE + 1; hazardType < HAZARDS_MAX_COUNT; hazardType++)
{
bool32 checkOrClear = clear ? IsHazardOnSideAndClear(side, hazardType) : IsHazardOnSide(side, hazardType);
if (checkOrClear)
{
if (clear)
{
gBattleStruct->numHazards[side]--;
gBattleCommunication[MULTISTRING_CHOOSER] = hazardType;
BattleScriptCall(BattleScript_DefogClearHazards);
}
else
{
gBattlerAttacker = saveBattler;
}
return TRUE;
}
}
return FALSE;
}
static bool32 TryDefogClear(u32 battlerAtk, bool32 clear)
{
s32 i;
@ -9589,11 +9622,8 @@ static bool32 TryDefogClear(u32 battlerAtk, bool32 clear)
if (B_DEFOG_EFFECT_CLEARING >= GEN_6)
{
gBattlerAttacker = i; // For correct battle string. Ally's / Foe's
DEFOG_CLEAR(SIDE_STATUS_SPIKES, spikesAmount, BattleScript_SpikesDefog, 0);
DEFOG_CLEAR(SIDE_STATUS_STEALTH_ROCK, stealthRockAmount, BattleScript_StealthRockDefog, 0);
DEFOG_CLEAR(SIDE_STATUS_TOXIC_SPIKES, toxicSpikesAmount, BattleScript_ToxicSpikesDefog, 0);
DEFOG_CLEAR(SIDE_STATUS_STICKY_WEB, stickyWebAmount, BattleScript_StickyWebDefog, 0);
DEFOG_CLEAR(SIDE_STATUS_STEELSURGE, steelsurgeAmount, BattleScript_SteelsurgeDefog, 0);
if (DefogClearHazards(saveBattler, i, clear))
return TRUE;
}
if (gBattleWeather & B_WEATHER_FOG)
{
@ -9621,14 +9651,9 @@ static bool32 TryTidyUpClear(u32 battlerAtk, bool32 clear)
for (i = 0; i < NUM_BATTLE_SIDES; i++)
{
struct SideTimer *sideTimer = &gSideTimers[i];
u32 *sideStatuses = &gSideStatuses[i];
gBattlerAttacker = i; // For correct battle string. Ally's / Foe's
DEFOG_CLEAR(SIDE_STATUS_SPIKES, spikesAmount, BattleScript_SpikesDefog, 0);
DEFOG_CLEAR(SIDE_STATUS_STEALTH_ROCK, stealthRockAmount, BattleScript_StealthRockDefog, 0);
DEFOG_CLEAR(SIDE_STATUS_TOXIC_SPIKES, toxicSpikesAmount, BattleScript_ToxicSpikesDefog, 0);
DEFOG_CLEAR(SIDE_STATUS_STICKY_WEB, stickyWebAmount, BattleScript_StickyWebDefog, 0);
if (DefogClearHazards(saveBattler, i, clear))
return TRUE;
}
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
@ -9780,17 +9805,23 @@ void BS_CourtChangeSwapSideStatuses(void)
COURTCHANGE_SWAP(SIDE_STATUS_TAILWIND, tailwindTimer, temp);
// Lucky Chant doesn't exist in gen 8, but seems like it should be affected by Court Change
COURTCHANGE_SWAP(SIDE_STATUS_LUCKY_CHANT, luckyChantTimer, temp);
COURTCHANGE_SWAP(SIDE_STATUS_SPIKES, spikesAmount, temp);
COURTCHANGE_SWAP(SIDE_STATUS_STEALTH_ROCK, stealthRockAmount, temp);
COURTCHANGE_SWAP(SIDE_STATUS_TOXIC_SPIKES, toxicSpikesAmount, temp);
COURTCHANGE_SWAP(SIDE_STATUS_STICKY_WEB, stickyWebAmount, temp);
COURTCHANGE_SWAP(SIDE_STATUS_STEELSURGE, steelsurgeAmount, temp);
COURTCHANGE_SWAP(SIDE_STATUS_DAMAGE_NON_TYPES, damageNonTypesTimer, temp);
// Track Pledge effect side
COURTCHANGE_SWAP(SIDE_STATUS_RAINBOW, rainbowTimer, temp);
COURTCHANGE_SWAP(SIDE_STATUS_SEA_OF_FIRE, seaOfFireTimer, temp);
COURTCHANGE_SWAP(SIDE_STATUS_SWAMP, swampTimer, temp);
// Hazards
u32 tempQueue[HAZARDS_MAX_COUNT] = {HAZARDS_NONE};
for (u32 i = 0; i < HAZARDS_MAX_COUNT; i++)
tempQueue[i] = gBattleStruct->hazardsQueue[B_SIDE_PLAYER][i];
for (u32 i = 0; i < HAZARDS_MAX_COUNT; i++)
gBattleStruct->hazardsQueue[B_SIDE_PLAYER][i] = gBattleStruct->hazardsQueue[B_SIDE_OPPONENT][i];
for (u32 i = 0; i < HAZARDS_MAX_COUNT; i++)
gBattleStruct->hazardsQueue[B_SIDE_OPPONENT][i] = tempQueue[i];
SWAP(sideTimerPlayer->spikesAmount, sideTimerOpp->spikesAmount, temp);
SWAP(sideTimerPlayer->toxicSpikesAmount, sideTimerOpp->toxicSpikesAmount, temp);
// Change battler IDs of swapped effects. Needed for the correct string when they expire
UPDATE_COURTCHANGED_BATTLER(stickyWebBattlerId);
@ -13950,7 +13981,8 @@ static void Cmd_trysetspikes(void)
}
else
{
gSideStatuses[targetSide] |= SIDE_STATUS_SPIKES;
if (gSideTimers[targetSide].spikesAmount == 0) // Add only once to the queue
PushHazardTypeToQueue(targetSide, HAZARDS_SPIKES);
gSideTimers[targetSide].spikesAmount++;
gBattlescriptCurrInstr = cmd->nextInstr;
}
@ -14313,35 +14345,18 @@ static void Cmd_rapidspinfree(void)
gStatuses3[gBattlerAttacker] &= ~STATUS3_LEECHSEED_BATTLER;
BattleScriptCall(BattleScript_LeechSeedFree);
}
else if (gSideStatuses[atkSide] & SIDE_STATUS_SPIKES)
else if (AreAnyHazardsOnSide(atkSide))
{
gSideStatuses[atkSide] &= ~SIDE_STATUS_SPIKES;
gSideTimers[atkSide].spikesAmount = 0;
BattleScriptCall(BattleScript_SpikesFree);
}
else if (gSideStatuses[atkSide] & SIDE_STATUS_TOXIC_SPIKES)
{
gSideStatuses[atkSide] &= ~SIDE_STATUS_TOXIC_SPIKES;
gSideTimers[atkSide].toxicSpikesAmount = 0;
BattleScriptCall(BattleScript_ToxicSpikesFree);
}
else if (gSideStatuses[atkSide] & SIDE_STATUS_STICKY_WEB)
{
gSideStatuses[atkSide] &= ~SIDE_STATUS_STICKY_WEB;
gSideTimers[atkSide].stickyWebAmount = 0;
BattleScriptCall(BattleScript_StickyWebFree);
}
else if (gSideStatuses[atkSide] & SIDE_STATUS_STEALTH_ROCK)
{
gSideStatuses[atkSide] &= ~SIDE_STATUS_STEALTH_ROCK;
gSideTimers[atkSide].stealthRockAmount = 0;
BattleScriptCall(BattleScript_StealthRockFree);
}
else if (gSideStatuses[atkSide] & SIDE_STATUS_STEELSURGE)
{
gSideStatuses[atkSide] &= ~SIDE_STATUS_STEELSURGE;
gSideTimers[atkSide].steelsurgeAmount = 0;
BattleScriptCall(BattleScript_SteelsurgeFree);
for (u32 hazardType = HAZARDS_NONE + 1; hazardType < HAZARDS_MAX_COUNT; hazardType++)
{
if (IsHazardOnSideAndClear(atkSide, hazardType))
{
gBattleStruct->numHazards[atkSide]--;
gBattleCommunication[MULTISTRING_CHOOSER] = hazardType;
BattleScriptCall(BattleScript_SpinHazardsAway);
return;
}
}
}
else
{
@ -14432,16 +14447,15 @@ static void Cmd_setstickyweb(void)
u8 targetSide = GetBattlerSide(gBattlerTarget);
if (gSideStatuses[targetSide] & SIDE_STATUS_STICKY_WEB)
if (IsHazardOnSide(targetSide, HAZARDS_STICKY_WEB))
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
gSideStatuses[targetSide] |= SIDE_STATUS_STICKY_WEB;
PushHazardTypeToQueue(targetSide, HAZARDS_STICKY_WEB);
gSideTimers[targetSide].stickyWebBattlerId = gBattlerAttacker; // For Mirror Armor
gSideTimers[targetSide].stickyWebBattlerSide = GetBattlerSide(gBattlerAttacker); // For Court Change/Defiant - set this to the user's side
gSideTimers[targetSide].stickyWebAmount = 1;
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
@ -14967,8 +14981,9 @@ static void Cmd_settoxicspikes(void)
}
else
{
if (gSideTimers[targetSide].toxicSpikesAmount == 0)
PushHazardTypeToQueue(targetSide, HAZARDS_TOXIC_SPIKES);
gSideTimers[targetSide].toxicSpikesAmount++;
gSideStatuses[targetSide] |= SIDE_STATUS_TOXIC_SPIKES;
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
@ -15158,14 +15173,13 @@ static void Cmd_setstealthrock(void)
CMD_ARGS(const u8 *failInstr);
u8 targetSide = GetBattlerSide(gBattlerTarget);
if (gSideStatuses[targetSide] & SIDE_STATUS_STEALTH_ROCK)
if (IsHazardOnSide(targetSide, HAZARDS_STEALTH_ROCK))
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
gSideStatuses[targetSide] |= SIDE_STATUS_STEALTH_ROCK;
gSideTimers[targetSide].stealthRockAmount = 1;
PushHazardTypeToQueue(targetSide, HAZARDS_STEALTH_ROCK);
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
@ -18544,14 +18558,13 @@ void BS_SetSteelsurge(void)
{
NATIVE_ARGS(const u8 *failInstr);
u8 targetSide = GetBattlerSide(gBattlerTarget);
if (gSideStatuses[targetSide] & SIDE_STATUS_STEELSURGE)
if (IsHazardOnSide(targetSide, HAZARDS_STEELSURGE))
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else
{
gSideStatuses[targetSide] |= SIDE_STATUS_STEELSURGE;
gSideTimers[targetSide].steelsurgeAmount = 1;
PushHazardTypeToQueue(targetSide, HAZARDS_STEELSURGE);
gBattlescriptCurrInstr = cmd->nextInstr;
}
}

View File

@ -11445,3 +11445,79 @@ void SetMonVolatile(u32 battler, enum Volatile _volatile, u32 newValue)
return;
}
}
// Hazards are added to a queue and applied based in order (FIFO)
void PushHazardTypeToQueue(u32 side, enum Hazards hazardType)
{
if (!IsHazardOnSide(side, hazardType)) // Failsafe
gBattleStruct->hazardsQueue[side][gBattleStruct->numHazards[side]++] = hazardType;
}
bool32 IsHazardOnSide(u32 side, enum Hazards hazardType)
{
for (u32 i = 0; i < HAZARDS_MAX_COUNT; i++)
{
if (gBattleStruct->hazardsQueue[side][i] == hazardType)
return TRUE;
}
return FALSE;
}
bool32 AreAnyHazardsOnSide(u32 side)
{
return gBattleStruct->numHazards[side] > 0;
}
bool32 IsHazardOnSideAndClear(u32 side, enum Hazards hazardType)
{
for (u32 i = 0; i < HAZARDS_MAX_COUNT; i++)
{
if (gBattleStruct->hazardsQueue[side][i] == hazardType)
{
gBattleStruct->hazardsQueue[side][i] = HAZARDS_NONE;
if (hazardType == HAZARDS_SPIKES)
gSideTimers[side].spikesAmount = 0;
else if (hazardType == HAZARDS_TOXIC_SPIKES)
gSideTimers[side].toxicSpikesAmount = 0;
return TRUE;
}
}
return FALSE;
}
void RemoveAllHazardsFromField(u32 side)
{
gSideTimers[side].spikesAmount = 0;
gSideTimers[side].toxicSpikesAmount = 0;
gBattleStruct->numHazards[side] = 0;
for (u32 i = 0; i < HAZARDS_MAX_COUNT; i++)
gBattleStruct->hazardsQueue[side][i] = HAZARDS_NONE;
}
void RemoveHazardFromField(u32 side, enum Hazards hazardType)
{
u32 i;
for (i = 0; i < HAZARDS_MAX_COUNT; i++)
{
if (gBattleStruct->hazardsQueue[side][i] == hazardType)
{
gBattleStruct->hazardsQueue[side][i] = HAZARDS_NONE;
gBattleStruct->numHazards[side]--;
if (hazardType == HAZARDS_SPIKES)
gSideTimers[side].spikesAmount = 0;
else if (hazardType == HAZARDS_TOXIC_SPIKES)
gSideTimers[side].toxicSpikesAmount = 0;
break;
}
}
while (i < HAZARDS_MAX_COUNT)
{
if (i+1 == HAZARDS_MAX_COUNT)
{
gBattleStruct->hazardsQueue[side][i] = HAZARDS_NONE;
break;
}
gBattleStruct->hazardsQueue[side][i] = gBattleStruct->hazardsQueue[side][i+1];
i++;
}
}

40
test/battle/hazards.c Normal file
View File

@ -0,0 +1,40 @@
#include "global.h"
#include "test/battle.h"
SINGLE_BATTLE_TEST("Hazards are applied based on order of set up")
{
GIVEN {
PLAYER(SPECIES_WYNAUT);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_GRIMER);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_TOXIC_SPIKES); }
TURN { MOVE(opponent, MOVE_STEALTH_ROCK); }
TURN { MOVE(opponent, MOVE_STICKY_WEB); }
TURN { MOVE(opponent, MOVE_SPIKES); }
TURN { MOVE(opponent, MOVE_STEALTH_ROCK); SWITCH(player, 1); }
TURN { MOVE(opponent, MOVE_STEALTH_ROCK); SWITCH(player, 2); }
TURN { MOVE(opponent, MOVE_STEALTH_ROCK); SWITCH(player, 0); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_SPIKES, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_STEALTH_ROCK, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPIKES, opponent);
MESSAGE("Wobbuffet was poisoned!");
MESSAGE("Pointed stones dug into Wobbuffet!");
MESSAGE("Wobbuffet was caught in a sticky web!");
MESSAGE("Wobbuffet was hurt by the spikes!");
MESSAGE("The poison spikes disappeared from the ground around your team!");
MESSAGE("Pointed stones dug into Wynaut!");
MESSAGE("Wynaut was caught in a sticky web!");
MESSAGE("Wynaut was hurt by the spikes!");
} THEN {
EXPECT_EQ(gBattleStruct->hazardsQueue[0][0], HAZARDS_STEALTH_ROCK);
EXPECT_EQ(gBattleStruct->hazardsQueue[0][1], HAZARDS_STICKY_WEB);
EXPECT_EQ(gBattleStruct->hazardsQueue[0][2], HAZARDS_SPIKES);
EXPECT_EQ(gBattleStruct->hazardsQueue[0][3], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[0][4], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[0][5], HAZARDS_NONE);
}
}

View File

@ -29,16 +29,16 @@ DOUBLE_BATTLE_TEST("Court Change swaps entry hazards used by the opponent")
MESSAGE("Wynaut swapped the battle effects affecting each side of the field!");
SEND_IN_MESSAGE("Wynaut");
NONE_OF {
MESSAGE("Wynaut was hurt by the spikes!");
MESSAGE("Pointed stones dug into Wynaut!");
MESSAGE("Wynaut was poisoned!");
MESSAGE("Wynaut was caught in a sticky web!");
MESSAGE("Pointed stones dug into Wynaut!");
MESSAGE("Wynaut was hurt by the spikes!");
MESSAGE("Wynaut was poisoned!");
}
MESSAGE("2 sent out Wobbuffet!");
MESSAGE("The opposing Wobbuffet was hurt by the spikes!");
MESSAGE("Pointed stones dug into the opposing Wobbuffet!");
MESSAGE("The opposing Wobbuffet was poisoned!");
MESSAGE("The opposing Wobbuffet was caught in a sticky web!");
MESSAGE("Pointed stones dug into the opposing Wobbuffet!");
MESSAGE("The opposing Wobbuffet was hurt by the spikes!");
MESSAGE("The opposing Wobbuffet was poisoned!");
}
}
@ -64,16 +64,16 @@ DOUBLE_BATTLE_TEST("Court Change swaps entry hazards used by the player")
MESSAGE("The opposing Wynaut used Court Change!");
MESSAGE("The opposing Wynaut swapped the battle effects affecting each side of the field!");
SEND_IN_MESSAGE("Wobbuffet");
MESSAGE("Wobbuffet was hurt by the spikes!");
MESSAGE("Pointed stones dug into Wobbuffet!");
MESSAGE("Wobbuffet was poisoned!");
MESSAGE("Wobbuffet was caught in a sticky web!");
MESSAGE("Pointed stones dug into Wobbuffet!");
MESSAGE("Wobbuffet was hurt by the spikes!");
MESSAGE("Wobbuffet was poisoned!");
MESSAGE("2 sent out Wynaut!");
NONE_OF {
MESSAGE("The opposing Wynaut was hurt by the spikes!");
MESSAGE("Pointed stones dug into the opposing Wynaut!");
MESSAGE("The opposing Wynaut was poisoned!");
MESSAGE("The opposing Wynaut was caught in a sticky web!");
MESSAGE("Pointed stones dug into the opposing Wynaut!");
MESSAGE("The opposing Wynaut was hurt by the spikes!");
MESSAGE("The opposing Wynaut was poisoned!");
}
}
}

View File

@ -154,8 +154,8 @@ DOUBLE_BATTLE_TEST("Defog removes Stealth Rock and Sticky Web from user's side (
ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponentRight);
ANIMATION(ANIM_TYPE_MOVE, move, playerLeft);
if (move == MOVE_DEFOG && B_DEFOG_EFFECT_CLEARING >= GEN_6) {
MESSAGE("The pointed stones disappeared from around your team!");
MESSAGE("The sticky web has disappeared from the ground around your team!");
MESSAGE("The pointed stones disappeared from around your team!");
}
// Switch happens
SWITCH_OUT_MESSAGE("Wobbuffet");
@ -365,16 +365,30 @@ DOUBLE_BATTLE_TEST("Defog removes everything it can")
if (B_DEFOG_EFFECT_CLEARING >= GEN_6) {
MESSAGE("The spikes disappeared from the ground around your team!");
MESSAGE("The pointed stones disappeared from around your team!");
MESSAGE("The poison spikes disappeared from the ground around your team!");
MESSAGE("The sticky web has disappeared from the ground around your team!");
MESSAGE("The poison spikes disappeared from the ground around your team!");
MESSAGE("The pointed stones disappeared from around your team!");
// Opponent side
MESSAGE("The spikes disappeared from the ground around the opposing team!");
MESSAGE("The pointed stones disappeared from around the opposing team!");
MESSAGE("The poison spikes disappeared from the ground around the opposing team!");
MESSAGE("The sticky web has disappeared from the ground around the opposing team!");
MESSAGE("The poison spikes disappeared from the ground around the opposing team!");
MESSAGE("The pointed stones disappeared from around the opposing team!");
}
} THEN {
EXPECT_EQ(gBattleStruct->hazardsQueue[0][0], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[0][1], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[0][2], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[0][3], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[0][4], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[0][5], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[1][0], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[1][1], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[1][2], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[1][3], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[1][4], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[1][5], HAZARDS_NONE);
}
}

View File

@ -159,8 +159,8 @@ SINGLE_BATTLE_TEST("Dragon Tail switches target out and incoming mon has Levitat
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, player);
HP_BAR(opponent);
MESSAGE("The opposing Weezing was dragged out!");
HP_BAR(opponent);
NOT STATUS_ICON(opponent, poison: TRUE);
MESSAGE("The poison spikes disappeared from the ground around the opposing team!");
NOT STATUS_ICON(opponent, poison: TRUE);
HP_BAR(opponent);
}
}

View File

@ -63,3 +63,32 @@ SINGLE_BATTLE_TEST("Rapid Spin: Mortal Spin blows away Wrap, hazards and poisons
MESSAGE("Wobbuffet blew away Stealth Rock!");
}
}
SINGLE_BATTLE_TEST("Rapid Spin blows away all hazards")
{
GIVEN {
ASSUME(GetMoveEffect(MOVE_RAPID_SPIN) == EFFECT_RAPID_SPIN);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_STEALTH_ROCK); }
TURN { MOVE(opponent, MOVE_STICKY_WEB); }
TURN { MOVE(opponent, MOVE_TOXIC_SPIKES); }
TURN { MOVE(opponent, MOVE_SPIKES); MOVE(player, MOVE_RAPID_SPIN); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_STEALTH_ROCK, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_RAPID_SPIN, player);
MESSAGE("Wobbuffet blew away Spikes!");
MESSAGE("Wobbuffet blew away Sticky Web!");
MESSAGE("Wobbuffet blew away Toxic Spikes!");
MESSAGE("Wobbuffet blew away Stealth Rock!");
} THEN {
EXPECT_EQ(gBattleStruct->hazardsQueue[0][0], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[0][1], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[0][2], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[0][3], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[0][4], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[0][5], HAZARDS_NONE);
}
}

View File

@ -133,3 +133,30 @@ SINGLE_BATTLE_TEST("Spikes do not damage airborne Pokemon")
}
}
}
SINGLE_BATTLE_TEST("Toxic Spikes: Only three layers can be set up")
{
GIVEN {
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_SPIKES); }
TURN { MOVE(opponent, MOVE_SPIKES); }
TURN { MOVE(opponent, MOVE_SPIKES); }
TURN { MOVE(opponent, MOVE_SPIKES); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPIKES, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPIKES, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPIKES, opponent);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SPIKES, opponent);
} THEN {
EXPECT_EQ(gBattleStruct->hazardsQueue[0][0], HAZARDS_SPIKES);
EXPECT_EQ(gBattleStruct->hazardsQueue[0][1], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[0][2], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[0][3], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[0][4], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[0][5], HAZARDS_NONE);
u32 spikesAmount = gSideTimers[0].spikesAmount;
EXPECT_EQ(spikesAmount, 3);
}
}

View File

@ -43,9 +43,9 @@ SINGLE_BATTLE_TEST("Tidy Up removes hazards and raises Stats")
MESSAGE("Wobbuffet used Tidy Up!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_TIDY_UP, player);
MESSAGE("The spikes disappeared from the ground around your team!");
MESSAGE("The pointed stones disappeared from around your team!");
MESSAGE("The poison spikes disappeared from the ground around your team!");
MESSAGE("The sticky web has disappeared from the ground around your team!");
MESSAGE("The poison spikes disappeared from the ground around your team!");
MESSAGE("The pointed stones disappeared from around your team!");
MESSAGE("Tidying up complete!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
MESSAGE("Wobbuffet's Attack rose!");

View File

@ -266,3 +266,28 @@ SINGLE_BATTLE_TEST("Toxic Spikes print bad poison for 2 layers")
MESSAGE("The opposing Wynaut was badly poisoned!");
}
}
SINGLE_BATTLE_TEST("Toxic Spikes: Only two layers can be set up")
{
GIVEN {
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_TOXIC_SPIKES); }
TURN { MOVE(opponent, MOVE_TOXIC_SPIKES); }
TURN { MOVE(opponent, MOVE_TOXIC_SPIKES); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_SPIKES, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_SPIKES, opponent);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_SPIKES, opponent);
} THEN {
EXPECT_EQ(gBattleStruct->hazardsQueue[0][0], HAZARDS_TOXIC_SPIKES);
EXPECT_EQ(gBattleStruct->hazardsQueue[0][1], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[0][2], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[0][3], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[0][4], HAZARDS_NONE);
EXPECT_EQ(gBattleStruct->hazardsQueue[0][5], HAZARDS_NONE);
u32 toxicSpikesAmount = gSideTimers[0].toxicSpikesAmount;
EXPECT_EQ(toxicSpikesAmount, 2);
}
}