mirror of
https://github.com/Ninjdai1/pokeemerald.git
synced 2024-12-26 03:34:15 +01:00
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.
|
||||
*
|
||||
* 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,
|
||||
* and TRUE with probability t/100.
|
||||
@ -47,6 +52,7 @@ enum RandomTag
|
||||
RNG_CRITICAL_HIT,
|
||||
RNG_CUTE_CHARM,
|
||||
RNG_DAMAGE_MODIFIER,
|
||||
RNG_DIRE_CLAW,
|
||||
RNG_FLAME_BODY,
|
||||
RNG_FORCE_RANDOM_SWITCH,
|
||||
RNG_FROZEN,
|
||||
@ -60,6 +66,7 @@ enum RandomTag
|
||||
RNG_SPEED_TIE,
|
||||
RNG_STATIC,
|
||||
RNG_STENCH,
|
||||
RNG_TRI_ATTACK,
|
||||
};
|
||||
|
||||
#define RandomWeighted(tag, ...) \
|
||||
@ -73,14 +80,34 @@ enum RandomTag
|
||||
|
||||
#define RandomPercentage(tag, t) \
|
||||
({ \
|
||||
const u8 weights[] = { 100 - t, t }; \
|
||||
RandomWeightedArray(tag, 100, ARRAY_COUNT(weights), weights); \
|
||||
u32 r; \
|
||||
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 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 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
|
||||
|
@ -3318,7 +3318,8 @@ void SetMoveEffect(bool32 primary, u32 certain)
|
||||
}
|
||||
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);
|
||||
}
|
||||
break;
|
||||
@ -3779,7 +3780,7 @@ void SetMoveEffect(bool32 primary, u32 certain)
|
||||
if (!gBattleMons[gEffectBattler].status1)
|
||||
{
|
||||
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);
|
||||
}
|
||||
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")))
|
||||
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)
|
||||
{
|
||||
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)
|
||||
@ -55,3 +58,8 @@ u32 RandomWeightedArrayDefault(enum RandomTag tag, u32 sum, u32 n, const u8 *wei
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
// 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")
|
||||
{
|
||||
u8 statusAnim;
|
||||
u32 rng;
|
||||
KNOWN_FAILING;
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PSN; rng = RNG_POISON; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = RNG_PARALYSIS; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_SLP; rng = RNG_SLEEP; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PSN; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_SLP; }
|
||||
PASSES_RANDOMLY(1, 3, RNG_DIRE_CLAW);
|
||||
GIVEN {
|
||||
RNGSeed(rng);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_WOBBUFFET);
|
||||
} WHEN {
|
||||
@ -48,15 +41,14 @@ SINGLE_BATTLE_TEST("Dire Claw cannot poison/paralyze poison/electric types respe
|
||||
u16 species;
|
||||
u32 rng;
|
||||
#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
|
||||
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 {
|
||||
RNGSeed(rng);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(species);
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_DIRE_CLAW); }
|
||||
TURN { MOVE(player, MOVE_DIRE_CLAW, WITH_RNG(RNG_DIRE_CLAW, rng)); }
|
||||
TURN {}
|
||||
} SCENE {
|
||||
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;
|
||||
u16 species, ability;
|
||||
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 = RNG_PARALYSIS; species = SPECIES_JOLTEON; ability = ABILITY_VOLT_ABSORB; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; species = SPECIES_RAICHU; ability = ABILITY_LIGHTNING_ROD; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; species = SPECIES_JOLTEON; ability = ABILITY_VOLT_ABSORB; }
|
||||
#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
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PSN; rng = RNG_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 = RNG_SLEEP; species = SPECIES_HYPNO; ability = ABILITY_INSOMNIA; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PSN; rng = MOVE_EFFECT_POISON; species = SPECIES_ZANGOOSE; ability = ABILITY_IMMUNITY; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_SLP; rng = MOVE_EFFECT_SLEEP; species = SPECIES_VIGOROTH; ability = ABILITY_VITAL_SPIRIT; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_SLP; rng = MOVE_EFFECT_SLEEP; species = SPECIES_HYPNO; ability = ABILITY_INSOMNIA; }
|
||||
|
||||
GIVEN {
|
||||
RNGSeed(rng);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(species) {Ability(ability);}
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_DIRE_CLAW); }
|
||||
TURN { MOVE(player, MOVE_DIRE_CLAW, WITH_RNG(RNG_DIRE_CLAW, rng)); }
|
||||
TURN {}
|
||||
} SCENE {
|
||||
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;
|
||||
u32 rng;
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PSN; rng = RNG_POISON; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = RNG_PARALYSIS; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_SLP; rng = RNG_SLEEP; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PSN; rng = MOVE_EFFECT_POISON; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_SLP; rng = MOVE_EFFECT_SLEEP; }
|
||||
GIVEN {
|
||||
RNGSeed(rng);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_WOBBUFFET) {Status1(STATUS1_BURN);}
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_DIRE_CLAW); }
|
||||
TURN { MOVE(player, MOVE_DIRE_CLAW, WITH_RNG(RNG_DIRE_CLAW, rng)); }
|
||||
TURN {}
|
||||
} SCENE {
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_DIRE_CLAW, player);
|
||||
|
@ -6,21 +6,14 @@ ASSUMPTIONS
|
||||
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")
|
||||
{
|
||||
u8 statusAnim;
|
||||
u32 rng;
|
||||
KNOWN_FAILING;
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = RNG_PARALYSIS; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = RNG_BURN; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = RNG_FREEZE; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; }
|
||||
PASSES_RANDOMLY(1, 3, RNG_TRI_ATTACK);
|
||||
GIVEN {
|
||||
RNGSeed(rng);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_WOBBUFFET);
|
||||
} WHEN {
|
||||
@ -48,16 +41,15 @@ SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze electric/fire/ice typ
|
||||
u16 species;
|
||||
u32 rng;
|
||||
#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
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = RNG_BURN; species = SPECIES_ARCANINE; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = RNG_FREEZE; species = SPECIES_GLALIE; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = MOVE_EFFECT_BURN; species = SPECIES_ARCANINE; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = MOVE_EFFECT_FREEZE; species = SPECIES_GLALIE; }
|
||||
GIVEN {
|
||||
RNGSeed(rng);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(species);
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_TRI_ATTACK); }
|
||||
TURN { MOVE(player, MOVE_TRI_ATTACK, WITH_RNG(RNG_TRI_ATTACK, rng)); }
|
||||
TURN {}
|
||||
} SCENE {
|
||||
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;
|
||||
u16 species, ability;
|
||||
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 = RNG_PARALYSIS; species = SPECIES_JOLTEON; ability = ABILITY_VOLT_ABSORB; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; species = SPECIES_RAICHU; ability = ABILITY_LIGHTNING_ROD; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; species = SPECIES_JOLTEON; ability = ABILITY_VOLT_ABSORB; }
|
||||
#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
|
||||
#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
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = RNG_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_BRN; rng = MOVE_EFFECT_BURN; species = SPECIES_SEAKING; ability = ABILITY_WATER_VEIL; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = MOVE_EFFECT_FREEZE; species = SPECIES_CAMERUPT; ability = ABILITY_MAGMA_ARMOR; }
|
||||
|
||||
GIVEN {
|
||||
RNGSeed(rng);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(species) {Ability(ability);}
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_TRI_ATTACK); }
|
||||
TURN { MOVE(player, MOVE_TRI_ATTACK, WITH_RNG(RNG_TRI_ATTACK, rng)); }
|
||||
TURN {}
|
||||
} SCENE {
|
||||
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;
|
||||
u32 rng;
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = RNG_PARALYSIS; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = RNG_BURN; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = RNG_FREEZE; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = MOVE_EFFECT_BURN; }
|
||||
PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = MOVE_EFFECT_FREEZE; }
|
||||
GIVEN {
|
||||
RNGSeed(rng);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_WOBBUFFET) {Status1(STATUS1_SLEEP);}
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_TRI_ATTACK); }
|
||||
TURN { MOVE(player, MOVE_TRI_ATTACK, WITH_RNG(RNG_TRI_ATTACK, rng)); }
|
||||
TURN {}
|
||||
} SCENE {
|
||||
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")
|
||||
{
|
||||
u32 i, error;
|
||||
@ -42,7 +53,7 @@ TEST("RandomUniform generates uniform distribution")
|
||||
memset(distribution, 0, sizeof(distribution));
|
||||
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));
|
||||
distribution[r]++;
|
||||
}
|
||||
@ -79,3 +90,23 @@ TEST("RandomWeighted generates distribution in proportion to the weights")
|
||||
|
||||
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
|
||||
* 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
|
||||
* must be specified. megaEvolve: TRUE causes the battler to Mega Evolve
|
||||
* 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
|
||||
* 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);
|
||||
* If the battler does not have an explicit Moves specified the moveset
|
||||
* will be populated based on the MOVEs it uses.
|
||||
@ -538,11 +539,18 @@ struct QueuedEvent
|
||||
} as;
|
||||
};
|
||||
|
||||
struct TurnRNG
|
||||
{
|
||||
u16 tag;
|
||||
u16 value;
|
||||
};
|
||||
|
||||
struct BattlerTurn
|
||||
{
|
||||
u8 hit:2;
|
||||
u8 criticalHit:2;
|
||||
u8 secondaryEffect:2;
|
||||
struct TurnRNG rng;
|
||||
};
|
||||
|
||||
struct BattleTestData
|
||||
@ -758,6 +766,8 @@ enum { TURN_CLOSED, TURN_OPEN, TURN_CLOSING };
|
||||
#define SKIP_TURN(battler) SkipTurn(__LINE__, battler)
|
||||
#define SEND_OUT(battler, partyIndex) SendOut(__LINE__, battler, partyIndex)
|
||||
|
||||
#define WITH_RNG(tag, value) rng: ((struct TurnRNG) { tag, value })
|
||||
|
||||
struct MoveContext
|
||||
{
|
||||
u16 move;
|
||||
@ -777,6 +787,8 @@ struct MoveContext
|
||||
u16 explicitAllowed:1;
|
||||
struct BattlePokemon *target;
|
||||
bool8 explicitTarget;
|
||||
struct TurnRNG rng;
|
||||
bool8 explicitRNG;
|
||||
};
|
||||
|
||||
void OpenTurn(u32 sourceLine);
|
||||
|
@ -292,6 +292,20 @@ static void BattleTest_Run(void *data)
|
||||
|
||||
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)
|
||||
{
|
||||
u32 n = hi - lo + 1;
|
||||
@ -308,7 +322,7 @@ u32 RandomUniform(enum RandomTag tag, u32 lo, u32 hi)
|
||||
return STATE->runTrial + lo;
|
||||
}
|
||||
|
||||
return hi;
|
||||
return default_;
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
switch (tag)
|
||||
if (turn && turn->rng.tag == tag)
|
||||
{
|
||||
case RNG_ACCURACY:
|
||||
ASSUME(n == 2);
|
||||
if (turn && turn->hit)
|
||||
return turn->hit - 1;
|
||||
default_ = TRUE;
|
||||
break;
|
||||
default_ = turn->rng.value;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (tag)
|
||||
{
|
||||
case RNG_ACCURACY:
|
||||
ASSUME(n == 2);
|
||||
if (turn && turn->hit)
|
||||
return turn->hit - 1;
|
||||
default_ = TRUE;
|
||||
break;
|
||||
|
||||
case RNG_CRITICAL_HIT:
|
||||
ASSUME(n == 2);
|
||||
if (turn && turn->criticalHit)
|
||||
return turn->criticalHit - 1;
|
||||
default_ = FALSE;
|
||||
break;
|
||||
case RNG_CRITICAL_HIT:
|
||||
ASSUME(n == 2);
|
||||
if (turn && turn->criticalHit)
|
||||
return turn->criticalHit - 1;
|
||||
default_ = FALSE;
|
||||
break;
|
||||
|
||||
case RNG_SECONDARY_EFFECT:
|
||||
ASSUME(n == 2);
|
||||
if (turn && turn->secondaryEffect)
|
||||
return turn->secondaryEffect - 1;
|
||||
default_ = TRUE;
|
||||
break;
|
||||
case RNG_SECONDARY_EFFECT:
|
||||
ASSUME(n == 2);
|
||||
if (turn && turn->secondaryEffect)
|
||||
return turn->secondaryEffect - 1;
|
||||
default_ = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tag == STATE->rngTag)
|
||||
@ -365,6 +386,52 @@ u32 RandomWeightedArray(enum RandomTag tag, u32 sum, u32 n, const u8 *weights)
|
||||
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)
|
||||
{
|
||||
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;
|
||||
if (ctx.explicitSecondaryEffect)
|
||||
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)))
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user