RandomElement for structured RNG (#2868)

This commit is contained in:
Eduardo Quezada D'Ottone 2023-04-09 21:20:04 -04:00 committed by GitHub
commit 5c1efe9419
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 214 additions and 86 deletions

View File

@ -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

View File

@ -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;

View File

@ -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);
}

View File

@ -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);

View File

@ -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);

View File

@ -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));
}

View File

@ -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);

View File

@ -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)))
{