Refactor Fury Cutter and Rollout (#8375)

This commit is contained in:
Alex 2025-12-02 16:45:34 +01:00 committed by GitHub
parent a285482310
commit 49a18df48a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 215 additions and 164 deletions

View File

@ -980,7 +980,7 @@
.4byte \failInstr
.endm
.macro handlerollout
.macro unused_0xb3
.byte 0xb3
.endm
@ -990,7 +990,7 @@
.4byte \jumpInstr
.endm
.macro handlefurycutter
.macro unused_0xb5
.byte 0xb5
.endm

View File

@ -3459,16 +3459,6 @@ BattleScript_EffectSandstorm::
setfieldweather BATTLE_WEATHER_SANDSTORM
goto BattleScript_MoveWeatherChange
BattleScript_EffectRollout::
attackcanceler
jumpifvolatile BS_ATTACKER, VOLATILE_MULTIPLETURNS, BattleScript_RolloutCheckAccuracy
BattleScript_RolloutCheckAccuracy::
accuracycheck BattleScript_RolloutHit, ACC_CURR_MOVE
BattleScript_RolloutHit::
typecalc
handlerollout
goto BattleScript_HitFromDamageCalc
BattleScript_EffectSwagger::
attackcanceler
jumpifsubstituteblocks BattleScript_MakeMoveMissed
@ -3487,16 +3477,6 @@ BattleScript_SwaggerTryConfuse:
seteffectprimary BS_ATTACKER, BS_TARGET, MOVE_EFFECT_CONFUSION
goto BattleScript_MoveEnd
BattleScript_EffectFuryCutter::
attackcanceler
accuracycheck BattleScript_FuryCutterHit, ACC_CURR_MOVE
BattleScript_FuryCutterHit:
handlefurycutter
damagecalc
jumpifmovehadnoeffect BattleScript_FuryCutterHit
adjustdamage
goto BattleScript_HitFromAtkAnimation
BattleScript_TryDestinyKnotTarget:
jumpifnoholdeffect BS_ATTACKER, HOLD_EFFECT_DESTINY_KNOT, BattleScript_TryDestinyKnotTargetRet
playanimation BS_ATTACKER, B_ANIM_HELD_ITEM_EFFECT

View File

@ -95,10 +95,10 @@ struct DisableStruct
u16 disableTimer;
u16 encoreTimer;
u16 perishSongTimer;
u16 rolloutTimer;
u16 rolloutTimerStartValue;
u8 rolloutTimer;
u16 tauntTimer;
u8 furyCutterCounter;
u8 metronomeItemCounter;
u8 battlerPreventingEscape;
u8 battlerWithSureHit;
u8 isFirstTurn;
@ -586,10 +586,11 @@ struct BattlerState
u32 wasAboveHalfHp:1; // For Berserk, Emergency Exit, Wimp Out and Anger Shell.
u32 commanderSpecies:11;
u32 selectionScriptFinished:1;
u32 switchIn:1;
u32 padding:3;
u32 lastMoveTarget:3; // The last target on which each mon used a move, for the sake of Instruct
// End of Word
u16 hpOnSwitchout;
u16 switchIn:1;
u16 padding:15;
};
struct PartyState
@ -701,9 +702,9 @@ struct BattleStruct
struct BattleTvMovePoints tvMovePoints;
struct BattleTv tv;
u8 AI_monToSwitchIntoId[MAX_BATTLERS_COUNT];
s8 arenaMindPoints[2];
s8 arenaSkillPoints[2];
u16 arenaStartHp[2];
s8 arenaMindPoints[NUM_BATTLE_SIDES];
s8 arenaSkillPoints[NUM_BATTLE_SIDES];
u16 arenaStartHp[NUM_BATTLE_SIDES];
u8 arenaLostPlayerMons; // Bits for party member, lost as in referee's decision, not by fainting.
u8 arenaLostOpponentMons;
u8 debugBattler;
@ -719,12 +720,10 @@ struct BattleStruct
struct BattleGimmickData gimmick;
const u8 *trainerSlideMsg;
u8 stolenStats[NUM_BATTLE_STATS]; // hp byte is used for which stats to raise, other inform about by how many stages
u8 lastMoveTarget[MAX_BATTLERS_COUNT]; // The last target on which each mon used a move, for the sake of Instruct
enum Ability tracedAbility[MAX_BATTLERS_COUNT];
struct Illusion illusion[MAX_BATTLERS_COUNT];
u8 soulheartBattlerId;
u8 friskedBattler; // Frisk needs to identify 2 battlers in double battles.
u8 metronomeItemCounter[MAX_BATTLERS_COUNT]; // For Metronome, number of times the same moves has been SUCCESFULLY used.
u8 quickClawBattlerId;
struct LostItem itemLost[NUM_BATTLE_SIDES][PARTY_SIZE]; // Pokemon that had items consumed or stolen (two bytes per party member per side)
u8 blunderPolicy:1; // should blunder policy activate

View File

@ -627,9 +627,7 @@ extern const u8 BattleScript_EffectForesight[];
extern const u8 BattleScript_EffectPerishSong[];
extern const u8 BattleScript_EffectSandstorm[];
extern const u8 BattleScript_EffectEndure[];
extern const u8 BattleScript_EffectRollout[];
extern const u8 BattleScript_EffectSwagger[];
extern const u8 BattleScript_EffectFuryCutter[];
extern const u8 BattleScript_EffectAttract[];
extern const u8 BattleScript_EffectPresent[];
extern const u8 BattleScript_EffectSafeguard[];

View File

@ -301,8 +301,6 @@ bool32 IsMoveMakingContact(u32 battlerAtk, u32 battlerDef, enum Ability abilityA
bool32 IsBattlerGrounded(u32 battler, enum Ability ability, enum HoldEffect holdEffect);
u32 GetMoveSlot(u16 *moves, u32 move);
u32 GetBattlerWeight(u32 battler);
u32 CalcRolloutBasePower(u32 battlerAtk, u32 basePower, u32 rolloutTimer);
u32 CalcFuryCutterBasePower(u32 basePower, u32 furyCutterCounter);
s32 CalcCritChanceStage(struct DamageContext *ctx);
s32 CalcCritChanceStageGen1(struct DamageContext *ctx);
s32 CalculateMoveDamage(struct DamageContext *ctx);

View File

@ -172,7 +172,6 @@ enum MoveEndEffects
MOVEEND_PICKPOCKET,
MOVEEND_THIRD_MOVE_BLOCK,
MOVEEND_CHANGED_ITEMS,
MOVEEND_SAME_MOVE_TURNS,
MOVEEND_CLEAR_BITS,
MOVEEND_DANCER,
MOVEEND_PURSUIT_NEXT_ACTION,

View File

@ -744,25 +744,6 @@ static inline s32 GetDamageByRollType(s32 dmg, enum DamageRollType rollType)
return DmgRoll(dmg);
}
static inline s32 SetFixedMoveBasePower(u32 battlerAtk, u32 move)
{
s32 fixedBasePower = 0, n = 0;
switch (GetMoveEffect(move))
{
case EFFECT_ROLLOUT:
n = gDisableStructs[battlerAtk].rolloutTimer - 1;
fixedBasePower = CalcRolloutBasePower(battlerAtk, GetMovePower(move), n < 0 ? 5 : n);
break;
case EFFECT_FURY_CUTTER:
fixedBasePower = CalcFuryCutterBasePower(GetMovePower(move), min(gDisableStructs[battlerAtk].furyCutterCounter + 1, 5));
break;
default:
fixedBasePower = 0;
break;
}
return fixedBasePower;
}
static inline void AI_StoreBattlerTypes(u32 battlerAtk, enum Type *types)
{
types[0] = gBattleMons[battlerAtk].types[0];
@ -937,7 +918,6 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u
{
struct SimulatedDamage simDamage = {0};
enum BattleMoveEffects moveEffect = GetMoveEffect(move);
bool32 isDamageMoveUnusable = FALSE;
bool32 toggledGimmickAtk = FALSE;
bool32 toggledGimmickDef = FALSE;
struct AiLogicData *aiData = gAiLogicData;
@ -978,7 +958,7 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u
ctx.randomFactor = FALSE;
ctx.updateFlags = FALSE;
ctx.weather = weather;
ctx.fixedBasePower = SetFixedMoveBasePower(battlerAtk, move);
ctx.fixedBasePower = 0;
ctx.holdEffectAtk = aiData->holdEffects[battlerAtk];
ctx.holdEffectDef = aiData->holdEffects[battlerDef];
ctx.abilityAtk = aiData->abilities[battlerAtk];
@ -987,10 +967,8 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u
ctx.typeEffectivenessModifier = CalcTypeEffectivenessMultiplier(&ctx);
u32 movePower = GetMovePower(move);
if (movePower)
isDamageMoveUnusable = IsDamageMoveUnusable(&ctx);
if (movePower && !isDamageMoveUnusable)
if (movePower && !IsDamageMoveUnusable(&ctx))
{
enum Type types[3];
AI_StoreBattlerTypes(battlerAtk, types);

View File

@ -1599,8 +1599,8 @@ static void AnimHailBegin(struct Sprite *sprite)
sprite->data[0] = spriteId;
if (spriteId != MAX_SPRITES)
{
// The sprite template we're using is shared amongst a few other
// places, which make the sprite flicker. That's not what we want
// The sprite template we're using is shared amongst a few other
// places, which make the sprite flicker. That's not what we want
// here, though. Override the callback.
gSprites[sprite->data[0]].callback = AnimHailContinue;
gSprites[sprite->data[0]].sOwnerTaskId = sprite->sOwnerTaskId;
@ -1649,10 +1649,7 @@ static void AnimHailContinue(struct Sprite *sprite)
// arg 5: arc height (negative)
static void InitIceBallAnim(struct Sprite *sprite)
{
u8 animNum = gAnimDisableStructPtr->rolloutTimerStartValue - gAnimDisableStructPtr->rolloutTimer - 1;
if (animNum > 4)
animNum = 4;
u32 animNum = gAnimDisableStructPtr->rolloutTimer + 1;
StartSpriteAffineAnim(sprite, animNum);
InitSpritePosToAnimAttacker(sprite, TRUE);
@ -1721,8 +1718,7 @@ static void AnimIceBallParticle(struct Sprite *sprite)
void AnimTask_GetIceBallCounter(u8 taskId)
{
u8 arg = gBattleAnimArgs[0];
gBattleAnimArgs[arg] = gAnimDisableStructPtr->rolloutTimerStartValue - gAnimDisableStructPtr->rolloutTimer - 1;
gBattleAnimArgs[arg] = gAnimDisableStructPtr->rolloutTimer + 1;
DestroyAnimVisualTask(taskId);
}

View File

@ -626,10 +626,10 @@ static void AnimTask_LoadSandstormBackground_Step(u8 taskId)
#define sFractionalY data[4] // 256ths of a pixel
#define sMirroredX data[5] // init'd from gBattleAnimArgs[3]
// The fields named "velocity" are arguably more like "acceleration,"
// The fields named "velocity" are arguably more like "acceleration,"
// and the fields named "fractional" are arguably more like "velocity."
//
// ...is what I WOULD say if the "fractional" fields weren't AND'd with
// ...is what I WOULD say if the "fractional" fields weren't AND'd with
// 0xFF after every frame.
void AnimFlyingSandCrescent(struct Sprite *sprite)
@ -926,12 +926,7 @@ static void AnimRolloutParticle(struct Sprite *sprite)
static u8 GetRolloutCounter(void)
{
u8 retVal = gAnimDisableStructPtr->rolloutTimerStartValue - gAnimDisableStructPtr->rolloutTimer;
u8 var0 = retVal - 1;
if (var0 > 4)
retVal = 1;
return retVal;
return gAnimDisableStructPtr->rolloutTimer + 1;
}
void AnimRockTomb(struct Sprite *sprite)

View File

@ -3231,7 +3231,6 @@ void SwitchInClearSetData(u32 battler, struct Volatiles *volatilesCopy)
gLastHitBy[battler] = 0xFF;
gBattleStruct->lastTakenMove[battler] = 0;
gBattleStruct->metronomeItemCounter[battler] = 0;
gBattleStruct->lastTakenMoveFrom[battler][0] = 0;
gBattleStruct->lastTakenMoveFrom[battler][1] = 0;
gBattleStruct->lastTakenMoveFrom[battler][2] = 0;
@ -3350,7 +3349,6 @@ const u8* FaintClearSetData(u32 battler)
gLastHitBy[battler] = 0xFF;
gBattleStruct->choicedMove[battler] = MOVE_NONE;
gBattleStruct->metronomeItemCounter[battler] = 0;
gBattleStruct->lastTakenMove[battler] = MOVE_NONE;
gBattleStruct->lastTakenMoveFrom[battler][0] = 0;
gBattleStruct->lastTakenMoveFrom[battler][1] = 0;

View File

@ -337,6 +337,7 @@ static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u
static void ResetValuesForCalledMove(void);
static bool32 TrySymbiosis(u32 battler, u32 itemId, bool32 moveEnd);
static bool32 CanAbilityShieldActivateForBattler(u32 battler);
static void SetSameMoveTurnValues(u32 moveEffect);
static void TryClearChargeVolatile(u32 moveType);
static bool32 IsAnyTargetAffected(void);
@ -519,9 +520,9 @@ static void Cmd_cursetarget(void);
static void Cmd_trysetspikes(void);
static void Cmd_setvolatile(void);
static void Cmd_trysetperishsong(void);
static void Cmd_handlerollout(void);
static void Cmd_unused_0xb3(void);
static void Cmd_jumpifconfusedandstatmaxed(void);
static void Cmd_handlefurycutter(void);
static void Cmd_unused_0xb5(void);
static void Cmd_setembargo(void);
static void Cmd_presentdamagecalculation(void);
static void Cmd_setsafeguard(void);
@ -778,9 +779,9 @@ void (*const gBattleScriptingCommandsTable[])(void) =
Cmd_trysetspikes, //0xB0
Cmd_setvolatile, //0xB1
Cmd_trysetperishsong, //0xB2
Cmd_handlerollout, //0xB3
Cmd_unused_0xb3, //0xB3
Cmd_jumpifconfusedandstatmaxed, //0xB4
Cmd_handlefurycutter, //0xB5
Cmd_unused_0xb5, //0xB5
Cmd_setembargo, //0xB6
Cmd_presentdamagecalculation, //0xB7
Cmd_setsafeguard, //0xB8
@ -1022,6 +1023,48 @@ bool32 IsMoveNotAllowedInSkyBattles(u32 move)
return (gBattleStruct->isSkyBattle && IsMoveSkyBattleBanned(gCurrentMove));
}
static void SetSameMoveTurnValues(u32 moveEffect)
{
bool32 increment = IsAnyTargetAffected()
&& !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE)
&& gLastResultingMoves[gBattlerAttacker] == gCurrentMove;
switch (moveEffect)
{
case EFFECT_FURY_CUTTER:
if (increment && gDisableStructs[gBattlerAttacker].furyCutterCounter < 5)
gDisableStructs[gBattlerAttacker].furyCutterCounter++;
else
gDisableStructs[gBattlerAttacker].furyCutterCounter = 0;
break;
case EFFECT_ROLLOUT:
if (increment && ++gDisableStructs[gBattlerAttacker].rolloutTimer < 5)
{
gBattleMons[gBattlerAttacker].volatiles.multipleTurns = TRUE;
gLockedMoves[gBattlerAttacker] = gCurrentMove;
}
else
{
gBattleMons[gBattlerAttacker].volatiles.multipleTurns = FALSE;
gDisableStructs[gBattlerAttacker].rolloutTimer = 0;
}
break;
case EFFECT_ECHOED_VOICE:
if (!(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE)) // Increment even if targets unaffected
gBattleStruct->incrementEchoedVoice = TRUE;
break;
default: // not consecutive
gDisableStructs[gBattlerAttacker].rolloutTimer = 0;
gDisableStructs[gBattlerAttacker].furyCutterCounter = 0;
break;
}
if (increment)
gDisableStructs[gBattlerAttacker].metronomeItemCounter++;
else
gDisableStructs[gBattlerAttacker].metronomeItemCounter = 0;
}
static void TryClearChargeVolatile(u32 moveType)
{
if (B_CHARGE < GEN_9) // Prior to gen9, charge is cleared during the end turn
@ -1035,9 +1078,6 @@ static void TryClearChargeVolatile(u32 moveType)
static bool32 IsAnyTargetAffected(void)
{
if (gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE)
return FALSE;
for (u32 battler = 0; battler < gBattlersCount; battler++)
{
if (battler == gBattlerAttacker)
@ -6202,7 +6242,7 @@ static void Cmd_moveend(void)
if (!gSpecialStatuses[gBattlerAttacker].dancerUsedMove)
{
gDisableStructs[gBattlerAttacker].usedMoves |= 1u << gCurrMovePos;
gBattleStruct->lastMoveTarget[gBattlerAttacker] = gBattlerTarget;
gBattleStruct->battlerState[gBattlerAttacker].lastMoveTarget = gBattlerTarget;
}
enum BattleMoveEffects originalEffect = GetMoveEffect(originallyUsedMove);
if (IsBattlerAlive(gBattlerAttacker)
@ -6831,13 +6871,6 @@ static void Cmd_moveend(void)
}
gBattleScripting.moveendState++;
break;
case MOVEEND_SAME_MOVE_TURNS:
if (gCurrentMove != gLastResultingMoves[gBattlerAttacker] || !IsAnyTargetAffected())
gBattleStruct->metronomeItemCounter[gBattlerAttacker] = 0;
else if (gCurrentMove == gLastResultingMoves[gBattlerAttacker] && gSpecialStatuses[gBattlerAttacker].parentalBondState != PARENTAL_BOND_1ST_HIT)
gBattleStruct->metronomeItemCounter[gBattlerAttacker]++;
gBattleScripting.moveendState++;
break;
case MOVEEND_CLEAR_BITS: // Clear/Set bits for things like using a move for all targets and all hits.
if (gSpecialStatuses[gBattlerAttacker].instructedChosenTarget)
gBattleStruct->moveTarget[gBattlerAttacker] = gSpecialStatuses[gBattlerAttacker].instructedChosenTarget & 0x3;
@ -6854,6 +6887,7 @@ static void Cmd_moveend(void)
&& gBattleMons[gBattlerAttacker].volatiles.lockConfusionTurns != 1) // And won't end this turn
CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_IGNORE); // Cancel it
SetSameMoveTurnValues(moveEffect);
TryClearChargeVolatile(moveType);
ValidateSavedBattlerCounts();
gProtectStructs[gBattlerAttacker].shellTrap = FALSE;
@ -6876,8 +6910,6 @@ static void Cmd_moveend(void)
SetActiveGimmick(gBattlerAttacker, GIMMICK_NONE);
if (gBattleMons[gBattlerAttacker].volatiles.destinyBond > 0)
gBattleMons[gBattlerAttacker].volatiles.destinyBond--;
if (moveEffect == EFFECT_ECHOED_VOICE && !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE))
gBattleStruct->incrementEchoedVoice = TRUE;
// check if Stellar type boost should be used up
if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_TERA
&& GetBattlerTeraType(gBattlerAttacker) == TYPE_STELLAR
@ -11480,31 +11512,8 @@ static void Cmd_trysetperishsong(void)
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_handlerollout(void)
static void Cmd_unused_0xb3(void)
{
CMD_ARGS();
if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)
{
CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_IGNORE);
gBattlescriptCurrInstr = BattleScript_MoveMissedPause;
}
else
{
if (!(gBattleMons[gBattlerAttacker].volatiles.multipleTurns)) // First hit.
{
gDisableStructs[gBattlerAttacker].rolloutTimer = 5;
gDisableStructs[gBattlerAttacker].rolloutTimerStartValue = 5;
gBattleMons[gBattlerAttacker].volatiles.multipleTurns = TRUE;
gLockedMoves[gBattlerAttacker] = gCurrentMove;
}
if (--gDisableStructs[gBattlerAttacker].rolloutTimer == 0) // Last hit.
{
gBattleMons[gBattlerAttacker].volatiles.multipleTurns = FALSE;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_jumpifconfusedandstatmaxed(void)
@ -11518,32 +11527,8 @@ static void Cmd_jumpifconfusedandstatmaxed(void)
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_handlefurycutter(void)
static void Cmd_unused_0xb5(void)
{
CMD_ARGS();
if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)
{
gDisableStructs[gBattlerAttacker].furyCutterCounter = 0;
gBattlescriptCurrInstr = BattleScript_MoveMissedPause;
}
else
{
u32 max;
if (B_UPDATED_MOVE_DATA >= GEN_6)
max = 3;
else if (B_UPDATED_MOVE_DATA == GEN_5)
max = 4;
else
max = 5;
if (gDisableStructs[gBattlerAttacker].furyCutterCounter < max
&& gSpecialStatuses[gBattlerAttacker].parentalBondState != PARENTAL_BOND_2ND_HIT) // Don't increment counter on second hit
gDisableStructs[gBattlerAttacker].furyCutterCounter++;
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static void Cmd_setembargo(void)
@ -16856,7 +16841,7 @@ void BS_TryInstruct(void)
}
else
{
gEffectBattler = gBattleStruct->lastMoveTarget[gBattlerTarget];
gEffectBattler = gBattleStruct->battlerState[gBattlerTarget].lastMoveTarget;
PREPARE_MON_NICK_WITH_PREFIX_BUFFER(gBattleTextBuff1, gBattlerTarget, gBattlerPartyIndexes[gBattlerTarget]);
gBattlescriptCurrInstr = cmd->nextInstr;
}

View File

@ -1949,8 +1949,6 @@ void TryClearRageAndFuryCutter(void)
{
if (gBattleMons[i].volatiles.rage && gChosenMoveByBattler[i] != MOVE_RAGE)
gBattleMons[i].volatiles.rage = FALSE;
if (gDisableStructs[i].furyCutterCounter != 0 && gChosenMoveByBattler[i] != MOVE_FURY_CUTTER)
gDisableStructs[i].furyCutterCounter = 0;
}
}
@ -2567,7 +2565,7 @@ static enum MoveCanceler CancelerPPDeduction(struct BattleContext *ctx)
// For item Metronome, echoed voice
if (ctx->currentMove != gLastResultingMoves[ctx->battlerAtk] || WasUnableToUseMove(ctx->battlerAtk))
gBattleStruct->metronomeItemCounter[ctx->battlerAtk] = 0;
gDisableStructs[ctx->battlerAtk].metronomeItemCounter = 0;
if (gBattleMons[ctx->battlerAtk].pp[movePosition] > ppToDeduct)
gBattleMons[ctx->battlerAtk].pp[movePosition] -= ppToDeduct;
@ -6877,22 +6875,21 @@ const struct TypePower gNaturalGiftTable[] =
[ITEM_TO_BERRY(ITEM_MARANGA_BERRY)] = {TYPE_DARK, 100},
};
u32 CalcRolloutBasePower(u32 battlerAtk, u32 basePower, u32 rolloutTimer)
static inline u32 CalcRolloutBasePower(u32 battlerAtk, u32 basePower)
{
u32 i;
for (i = 1; i < (5 - rolloutTimer); i++)
for (i = 0; i < gDisableStructs[battlerAtk].rolloutTimer; i++)
basePower *= 2;
if (gBattleMons[battlerAtk].volatiles.defenseCurl)
basePower *= 2;
return basePower;
}
u32 CalcFuryCutterBasePower(u32 basePower, u32 furyCutterCounter)
static inline u32 CalcFuryCutterBasePower(u32 battlerAtk, u32 basePower)
{
u32 i;
for (i = 1; i < furyCutterCounter; i++)
for (u32 i = 0; i < gDisableStructs[battlerAtk].furyCutterCounter; i++)
basePower *= 2;
return basePower;
return min(basePower, 160); // The duration to reach 160 depends on a gen
}
static inline u32 IsFieldMudSportAffected(enum Type moveType)
@ -6974,10 +6971,10 @@ static inline u32 CalcMoveBasePower(struct DamageContext *ctx)
basePower = 10 * (MAX_FRIENDSHIP - gBattleMons[battlerAtk].friendship) / 25;
break;
case EFFECT_FURY_CUTTER:
basePower = CalcFuryCutterBasePower(basePower, gDisableStructs[battlerAtk].furyCutterCounter);
basePower = CalcFuryCutterBasePower(battlerAtk, basePower);
break;
case EFFECT_ROLLOUT:
basePower = CalcRolloutBasePower(battlerAtk, basePower, gDisableStructs[battlerAtk].rolloutTimer);
basePower = CalcRolloutBasePower(battlerAtk, basePower);
break;
case EFFECT_MAGNITUDE:
basePower = gBattleStruct->magnitudeBasePower;
@ -8278,7 +8275,7 @@ static inline uq4_12_t GetAttackerItemsModifier(u32 battlerAtk, uq4_12_t typeEff
{
case HOLD_EFFECT_METRONOME:
metronomeBoostBase = PercentToUQ4_12(GetBattlerHoldEffectParam(battlerAtk));
metronomeTurns = min(gBattleStruct->metronomeItemCounter[battlerAtk], 5);
metronomeTurns = min(gDisableStructs[battlerAtk].metronomeItemCounter, 5);
// according to bulbapedia this is the "correct" way to calculate the metronome boost
// due to the limited domain of damage numbers it will never really matter whether this is off by one
return uq4_12_add(UQ_4_12(1.0), metronomeBoostBase * metronomeTurns);
@ -8472,11 +8469,6 @@ static inline s32 DoMoveDamageCalc(struct DamageContext *ctx)
if (dmg != INT32_MAX)
return dmg;
ctx->abilityAtk = GetBattlerAbility(ctx->battlerAtk);
ctx->abilityDef = GetBattlerAbility(ctx->battlerDef);
ctx->holdEffectDef = GetBattlerHoldEffect(ctx->battlerDef);
ctx->holdEffectAtk = GetBattlerHoldEffect(ctx->battlerAtk);
return DoMoveDamageCalcVars(ctx);
}

View File

@ -623,7 +623,7 @@ const struct BattleMoveEffect gBattleMoveEffects[NUM_BATTLE_MOVE_EFFECTS] =
[EFFECT_ROLLOUT] =
{
.battleScript = BattleScript_EffectRollout,
.battleScript = BattleScript_EffectHit,
.battleTvScore = 3,
},
@ -636,7 +636,7 @@ const struct BattleMoveEffect gBattleMoveEffects[NUM_BATTLE_MOVE_EFFECTS] =
[EFFECT_FURY_CUTTER] =
{
.battleScript = BattleScript_EffectFuryCutter,
.battleScript = BattleScript_EffectHit,
.battleTvScore = 2,
},

View File

@ -37,6 +37,51 @@ SINGLE_BATTLE_TEST("Fury Cutter's power doubles with each use, up to 160 power")
}
}
SINGLE_BATTLE_TEST("Fury Cutter's base power resets if the chain has been broken")
{
s16 damage[2];
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_FURY_CUTTER); }
TURN { MOVE(opponent, MOVE_PROTECT); }
TURN { MOVE(player, MOVE_FURY_CUTTER); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_CUTTER, player);
HP_BAR(opponent, captureDamage: &damage[0]);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_CUTTER, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_CUTTER, player);
HP_BAR(opponent, captureDamage: &damage[1]);
} THEN {
EXPECT_EQ(damage[0], damage[1]);
}
}
SINGLE_BATTLE_TEST("Fury Cutter's base power resets if the it is used again but a different user switched in")
{
s16 damage[2];
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_FURY_CUTTER); }
TURN { SWITCH(player, 1); }
TURN { MOVE(player, MOVE_FURY_CUTTER); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_CUTTER, player);
HP_BAR(opponent, captureDamage: &damage[0]);
ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_CUTTER, player);
HP_BAR(opponent, captureDamage: &damage[1]);
} THEN {
EXPECT_EQ(damage[0], damage[1]);
}
}
TO_DO_BATTLE_TEST("Fury Cutter's power is reset if the user misses")
TO_DO_BATTLE_TEST("Fury Cutter's power is reset if the user is switched out")
TO_DO_BATTLE_TEST("Fury Cutter's power is reset if the trainer uses an item")
@ -64,3 +109,23 @@ SINGLE_BATTLE_TEST("Fury Cutter counter is the same for both hits of Parental Bo
EXPECT_NE(damage[0], damage[2]);
}
}
SINGLE_BATTLE_TEST("Fury Cutter's base power resets if original user is forced to switch out")
{
s16 damage[2];
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_FURY_CUTTER); }
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_FURY_CUTTER); }
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); }
} WHEN {
TURN { MOVE(player, MOVE_FURY_CUTTER); }
TURN { MOVE(player, MOVE_FURY_CUTTER); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_CUTTER, player);
HP_BAR(opponent, captureDamage: &damage[0]);
ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_CUTTER, player);
HP_BAR(opponent, captureDamage: &damage[1]);
} THEN {
EXPECT_EQ(damage[0], damage[1]);
}
}

View File

@ -2,3 +2,71 @@
#include "test/battle.h"
TO_DO_BATTLE_TEST("TODO: Write Rollout (Move Effect) test titles")
SINGLE_BATTLE_TEST("Rollout's power doubles after each hit and resest after the 5th hit")
{
s16 damage[6];
int turn;
int maxTurns = 6;
GIVEN {
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_STEELIX);
} WHEN {
TURN { MOVE(player, MOVE_ROLLOUT); }
TURN { SKIP_TURN(player); }
TURN { SKIP_TURN(player); }
TURN { SKIP_TURN(player); }
TURN { SKIP_TURN(player); }
TURN { MOVE(player, MOVE_ROLLOUT); }
} SCENE {
for (turn = 0; turn < maxTurns; turn++) {
ANIMATION(ANIM_TYPE_MOVE, MOVE_ROLLOUT, player);
HP_BAR(opponent, captureDamage: &damage[turn]);
}
} THEN {
for (turn = 1; turn < maxTurns - 1; turn++)
EXPECT_MUL_EQ(damage[turn - 1], UQ_4_12(2.0), damage[turn]);
EXPECT_EQ(damage[0], damage[maxTurns - 1]);
}
}
SINGLE_BATTLE_TEST("Rollout's base power resets if the chain has been broken")
{
s16 damage[2];
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_ROLLOUT, MOVE_CELEBRATE); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_ROLLOUT); }
TURN { MOVE(opponent, MOVE_PROTECT); SKIP_TURN(player); }
TURN { MOVE(player, MOVE_ROLLOUT); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_ROLLOUT, player);
HP_BAR(opponent, captureDamage: &damage[0]);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_ROLLOUT, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_ROLLOUT, player);
HP_BAR(opponent, captureDamage: &damage[1]);
} THEN {
EXPECT_EQ(damage[0], damage[1]);
}
}
SINGLE_BATTLE_TEST("Rollout resets if original user is forced to switch out")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_ROLLOUT, MOVE_CELEBRATE); }
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_ROLLOUT, MOVE_CELEBRATE); }
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); }
} WHEN {
TURN { MOVE(player, MOVE_ROLLOUT); }
TURN { MOVE(player, MOVE_CELEBRATE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_ROLLOUT, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player);
}
}