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

Add RandomChance macro and ability trigger chance config #4829

Merged
merged 2 commits into from
Jun 18, 2024
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
3 changes: 3 additions & 0 deletions include/config/battle.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@
#define B_INTREPID_SWORD GEN_LATEST // In Gen9+, Intrepid Sword raises Attack by one stage only once per Battle.
#define B_DAUNTLESS_SHIELD GEN_LATEST // In Gen9+, Dauntless Shield raises Defense by one stage only once per Battle.
#define B_DISGUISE_HP_LOSS GEN_LATEST // In Gen8+, when a Disguised Mimikyu's Disguise is busted, upon changing to its Busted Form it loses HP equal to 1/8 of its maximum HP.
#define B_ABILITY_TRIGGER_CHANCE GEN_LATEST // In Gen3, Shed Skin, Cute Charm, Flame Body, Static and Poison Point have a 1/3 chance to trigger. In Gen 4+ it's 30%.
// In Gen3, Effect Spore has a 10% chance to sleep, poison or paralyze, with an equal chance.
// In Gen4, it's 30%. In Gen5+ it has 11% to sleep, 9% chance to poison and 10% chance to paralyze.

// Item settings
#define B_HP_BERRIES GEN_LATEST // In Gen4+, berries which restore HP activate immediately after HP drops to half. In Gen3, the effect occurs at the end of the turn.
Expand Down
11 changes: 9 additions & 2 deletions include/random.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,16 @@ static inline void Shuffle(void *data, size_t n, size_t size)
* probability. The array must be known at compile-time (e.g. a global
* const array).
*
* RandomPercentage(tag, t) returns FALSE with probability (1-t)/100,
* RandomPercentage(tag, t) returns FALSE with probability 1-t/100,
* and TRUE with probability t/100.
*
* RandomWeighted(tag, w0, w1, ... wN) returns a number from 0 to N
* inclusive. The return value is proportional to the weights, e.g.
* RandomWeighted(..., 1, 1) returns 50% 0s and 50% 1s.
* RandomWeighted(..., 2, 1) returns 2/3 0s and 1/3 1s. */
* RandomWeighted(..., 2, 1) returns 2/3 0s and 1/3 1s.
*
* RandomChance(tag, successes, total) returns FALSE with probability
* 1-successes/total, and TRUE with probability successes/total. */

enum RandomTag
{
Expand All @@ -161,6 +164,7 @@ enum RandomTag
RNG_CUTE_CHARM,
RNG_DAMAGE_MODIFIER,
RNG_DIRE_CLAW,
RNG_EFFECT_SPORE,
RNG_FLAME_BODY,
RNG_FORCE_RANDOM_SWITCH,
RNG_FROZEN,
Expand All @@ -179,6 +183,7 @@ enum RandomTag
RNG_SECONDARY_EFFECT,
RNG_SECONDARY_EFFECT_2,
RNG_SECONDARY_EFFECT_3,
RNG_SHED_SKIN,
RNG_SLEEP_TURNS,
RNG_SPEED_TIE,
RNG_STATIC,
Expand All @@ -201,6 +206,8 @@ enum RandomTag
RandomWeightedArray(tag, sum, ARRAY_COUNT(weights), weights); \
})

#define RandomChance(tag, successes, total) (RandomWeighted(tag, total - successes, successes))

#define RandomPercentage(tag, t) \
({ \
u32 r; \
Expand Down
2 changes: 1 addition & 1 deletion src/battle_script_commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -1943,7 +1943,7 @@ static void Cmd_critcalc(void)
else if (critChance == -2)
gIsCriticalHit = TRUE;
else
gIsCriticalHit = RandomWeighted(RNG_CRITICAL_HIT, sCriticalHitOdds[critChance] - 1, 1);
gIsCriticalHit = RandomChance(RNG_CRITICAL_HIT, 1, sCriticalHitOdds[critChance]);

// Counter for EVO_CRITICAL_HITS.
partySlot = gBattlerPartyIndexes[gBattlerAttacker];
Expand Down
101 changes: 60 additions & 41 deletions src/battle_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -4861,7 +4861,8 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
}
break;
case ABILITY_SHED_SKIN:
if ((gBattleMons[battler].status1 & STATUS1_ANY) && (Random() % 3) == 0)
if ((gBattleMons[battler].status1 & STATUS1_ANY)
&& (B_ABILITY_TRIGGER_CHANCE >= GEN_4 ? RandomPercentage(RNG_SHED_SKIN, 30) : RandomChance(RNG_SHED_SKIN, 1, 3)))
{
ABILITY_HEAL_MON_STATUS:
if (gBattleMons[battler].status1 & (STATUS1_POISON | STATUS1_TOXIC_POISON))
Expand Down Expand Up @@ -5502,24 +5503,38 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
}
break;
case ABILITY_EFFECT_SPORE:
if (!IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_GRASS)
if ((!IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_GRASS) || B_POWDER_GRASS < GEN_6)
&& GetBattlerAbility(gBattlerAttacker) != ABILITY_OVERCOAT
&& GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_SAFETY_GOGGLES)
{
i = Random() % 3;
if (i == 0)
u32 poison, paralysis, sleep;

if (B_ABILITY_TRIGGER_CHANCE >= GEN_5)
{
poison = 9;
paralysis = 19;
}
else
{
poison = 10;
paralysis = 20;
}
sleep = 30;

i = RandomUniform(RNG_EFFECT_SPORE, 0, B_ABILITY_TRIGGER_CHANCE >= GEN_4 ? 99 : 299);
if (i < poison)
goto POISON_POINT;
if (i == 1)
if (i < paralysis)
goto STATIC;
// Sleep
if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)
if (i < sleep
&& !(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)
&& IsBattlerAlive(gBattlerAttacker)
&& !gProtectStructs[gBattlerAttacker].confusionSelfDmg
&& TARGET_TURN_DAMAGED
&& CanSleep(gBattlerAttacker)
&& GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS
&& IsMoveMakingContact(move, gBattlerAttacker)
&& (Random() % 3) == 0)
&& IsMoveMakingContact(move, gBattlerAttacker))
{
gBattleScripting.moveEffect = MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_SLEEP;
PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility);
Expand All @@ -5530,42 +5545,46 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
}
}
break;
POISON_POINT:
case ABILITY_POISON_POINT:
if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)
&& IsBattlerAlive(gBattlerAttacker)
&& !gProtectStructs[gBattlerAttacker].confusionSelfDmg
&& TARGET_TURN_DAMAGED
&& CanBePoisoned(gBattlerTarget, gBattlerAttacker)
&& GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS
&& IsMoveMakingContact(move, gBattlerAttacker)
&& RandomWeighted(RNG_POISON_POINT, 2, 1))
if (B_ABILITY_TRIGGER_CHANCE >= GEN_4 ? RandomPercentage(RNG_POISON_POINT, 30) : RandomChance(RNG_POISON_POINT, 1, 3))
{
gBattleScripting.moveEffect = MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_POISON;
PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility);
BattleScriptPushCursor();
gBattlescriptCurrInstr = BattleScript_AbilityStatusEffect;
gHitMarker |= HITMARKER_STATUS_ABILITY_EFFECT;
effect++;
POISON_POINT:
if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)
&& IsBattlerAlive(gBattlerAttacker)
&& !gProtectStructs[gBattlerAttacker].confusionSelfDmg
&& TARGET_TURN_DAMAGED
&& CanBePoisoned(gBattlerTarget, gBattlerAttacker)
&& GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS
&& IsMoveMakingContact(move, gBattlerAttacker))
{
gBattleScripting.moveEffect = MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_POISON;
PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility);
BattleScriptPushCursor();
gBattlescriptCurrInstr = BattleScript_AbilityStatusEffect;
gHitMarker |= HITMARKER_STATUS_ABILITY_EFFECT;
effect++;
}
}
break;
STATIC:
case ABILITY_STATIC:
if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)
&& IsBattlerAlive(gBattlerAttacker)
&& !gProtectStructs[gBattlerAttacker].confusionSelfDmg
&& TARGET_TURN_DAMAGED
&& CanBeParalyzed(gBattlerAttacker)
&& GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS
&& IsMoveMakingContact(move, gBattlerAttacker)
&& RandomWeighted(RNG_STATIC, 2, 1))
if (B_ABILITY_TRIGGER_CHANCE >= GEN_4 ? RandomPercentage(RNG_STATIC, 30) : RandomChance(RNG_STATIC, 1, 3))
{
gBattleScripting.moveEffect = MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_PARALYSIS;
PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility);
BattleScriptPushCursor();
gBattlescriptCurrInstr = BattleScript_AbilityStatusEffect;
gHitMarker |= HITMARKER_STATUS_ABILITY_EFFECT;
effect++;
STATIC:
if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)
&& IsBattlerAlive(gBattlerAttacker)
&& !gProtectStructs[gBattlerAttacker].confusionSelfDmg
&& TARGET_TURN_DAMAGED
&& CanBeParalyzed(gBattlerAttacker)
&& GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS
&& IsMoveMakingContact(move, gBattlerAttacker))
{
gBattleScripting.moveEffect = MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_PARALYSIS;
PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility);
BattleScriptPushCursor();
gBattlescriptCurrInstr = BattleScript_AbilityStatusEffect;
gHitMarker |= HITMARKER_STATUS_ABILITY_EFFECT;
effect++;
}
}
break;
case ABILITY_FLAME_BODY:
Expand All @@ -5576,7 +5595,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
&& (IsMoveMakingContact(move, gBattlerAttacker))
&& TARGET_TURN_DAMAGED
&& CanBeBurned(gBattlerAttacker)
&& RandomWeighted(RNG_FLAME_BODY, 2, 1))
&& (B_ABILITY_TRIGGER_CHANCE >= GEN_4 ? RandomPercentage(RNG_FLAME_BODY, 30) : RandomChance(RNG_FLAME_BODY, 1, 3)))
{
gBattleScripting.moveEffect = MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_BURN;
PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility);
Expand All @@ -5592,7 +5611,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
&& !gProtectStructs[gBattlerAttacker].confusionSelfDmg
&& TARGET_TURN_DAMAGED
&& IsBattlerAlive(gBattlerTarget)
&& RandomWeighted(RNG_CUTE_CHARM, 2, 1)
&& (B_ABILITY_TRIGGER_CHANCE >= GEN_4 ? RandomPercentage(RNG_CUTE_CHARM, 30) : RandomChance(RNG_CUTE_CHARM, 1, 3))
&& !(gBattleMons[gBattlerAttacker].status2 & STATUS2_INFATUATION)
&& AreBattlersOfOppositeGender(gBattlerAttacker, gBattlerTarget)
&& GetBattlerAbility(gBattlerAttacker) != ABILITY_OBLIVIOUS
Expand Down Expand Up @@ -5810,7 +5829,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)
&& IsBattlerAlive(gBattlerTarget)
&& !gProtectStructs[gBattlerAttacker].confusionSelfDmg
&& RandomWeighted(RNG_STENCH, 9, 1)
&& RandomChance(RNG_STENCH, 1, 10)
&& TARGET_TURN_DAMAGED
&& !MoveHasAdditionalEffect(gCurrentMove, MOVE_EFFECT_FLINCH))
{
Expand Down
19 changes: 19 additions & 0 deletions test/battle/ability/cute_charm.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,22 @@ SINGLE_BATTLE_TEST("Cute Charm cannot infatuate same gender")
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player);
}
}

SINGLE_BATTLE_TEST("Cute Charm triggers 30% of the time")
{
PASSES_RANDOMLY(3, 10, RNG_CUTE_CHARM);
GIVEN {
ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_4);
ASSUME(gMovesInfo[MOVE_TACKLE].makesContact);
PLAYER(SPECIES_WOBBUFFET) { Gender(MON_MALE); }
OPPONENT(SPECIES_CLEFAIRY) { Gender(MON_FEMALE); Ability(ABILITY_CUTE_CHARM); }
} WHEN {
TURN { MOVE(player, MOVE_TACKLE); }
TURN { MOVE(player, MOVE_TACKLE); }
} SCENE {
ABILITY_POPUP(opponent, ABILITY_CUTE_CHARM);
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_INFATUATION, player);
MESSAGE("Foe Clefairy's Cute Charm infatuated Wobbuffet!");
MESSAGE("Wobbuffet is in love with Foe Clefairy!");
}
}
90 changes: 90 additions & 0 deletions test/battle/ability/effect_spore.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#include "global.h"
#include "test/battle.h"

SINGLE_BATTLE_TEST("Effect Spore only inflicts status on contact")
{
u32 move;

PARAMETRIZE { move = MOVE_TACKLE; }
PARAMETRIZE { move = MOVE_SWIFT; }
GIVEN {
ASSUME(gMovesInfo[MOVE_TACKLE].makesContact);
ASSUME(!gMovesInfo[MOVE_SWIFT].makesContact);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_EFFECT_SPORE); }
} WHEN {
TURN { MOVE(player, move, WITH_RNG(RNG_EFFECT_SPORE, 1)); }
TURN {}
} SCENE {
if (gMovesInfo[move].makesContact) {
ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE);
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player);
MESSAGE("Wobbuffet was poisoned by Foe Breloom's Effect Spore!");
STATUS_ICON(player, poison: TRUE);
} else {
NONE_OF {
ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE);
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player);
MESSAGE("Wobbuffet was poisoned by Foe Breloom's Effect Spore!");
STATUS_ICON(player, poison: TRUE);
}
}
}
}

SINGLE_BATTLE_TEST("Effect Spore causes poison 9% of the time")
{
PASSES_RANDOMLY(9, 100, RNG_EFFECT_SPORE);
GIVEN {
ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_5);
ASSUME(gMovesInfo[MOVE_TACKLE].makesContact);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_EFFECT_SPORE); }
} WHEN {
TURN { MOVE(player, MOVE_TACKLE); }
TURN {}
} SCENE {
ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE);
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player);
MESSAGE("Wobbuffet was poisoned by Foe Breloom's Effect Spore!");
STATUS_ICON(player, poison: TRUE);
}
}

SINGLE_BATTLE_TEST("Effect Spore causes paralysis 10% of the time")
{
PASSES_RANDOMLY(10, 100, RNG_EFFECT_SPORE);
GIVEN {
ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_5);
ASSUME(gMovesInfo[MOVE_TACKLE].makesContact);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_EFFECT_SPORE); }
} WHEN {
TURN { MOVE(player, MOVE_TACKLE); }
TURN {}
} SCENE {
ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE);
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, player);
MESSAGE("Foe Breloom's Effect Spore paralyzed Wobbuffet! It may be unable to move!");
STATUS_ICON(player, paralysis: TRUE);
}
}

SINGLE_BATTLE_TEST("Effect Spore causes sleep 11% of the time")
{
PASSES_RANDOMLY(11, 100, RNG_EFFECT_SPORE);
GIVEN {
ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_5);
ASSUME(gMovesInfo[MOVE_TACKLE].makesContact);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_EFFECT_SPORE); }
} WHEN {
TURN { MOVE(player, MOVE_TACKLE); }
TURN {}
} SCENE {
ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE);
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, player);
MESSAGE("Foe Breloom's Effect Spore made Wobbuffet sleep!");
STATUS_ICON(player, sleep: TRUE);
}
}
18 changes: 18 additions & 0 deletions test/battle/ability/flame_body.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,21 @@ SINGLE_BATTLE_TEST("Flame Body inflicts burn on contact")
}
}
}

SINGLE_BATTLE_TEST("Flame Body triggers 30% of the time")
{
PASSES_RANDOMLY(3, 10, RNG_FLAME_BODY);
GIVEN {
ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_4);
ASSUME(gMovesInfo[MOVE_TACKLE].makesContact);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_MAGMAR) { Ability(ABILITY_FLAME_BODY); }
} WHEN {
TURN { MOVE(player, MOVE_TACKLE); }
} SCENE {
ABILITY_POPUP(opponent, ABILITY_FLAME_BODY);
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, player);
MESSAGE("Foe Magmar's Flame Body burned Wobbuffet!");
STATUS_ICON(player, burn: TRUE);
}
}
19 changes: 19 additions & 0 deletions test/battle/ability/poison_point.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,22 @@ SINGLE_BATTLE_TEST("Poison Point inflicts poison on contact")
}
}
}

SINGLE_BATTLE_TEST("Poison Point triggers 30% of the time")
{
PASSES_RANDOMLY(3, 10, RNG_POISON_POINT);
GIVEN {
ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_4);
ASSUME(gMovesInfo[MOVE_TACKLE].makesContact);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_NIDORAN_M) { Ability(ABILITY_POISON_POINT); }
} WHEN {
TURN { MOVE(player, MOVE_TACKLE); }
TURN {}
} SCENE {
ABILITY_POPUP(opponent, ABILITY_POISON_POINT);
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player);
MESSAGE("Wobbuffet was poisoned by Foe Nidoran♂'s Poison Point!");
STATUS_ICON(player, poison: TRUE);
}
}
Loading
Loading