Opportunist Ability (#2994)
Co-authored-by: Eduardo Quezada D'Ottone <eduardo602002@gmail.com> Co-authored-by: ghoulslash <pokevoyager0@gmail.com>
This commit is contained in:
parent
2fac60055f
commit
70fbf9e9df
12 changed files with 187 additions and 46 deletions
|
@ -8712,28 +8712,6 @@ BattleScript_ActivateTerrainEffects_Increment:
|
||||||
restoretarget
|
restoretarget
|
||||||
return
|
return
|
||||||
|
|
||||||
BattleScript_ActivateSwitchInAbilities:
|
|
||||||
copybyte sBATTLER, gBattlerAttacker
|
|
||||||
setbyte gBattlerAttacker, 0
|
|
||||||
BattleScript_ActivateSwitchInAbilities_Loop:
|
|
||||||
switchinabilities BS_ATTACKER
|
|
||||||
BattleScript_ActivateSwitchInAbilities_Increment:
|
|
||||||
addbyte gBattlerAttacker, 1
|
|
||||||
jumpifbytenotequal gBattlerAttacker, gBattlersCount, BattleScript_ActivateSwitchInAbilities_Loop
|
|
||||||
copybyte gBattlerAttacker, sBATTLER
|
|
||||||
return
|
|
||||||
|
|
||||||
BattleScript_ActivateTerrainAbilities:
|
|
||||||
savetarget
|
|
||||||
setbyte gBattlerTarget, 0
|
|
||||||
BattleScript_ActivateTerrainAbilities_Loop:
|
|
||||||
activateterrainchangeabilities BS_ATTACKER
|
|
||||||
BattleScript_ActivateTerrainAbilities_Increment:
|
|
||||||
addbyte gBattlerTarget, 1
|
|
||||||
jumpifbytenotequal gBattlerTarget, gBattlersCount, BattleScript_ActivateTerrainAbilities_Loop
|
|
||||||
restoretarget
|
|
||||||
return
|
|
||||||
|
|
||||||
BattleScript_ElectricSurgeActivates::
|
BattleScript_ElectricSurgeActivates::
|
||||||
pause B_WAIT_TIME_SHORT
|
pause B_WAIT_TIME_SHORT
|
||||||
call BattleScript_AbilityPopUp
|
call BattleScript_AbilityPopUp
|
||||||
|
@ -9902,6 +9880,14 @@ BattleScript_MirrorHerbCopyStatChange::
|
||||||
copybyte gBattlerAttacker, sSAVED_BATTLER @ restore the original attacker just to be safe
|
copybyte gBattlerAttacker, sSAVED_BATTLER @ restore the original attacker just to be safe
|
||||||
return
|
return
|
||||||
|
|
||||||
|
BattleScript_OpportunistCopyStatChange::
|
||||||
|
call BattleScript_AbilityPopUp
|
||||||
|
printstring STRINGID_OPPORTUNISTCOPIED
|
||||||
|
waitmessage B_WAIT_TIME_LONG
|
||||||
|
call BattleScript_TotemVar_Ret
|
||||||
|
copybyte gBattlerAttacker, sSAVED_BATTLER @ restore the original attacker just to be safe
|
||||||
|
end3
|
||||||
|
|
||||||
BattleScript_TotemVar::
|
BattleScript_TotemVar::
|
||||||
call BattleScript_TotemVar_Ret
|
call BattleScript_TotemVar_Ret
|
||||||
end2
|
end2
|
||||||
|
|
|
@ -149,6 +149,7 @@ struct ProtectStruct
|
||||||
u16 shellTrap:1;
|
u16 shellTrap:1;
|
||||||
u16 silkTrapped:1;
|
u16 silkTrapped:1;
|
||||||
u16 eatMirrorHerb:1;
|
u16 eatMirrorHerb:1;
|
||||||
|
u16 activateOpportunist:2; // 2 - to copy stats. 1 - stats copied (do not repeat). 0 - no stats to copy
|
||||||
u32 physicalDmg;
|
u32 physicalDmg;
|
||||||
u32 specialDmg;
|
u32 specialDmg;
|
||||||
u8 physicalBattlerId;
|
u8 physicalBattlerId;
|
||||||
|
@ -901,7 +902,7 @@ struct MonSpritesGfx
|
||||||
u16 *buffer;
|
u16 *buffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TotemBoost
|
struct QueuedStatBoost
|
||||||
{
|
{
|
||||||
u8 stats; // bitfield for each battle stat that is set if the stat changes
|
u8 stats; // bitfield for each battle stat that is set if the stat changes
|
||||||
s8 statChanges[NUM_BATTLE_STATS - 1]; // highest bit being set decreases the stat
|
s8 statChanges[NUM_BATTLE_STATS - 1]; // highest bit being set decreases the stat
|
||||||
|
@ -1014,7 +1015,7 @@ extern u32 gFieldStatuses;
|
||||||
extern struct FieldTimer gFieldTimers;
|
extern struct FieldTimer gFieldTimers;
|
||||||
extern u8 gBattlerAbility;
|
extern u8 gBattlerAbility;
|
||||||
extern u16 gPartnerSpriteId;
|
extern u16 gPartnerSpriteId;
|
||||||
extern struct TotemBoost gTotemBoosts[MAX_BATTLERS_COUNT];
|
extern struct QueuedStatBoost gQueuedStatBoosts[MAX_BATTLERS_COUNT];
|
||||||
|
|
||||||
extern void (*gPreBattleCallback1)(void);
|
extern void (*gPreBattleCallback1)(void);
|
||||||
extern void (*gBattleMainFunc)(void);
|
extern void (*gBattleMainFunc)(void);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#ifndef GUARD_BATTLE_SCRIPTS_H
|
#ifndef GUARD_BATTLE_SCRIPTS_H
|
||||||
#define GUARD_BATTLE_SCRIPTS_H
|
#define GUARD_BATTLE_SCRIPTS_H
|
||||||
|
|
||||||
|
extern const u8 BattleScript_OpportunistCopyStatChange[];
|
||||||
extern const u8 BattleScript_MirrorHerbCopyStatChange[];
|
extern const u8 BattleScript_MirrorHerbCopyStatChange[];
|
||||||
extern const u8 BattleScript_MirrorHerbCopyStatChangeEnd2[];
|
extern const u8 BattleScript_MirrorHerbCopyStatChangeEnd2[];
|
||||||
extern const u8 BattleScript_NotAffected[];
|
extern const u8 BattleScript_NotAffected[];
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
#define ABILITYEFFECT_ON_TERRAIN 15
|
#define ABILITYEFFECT_ON_TERRAIN 15
|
||||||
#define ABILITYEFFECT_SWITCH_IN_TERRAIN 16
|
#define ABILITYEFFECT_SWITCH_IN_TERRAIN 16
|
||||||
#define ABILITYEFFECT_SWITCH_IN_WEATHER 17
|
#define ABILITYEFFECT_SWITCH_IN_WEATHER 17
|
||||||
|
#define ABILITYEFFECT_OPPORTUNIST 18
|
||||||
// Special cases
|
// Special cases
|
||||||
#define ABILITYEFFECT_MUD_SPORT 252 // Only used if B_SPORT_TURNS < GEN_6
|
#define ABILITYEFFECT_MUD_SPORT 252 // Only used if B_SPORT_TURNS < GEN_6
|
||||||
#define ABILITYEFFECT_WATER_SPORT 253 // Only used if B_SPORT_TURNS < GEN_6
|
#define ABILITYEFFECT_WATER_SPORT 253 // Only used if B_SPORT_TURNS < GEN_6
|
||||||
|
|
|
@ -323,8 +323,9 @@
|
||||||
#define MOVEEND_DANCER 31
|
#define MOVEEND_DANCER 31
|
||||||
#define MOVEEND_EMERGENCY_EXIT 32
|
#define MOVEEND_EMERGENCY_EXIT 32
|
||||||
#define MOVEEND_SYMBIOSIS 33
|
#define MOVEEND_SYMBIOSIS 33
|
||||||
#define MOVEEND_CLEAR_BITS 34
|
#define MOVEEND_OPPORTUNIST 34 // Occurs after other stat change items/abilities to try and copy the boosts
|
||||||
#define MOVEEND_COUNT 35
|
#define MOVEEND_CLEAR_BITS 35
|
||||||
|
#define MOVEEND_COUNT 36
|
||||||
|
|
||||||
// switch cases
|
// switch cases
|
||||||
#define B_SWITCH_NORMAL 0
|
#define B_SWITCH_NORMAL 0
|
||||||
|
|
|
@ -670,8 +670,9 @@
|
||||||
#define STRINGID_CURRENTMOVECANTSELECT 668
|
#define STRINGID_CURRENTMOVECANTSELECT 668
|
||||||
#define STRINGID_TARGETISBEINGSALTCURED 669
|
#define STRINGID_TARGETISBEINGSALTCURED 669
|
||||||
#define STRINGID_TARGETISHURTBYSALTCURE 670
|
#define STRINGID_TARGETISHURTBYSALTCURE 670
|
||||||
|
#define STRINGID_OPPORTUNISTCOPIED 671
|
||||||
|
|
||||||
#define BATTLESTRINGS_COUNT 671
|
#define BATTLESTRINGS_COUNT 672
|
||||||
|
|
||||||
// This is the string id that gBattleStringsTable starts with.
|
// This is the string id that gBattleStringsTable starts with.
|
||||||
// String ids before this (e.g. STRINGID_INTROMSG) are not in the table,
|
// String ids before this (e.g. STRINGID_INTROMSG) are not in the table,
|
||||||
|
|
|
@ -236,7 +236,7 @@ EWRAM_DATA u32 gFieldStatuses = 0;
|
||||||
EWRAM_DATA struct FieldTimer gFieldTimers = {0};
|
EWRAM_DATA struct FieldTimer gFieldTimers = {0};
|
||||||
EWRAM_DATA u8 gBattlerAbility = 0;
|
EWRAM_DATA u8 gBattlerAbility = 0;
|
||||||
EWRAM_DATA u16 gPartnerSpriteId = 0;
|
EWRAM_DATA u16 gPartnerSpriteId = 0;
|
||||||
EWRAM_DATA struct TotemBoost gTotemBoosts[MAX_BATTLERS_COUNT] = {0};
|
EWRAM_DATA struct QueuedStatBoost gQueuedStatBoosts[MAX_BATTLERS_COUNT] = {0};
|
||||||
EWRAM_DATA bool8 gHasFetchedBall = FALSE;
|
EWRAM_DATA bool8 gHasFetchedBall = FALSE;
|
||||||
EWRAM_DATA u8 gLastUsedBall = 0;
|
EWRAM_DATA u8 gLastUsedBall = 0;
|
||||||
EWRAM_DATA u16 gLastThrownBall = 0;
|
EWRAM_DATA u16 gLastThrownBall = 0;
|
||||||
|
@ -3790,14 +3790,13 @@ static void TryDoEventsBeforeFirstTurn(void)
|
||||||
// Totem boosts
|
// Totem boosts
|
||||||
for (i = 0; i < gBattlersCount; i++)
|
for (i = 0; i < gBattlersCount; i++)
|
||||||
{
|
{
|
||||||
if (gTotemBoosts[i].stats != 0)
|
if (gQueuedStatBoosts[i].stats != 0 && !gProtectStructs[i].eatMirrorHerb && gProtectStructs[i].activateOpportunist == 0)
|
||||||
{
|
{
|
||||||
gBattlerAttacker = i;
|
gBattlerAttacker = i;
|
||||||
BattleScriptExecute(BattleScript_TotemVar);
|
BattleScriptExecute(BattleScript_TotemVar);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
memset(gTotemBoosts, 0, sizeof(gTotemBoosts)); // erase all totem boosts just to be safe
|
|
||||||
|
|
||||||
// Check neutralizing gas
|
// Check neutralizing gas
|
||||||
if (AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS, 0, 0, 0, 0) != 0)
|
if (AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS, 0, 0, 0, 0) != 0)
|
||||||
|
@ -3821,6 +3820,9 @@ static void TryDoEventsBeforeFirstTurn(void)
|
||||||
if (ItemBattleEffects(ITEMEFFECT_ON_SWITCH_IN, gBattlerByTurnOrder[gBattleStruct->switchInItemsCounter++], FALSE))
|
if (ItemBattleEffects(ITEMEFFECT_ON_SWITCH_IN, gBattlerByTurnOrder[gBattleStruct->switchInItemsCounter++], FALSE))
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, 0, 0, 0, 0))
|
||||||
|
return;
|
||||||
|
|
||||||
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
|
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
|
||||||
{
|
{
|
||||||
|
@ -3854,6 +3856,8 @@ static void TryDoEventsBeforeFirstTurn(void)
|
||||||
gMoveResultFlags = 0;
|
gMoveResultFlags = 0;
|
||||||
|
|
||||||
gRandomTurnNumber = Random();
|
gRandomTurnNumber = Random();
|
||||||
|
|
||||||
|
memset(gQueuedStatBoosts, 0, sizeof(gQueuedStatBoosts)); // erase all totem boosts just to be safe
|
||||||
|
|
||||||
SetAiLogicDataForTurn(AI_DATA); // get assumed abilities, hold effects, etc of all battlers
|
SetAiLogicDataForTurn(AI_DATA); // get assumed abilities, hold effects, etc of all battlers
|
||||||
|
|
||||||
|
@ -5737,9 +5741,9 @@ void SetTotemBoost(void)
|
||||||
{
|
{
|
||||||
if (*(&gSpecialVar_0x8001 + i))
|
if (*(&gSpecialVar_0x8001 + i))
|
||||||
{
|
{
|
||||||
gTotemBoosts[battler].stats |= (1 << i);
|
gQueuedStatBoosts[battler].stats |= (1 << i);
|
||||||
gTotemBoosts[battler].statChanges[i] = *(&gSpecialVar_0x8001 + i);
|
gQueuedStatBoosts[battler].statChanges[i] = *(&gSpecialVar_0x8001 + i);
|
||||||
gTotemBoosts[battler].stats |= 0x80; // used as a flag for the "totem flared to life" script
|
gQueuedStatBoosts[battler].stats |= 0x80; // used as a flag for the "totem flared to life" script
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -807,9 +807,11 @@ static const u8 sText_TeamGainedEXP[] = _("The rest of your team gained EXP.\nPo
|
||||||
static const u8 sText_CurrentMoveCantSelect[] = _("{B_BUFF1} cannot be used!\p");
|
static const u8 sText_CurrentMoveCantSelect[] = _("{B_BUFF1} cannot be used!\p");
|
||||||
static const u8 sText_TargetIsBeingSaltCured[] = _("{B_DEF_NAME_WITH_PREFIX} is being salt cured!");
|
static const u8 sText_TargetIsBeingSaltCured[] = _("{B_DEF_NAME_WITH_PREFIX} is being salt cured!");
|
||||||
static const u8 sText_TargetIsHurtBySaltCure[] = _("{B_DEF_NAME_WITH_PREFIX} is hurt by {B_BUFF1}!");
|
static const u8 sText_TargetIsHurtBySaltCure[] = _("{B_DEF_NAME_WITH_PREFIX} is hurt by {B_BUFF1}!");
|
||||||
|
static const u8 sText_OpportunistCopied[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} copied its\nopponent's stat changes!");
|
||||||
|
|
||||||
const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] =
|
const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] =
|
||||||
{
|
{
|
||||||
|
[STRINGID_OPPORTUNISTCOPIED - BATTLESTRINGS_TABLE_START] = sText_OpportunistCopied,
|
||||||
[STRINGID_TARGETISHURTBYSALTCURE - BATTLESTRINGS_TABLE_START] = sText_TargetIsHurtBySaltCure,
|
[STRINGID_TARGETISHURTBYSALTCURE - BATTLESTRINGS_TABLE_START] = sText_TargetIsHurtBySaltCure,
|
||||||
[STRINGID_TARGETISBEINGSALTCURED - BATTLESTRINGS_TABLE_START] = sText_TargetIsBeingSaltCured,
|
[STRINGID_TARGETISBEINGSALTCURED - BATTLESTRINGS_TABLE_START] = sText_TargetIsBeingSaltCured,
|
||||||
[STRINGID_CURRENTMOVECANTSELECT - BATTLESTRINGS_TABLE_START] = sText_CurrentMoveCantSelect,
|
[STRINGID_CURRENTMOVECANTSELECT - BATTLESTRINGS_TABLE_START] = sText_CurrentMoveCantSelect,
|
||||||
|
|
|
@ -5429,6 +5429,12 @@ static void Cmd_moveend(void)
|
||||||
effect = TRUE;
|
effect = TRUE;
|
||||||
gBattleScripting.moveendState++;
|
gBattleScripting.moveendState++;
|
||||||
break;
|
break;
|
||||||
|
case MOVEEND_OPPORTUNIST:
|
||||||
|
if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, 0, 0, 0, 0))
|
||||||
|
effect = TRUE; // it loops through all battlers, so we increment after its done with all battlers
|
||||||
|
else
|
||||||
|
gBattleScripting.moveendState++;
|
||||||
|
break;
|
||||||
case MOVEEND_STATUS_IMMUNITY_ABILITIES: // status immunities
|
case MOVEEND_STATUS_IMMUNITY_ABILITIES: // status immunities
|
||||||
if (AbilityBattleEffects(ABILITYEFFECT_IMMUNITY, 0, 0, 0, 0))
|
if (AbilityBattleEffects(ABILITYEFFECT_IMMUNITY, 0, 0, 0, 0))
|
||||||
effect = TRUE; // it loops through all battlers, so we increment after its done with all battlers
|
effect = TRUE; // it loops through all battlers, so we increment after its done with all battlers
|
||||||
|
@ -6963,6 +6969,8 @@ static void Cmd_switchineffects(void)
|
||||||
{
|
{
|
||||||
if (DoSwitchInAbilitiesItems(battler))
|
if (DoSwitchInAbilitiesItems(battler))
|
||||||
return;
|
return;
|
||||||
|
else if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, battler, 0, 0, 0))
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
gDisableStructs[battler].stickyWebDone = FALSE;
|
gDisableStructs[battler].stickyWebDone = FALSE;
|
||||||
|
@ -9062,6 +9070,7 @@ static void Cmd_various(void)
|
||||||
AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS, battler, 0, 0, 0);
|
AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS, battler, 0, 0, 0);
|
||||||
AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, battler, 0, 0, 0);
|
AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, battler, 0, 0, 0);
|
||||||
AbilityBattleEffects(ABILITYEFFECT_TRACE2, battler, 0, 0, 0);
|
AbilityBattleEffects(ABILITYEFFECT_TRACE2, battler, 0, 0, 0);
|
||||||
|
AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, battler, 0, 0, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case VARIOUS_SAVE_TARGET:
|
case VARIOUS_SAVE_TARGET:
|
||||||
|
@ -9883,7 +9892,7 @@ static void Cmd_various(void)
|
||||||
{
|
{
|
||||||
VARIOUS_ARGS(const u8 *jumpInstr);
|
VARIOUS_ARGS(const u8 *jumpInstr);
|
||||||
battler = gBattlerAttacker;
|
battler = gBattlerAttacker;
|
||||||
if (gTotemBoosts[battler].stats == 0)
|
if (gQueuedStatBoosts[battler].stats == 0)
|
||||||
{
|
{
|
||||||
gBattlescriptCurrInstr = cmd->nextInstr; // stats done, exit
|
gBattlescriptCurrInstr = cmd->nextInstr; // stats done, exit
|
||||||
}
|
}
|
||||||
|
@ -9891,19 +9900,19 @@ static void Cmd_various(void)
|
||||||
{
|
{
|
||||||
for (i = 0; i < (NUM_BATTLE_STATS - 1); i++)
|
for (i = 0; i < (NUM_BATTLE_STATS - 1); i++)
|
||||||
{
|
{
|
||||||
if (gTotemBoosts[battler].stats & (1 << i))
|
if (gQueuedStatBoosts[battler].stats & (1 << i))
|
||||||
{
|
{
|
||||||
if (gTotemBoosts[battler].statChanges[i] <= -1)
|
if (gQueuedStatBoosts[battler].statChanges[i] <= -1)
|
||||||
SET_STATCHANGER(i + 1, abs(gTotemBoosts[battler].statChanges[i]), TRUE);
|
SET_STATCHANGER(i + 1, abs(gQueuedStatBoosts[battler].statChanges[i]), TRUE);
|
||||||
else
|
else
|
||||||
SET_STATCHANGER(i + 1, gTotemBoosts[battler].statChanges[i], FALSE);
|
SET_STATCHANGER(i + 1, gQueuedStatBoosts[battler].statChanges[i], FALSE);
|
||||||
|
|
||||||
gTotemBoosts[battler].stats &= ~(1 << i);
|
gQueuedStatBoosts[battler].stats &= ~(1 << i);
|
||||||
gBattleScripting.battler = battler;
|
gBattleScripting.battler = battler;
|
||||||
gBattlerTarget = battler;
|
gBattlerTarget = battler;
|
||||||
if (gTotemBoosts[battler].stats & 0x80)
|
if (gQueuedStatBoosts[battler].stats & 0x80)
|
||||||
{
|
{
|
||||||
gTotemBoosts[battler].stats &= ~0x80; // set 'aura flared to life' flag
|
gQueuedStatBoosts[battler].stats &= ~0x80; // set 'aura flared to life' flag
|
||||||
gBattlescriptCurrInstr = BattleScript_TotemFlaredToLife;
|
gBattlescriptCurrInstr = BattleScript_TotemFlaredToLife;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -11617,12 +11626,19 @@ static u32 ChangeStatBuffs(s8 statValue, u32 statId, u32 flags, const u8 *BS_ptr
|
||||||
{
|
{
|
||||||
if (GetBattlerSide(index) == GetBattlerSide(battler))
|
if (GetBattlerSide(index) == GetBattlerSide(battler))
|
||||||
continue; // Only triggers on opposing side
|
continue; // Only triggers on opposing side
|
||||||
if (GetBattlerHoldEffect(index, TRUE) == HOLD_EFFECT_MIRROR_HERB
|
if (GetBattlerAbility(index) == ABILITY_OPPORTUNIST
|
||||||
|
&& gProtectStructs[battler].activateOpportunist == 0) // don't activate opportunist on other mon's opportunist raises
|
||||||
|
{
|
||||||
|
gProtectStructs[index].activateOpportunist = 2; // set stats to copy
|
||||||
|
gQueuedStatBoosts[index].stats |= (1 << (statId - 1)); // -1 to start at atk
|
||||||
|
gQueuedStatBoosts[index].statChanges[statId - 1] += statValue; // cumulative in case of multiple opponent boosts
|
||||||
|
}
|
||||||
|
else if (GetBattlerHoldEffect(index, TRUE) == HOLD_EFFECT_MIRROR_HERB
|
||||||
&& gBattleMons[index].statStages[statId] < MAX_STAT_STAGE)
|
&& gBattleMons[index].statStages[statId] < MAX_STAT_STAGE)
|
||||||
{
|
{
|
||||||
gProtectStructs[index].eatMirrorHerb = 1;
|
gProtectStructs[index].eatMirrorHerb = 1;
|
||||||
gTotemBoosts[index].stats |= (1 << (statId - 1)); // -1 to start at atk
|
gQueuedStatBoosts[index].stats |= (1 << (statId - 1)); // -1 to start at atk
|
||||||
gTotemBoosts[index].statChanges[statId - 1] = statValue;
|
gQueuedStatBoosts[index].statChanges[statId - 1] = statValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5789,6 +5789,27 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case ABILITYEFFECT_OPPORTUNIST:
|
||||||
|
/* Similar to ABILITYEFFECT_IMMUNITY in that it loops through all battlers.
|
||||||
|
* Is called after ABILITYEFFECT_ON_SWITCHIN to copy any boosts
|
||||||
|
* from switch in abilities e.g. intrepid sword, as
|
||||||
|
*/
|
||||||
|
for (battler = 0; battler < gBattlersCount; battler++)
|
||||||
|
{
|
||||||
|
switch (GetBattlerAbility(battler))
|
||||||
|
{
|
||||||
|
case ABILITY_OPPORTUNIST:
|
||||||
|
if (gProtectStructs[battler].activateOpportunist == 2) {
|
||||||
|
gBattleScripting.savedBattler = gBattlerAttacker;
|
||||||
|
gBattleScripting.battler = gBattlerAttacker = gBattlerAbility = battler;
|
||||||
|
gProtectStructs[battler].activateOpportunist--;
|
||||||
|
BattleScriptPushCursorAndCallback(BattleScript_OpportunistCopyStatChange);
|
||||||
|
effect = 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
case ABILITYEFFECT_IMMUNITY: // 5
|
case ABILITYEFFECT_IMMUNITY: // 5
|
||||||
for (battler = 0; battler < gBattlersCount; battler++)
|
for (battler = 0; battler < gBattlersCount; battler++)
|
||||||
{
|
{
|
||||||
|
@ -5847,6 +5868,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
|
||||||
effect = 4;
|
effect = 4;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (effect != 0)
|
if (effect != 0)
|
||||||
{
|
{
|
||||||
switch (effect)
|
switch (effect)
|
||||||
|
|
106
test/battle/ability/opportunist.c
Normal file
106
test/battle/ability/opportunist.c
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
#include "global.h"
|
||||||
|
#include "test/battle.h"
|
||||||
|
|
||||||
|
SINGLE_BATTLE_TEST("Opportunist only copies foe's positive stat changes in a turn", s16 damage)
|
||||||
|
{
|
||||||
|
u32 ability;
|
||||||
|
PARAMETRIZE { ability = ABILITY_NONE; }
|
||||||
|
PARAMETRIZE { ability = ABILITY_OPPORTUNIST; }
|
||||||
|
GIVEN {
|
||||||
|
PLAYER(SPECIES_WOBBUFFET) { Speed(4); }
|
||||||
|
OPPONENT(SPECIES_WOBBUFFET) { Speed(5); Ability(ability); }
|
||||||
|
} WHEN {
|
||||||
|
TURN { MOVE(player, MOVE_SHELL_SMASH); }
|
||||||
|
TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_TACKLE); }
|
||||||
|
} SCENE {
|
||||||
|
if (ability == ABILITY_NONE) {
|
||||||
|
ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SMASH, player);
|
||||||
|
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
|
||||||
|
HP_BAR(player, captureDamage: &results[i].damage);
|
||||||
|
} else {
|
||||||
|
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
|
||||||
|
HP_BAR(player, captureDamage: &results[i].damage);
|
||||||
|
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player);
|
||||||
|
}
|
||||||
|
} FINALLY {
|
||||||
|
EXPECT_MUL_EQ(results[0].damage, Q_4_12(2.0), results[1].damage);
|
||||||
|
// stat boosts should be the same
|
||||||
|
EXPECT_EQ(player->statStages[STAT_ATK], opponent->statStages[STAT_ATK]);
|
||||||
|
EXPECT_EQ(player->statStages[STAT_SPATK], opponent->statStages[STAT_SPATK]);
|
||||||
|
EXPECT_EQ(player->statStages[STAT_SPEED], opponent->statStages[STAT_SPEED]);
|
||||||
|
// opportunist should not copy stat drops from shell smash
|
||||||
|
EXPECT_LT(player->statStages[STAT_DEF], opponent->statStages[STAT_DEF]);
|
||||||
|
EXPECT_LT(player->statStages[STAT_SPDEF], opponent->statStages[STAT_SPDEF]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DOUBLE_BATTLE_TEST("Opportunist raises Attack only once when partner has Intimidate against Contrary foe in a double battle", s16 damageLeft, s16 damageRight)
|
||||||
|
{
|
||||||
|
u32 abilityLeft, abilityRight;
|
||||||
|
|
||||||
|
PARAMETRIZE { abilityLeft = ABILITY_CONTRARY; abilityRight = ABILITY_CONTRARY; }
|
||||||
|
PARAMETRIZE { abilityLeft = ABILITY_TANGLED_FEET; abilityRight = ABILITY_TANGLED_FEET; }
|
||||||
|
PARAMETRIZE { abilityLeft = ABILITY_CONTRARY; abilityRight = ABILITY_TANGLED_FEET; }
|
||||||
|
PARAMETRIZE { abilityLeft = ABILITY_TANGLED_FEET; abilityRight = ABILITY_CONTRARY; }
|
||||||
|
|
||||||
|
GIVEN {
|
||||||
|
PLAYER(SPECIES_MIGHTYENA) { Ability(ABILITY_INTIMIDATE); }
|
||||||
|
PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_OPPORTUNIST); }
|
||||||
|
OPPONENT(SPECIES_SPINDA) { Ability(abilityLeft); }
|
||||||
|
OPPONENT(SPECIES_SPINDA) { Ability(abilityRight); }
|
||||||
|
} WHEN {
|
||||||
|
TURN { MOVE(opponentLeft, MOVE_TACKLE, target: playerLeft); MOVE(opponentRight, MOVE_TACKLE, target: playerRight); }
|
||||||
|
} SCENE {
|
||||||
|
ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE);
|
||||||
|
if (abilityLeft == ABILITY_CONTRARY) {
|
||||||
|
ABILITY_POPUP(opponentLeft, ABILITY_CONTRARY);
|
||||||
|
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft);
|
||||||
|
MESSAGE("Foe Spinda's Attack rose!");
|
||||||
|
} else {
|
||||||
|
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft);
|
||||||
|
MESSAGE("Mightyena's Intimidate cuts Foe Spinda's attack!");
|
||||||
|
}
|
||||||
|
if (abilityRight == ABILITY_CONTRARY) {
|
||||||
|
ABILITY_POPUP(opponentRight, ABILITY_CONTRARY);
|
||||||
|
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight);
|
||||||
|
MESSAGE("Foe Spinda's Attack rose!");
|
||||||
|
} else {
|
||||||
|
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight);
|
||||||
|
MESSAGE("Mightyena's Intimidate cuts Foe Spinda's attack!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((abilityLeft == ABILITY_CONTRARY && abilityRight != ABILITY_CONTRARY)
|
||||||
|
|| (abilityLeft != ABILITY_CONTRARY && abilityRight == ABILITY_CONTRARY)) {
|
||||||
|
ABILITY_POPUP(playerRight, ABILITY_OPPORTUNIST);
|
||||||
|
MESSAGE("Wobbuffet copied its opponent's stat changes!");
|
||||||
|
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight);
|
||||||
|
MESSAGE("Wobbuffet's Attack rose!");
|
||||||
|
} else if (abilityLeft == ABILITY_CONTRARY && abilityRight == ABILITY_CONTRARY) {
|
||||||
|
ABILITY_POPUP(playerRight, ABILITY_OPPORTUNIST);
|
||||||
|
MESSAGE("Wobbuffet copied its opponent's stat changes!");
|
||||||
|
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight);
|
||||||
|
MESSAGE("Wobbuffet's Attack sharply rose!");
|
||||||
|
}
|
||||||
|
|
||||||
|
HP_BAR(playerLeft, captureDamage: &results[i].damageLeft);
|
||||||
|
HP_BAR(playerRight, captureDamage: &results[i].damageRight);
|
||||||
|
} THEN {
|
||||||
|
EXPECT_EQ(opponentLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE + (abilityLeft == ABILITY_CONTRARY ? 1 : - 1));
|
||||||
|
EXPECT_EQ(opponentRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE + (abilityRight == ABILITY_CONTRARY ? 1 : - 1));
|
||||||
|
if ((abilityLeft == ABILITY_CONTRARY && abilityRight != ABILITY_CONTRARY)
|
||||||
|
|| (abilityLeft != ABILITY_CONTRARY && abilityRight == ABILITY_CONTRARY)) {
|
||||||
|
EXPECT_EQ(playerRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1);
|
||||||
|
} else if (abilityLeft == ABILITY_CONTRARY && abilityRight == ABILITY_CONTRARY) {
|
||||||
|
EXPECT_EQ(playerRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FINALLY {
|
||||||
|
EXPECT_MUL_EQ(results[1].damageLeft, Q_4_12(2.25), results[0].damageLeft);
|
||||||
|
EXPECT_MUL_EQ(results[1].damageRight, Q_4_12(2.25), results[0].damageRight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TO_DO_BATTLE_TEST("Opportunist doesn't copy ally stat increases");
|
||||||
|
TO_DO_BATTLE_TEST("Opportunist doesn't copy foe stat increases gained via Opportunist");
|
||||||
|
TO_DO_BATTLE_TEST("Opportunist copies foe stat increased gained via Swagger and Flatter");
|
|
@ -6,7 +6,7 @@ ASSUMPTIONS
|
||||||
ASSUME(gItems[ITEM_MIRROR_HERB].holdEffect == HOLD_EFFECT_MIRROR_HERB);
|
ASSUME(gItems[ITEM_MIRROR_HERB].holdEffect == HOLD_EFFECT_MIRROR_HERB);
|
||||||
}
|
}
|
||||||
|
|
||||||
SINGLE_BATTLE_TEST("Mirror Herb copies all of foe's stat changes in a turn", s16 damage)
|
SINGLE_BATTLE_TEST("Mirror Herb copies all of foe's positive stat changes in a turn", s16 damage)
|
||||||
{
|
{
|
||||||
u32 item;
|
u32 item;
|
||||||
PARAMETRIZE { item = ITEM_NONE; }
|
PARAMETRIZE { item = ITEM_NONE; }
|
||||||
|
@ -34,7 +34,7 @@ SINGLE_BATTLE_TEST("Mirror Herb copies all of foe's stat changes in a turn", s16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SINGLE_BATTLE_TEST("Mirror Herb copies all of of Stuff Cheeks")
|
SINGLE_BATTLE_TEST("Mirror Herb copies all of Stuff Cheeks' stat boosts")
|
||||||
{
|
{
|
||||||
GIVEN {
|
GIVEN {
|
||||||
ASSUME(gItems[ITEM_LIECHI_BERRY].holdEffect == HOLD_EFFECT_ATTACK_UP);
|
ASSUME(gItems[ITEM_LIECHI_BERRY].holdEffect == HOLD_EFFECT_ATTACK_UP);
|
||||||
|
|
Loading…
Reference in a new issue