Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Opportunist Ability #2994

Merged
merged 18 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 8 additions & 22 deletions data/battle_scripts_1.s
Original file line number Diff line number Diff line change
Expand Up @@ -8711,28 +8711,6 @@ BattleScript_ActivateTerrainEffects_Increment:
restoretarget
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::
pause B_WAIT_TIME_SHORT
call BattleScript_AbilityPopUp
Expand Down Expand Up @@ -9901,6 +9879,14 @@ BattleScript_MirrorHerbCopyStatChange::
copybyte gBattlerAttacker, sSAVED_BATTLER @ restore the original attacker just to be safe
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::
call BattleScript_TotemVar_Ret
end2
Expand Down
5 changes: 3 additions & 2 deletions include/battle.h
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ struct ProtectStruct
u16 shellTrap:1;
u16 silkTrapped: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 specialDmg;
u8 physicalBattlerId;
Expand Down Expand Up @@ -901,7 +902,7 @@ struct MonSpritesGfx
u16 *buffer;
};

struct TotemBoost
struct QueuedStatBoost
{
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
Expand Down Expand Up @@ -1014,7 +1015,7 @@ extern u32 gFieldStatuses;
extern struct FieldTimer gFieldTimers;
extern u8 gBattlerAbility;
extern u16 gPartnerSpriteId;
extern struct TotemBoost gTotemBoosts[MAX_BATTLERS_COUNT];
extern struct QueuedStatBoost gQueuedStatBoosts[MAX_BATTLERS_COUNT];

extern void (*gPreBattleCallback1)(void);
extern void (*gBattleMainFunc)(void);
Expand Down
1 change: 1 addition & 0 deletions include/battle_scripts.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef GUARD_BATTLE_SCRIPTS_H
#define GUARD_BATTLE_SCRIPTS_H

extern const u8 BattleScript_OpportunistCopyStatChange[];
extern const u8 BattleScript_MirrorHerbCopyStatChange[];
extern const u8 BattleScript_MirrorHerbCopyStatChangeEnd2[];
extern const u8 BattleScript_NotAffected[];
Expand Down
1 change: 1 addition & 0 deletions include/battle_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#define ABILITYEFFECT_ON_TERRAIN 15
#define ABILITYEFFECT_SWITCH_IN_TERRAIN 16
#define ABILITYEFFECT_SWITCH_IN_WEATHER 17
#define ABILITYEFFECT_OPPORTUNIST 18
// Special cases
#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
Expand Down
5 changes: 3 additions & 2 deletions include/constants/battle_script_commands.h
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,9 @@
#define MOVEEND_DANCER 31
#define MOVEEND_EMERGENCY_EXIT 32
#define MOVEEND_SYMBIOSIS 33
#define MOVEEND_CLEAR_BITS 34
#define MOVEEND_COUNT 35
#define MOVEEND_OPPORTUNIST 34 // Occurs after other stat change items/abilities to try and copy the boosts
#define MOVEEND_CLEAR_BITS 35
#define MOVEEND_COUNT 36

// switch cases
#define B_SWITCH_NORMAL 0
Expand Down
3 changes: 2 additions & 1 deletion include/constants/battle_string_ids.h
Original file line number Diff line number Diff line change
Expand Up @@ -670,8 +670,9 @@
#define STRINGID_CURRENTMOVECANTSELECT 668
#define STRINGID_TARGETISBEINGSALTCURED 669
#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.
// String ids before this (e.g. STRINGID_INTROMSG) are not in the table,
Expand Down
16 changes: 10 additions & 6 deletions src/battle_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ EWRAM_DATA u32 gFieldStatuses = 0;
EWRAM_DATA struct FieldTimer gFieldTimers = {0};
EWRAM_DATA u8 gBattlerAbility = 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 u8 gLastUsedBall = 0;
EWRAM_DATA u16 gLastThrownBall = 0;
Expand Down Expand Up @@ -3790,14 +3790,13 @@ static void TryDoEventsBeforeFirstTurn(void)
// Totem boosts
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;
BattleScriptExecute(BattleScript_TotemVar);
return;
}
}
memset(gTotemBoosts, 0, sizeof(gTotemBoosts)); // erase all totem boosts just to be safe

// Check neutralizing gas
if (AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS, 0, 0, 0, 0) != 0)
Expand All @@ -3821,6 +3820,9 @@ static void TryDoEventsBeforeFirstTurn(void)
if (ItemBattleEffects(ITEMEFFECT_ON_SWITCH_IN, gBattlerByTurnOrder[gBattleStruct->switchInItemsCounter++], FALSE))
return;
}

if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, 0, 0, 0, 0))
return;

for (i = 0; i < MAX_BATTLERS_COUNT; i++)
{
Expand Down Expand Up @@ -3854,6 +3856,8 @@ static void TryDoEventsBeforeFirstTurn(void)
gMoveResultFlags = 0;

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

Expand Down Expand Up @@ -5737,9 +5741,9 @@ void SetTotemBoost(void)
{
if (*(&gSpecialVar_0x8001 + i))
{
gTotemBoosts[battler].stats |= (1 << i);
gTotemBoosts[battler].statChanges[i] = *(&gSpecialVar_0x8001 + i);
gTotemBoosts[battler].stats |= 0x80; // used as a flag for the "totem flared to life" script
gQueuedStatBoosts[battler].stats |= (1 << i);
gQueuedStatBoosts[battler].statChanges[i] = *(&gSpecialVar_0x8001 + i);
gQueuedStatBoosts[battler].stats |= 0x80; // used as a flag for the "totem flared to life" script
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/battle_message.c
Original file line number Diff line number Diff line change
Expand Up @@ -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_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_OpportunistCopied[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} copied its\nopponent's stat changes!");

const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] =
{
[STRINGID_OPPORTUNISTCOPIED - BATTLESTRINGS_TABLE_START] = sText_OpportunistCopied,
[STRINGID_TARGETISHURTBYSALTCURE - BATTLESTRINGS_TABLE_START] = sText_TargetIsHurtBySaltCure,
[STRINGID_TARGETISBEINGSALTCURED - BATTLESTRINGS_TABLE_START] = sText_TargetIsBeingSaltCured,
[STRINGID_CURRENTMOVECANTSELECT - BATTLESTRINGS_TABLE_START] = sText_CurrentMoveCantSelect,
Expand Down
38 changes: 27 additions & 11 deletions src/battle_script_commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -5429,6 +5429,12 @@ static void Cmd_moveend(void)
effect = TRUE;
gBattleScripting.moveendState++;
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
if (AbilityBattleEffects(ABILITYEFFECT_IMMUNITY, 0, 0, 0, 0))
effect = TRUE; // it loops through all battlers, so we increment after its done with all battlers
Expand Down Expand Up @@ -6963,6 +6969,8 @@ static void Cmd_switchineffects(void)
{
if (DoSwitchInAbilitiesItems(battler))
return;
else if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, battler, 0, 0, 0))
return;
}

gDisableStructs[battler].stickyWebDone = FALSE;
Expand Down Expand Up @@ -9062,6 +9070,7 @@ static void Cmd_various(void)
AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS, battler, 0, 0, 0);
AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, battler, 0, 0, 0);
AbilityBattleEffects(ABILITYEFFECT_TRACE2, battler, 0, 0, 0);
AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, battler, 0, 0, 0);
return;
}
case VARIOUS_SAVE_TARGET:
Expand Down Expand Up @@ -9883,27 +9892,27 @@ static void Cmd_various(void)
{
VARIOUS_ARGS(const u8 *jumpInstr);
battler = gBattlerAttacker;
if (gTotemBoosts[battler].stats == 0)
if (gQueuedStatBoosts[battler].stats == 0)
{
gBattlescriptCurrInstr = cmd->nextInstr; // stats done, exit
}
else
{
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)
SET_STATCHANGER(i + 1, abs(gTotemBoosts[battler].statChanges[i]), TRUE);
if (gQueuedStatBoosts[battler].statChanges[i] <= -1)
SET_STATCHANGER(i + 1, abs(gQueuedStatBoosts[battler].statChanges[i]), TRUE);
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;
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;
}
else
Expand Down Expand Up @@ -11617,12 +11626,19 @@ static u32 ChangeStatBuffs(s8 statValue, u32 statId, u32 flags, const u8 *BS_ptr
{
if (GetBattlerSide(index) == GetBattlerSide(battler))
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)
{
gProtectStructs[index].eatMirrorHerb = 1;
gTotemBoosts[index].stats |= (1 << (statId - 1)); // -1 to start at atk
gTotemBoosts[index].statChanges[statId - 1] = statValue;
gQueuedStatBoosts[index].stats |= (1 << (statId - 1)); // -1 to start at atk
gQueuedStatBoosts[index].statChanges[statId - 1] = statValue;
}
}
}
Expand Down
22 changes: 22 additions & 0 deletions src/battle_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -5789,6 +5789,27 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
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
for (battler = 0; battler < gBattlersCount; battler++)
{
Expand Down Expand Up @@ -5847,6 +5868,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
effect = 4;
break;
}

if (effect != 0)
{
switch (effect)
Expand Down
Loading
Loading