RandomElement for structured RNG (#2868)
This commit is contained in:
commit
5c1efe9419
@ -29,7 +29,12 @@ void SeedRng2(u16 seed);
|
|||||||
*
|
*
|
||||||
* RandomTag identifies the purpose of the value.
|
* RandomTag identifies the purpose of the value.
|
||||||
*
|
*
|
||||||
* RandomUniform(tag, lo, hi) returns a number from lo to hi inclusive.
|
* RandomUniform(tag, lo, hi) returns a number from lo to hi inclusive
|
||||||
|
* with uniform probability.
|
||||||
|
*
|
||||||
|
* RandomElement(tag, array) returns an element in array with uniform
|
||||||
|
* 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.
|
* and TRUE with probability t/100.
|
||||||
@ -47,6 +52,7 @@ enum RandomTag
|
|||||||
RNG_CRITICAL_HIT,
|
RNG_CRITICAL_HIT,
|
||||||
RNG_CUTE_CHARM,
|
RNG_CUTE_CHARM,
|
||||||
RNG_DAMAGE_MODIFIER,
|
RNG_DAMAGE_MODIFIER,
|
||||||
|
RNG_DIRE_CLAW,
|
||||||
RNG_FLAME_BODY,
|
RNG_FLAME_BODY,
|
||||||
RNG_FORCE_RANDOM_SWITCH,
|
RNG_FORCE_RANDOM_SWITCH,
|
||||||
RNG_FROZEN,
|
RNG_FROZEN,
|
||||||
@ -60,6 +66,7 @@ enum RandomTag
|
|||||||
RNG_SPEED_TIE,
|
RNG_SPEED_TIE,
|
||||||
RNG_STATIC,
|
RNG_STATIC,
|
||||||
RNG_STENCH,
|
RNG_STENCH,
|
||||||
|
RNG_TRI_ATTACK,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define RandomWeighted(tag, ...) \
|
#define RandomWeighted(tag, ...) \
|
||||||
@ -73,14 +80,34 @@ enum RandomTag
|
|||||||
|
|
||||||
#define RandomPercentage(tag, t) \
|
#define RandomPercentage(tag, t) \
|
||||||
({ \
|
({ \
|
||||||
const u8 weights[] = { 100 - t, t }; \
|
u32 r; \
|
||||||
RandomWeightedArray(tag, 100, ARRAY_COUNT(weights), weights); \
|
if (t <= 0) \
|
||||||
|
{ \
|
||||||
|
r = FALSE; \
|
||||||
|
} \
|
||||||
|
else if (t >= 100) \
|
||||||
|
{ \
|
||||||
|
r = TRUE; \
|
||||||
|
} \
|
||||||
|
else \
|
||||||
|
{ \
|
||||||
|
const u8 weights[] = { 100 - t, t }; \
|
||||||
|
r = RandomWeightedArray(tag, 100, ARRAY_COUNT(weights), weights); \
|
||||||
|
} \
|
||||||
|
r; \
|
||||||
|
})
|
||||||
|
|
||||||
|
#define RandomElement(tag, array) \
|
||||||
|
({ \
|
||||||
|
*(typeof((array)[0]) *)RandomElementArray(tag, array, sizeof((array)[0]), ARRAY_COUNT(array)); \
|
||||||
})
|
})
|
||||||
|
|
||||||
u32 RandomUniform(enum RandomTag, u32 lo, u32 hi);
|
u32 RandomUniform(enum RandomTag, u32 lo, u32 hi);
|
||||||
u32 RandomWeightedArray(enum RandomTag, u32 sum, u32 n, const u8 *weights);
|
u32 RandomWeightedArray(enum RandomTag, u32 sum, u32 n, const u8 *weights);
|
||||||
|
const void *RandomElementArray(enum RandomTag, const void *array, size_t size, size_t count);
|
||||||
|
|
||||||
u32 RandomUniformDefault(enum RandomTag, u32 lo, u32 hi);
|
u32 RandomUniformDefault(enum RandomTag, u32 lo, u32 hi);
|
||||||
u32 RandomWeightedArrayDefault(enum RandomTag, u32 sum, u32 n, const u8 *weights);
|
u32 RandomWeightedArrayDefault(enum RandomTag, u32 sum, u32 n, const u8 *weights);
|
||||||
|
const void *RandomElementArrayDefault(enum RandomTag, const void *array, size_t size, size_t count);
|
||||||
|
|
||||||
#endif // GUARD_RANDOM_H
|
#endif // GUARD_RANDOM_H
|
||||||
|
|||||||
@ -3318,7 +3318,8 @@ void SetMoveEffect(bool32 primary, u32 certain)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
gBattleScripting.moveEffect = Random() % 3 + 3;
|
static const u8 sTriAttackEffects[] = { MOVE_EFFECT_BURN, MOVE_EFFECT_FREEZE, MOVE_EFFECT_PARALYSIS };
|
||||||
|
gBattleScripting.moveEffect = RandomElement(RNG_TRI_ATTACK, sTriAttackEffects);
|
||||||
SetMoveEffect(FALSE, 0);
|
SetMoveEffect(FALSE, 0);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -3779,7 +3780,7 @@ void SetMoveEffect(bool32 primary, u32 certain)
|
|||||||
if (!gBattleMons[gEffectBattler].status1)
|
if (!gBattleMons[gEffectBattler].status1)
|
||||||
{
|
{
|
||||||
static const u8 sDireClawEffects[] = { MOVE_EFFECT_POISON, MOVE_EFFECT_PARALYSIS, MOVE_EFFECT_SLEEP };
|
static const u8 sDireClawEffects[] = { MOVE_EFFECT_POISON, MOVE_EFFECT_PARALYSIS, MOVE_EFFECT_SLEEP };
|
||||||
gBattleScripting.moveEffect = sDireClawEffects[Random() % ARRAY_COUNT(sDireClawEffects)];
|
gBattleScripting.moveEffect = RandomElement(RNG_DIRE_CLAW, sDireClawEffects);
|
||||||
SetMoveEffect(TRUE, 0);
|
SetMoveEffect(TRUE, 0);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
10
src/random.c
10
src/random.c
@ -38,9 +38,12 @@ u32 RandomUniform(enum RandomTag tag, u32 lo, u32 hi);
|
|||||||
__attribute__((weak, alias("RandomWeightedArrayDefault")))
|
__attribute__((weak, alias("RandomWeightedArrayDefault")))
|
||||||
u32 RandomWeightedArray(enum RandomTag tag, u32 sum, u32 n, const u8 *weights);
|
u32 RandomWeightedArray(enum RandomTag tag, u32 sum, u32 n, const u8 *weights);
|
||||||
|
|
||||||
|
__attribute__((weak, alias("RandomElementArrayDefault")))
|
||||||
|
const void *RandomElementArray(enum RandomTag tag, const void *array, size_t size, size_t count);
|
||||||
|
|
||||||
u32 RandomUniformDefault(enum RandomTag tag, u32 lo, u32 hi)
|
u32 RandomUniformDefault(enum RandomTag tag, u32 lo, u32 hi)
|
||||||
{
|
{
|
||||||
return lo + (((hi - lo) * Random()) >> 16);
|
return lo + (((hi - lo + 1) * Random()) >> 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 RandomWeightedArrayDefault(enum RandomTag tag, u32 sum, u32 n, const u8 *weights)
|
u32 RandomWeightedArrayDefault(enum RandomTag tag, u32 sum, u32 n, const u8 *weights)
|
||||||
@ -55,3 +58,8 @@ u32 RandomWeightedArrayDefault(enum RandomTag tag, u32 sum, u32 n, const u8 *wei
|
|||||||
}
|
}
|
||||||
return n - 1;
|
return n - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const void *RandomElementArrayDefault(enum RandomTag tag, const void *array, size_t size, size_t count)
|
||||||
|
{
|
||||||
|
return (const u8 *)array + size * RandomUniformDefault(tag, 0, count - 1);
|
||||||
|
}
|
||||||
|
|||||||
@ -6,21 +6,14 @@ ASSUMPTIONS
|
|||||||
ASSUME(gBattleMoves[MOVE_DIRE_CLAW].effect == EFFECT_DIRE_CLAW);
|
ASSUME(gBattleMoves[MOVE_DIRE_CLAW].effect == EFFECT_DIRE_CLAW);
|
||||||
}
|
}
|
||||||
|
|
||||||
// found by brute-force
|
|
||||||
#define RNG_SLEEP 0xcb0
|
|
||||||
#define RNG_POISON 0x2BE
|
|
||||||
#define RNG_PARALYSIS 5
|
|
||||||
|
|
||||||
SINGLE_BATTLE_TEST("Dire Claw can inflict poison, paralysis or sleep")
|
SINGLE_BATTLE_TEST("Dire Claw can inflict poison, paralysis or sleep")
|
||||||
{
|
{
|
||||||
u8 statusAnim;
|
u8 statusAnim;
|
||||||
u32 rng;
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PSN; }
|
||||||
KNOWN_FAILING;
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; }
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PSN; rng = RNG_POISON; }
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_SLP; }
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = RNG_PARALYSIS; }
|
PASSES_RANDOMLY(1, 3, RNG_DIRE_CLAW);
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_SLP; rng = RNG_SLEEP; }
|
|
||||||
GIVEN {
|
GIVEN {
|
||||||
RNGSeed(rng);
|
|
||||||
PLAYER(SPECIES_WOBBUFFET);
|
PLAYER(SPECIES_WOBBUFFET);
|
||||||
OPPONENT(SPECIES_WOBBUFFET);
|
OPPONENT(SPECIES_WOBBUFFET);
|
||||||
} WHEN {
|
} WHEN {
|
||||||
@ -48,15 +41,14 @@ SINGLE_BATTLE_TEST("Dire Claw cannot poison/paralyze poison/electric types respe
|
|||||||
u16 species;
|
u16 species;
|
||||||
u32 rng;
|
u32 rng;
|
||||||
#if B_PARALYZE_ELECTRIC >= GEN_6
|
#if B_PARALYZE_ELECTRIC >= GEN_6
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = RNG_PARALYSIS; species = SPECIES_RAICHU; }
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; species = SPECIES_RAICHU; }
|
||||||
#endif // B_PARALYZE_ELECTRIC
|
#endif // B_PARALYZE_ELECTRIC
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PSN; rng = RNG_POISON; species = SPECIES_ARBOK;}
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PSN; rng = MOVE_EFFECT_POISON; species = SPECIES_ARBOK;}
|
||||||
GIVEN {
|
GIVEN {
|
||||||
RNGSeed(rng);
|
|
||||||
PLAYER(SPECIES_WOBBUFFET);
|
PLAYER(SPECIES_WOBBUFFET);
|
||||||
OPPONENT(species);
|
OPPONENT(species);
|
||||||
} WHEN {
|
} WHEN {
|
||||||
TURN { MOVE(player, MOVE_DIRE_CLAW); }
|
TURN { MOVE(player, MOVE_DIRE_CLAW, WITH_RNG(RNG_DIRE_CLAW, rng)); }
|
||||||
TURN {}
|
TURN {}
|
||||||
} SCENE {
|
} SCENE {
|
||||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_DIRE_CLAW, player);
|
ANIMATION(ANIM_TYPE_MOVE, MOVE_DIRE_CLAW, player);
|
||||||
@ -76,21 +68,20 @@ SINGLE_BATTLE_TEST("Dire Claw cannot poison/paralyze/cause to fall asleep pokemo
|
|||||||
u8 statusAnim;
|
u8 statusAnim;
|
||||||
u16 species, ability;
|
u16 species, ability;
|
||||||
u32 rng;
|
u32 rng;
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = RNG_PARALYSIS; species = SPECIES_RAICHU; ability = ABILITY_LIGHTNING_ROD; }
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; species = SPECIES_RAICHU; ability = ABILITY_LIGHTNING_ROD; }
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = RNG_PARALYSIS; species = SPECIES_JOLTEON; ability = ABILITY_VOLT_ABSORB; }
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; species = SPECIES_JOLTEON; ability = ABILITY_VOLT_ABSORB; }
|
||||||
#if P_GEN_4_POKEMON == TRUE
|
#if P_GEN_4_POKEMON == TRUE
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = RNG_PARALYSIS; species = SPECIES_ELECTIVIRE; ability = ABILITY_MOTOR_DRIVE; }
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; species = SPECIES_ELECTIVIRE; ability = ABILITY_MOTOR_DRIVE; }
|
||||||
#endif // P_GEN_4_POKEMON
|
#endif // P_GEN_4_POKEMON
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PSN; rng = RNG_POISON; species = SPECIES_ZANGOOSE; ability = ABILITY_IMMUNITY; }
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PSN; rng = MOVE_EFFECT_POISON; species = SPECIES_ZANGOOSE; ability = ABILITY_IMMUNITY; }
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_SLP; rng = RNG_SLEEP; species = SPECIES_VIGOROTH; ability = ABILITY_VITAL_SPIRIT; }
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_SLP; rng = MOVE_EFFECT_SLEEP; species = SPECIES_VIGOROTH; ability = ABILITY_VITAL_SPIRIT; }
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_SLP; rng = RNG_SLEEP; species = SPECIES_HYPNO; ability = ABILITY_INSOMNIA; }
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_SLP; rng = MOVE_EFFECT_SLEEP; species = SPECIES_HYPNO; ability = ABILITY_INSOMNIA; }
|
||||||
|
|
||||||
GIVEN {
|
GIVEN {
|
||||||
RNGSeed(rng);
|
|
||||||
PLAYER(SPECIES_WOBBUFFET);
|
PLAYER(SPECIES_WOBBUFFET);
|
||||||
OPPONENT(species) {Ability(ability);}
|
OPPONENT(species) {Ability(ability);}
|
||||||
} WHEN {
|
} WHEN {
|
||||||
TURN { MOVE(player, MOVE_DIRE_CLAW); }
|
TURN { MOVE(player, MOVE_DIRE_CLAW, WITH_RNG(RNG_DIRE_CLAW, rng)); }
|
||||||
TURN {}
|
TURN {}
|
||||||
} SCENE {
|
} SCENE {
|
||||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_DIRE_CLAW, player);
|
ANIMATION(ANIM_TYPE_MOVE, MOVE_DIRE_CLAW, player);
|
||||||
@ -112,15 +103,14 @@ SINGLE_BATTLE_TEST("Dire Claw cannot poison/paralyze/cause to fall asleep a mon
|
|||||||
{
|
{
|
||||||
u8 statusAnim;
|
u8 statusAnim;
|
||||||
u32 rng;
|
u32 rng;
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PSN; rng = RNG_POISON; }
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PSN; rng = MOVE_EFFECT_POISON; }
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = RNG_PARALYSIS; }
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; }
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_SLP; rng = RNG_SLEEP; }
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_SLP; rng = MOVE_EFFECT_SLEEP; }
|
||||||
GIVEN {
|
GIVEN {
|
||||||
RNGSeed(rng);
|
|
||||||
PLAYER(SPECIES_WOBBUFFET);
|
PLAYER(SPECIES_WOBBUFFET);
|
||||||
OPPONENT(SPECIES_WOBBUFFET) {Status1(STATUS1_BURN);}
|
OPPONENT(SPECIES_WOBBUFFET) {Status1(STATUS1_BURN);}
|
||||||
} WHEN {
|
} WHEN {
|
||||||
TURN { MOVE(player, MOVE_DIRE_CLAW); }
|
TURN { MOVE(player, MOVE_DIRE_CLAW, WITH_RNG(RNG_DIRE_CLAW, rng)); }
|
||||||
TURN {}
|
TURN {}
|
||||||
} SCENE {
|
} SCENE {
|
||||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_DIRE_CLAW, player);
|
ANIMATION(ANIM_TYPE_MOVE, MOVE_DIRE_CLAW, player);
|
||||||
|
|||||||
@ -6,21 +6,14 @@ ASSUMPTIONS
|
|||||||
ASSUME(gBattleMoves[MOVE_TRI_ATTACK].effect == EFFECT_TRI_ATTACK);
|
ASSUME(gBattleMoves[MOVE_TRI_ATTACK].effect == EFFECT_TRI_ATTACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
// found by brute-force
|
|
||||||
#define RNG_PARALYSIS 0xcb0
|
|
||||||
#define RNG_BURN 0x2BE
|
|
||||||
#define RNG_FREEZE 5
|
|
||||||
|
|
||||||
SINGLE_BATTLE_TEST("Tri Attack can inflict paralysis, burn or freeze")
|
SINGLE_BATTLE_TEST("Tri Attack can inflict paralysis, burn or freeze")
|
||||||
{
|
{
|
||||||
u8 statusAnim;
|
u8 statusAnim;
|
||||||
u32 rng;
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; }
|
||||||
KNOWN_FAILING;
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; }
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = RNG_PARALYSIS; }
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; }
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = RNG_BURN; }
|
PASSES_RANDOMLY(1, 3, RNG_TRI_ATTACK);
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = RNG_FREEZE; }
|
|
||||||
GIVEN {
|
GIVEN {
|
||||||
RNGSeed(rng);
|
|
||||||
PLAYER(SPECIES_WOBBUFFET);
|
PLAYER(SPECIES_WOBBUFFET);
|
||||||
OPPONENT(SPECIES_WOBBUFFET);
|
OPPONENT(SPECIES_WOBBUFFET);
|
||||||
} WHEN {
|
} WHEN {
|
||||||
@ -48,16 +41,15 @@ SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze electric/fire/ice typ
|
|||||||
u16 species;
|
u16 species;
|
||||||
u32 rng;
|
u32 rng;
|
||||||
#if B_PARALYZE_ELECTRIC >= GEN_6
|
#if B_PARALYZE_ELECTRIC >= GEN_6
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = RNG_PARALYSIS; species = SPECIES_RAICHU;}
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; species = SPECIES_RAICHU;}
|
||||||
#endif // B_PARALYZE_ELECTRIC
|
#endif // B_PARALYZE_ELECTRIC
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = RNG_BURN; species = SPECIES_ARCANINE; }
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = MOVE_EFFECT_BURN; species = SPECIES_ARCANINE; }
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = RNG_FREEZE; species = SPECIES_GLALIE; }
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = MOVE_EFFECT_FREEZE; species = SPECIES_GLALIE; }
|
||||||
GIVEN {
|
GIVEN {
|
||||||
RNGSeed(rng);
|
|
||||||
PLAYER(SPECIES_WOBBUFFET);
|
PLAYER(SPECIES_WOBBUFFET);
|
||||||
OPPONENT(species);
|
OPPONENT(species);
|
||||||
} WHEN {
|
} WHEN {
|
||||||
TURN { MOVE(player, MOVE_TRI_ATTACK); }
|
TURN { MOVE(player, MOVE_TRI_ATTACK, WITH_RNG(RNG_TRI_ATTACK, rng)); }
|
||||||
TURN {}
|
TURN {}
|
||||||
} SCENE {
|
} SCENE {
|
||||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_TRI_ATTACK, player);
|
ANIMATION(ANIM_TYPE_MOVE, MOVE_TRI_ATTACK, player);
|
||||||
@ -80,23 +72,22 @@ SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze pokemon with abilitie
|
|||||||
u8 statusAnim;
|
u8 statusAnim;
|
||||||
u16 species, ability;
|
u16 species, ability;
|
||||||
u32 rng;
|
u32 rng;
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = RNG_PARALYSIS; species = SPECIES_RAICHU; ability = ABILITY_LIGHTNING_ROD; }
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; species = SPECIES_RAICHU; ability = ABILITY_LIGHTNING_ROD; }
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = RNG_PARALYSIS; species = SPECIES_JOLTEON; ability = ABILITY_VOLT_ABSORB; }
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; species = SPECIES_JOLTEON; ability = ABILITY_VOLT_ABSORB; }
|
||||||
#if P_GEN_4_POKEMON == TRUE
|
#if P_GEN_4_POKEMON == TRUE
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = RNG_PARALYSIS; species = SPECIES_ELECTIVIRE; ability = ABILITY_MOTOR_DRIVE; }
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; species = SPECIES_ELECTIVIRE; ability = ABILITY_MOTOR_DRIVE; }
|
||||||
#endif // P_GEN_4_POKEMON
|
#endif // P_GEN_4_POKEMON
|
||||||
#if P_GEN_7_POKEMON == TRUE
|
#if P_GEN_7_POKEMON == TRUE
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = RNG_BURN; species = SPECIES_DEWPIDER; ability = ABILITY_WATER_BUBBLE; }
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = MOVE_EFFECT_BURN; species = SPECIES_DEWPIDER; ability = ABILITY_WATER_BUBBLE; }
|
||||||
#endif // P_GEN_7_POKEMON
|
#endif // P_GEN_7_POKEMON
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = RNG_BURN; species = SPECIES_SEAKING; ability = ABILITY_WATER_VEIL; }
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = MOVE_EFFECT_BURN; species = SPECIES_SEAKING; ability = ABILITY_WATER_VEIL; }
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = RNG_FREEZE; species = SPECIES_CAMERUPT; ability = ABILITY_MAGMA_ARMOR; }
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = MOVE_EFFECT_FREEZE; species = SPECIES_CAMERUPT; ability = ABILITY_MAGMA_ARMOR; }
|
||||||
|
|
||||||
GIVEN {
|
GIVEN {
|
||||||
RNGSeed(rng);
|
|
||||||
PLAYER(SPECIES_WOBBUFFET);
|
PLAYER(SPECIES_WOBBUFFET);
|
||||||
OPPONENT(species) {Ability(ability);}
|
OPPONENT(species) {Ability(ability);}
|
||||||
} WHEN {
|
} WHEN {
|
||||||
TURN { MOVE(player, MOVE_TRI_ATTACK); }
|
TURN { MOVE(player, MOVE_TRI_ATTACK, WITH_RNG(RNG_TRI_ATTACK, rng)); }
|
||||||
TURN {}
|
TURN {}
|
||||||
} SCENE {
|
} SCENE {
|
||||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_TRI_ATTACK, player);
|
ANIMATION(ANIM_TYPE_MOVE, MOVE_TRI_ATTACK, player);
|
||||||
@ -118,15 +109,14 @@ SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze a mon which is alread
|
|||||||
{
|
{
|
||||||
u8 statusAnim;
|
u8 statusAnim;
|
||||||
u32 rng;
|
u32 rng;
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = RNG_PARALYSIS; }
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; }
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = RNG_BURN; }
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = MOVE_EFFECT_BURN; }
|
||||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = RNG_FREEZE; }
|
PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = MOVE_EFFECT_FREEZE; }
|
||||||
GIVEN {
|
GIVEN {
|
||||||
RNGSeed(rng);
|
|
||||||
PLAYER(SPECIES_WOBBUFFET);
|
PLAYER(SPECIES_WOBBUFFET);
|
||||||
OPPONENT(SPECIES_WOBBUFFET) {Status1(STATUS1_SLEEP);}
|
OPPONENT(SPECIES_WOBBUFFET) {Status1(STATUS1_SLEEP);}
|
||||||
} WHEN {
|
} WHEN {
|
||||||
TURN { MOVE(player, MOVE_TRI_ATTACK); }
|
TURN { MOVE(player, MOVE_TRI_ATTACK, WITH_RNG(RNG_TRI_ATTACK, rng)); }
|
||||||
TURN {}
|
TURN {}
|
||||||
} SCENE {
|
} SCENE {
|
||||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_TRI_ATTACK, player);
|
ANIMATION(ANIM_TYPE_MOVE, MOVE_TRI_ATTACK, player);
|
||||||
|
|||||||
@ -34,6 +34,17 @@ TEST("RandomWeighted generates 0..n-1")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST("RandomElement generates an element")
|
||||||
|
{
|
||||||
|
u32 i;
|
||||||
|
static const u8 es[4] = { 1, 2, 4, 8 };
|
||||||
|
for (i = 0; i < 1024; i++)
|
||||||
|
{
|
||||||
|
u32 e = *(const u8 *)RandomElementArrayDefault(RNG_NONE, es, sizeof(es[0]), ARRAY_COUNT(es));
|
||||||
|
EXPECT(e == 1 || e == 2 || e == 4 || e == 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST("RandomUniform generates uniform distribution")
|
TEST("RandomUniform generates uniform distribution")
|
||||||
{
|
{
|
||||||
u32 i, error;
|
u32 i, error;
|
||||||
@ -42,7 +53,7 @@ TEST("RandomUniform generates uniform distribution")
|
|||||||
memset(distribution, 0, sizeof(distribution));
|
memset(distribution, 0, sizeof(distribution));
|
||||||
for (i = 0; i < 4096; i++)
|
for (i = 0; i < 4096; i++)
|
||||||
{
|
{
|
||||||
u32 r = RandomUniformDefault(RNG_NONE, 0, ARRAY_COUNT(distribution));
|
u32 r = RandomUniformDefault(RNG_NONE, 0, ARRAY_COUNT(distribution) - 1);
|
||||||
EXPECT(0 <= r && r < ARRAY_COUNT(distribution));
|
EXPECT(0 <= r && r < ARRAY_COUNT(distribution));
|
||||||
distribution[r]++;
|
distribution[r]++;
|
||||||
}
|
}
|
||||||
@ -79,3 +90,23 @@ TEST("RandomWeighted generates distribution in proportion to the weights")
|
|||||||
|
|
||||||
EXPECT_LT(error, UQ_4_12(0.025));
|
EXPECT_LT(error, UQ_4_12(0.025));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST("RandomElement generates a uniform distribution")
|
||||||
|
{
|
||||||
|
u32 i, error;
|
||||||
|
static const u8 es[4] = { 1, 2, 4, 8 };
|
||||||
|
u16 distribution[9];
|
||||||
|
|
||||||
|
memset(distribution, 0, sizeof(distribution));
|
||||||
|
for (i = 0; i < 4096; i++)
|
||||||
|
{
|
||||||
|
u32 e = *(const u8 *)RandomElementArrayDefault(RNG_NONE, es, sizeof(es[0]), ARRAY_COUNT(es));
|
||||||
|
distribution[e]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = 0;
|
||||||
|
for (i = 0; i < ARRAY_COUNT(es); i++)
|
||||||
|
error += abs(UQ_4_12(0.25) - distribution[es[i]]);
|
||||||
|
|
||||||
|
EXPECT_LT(error, UQ_4_12(0.025));
|
||||||
|
}
|
||||||
|
|||||||
@ -303,13 +303,14 @@
|
|||||||
* The inference process is naive, if your test contains anything that
|
* The inference process is naive, if your test contains anything that
|
||||||
* modifies the speed of a battler you should specify them explicitly.
|
* modifies the speed of a battler you should specify them explicitly.
|
||||||
*
|
*
|
||||||
* MOVE(battler, move | moveSlot:, [megaEvolve:], [hit:], [criticalHit:], [target:], [allowed:])
|
* MOVE(battler, move | moveSlot:, [megaEvolve:], [hit:], [criticalHit:], [target:], [allowed:], [WITH_RNG(tag, value])
|
||||||
* Used when the battler chooses Fight. Either the move ID or move slot
|
* Used when the battler chooses Fight. Either the move ID or move slot
|
||||||
* must be specified. megaEvolve: TRUE causes the battler to Mega Evolve
|
* must be specified. megaEvolve: TRUE causes the battler to Mega Evolve
|
||||||
* if able, hit: FALSE causes the move to miss, criticalHit: TRUE causes
|
* if able, hit: FALSE causes the move to miss, criticalHit: TRUE causes
|
||||||
* the move to land a critical hit, target: is used in double battles to
|
* the move to land a critical hit, target: is used in double battles to
|
||||||
* choose the target (when necessary), and allowed: FALSE is used to
|
* choose the target (when necessary), and allowed: FALSE is used to
|
||||||
* reject an illegal move e.g. a Disabled one.
|
* reject an illegal move e.g. a Disabled one. WITH_RNG allows the move
|
||||||
|
* to specify an explicit outcome for an RNG tag.
|
||||||
* MOVE(playerLeft, MOVE_TACKLE, target: opponentRight);
|
* MOVE(playerLeft, MOVE_TACKLE, target: opponentRight);
|
||||||
* If the battler does not have an explicit Moves specified the moveset
|
* If the battler does not have an explicit Moves specified the moveset
|
||||||
* will be populated based on the MOVEs it uses.
|
* will be populated based on the MOVEs it uses.
|
||||||
@ -538,11 +539,18 @@ struct QueuedEvent
|
|||||||
} as;
|
} as;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct TurnRNG
|
||||||
|
{
|
||||||
|
u16 tag;
|
||||||
|
u16 value;
|
||||||
|
};
|
||||||
|
|
||||||
struct BattlerTurn
|
struct BattlerTurn
|
||||||
{
|
{
|
||||||
u8 hit:2;
|
u8 hit:2;
|
||||||
u8 criticalHit:2;
|
u8 criticalHit:2;
|
||||||
u8 secondaryEffect:2;
|
u8 secondaryEffect:2;
|
||||||
|
struct TurnRNG rng;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BattleTestData
|
struct BattleTestData
|
||||||
@ -758,6 +766,8 @@ enum { TURN_CLOSED, TURN_OPEN, TURN_CLOSING };
|
|||||||
#define SKIP_TURN(battler) SkipTurn(__LINE__, battler)
|
#define SKIP_TURN(battler) SkipTurn(__LINE__, battler)
|
||||||
#define SEND_OUT(battler, partyIndex) SendOut(__LINE__, battler, partyIndex)
|
#define SEND_OUT(battler, partyIndex) SendOut(__LINE__, battler, partyIndex)
|
||||||
|
|
||||||
|
#define WITH_RNG(tag, value) rng: ((struct TurnRNG) { tag, value })
|
||||||
|
|
||||||
struct MoveContext
|
struct MoveContext
|
||||||
{
|
{
|
||||||
u16 move;
|
u16 move;
|
||||||
@ -777,6 +787,8 @@ struct MoveContext
|
|||||||
u16 explicitAllowed:1;
|
u16 explicitAllowed:1;
|
||||||
struct BattlePokemon *target;
|
struct BattlePokemon *target;
|
||||||
bool8 explicitTarget;
|
bool8 explicitTarget;
|
||||||
|
struct TurnRNG rng;
|
||||||
|
bool8 explicitRNG;
|
||||||
};
|
};
|
||||||
|
|
||||||
void OpenTurn(u32 sourceLine);
|
void OpenTurn(u32 sourceLine);
|
||||||
|
|||||||
@ -292,6 +292,20 @@ static void BattleTest_Run(void *data)
|
|||||||
|
|
||||||
u32 RandomUniform(enum RandomTag tag, u32 lo, u32 hi)
|
u32 RandomUniform(enum RandomTag tag, u32 lo, u32 hi)
|
||||||
{
|
{
|
||||||
|
const struct BattlerTurn *turn = NULL;
|
||||||
|
u32 default_ = hi;
|
||||||
|
|
||||||
|
if (gCurrentTurnActionNumber < gBattlersCount)
|
||||||
|
{
|
||||||
|
u32 battlerId = gBattlerByTurnOrder[gCurrentTurnActionNumber];
|
||||||
|
turn = &DATA.battleRecordTurns[gBattleResults.battleTurnCounter][battlerId];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (turn && turn->rng.tag == tag)
|
||||||
|
{
|
||||||
|
default_ = turn->rng.value;
|
||||||
|
}
|
||||||
|
|
||||||
if (tag == STATE->rngTag)
|
if (tag == STATE->rngTag)
|
||||||
{
|
{
|
||||||
u32 n = hi - lo + 1;
|
u32 n = hi - lo + 1;
|
||||||
@ -308,7 +322,7 @@ u32 RandomUniform(enum RandomTag tag, u32 lo, u32 hi)
|
|||||||
return STATE->runTrial + lo;
|
return STATE->runTrial + lo;
|
||||||
}
|
}
|
||||||
|
|
||||||
return hi;
|
return default_;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 RandomWeightedArray(enum RandomTag tag, u32 sum, u32 n, const u8 *weights)
|
u32 RandomWeightedArray(enum RandomTag tag, u32 sum, u32 n, const u8 *weights)
|
||||||
@ -322,28 +336,35 @@ u32 RandomWeightedArray(enum RandomTag tag, u32 sum, u32 n, const u8 *weights)
|
|||||||
turn = &DATA.battleRecordTurns[gBattleResults.battleTurnCounter][battlerId];
|
turn = &DATA.battleRecordTurns[gBattleResults.battleTurnCounter][battlerId];
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (tag)
|
if (turn && turn->rng.tag == tag)
|
||||||
{
|
{
|
||||||
case RNG_ACCURACY:
|
default_ = turn->rng.value;
|
||||||
ASSUME(n == 2);
|
}
|
||||||
if (turn && turn->hit)
|
else
|
||||||
return turn->hit - 1;
|
{
|
||||||
default_ = TRUE;
|
switch (tag)
|
||||||
break;
|
{
|
||||||
|
case RNG_ACCURACY:
|
||||||
|
ASSUME(n == 2);
|
||||||
|
if (turn && turn->hit)
|
||||||
|
return turn->hit - 1;
|
||||||
|
default_ = TRUE;
|
||||||
|
break;
|
||||||
|
|
||||||
case RNG_CRITICAL_HIT:
|
case RNG_CRITICAL_HIT:
|
||||||
ASSUME(n == 2);
|
ASSUME(n == 2);
|
||||||
if (turn && turn->criticalHit)
|
if (turn && turn->criticalHit)
|
||||||
return turn->criticalHit - 1;
|
return turn->criticalHit - 1;
|
||||||
default_ = FALSE;
|
default_ = FALSE;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RNG_SECONDARY_EFFECT:
|
case RNG_SECONDARY_EFFECT:
|
||||||
ASSUME(n == 2);
|
ASSUME(n == 2);
|
||||||
if (turn && turn->secondaryEffect)
|
if (turn && turn->secondaryEffect)
|
||||||
return turn->secondaryEffect - 1;
|
return turn->secondaryEffect - 1;
|
||||||
default_ = TRUE;
|
default_ = TRUE;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag == STATE->rngTag)
|
if (tag == STATE->rngTag)
|
||||||
@ -365,6 +386,52 @@ u32 RandomWeightedArray(enum RandomTag tag, u32 sum, u32 n, const u8 *weights)
|
|||||||
return default_;
|
return default_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const void *RandomElementArray(enum RandomTag tag, const void *array, size_t size, size_t count)
|
||||||
|
{
|
||||||
|
const struct BattlerTurn *turn = NULL;
|
||||||
|
u32 index = count-1;
|
||||||
|
|
||||||
|
if (gCurrentTurnActionNumber < gBattlersCount)
|
||||||
|
{
|
||||||
|
u32 battlerId = gBattlerByTurnOrder[gCurrentTurnActionNumber];
|
||||||
|
turn = &DATA.battleRecordTurns[gBattleResults.battleTurnCounter][battlerId];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (turn && turn->rng.tag == tag)
|
||||||
|
{
|
||||||
|
u32 element;
|
||||||
|
for (index = 0; index < count; index++)
|
||||||
|
{
|
||||||
|
memcpy(&element, (const u8 *)array + size * index, size);
|
||||||
|
if (element == turn->rng.value)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (index == count)
|
||||||
|
{
|
||||||
|
// TODO: Incorporate the line number.
|
||||||
|
const char *filename = gTestRunnerState.test->filename;
|
||||||
|
Test_ExitWithResult(TEST_RESULT_ERROR, "%s: RandomElement illegal value requested: %d", filename, turn->rng.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tag == STATE->rngTag)
|
||||||
|
{
|
||||||
|
if (STATE->trials == 1)
|
||||||
|
{
|
||||||
|
STATE->trials = count;
|
||||||
|
PrintTestName();
|
||||||
|
}
|
||||||
|
else if (STATE->trials != count)
|
||||||
|
{
|
||||||
|
Test_ExitWithResult(TEST_RESULT_ERROR, "RandomElement called with inconsistent trials %d and %d", STATE->trials, count);
|
||||||
|
}
|
||||||
|
STATE->trialRatio = Q_4_12(1) / count;
|
||||||
|
index = STATE->runTrial;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (const u8 *)array + size * index;
|
||||||
|
}
|
||||||
|
|
||||||
static s32 TryAbilityPopUp(s32 i, s32 n, u32 battlerId, u32 ability)
|
static s32 TryAbilityPopUp(s32 i, s32 n, u32 battlerId, u32 ability)
|
||||||
{
|
{
|
||||||
struct QueuedAbilityEvent *event;
|
struct QueuedAbilityEvent *event;
|
||||||
@ -1357,6 +1424,8 @@ void Move(u32 sourceLine, struct BattlePokemon *battler, struct MoveContext ctx)
|
|||||||
DATA.battleRecordTurns[DATA.turns][battlerId].criticalHit = 1 + ctx.criticalHit;
|
DATA.battleRecordTurns[DATA.turns][battlerId].criticalHit = 1 + ctx.criticalHit;
|
||||||
if (ctx.explicitSecondaryEffect)
|
if (ctx.explicitSecondaryEffect)
|
||||||
DATA.battleRecordTurns[DATA.turns][battlerId].secondaryEffect = 1 + ctx.secondaryEffect;
|
DATA.battleRecordTurns[DATA.turns][battlerId].secondaryEffect = 1 + ctx.secondaryEffect;
|
||||||
|
if (ctx.explicitRNG)
|
||||||
|
DATA.battleRecordTurns[DATA.turns][battlerId].rng = ctx.rng;
|
||||||
|
|
||||||
if (!(DATA.actionBattlers & (1 << battlerId)))
|
if (!(DATA.actionBattlers & (1 << battlerId)))
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user