Skip to content

Commit

Permalink
Add RandomChance macro and ability trigger chance config (#4829)
Browse files Browse the repository at this point in the history
* RandomChance macro, contact ability chance config

* Unify config, add shed skin
  • Loading branch information
Sneed69 committed Jun 18, 2024
1 parent f106b21 commit 609754f
Show file tree
Hide file tree
Showing 11 changed files with 274 additions and 62 deletions.
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 @@ -202,6 +207,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 @@ -5826,7 +5845,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

0 comments on commit 609754f

Please sign in to comment.