Move crit calculation into the damage calc (#8365)
Some checks failed
CI / build (push) Has been cancelled
CI / allcontributors (push) Has been cancelled

Co-authored-by: Pawkkie <61265402+Pawkkie@users.noreply.github.com>
This commit is contained in:
Alex 2025-11-30 19:58:51 +01:00 committed by GitHub
parent 1fa97941c8
commit 55fbbfa586
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 235 additions and 263 deletions

View File

@ -18,6 +18,7 @@
.endm
.macro critcalc
.warning "critcalc macro has been deprecated, please remove from scripts"
.byte 0x4
.endm

View File

@ -25,12 +25,12 @@ BattleScript_EffectFickleBeam::
attackcanceler
accuracycheck BattleScript_MoveMissedPause, ACC_CURR_MOVE
ficklebeamdamagecalculation
goto BattleScript_HitFromCritCalc
goto BattleScript_HitFromDamageCalc
BattleScript_FickleBeamDoubled::
pause B_WAIT_TIME_SHORTEST
printstring STRINGID_FICKLEBEAMDOUBLED
waitmessage B_WAIT_TIME_LONG
goto BattleScript_HitFromCritCalc
goto BattleScript_HitFromDamageCalc
BattleScript_Terastallization::
@ TODO: no string prints in S/V, but right now this helps with clarity
@ -655,7 +655,7 @@ BattleScript_SkyDropTurn2:
call BattleScript_TwoTurnMovesSecondTurnRet
clearskydrop BattleScript_SkyDropChangedTarget
jumpiftype BS_TARGET, TYPE_FLYING, BattleScript_SkyDropFlyingType
goto BattleScript_HitFromCritCalc
goto BattleScript_HitFromDamageCalc
BattleScript_SkyDropFlyingType:
makevisible BS_TARGET
printstring STRINGID_ITDOESNTAFFECT
@ -687,7 +687,6 @@ BattleScript_EffectFling::
pause B_WAIT_TIME_SHORT
printstring STRINGID_PKMNFLUNG
waitmessage B_WAIT_TIME_SHORT
critcalc
damagecalc
adjustdamage
removeitem BS_ATTACKER
@ -773,7 +772,7 @@ BattleScript_EffectPoltergeist::
setpoltergeistmessage BattleScript_ButItFailed
printstring STRINGID_ABOUTTOUSEPOLTERGEIST
waitmessage B_WAIT_TIME_LONG
goto BattleScript_HitFromCritCalc
goto BattleScript_HitFromDamageCalc
BattleScript_EffectTarShot::
attackcanceler
@ -1162,7 +1161,6 @@ BattleScript_EffectSpectralThief::
typecalc
tryspectralthiefsteal BattleScript_SpectralThiefSteal
BattleScript_EffectSpectralThiefFromDamage:
critcalc
damagecalc
adjustdamage
call BattleScript_Hit_RetFromAtkAnimation
@ -1471,7 +1469,7 @@ BattleScript_EffectSynchronoise::
pause B_WAIT_TIME_MED
trysynchronoise BattleScript_MoveEnd
accuracycheck BattleScript_ButItFailed, ACC_CURR_MOVE
goto BattleScript_HitFromCritCalc
goto BattleScript_HitFromDamageCalc
BattleScript_ItDoesntAffectFoe::
savetarget
@ -2386,8 +2384,7 @@ BattleScript_EffectHit::
attackcanceler
BattleScript_HitFromAccCheck::
accuracycheck BattleScript_MoveMissedPause, ACC_CURR_MOVE
BattleScript_HitFromCritCalc::
critcalc
BattleScript_HitFromDamageCalc::
damagecalc
adjustdamage
BattleScript_HitFromAtkAnimation::
@ -2402,8 +2399,6 @@ BattleScript_EffectHit_Ret::
attackcanceler
BattleScript_EffectHit_RetFromAccCheck::
accuracycheck BattleScript_MoveMissedPause, ACC_CURR_MOVE
BattleScript_EffectHit_RetFromCritCalc::
critcalc
damagecalc
adjustdamage
BattleScript_Hit_RetFromAtkAnimation::
@ -2428,7 +2423,7 @@ BattleScript_EffectNaturalGift::
jumpifability BS_ATTACKER, ABILITY_KLUTZ, BattleScript_ButItFailed
jumpifvolatile BS_ATTACKER, VOLATILE_EMBARGO, BattleScript_ButItFailed
accuracycheck BattleScript_MoveMissedPause, ACC_CURR_MOVE
call BattleScript_HitFromCritCalc
call BattleScript_HitFromDamageCalc
BattleScript_MakeMoveMissed::
setmoveresultflags MOVE_RESULT_MISSED
@ -2559,7 +2554,7 @@ BattleScript_EffectExplosion::
waitstate
jumpiffainted BS_TARGET, TRUE, BattleScript_MoveEnd
accuracycheck BattleScript_MoveMissedPause, ACC_CURR_MOVE
goto BattleScript_HitFromCritCalc
goto BattleScript_HitFromDamageCalc
BattleScript_FaintAttackerForExplosion::
tryfaintmon BS_ATTACKER
@ -3072,7 +3067,7 @@ BattleScript_TwoTurnMovesSecondPowerHerbActivates:
BattleScript_FromTwoTurnMovesSecondTurnRet:
call BattleScript_TwoTurnMovesSecondTurnRet
accuracycheck BattleScript_MoveMissedPause, ACC_CURR_MOVE
goto BattleScript_HitFromCritCalc
goto BattleScript_HitFromDamageCalc
BattleScript_TwoTurnMovesSecondTurn::
attackcanceler
@ -3117,7 +3112,7 @@ BattleScript_EffectRage::
attackcanceler
accuracycheck BattleScript_RageMiss, ACC_CURR_MOVE
seteffectprimary BS_ATTACKER, BS_TARGET, MOVE_EFFECT_RAGE
goto BattleScript_HitFromCritCalc
goto BattleScript_HitFromDamageCalc
BattleScript_RageMiss::
clearvolatile BS_ATTACKER, VOLATILE_RAGE
goto BattleScript_MoveMissedPause
@ -3231,7 +3226,7 @@ BattleScript_EffectSnore::
statusanimation BS_ATTACKER
BattleScript_DoSnore::
accuracycheck BattleScript_MoveMissedPause, ACC_CURR_MOVE
goto BattleScript_HitFromCritCalc
goto BattleScript_HitFromDamageCalc
BattleScript_EffectConversion2::
attackcanceler
@ -3478,7 +3473,7 @@ BattleScript_RolloutCheckAccuracy::
BattleScript_RolloutHit::
typecalc
handlerollout
goto BattleScript_HitFromCritCalc
goto BattleScript_HitFromDamageCalc
BattleScript_EffectSwagger::
attackcanceler
@ -3503,7 +3498,6 @@ BattleScript_EffectFuryCutter::
accuracycheck BattleScript_FuryCutterHit, ACC_CURR_MOVE
BattleScript_FuryCutterHit:
handlefurycutter
critcalc
damagecalc
jumpifmovehadnoeffect BattleScript_FuryCutterHit
adjustdamage
@ -3564,7 +3558,7 @@ BattleScript_EffectMagnitude::
printstring STRINGID_MAGNITUDESTRENGTH
waitmessage B_WAIT_TIME_LONG
accuracycheck BattleScript_MoveMissedPause, ACC_CURR_MOVE
goto BattleScript_HitFromCritCalc
goto BattleScript_HitFromDamageCalc
BattleScript_EffectBatonPass::
attackcanceler
@ -3714,7 +3708,7 @@ BattleScript_EffectBeatUpGen3:
pause B_WAIT_TIME_SHORT
trydobeatup BattleScript_MoveEnd, BattleScript_ButItFailed
printstring STRINGID_PKMNATTACK
goto BattleScript_HitFromCritCalc
goto BattleScript_HitFromDamageCalc
BattleScript_EffectDefenseCurl::
attackcanceler
@ -4069,7 +4063,6 @@ BattleScript_EffectBrickBreak::
accuracycheck BattleScript_MoveMissedPause, ACC_CURR_MOVE
typecalc
removescreens
critcalc
damagecalc
adjustdamage
jumpifbyte CMP_EQUAL, sB_ANIM_TURN, 0, BattleScript_BrickBreakAnim
@ -5353,7 +5346,6 @@ BattleScript_MonTookFutureAttack::
BattleScript_CheckDoomDesireMiss::
accuracycheck BattleScript_FutureAttackMiss, MOVE_DOOM_DESIRE
BattleScript_FutureAttackAnimate::
critcalc
damagecalc
adjustdamage
jumpifmovehadnoeffect BattleScript_DoFutureAttackResult
@ -8215,7 +8207,7 @@ BattleScript_TargetAbilityStatRaiseRet_End:
BattleScript_EffectMaxMove::
attackcanceler
accuracycheck BattleScript_ButItFailed, NO_ACC_CALC_CHECK_LOCK_ON
goto BattleScript_HitFromCritCalc
goto BattleScript_HitFromDamageCalc
BattleScript_EffectRaiseStatAllies::
savetarget

View File

@ -764,7 +764,6 @@ struct BattleStruct
u16 prevTurnSpecies[MAX_BATTLERS_COUNT]; // Stores species the AI has in play at start of turn
s16 passiveHpUpdate[MAX_BATTLERS_COUNT]; // non-move damage and healing
s16 moveDamage[MAX_BATTLERS_COUNT];
s16 critChance[MAX_BATTLERS_COUNT];
u16 moveResultFlags[MAX_BATTLERS_COUNT];
u8 missStringId[MAX_BATTLERS_COUNT];
enum CalcDamageState noResultString[MAX_BATTLERS_COUNT];

View File

@ -40,9 +40,6 @@ union TRANSPARENT StatChangeFlags
};
};
s32 CalcCritChanceStage(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, enum Ability abilityAtk, enum Ability abilityDef, enum HoldEffect holdEffectAtk);
s32 CalcCritChanceStageGen1(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, enum Ability abilityAtk, enum Ability abilityDef, enum HoldEffect holdEffectAtk);
s32 GetCritHitOdds(s32 critChanceIndex);
bool32 HasBattlerActedThisTurn(u32 battler);
u32 GetBattlerTurnOrderNum(u32 battler);
bool32 NoAliveMonsForBattlerSide(u32 battler);

View File

@ -6,7 +6,7 @@ extern const u8 BattleScript_OpportunistCopyStatChange[];
extern const u8 BattleScript_MirrorHerbCopyStatChange[];
extern const u8 BattleScript_MirrorHerbCopyStatChangeEnd2[];
extern const u8 BattleScript_NotAffected[];
extern const u8 BattleScript_HitFromCritCalc[];
extern const u8 BattleScript_HitFromDamageCalc[];
extern const u8 BattleScript_MoveEnd[];
extern const u8 BattleScript_MakeMoveMissed[];
extern const u8 BattleScript_MoveMissedPause[];

View File

@ -301,6 +301,8 @@ 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);
s32 CalculateMoveDamageVars(struct DamageContext *ctx);
s32 DoFixedDamageMoveCalc(struct DamageContext *ctx);

View File

@ -868,26 +868,27 @@ static inline void CalcDynamicMoveDamage(struct DamageContext *ctx, u16 *medianD
*maximumDamage = maximum;
}
static inline bool32 ShouldCalcCritDamage(u32 battlerAtk, u32 battlerDef, u32 move, struct AiLogicData *aiData)
static inline bool32 ShouldCalcCritDamage(struct DamageContext *ctx)
{
s32 critChanceIndex = 0;
// Get crit chance
if (GetGenConfig(GEN_CONFIG_CRIT_CHANCE) == GEN_1)
critChanceIndex = CalcCritChanceStageGen1(battlerAtk, battlerDef, move, FALSE, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], aiData->holdEffects[battlerAtk]);
critChanceIndex = CalcCritChanceStageGen1(ctx);
else
critChanceIndex = CalcCritChanceStage(battlerAtk, battlerDef, move, FALSE, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], aiData->holdEffects[battlerAtk]);
critChanceIndex = CalcCritChanceStage(ctx);
if (critChanceIndex == CRITICAL_HIT_ALWAYS)
return TRUE;
if (critChanceIndex >= RISKY_AI_CRIT_STAGE_THRESHOLD // Not guaranteed but above Risky threshold
&& (gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_RISKY)
&& (gAiThinkingStruct->aiFlags[ctx->battlerAtk] & AI_FLAG_RISKY)
&& GetGenConfig(GEN_CONFIG_CRIT_CHANCE) != GEN_1)
return TRUE;
if (critChanceIndex >= RISKY_AI_CRIT_THRESHOLD_GEN_1 // Not guaranteed but above Risky threshold
&& (gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_RISKY)
&& (gAiThinkingStruct->aiFlags[ctx->battlerAtk] & AI_FLAG_RISKY)
&& GetGenConfig(GEN_CONFIG_CRIT_CHANCE) == GEN_1)
return TRUE;
return FALSE;
}
@ -931,7 +932,6 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u
ctx.battlerDef = battlerDef;
ctx.move = ctx.chosenMove = move;
ctx.moveType = GetBattleMoveType(move);
ctx.isCrit = ShouldCalcCritDamage(battlerAtk, battlerDef, move, aiData);
ctx.randomFactor = FALSE;
ctx.updateFlags = FALSE;
ctx.weather = weather;
@ -940,6 +940,7 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u
ctx.holdEffectDef = aiData->holdEffects[battlerDef];
ctx.abilityAtk = aiData->abilities[battlerAtk];
ctx.abilityDef = AI_GetMoldBreakerSanitizedAbility(battlerAtk, ctx.abilityAtk, aiData->abilities[battlerDef], ctx.holdEffectDef, move);
ctx.isCrit = ShouldCalcCritDamage(&ctx);
ctx.typeEffectivenessModifier = CalcTypeEffectivenessMultiplier(&ctx);
u32 movePower = GetMovePower(move);

View File

@ -1544,222 +1544,11 @@ static void Cmd_unused_0x3(void)
{
}
// The chance is 1/N for each stage.
static const u32 sGen7CriticalHitOdds[] = {24, 8, 2, 1, 1}; // 1/X
static const u32 sGen6CriticalHitOdds[] = {16, 8, 2, 1, 1}; // 1/X
static const u32 sCriticalHitOdds[] = {16, 8, 4, 3, 2}; // 1/X, Gens 3,4,5
static const u32 sGen2CriticalHitOdds[] = {17, 32, 64, 85, 128}; // X/256
static inline u32 GetCriticalHitOdds(u32 critChance)
{
if (GetGenConfig(GEN_CONFIG_CRIT_CHANCE) >= GEN_7)
return sGen7CriticalHitOdds[critChance];
if (GetGenConfig(GEN_CONFIG_CRIT_CHANCE) == GEN_6)
return sGen6CriticalHitOdds[critChance];
if (GetGenConfig(GEN_CONFIG_CRIT_CHANCE) == GEN_2)
return sGen2CriticalHitOdds[critChance];
return sCriticalHitOdds[critChance];
}
static inline u32 IsBattlerLeekAffected(u32 battler, enum HoldEffect holdEffect)
{
if (holdEffect == HOLD_EFFECT_LEEK)
{
return GET_BASE_SPECIES_ID(gBattleMons[battler].species) == SPECIES_FARFETCHD
|| gBattleMons[battler].species == SPECIES_SIRFETCHD;
}
return FALSE;
}
static inline u32 GetHoldEffectCritChanceIncrease(u32 battler, enum HoldEffect holdEffect)
{
u32 critStageIncrease = 0;
switch (holdEffect)
{
case HOLD_EFFECT_SCOPE_LENS:
critStageIncrease = 1;
break;
case HOLD_EFFECT_LUCKY_PUNCH:
if (gBattleMons[battler].species == SPECIES_CHANSEY)
critStageIncrease = 2;
break;
case HOLD_EFFECT_LEEK:
if (IsBattlerLeekAffected(battler, holdEffect))
critStageIncrease = 2;
break;
default:
critStageIncrease = 0;
break;
}
return critStageIncrease;
}
s32 CalcCritChanceStage(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, enum Ability abilityAtk, enum Ability abilityDef, enum HoldEffect holdEffectAtk)
{
s32 critChance = 0;
if (gSideStatuses[battlerDef] & SIDE_STATUS_LUCKY_CHANT)
{
critChance = CRITICAL_HIT_BLOCKED;
}
else if (gBattleMons[battlerAtk].volatiles.laserFocus
|| MoveAlwaysCrits(move)
|| (abilityAtk == ABILITY_MERCILESS && gBattleMons[battlerDef].status1 & STATUS1_PSN_ANY))
{
critChance = CRITICAL_HIT_ALWAYS;
}
else
{
critChance = (gBattleMons[battlerAtk].volatiles.focusEnergy != 0 ? 2 : 0)
+ (gBattleMons[battlerAtk].volatiles.dragonCheer != 0 ? 1 : 0)
+ GetMoveCriticalHitStage(move)
+ GetHoldEffectCritChanceIncrease(battlerAtk, holdEffectAtk)
+ ((B_AFFECTION_MECHANICS == TRUE && GetBattlerAffectionHearts(battlerAtk) == AFFECTION_FIVE_HEARTS) ? 2 : 0)
+ (abilityAtk == ABILITY_SUPER_LUCK ? 1 : 0)
+ gBattleStruct->bonusCritStages[gBattlerAttacker];
if (critChance >= ARRAY_COUNT(sCriticalHitOdds))
critChance = ARRAY_COUNT(sCriticalHitOdds) - 1;
}
if (critChance != CRITICAL_HIT_BLOCKED && (abilityDef == ABILITY_BATTLE_ARMOR || abilityDef == ABILITY_SHELL_ARMOR))
{
// Record ability only if move had 100% chance to get a crit
if (recordAbility)
{
if (critChance == CRITICAL_HIT_ALWAYS)
RecordAbilityBattle(battlerDef, abilityDef);
else if (GetCriticalHitOdds(critChance) == 1)
RecordAbilityBattle(battlerDef, abilityDef);
}
critChance = CRITICAL_HIT_BLOCKED;
}
return critChance;
}
// Bulbapedia: https://bulbapedia.bulbagarden.net/wiki/Critical_hit#Generation_I
// Crit chance = Threshold / 256, Threshold maximum of 255
// Threshold = Base Speed / 2
// High crit move = 8 * (Base Speed / 2)
// Focus Energy = 4 * (Base Speed / 2)
s32 CalcCritChanceStageGen1(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, enum Ability abilityAtk, enum Ability abilityDef, enum HoldEffect holdEffectAtk)
{
s32 critChance = 0;
s32 moveCritStage = GetMoveCriticalHitStage(gCurrentMove);
s32 bonusCritStage = gBattleStruct->bonusCritStages[battlerAtk]; // G-Max Chi Strike
u32 holdEffectCritStage = GetHoldEffectCritChanceIncrease(battlerAtk, holdEffectAtk);
u16 baseSpeed = GetSpeciesBaseSpeed(gBattleMons[battlerAtk].species);
critChance = baseSpeed / 2;
// Crit scaling
if (moveCritStage > 0)
critChance *= 8 * moveCritStage;
if (bonusCritStage > 0)
critChance *= bonusCritStage;
if (gBattleMons[battlerAtk].volatiles.focusEnergy)
critChance *= 4;
else if (gBattleMons[battlerAtk].volatiles.dragonCheer)
critChance *= 2;
if (holdEffectCritStage > 0)
critChance *= 4 * holdEffectCritStage;
if (abilityAtk == ABILITY_SUPER_LUCK)
critChance *= 4;
if (critChance > 255)
critChance = 255;
// Prevented crits
if (gSideStatuses[battlerDef] & SIDE_STATUS_LUCKY_CHANT)
critChance = CRITICAL_HIT_BLOCKED;
else if (abilityDef == ABILITY_BATTLE_ARMOR || abilityDef == ABILITY_SHELL_ARMOR)
{
if (recordAbility)
RecordAbilityBattle(battlerDef, abilityDef);
critChance = CRITICAL_HIT_BLOCKED;
}
// Guaranteed crits
else if (gBattleMons[battlerAtk].volatiles.laserFocus
|| MoveAlwaysCrits(move)
|| (abilityAtk == ABILITY_MERCILESS && gBattleMons[battlerDef].status1 & STATUS1_PSN_ANY))
{
critChance = CRITICAL_HIT_ALWAYS;
}
return critChance;
}
s32 GetCritHitOdds(s32 critChanceIndex)
{
if (critChanceIndex < 0)
return -1;
else
return GetCriticalHitOdds(critChanceIndex);
}
// Calculations have been moved to cmd_damagecalc. Please remove the macro from scripts
// Will be set to unused next cycle
static void Cmd_critcalc(void)
{
CMD_ARGS();
u32 partySlot = gBattlerPartyIndexes[gBattlerAttacker],
moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove),
battlerDef;
bool32 calcSpreadMoveDamage = IsSpreadMove(moveTarget) && !IsBattleMoveStatus(gCurrentMove);
enum HoldEffect holdEffectAtk = GetBattlerHoldEffect(gBattlerAttacker);
gPotentialItemEffectBattler = gBattlerAttacker;
for (battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
{
if (gBattleStruct->calculatedDamageDone)
break;
if (!calcSpreadMoveDamage && battlerDef != gBattlerTarget)
continue;
if (IsBattlerInvalidForSpreadMove(gBattlerAttacker, battlerDef, moveTarget)
|| gBattleStruct->noResultString[battlerDef] != CAN_DAMAGE
|| gBattleStruct->moveResultFlags[battlerDef] & MOVE_RESULT_NO_EFFECT)
continue;
enum Ability abilityDef = GetBattlerAbility(battlerDef);
enum Ability abilityAtk = GetBattlerAbility(gBattlerAttacker);
if (GetGenConfig(GEN_CONFIG_CRIT_CHANCE) == GEN_1)
gBattleStruct->critChance[battlerDef] = CalcCritChanceStageGen1(gBattlerAttacker, battlerDef, gCurrentMove, TRUE, abilityAtk, abilityDef, holdEffectAtk);
else
gBattleStruct->critChance[battlerDef] = CalcCritChanceStage(gBattlerAttacker, battlerDef, gCurrentMove, TRUE, abilityAtk, abilityDef, holdEffectAtk);
if (gBattleTypeFlags & (BATTLE_TYPE_WALLY_TUTORIAL | BATTLE_TYPE_FIRST_BATTLE))
gSpecialStatuses[battlerDef].criticalHit = FALSE;
else if (gBattleStruct->critChance[battlerDef] == -1)
gSpecialStatuses[battlerDef].criticalHit = FALSE;
else if (gBattleStruct->critChance[battlerDef] == -2)
gSpecialStatuses[battlerDef].criticalHit = TRUE;
else
{
if (GetGenConfig(GEN_CONFIG_CRIT_CHANCE) == GEN_1)
gSpecialStatuses[battlerDef].criticalHit = RandomChance(RNG_CRITICAL_HIT, gBattleStruct->critChance[battlerDef], 256);
else if (GetGenConfig(GEN_CONFIG_CRIT_CHANCE) == GEN_2)
gSpecialStatuses[battlerDef].criticalHit = RandomChance(RNG_CRITICAL_HIT, GetCriticalHitOdds(gBattleStruct->critChance[battlerDef]), 256);
else
gSpecialStatuses[battlerDef].criticalHit = RandomChance(RNG_CRITICAL_HIT, 1, GetCriticalHitOdds(gBattleStruct->critChance[battlerDef]));
}
// Counter for IF_CRITICAL_HITS_GE evolution condition.
if (gSpecialStatuses[battlerDef].criticalHit && IsOnPlayerSide(gBattlerAttacker)
&& !(gBattleTypeFlags & BATTLE_TYPE_MULTI && GetBattlerPosition(gBattlerAttacker) == B_POSITION_PLAYER_LEFT))
gPartyCriticalHits[partySlot]++;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
@ -11804,7 +11593,7 @@ static void Cmd_presentdamagecalculation(void)
if (gBattleStruct->presentBasePower)
{
gBattlescriptCurrInstr = BattleScript_HitFromCritCalc;
gBattlescriptCurrInstr = BattleScript_HitFromDamageCalc;
}
else if (gBattleMons[gBattlerTarget].maxHP == gBattleMons[gBattlerTarget].hp)
{

View File

@ -7429,7 +7429,7 @@ static inline u32 CalcMoveBasePowerAfterModifiers(struct DamageContext *ctx)
case ABILITY_PROTOSYNTHESIS:
{
enum Stat defHighestStat = GetParadoxBoostedStatId(battlerDef);
if (((ctx->weather & B_WEATHER_SUN && HasWeatherEffect()) || gDisableStructs[battlerDef].boosterEnergyActivated)
if ((ctx->weather & B_WEATHER_SUN || gDisableStructs[battlerDef].boosterEnergyActivated)
&& ((IsBattleMovePhysical(move) && defHighestStat == STAT_DEF) || (IsBattleMoveSpecial(move) && defHighestStat == STAT_SPDEF))
&& !(gBattleMons[battlerDef].volatiles.transformed))
modifier = uq4_12_multiply(modifier, UQ_4_12(0.7));
@ -7729,7 +7729,7 @@ static inline u32 CalcAttackStat(struct DamageContext *ctx)
if (!(gBattleMons[battlerAtk].volatiles.transformed))
{
enum Stat atkHighestStat = GetParadoxBoostedStatId(battlerAtk);
if (((ctx->weather & B_WEATHER_SUN) && HasWeatherEffect()) || gDisableStructs[battlerAtk].boosterEnergyActivated)
if (ctx->weather & B_WEATHER_SUN || gDisableStructs[battlerAtk].boosterEnergyActivated)
{
if ((IsBattleMovePhysical(move) && atkHighestStat == STAT_ATK) || (IsBattleMoveSpecial(move) && atkHighestStat == STAT_SPATK))
modifier = uq4_12_multiply(modifier, UQ_4_12(1.3));
@ -7748,7 +7748,7 @@ static inline u32 CalcAttackStat(struct DamageContext *ctx)
}
break;
case ABILITY_ORICHALCUM_PULSE:
if ((ctx->weather & B_WEATHER_SUN) && HasWeatherEffect() && IsBattleMovePhysical(move))
if (ctx->weather & B_WEATHER_SUN && IsBattleMovePhysical(move))
modifier = uq4_12_multiply(modifier, UQ_4_12(1.3333));
break;
case ABILITY_HADRON_ENGINE:
@ -8531,16 +8531,6 @@ static inline s32 DoFutureSightAttackDamageCalc(struct DamageContext *ctx)
return DoFutureSightAttackDamageCalcVars(ctx);
}
#undef DAMAGE_APPLY_MODIFIER
static u32 GetWeather(void)
{
if (gBattleWeather == B_WEATHER_NONE || !HasWeatherEffect())
return B_WEATHER_NONE;
else
return gBattleWeather;
}
bool32 IsFutureSightAttackerInParty(u32 battlerAtk, u32 battlerDef, u32 move)
{
if (GetMoveEffect(move) != EFFECT_FUTURE_SIGHT)
@ -8556,6 +8546,206 @@ bool32 IsFutureSightAttackerInParty(u32 battlerAtk, u32 battlerDef, u32 move)
return &party[gWishFutureKnock.futureSightPartyIndex[battlerDef]] != &party[gBattlerPartyIndexes[battlerAtk]];
}
#undef DAMAGE_APPLY_MODIFIER
static u32 GetWeather(void)
{
if (gBattleWeather == B_WEATHER_NONE || !HasWeatherEffect())
return B_WEATHER_NONE;
else
return gBattleWeather;
}
// The chance is 1/N for each stage.
static const u32 sGen7CriticalHitOdds[] = {24, 8, 2, 1, 1}; // 1/X
static const u32 sGen6CriticalHitOdds[] = {16, 8, 2, 1, 1}; // 1/X
static const u32 sCriticalHitOdds[] = {16, 8, 4, 3, 2}; // 1/X, Gens 3,4,5
static const u32 sGen2CriticalHitOdds[] = {17, 32, 64, 85, 128}; // X/256
static inline u32 GetCriticalHitOdds(u32 critChance)
{
if (GetGenConfig(GEN_CONFIG_CRIT_CHANCE) >= GEN_7)
return sGen7CriticalHitOdds[critChance];
if (GetGenConfig(GEN_CONFIG_CRIT_CHANCE) == GEN_6)
return sGen6CriticalHitOdds[critChance];
if (GetGenConfig(GEN_CONFIG_CRIT_CHANCE) == GEN_2)
return sGen2CriticalHitOdds[critChance];
return sCriticalHitOdds[critChance];
}
static inline u32 IsBattlerLeekAffected(u32 battler, enum HoldEffect holdEffect)
{
if (holdEffect == HOLD_EFFECT_LEEK)
{
return GET_BASE_SPECIES_ID(gBattleMons[battler].species) == SPECIES_FARFETCHD
|| gBattleMons[battler].species == SPECIES_SIRFETCHD;
}
return FALSE;
}
static inline u32 GetHoldEffectCritChanceIncrease(u32 battler, enum HoldEffect holdEffect)
{
u32 critStageIncrease = 0;
switch (holdEffect)
{
case HOLD_EFFECT_SCOPE_LENS:
critStageIncrease = 1;
break;
case HOLD_EFFECT_LUCKY_PUNCH:
if (gBattleMons[battler].species == SPECIES_CHANSEY)
critStageIncrease = 2;
break;
case HOLD_EFFECT_LEEK:
if (IsBattlerLeekAffected(battler, holdEffect))
critStageIncrease = 2;
break;
default:
critStageIncrease = 0;
break;
}
return critStageIncrease;
}
s32 CalcCritChanceStage(struct DamageContext *ctx)
{
s32 critChance = 0;
if (gSideStatuses[ctx->battlerDef] & SIDE_STATUS_LUCKY_CHANT)
{
critChance = CRITICAL_HIT_BLOCKED;
}
else if (gBattleMons[ctx->battlerAtk].volatiles.laserFocus
|| MoveAlwaysCrits(ctx->move)
|| (ctx->abilityAtk == ABILITY_MERCILESS && gBattleMons[ctx->battlerDef].status1 & STATUS1_PSN_ANY))
{
critChance = CRITICAL_HIT_ALWAYS;
}
else
{
critChance = (gBattleMons[ctx->battlerAtk].volatiles.focusEnergy != 0 ? 2 : 0)
+ (gBattleMons[ctx->battlerAtk].volatiles.dragonCheer != 0 ? 1 : 0)
+ GetMoveCriticalHitStage(ctx->move)
+ GetHoldEffectCritChanceIncrease(ctx->battlerAtk, ctx->holdEffectAtk)
+ ((B_AFFECTION_MECHANICS == TRUE && GetBattlerAffectionHearts(ctx->battlerAtk) == AFFECTION_FIVE_HEARTS) ? 2 : 0)
+ (ctx->abilityAtk == ABILITY_SUPER_LUCK ? 1 : 0)
+ gBattleStruct->bonusCritStages[ctx->battlerAtk];
if (critChance >= ARRAY_COUNT(sCriticalHitOdds))
critChance = ARRAY_COUNT(sCriticalHitOdds) - 1;
}
if (critChance != CRITICAL_HIT_BLOCKED && (ctx->abilityDef == ABILITY_BATTLE_ARMOR || ctx->abilityDef == ABILITY_SHELL_ARMOR))
{
// Record ability only if move had 100% chance to get a crit
if (ctx->updateFlags)
{
if (critChance == CRITICAL_HIT_ALWAYS)
RecordAbilityBattle(ctx->battlerDef, ctx->abilityDef);
else if (GetCriticalHitOdds(critChance) == 1)
RecordAbilityBattle(ctx->battlerDef, ctx->abilityDef);
}
critChance = CRITICAL_HIT_BLOCKED;
}
return critChance;
}
// Bulbapedia: https://bulbapedia.bulbagarden.net/wiki/Critical_hit#Generation_I
// Crit chance = Threshold / 256, Threshold maximum of 255
// Threshold = Base Speed / 2
// High crit move = 8 * (Base Speed / 2)
// Focus Energy = 4 * (Base Speed / 2)
s32 CalcCritChanceStageGen1(struct DamageContext *ctx)
{
s32 critChance = 0;
s32 moveCritStage = GetMoveCriticalHitStage(ctx->move);
s32 bonusCritStage = gBattleStruct->bonusCritStages[ctx->battlerAtk]; // G-Max Chi Strike
u32 holdEffectCritStage = GetHoldEffectCritChanceIncrease(ctx->battlerAtk, ctx->holdEffectAtk);
u16 baseSpeed = GetSpeciesBaseSpeed(gBattleMons[ctx->battlerAtk].species);
critChance = baseSpeed / 2;
// Crit scaling
if (moveCritStage > 0)
critChance *= 8 * moveCritStage;
if (bonusCritStage > 0)
critChance *= bonusCritStage;
if (gBattleMons[ctx->battlerAtk].volatiles.focusEnergy)
critChance *= 4;
else if (gBattleMons[ctx->battlerAtk].volatiles.dragonCheer)
critChance *= 2;
if (holdEffectCritStage > 0)
critChance *= 4 * holdEffectCritStage;
if (ctx->abilityAtk == ABILITY_SUPER_LUCK)
critChance *= 4;
if (critChance > 255)
critChance = 255;
// Prevented crits
if (gSideStatuses[ctx->battlerDef] & SIDE_STATUS_LUCKY_CHANT)
critChance = CRITICAL_HIT_BLOCKED;
else if (ctx->abilityDef == ABILITY_BATTLE_ARMOR || ctx->abilityDef == ABILITY_SHELL_ARMOR)
{
if (ctx->updateFlags)
RecordAbilityBattle(ctx->battlerDef, ctx->abilityDef);
critChance = CRITICAL_HIT_BLOCKED;
}
// Guaranteed crits
else if (gBattleMons[ctx->battlerAtk].volatiles.laserFocus
|| MoveAlwaysCrits(ctx->move)
|| (ctx->abilityAtk == ABILITY_MERCILESS && gBattleMons[ctx->battlerDef].status1 & STATUS1_PSN_ANY))
{
critChance = CRITICAL_HIT_ALWAYS;
}
return critChance;
}
static bool32 IsCriticalHit(struct DamageContext *ctx)
{
bool32 isCrit = FALSE;
s32 critChance = 0;
if (GetGenConfig(GEN_CONFIG_CRIT_CHANCE) == GEN_1)
critChance = CalcCritChanceStageGen1(ctx);
else
critChance = CalcCritChanceStage(ctx);
if (gBattleTypeFlags & (BATTLE_TYPE_WALLY_TUTORIAL | BATTLE_TYPE_FIRST_BATTLE))
isCrit = FALSE;
else if (critChance == -1)
isCrit = FALSE;
else if (critChance == -2)
isCrit = TRUE;
else
{
if (GetGenConfig(GEN_CONFIG_CRIT_CHANCE) == GEN_1)
isCrit = RandomChance(RNG_CRITICAL_HIT, critChance, 256);
else if (GetGenConfig(GEN_CONFIG_CRIT_CHANCE) == GEN_2)
isCrit = RandomChance(RNG_CRITICAL_HIT, GetCriticalHitOdds(critChance), 256);
else
isCrit = RandomChance(RNG_CRITICAL_HIT, 1, GetCriticalHitOdds(critChance));
}
// Counter for IF_CRITICAL_HITS_GE evolution condition.
if (isCrit && IsOnPlayerSide(ctx->battlerAtk)
&& !(gBattleTypeFlags & BATTLE_TYPE_MULTI && GetBattlerPosition(ctx->battlerAtk) == B_POSITION_PLAYER_LEFT))
gPartyCriticalHits[gBattlerPartyIndexes[ctx->battlerAtk]]++;
gSpecialStatuses[ctx->battlerDef].criticalHit = isCrit;
return isCrit;
}
s32 CalculateMoveDamage(struct DamageContext *ctx)
{
ctx->weather = GetWeather();
@ -8565,6 +8755,8 @@ s32 CalculateMoveDamage(struct DamageContext *ctx)
ctx->holdEffectDef = GetBattlerHoldEffect(ctx->battlerDef);
ctx->typeEffectivenessModifier = CalcTypeEffectivenessMultiplier(ctx);
if (!ctx->isSelfInflicted)
ctx->isCrit = IsCriticalHit(ctx);
if (IsFutureSightAttackerInParty(ctx->battlerAtk, ctx->battlerDef, ctx->move))
return DoFutureSightAttackDamageCalc(ctx);
@ -8615,7 +8807,7 @@ static inline void MulByTypeEffectiveness(struct DamageContext *ctx, uq4_12_t *m
mod = UQ_4_12(2.0);
// B_WEATHER_STRONG_WINDS weakens Super Effective moves against Flying-type Pokémon
if (gBattleWeather & B_WEATHER_STRONG_WINDS && HasWeatherEffect() && !ctx->isAnticipation)
if (ctx->weather & B_WEATHER_STRONG_WINDS && !ctx->isAnticipation)
{
if (defType == TYPE_FLYING && mod >= UQ_4_12(2.0))
mod = UQ_4_12(1.0);
@ -10371,7 +10563,6 @@ void ClearDamageCalcResults(void)
for (u32 battler = 0; battler < MAX_BATTLERS_COUNT; battler++)
{
gBattleStruct->moveDamage[battler] = 0;
gBattleStruct->critChance[battler] = 0;
gBattleStruct->moveResultFlags[battler] = CAN_DAMAGE;
gBattleStruct->noResultString[battler] = 0;
gBattleStruct->missStringId[battler] = 0;