From fc321965a9fcf718e53bb68bc934f4d6f9bd20ee Mon Sep 17 00:00:00 2001 From: Martin Griffin Date: Thu, 20 Jul 2023 10:14:12 +0100 Subject: [PATCH] RNG_HITS and RNG_LOADED_DICE --- include/random.h | 2 + src/battle_script_commands.c | 32 ++++--------- src/battle_util.c | 40 +++++----------- test/move_effect_metronome.c | 2 +- test/move_effect_mirror_move.c | 21 ++++---- test/test_runner_battle.c | 87 +++++++++++++++++++++------------- 6 files changed, 86 insertions(+), 98 deletions(-) diff --git a/include/random.h b/include/random.h index 7770880053..83bc3e0d56 100644 --- a/include/random.h +++ b/include/random.h @@ -60,8 +60,10 @@ enum RandomTag RNG_FLAME_BODY, RNG_FORCE_RANDOM_SWITCH, RNG_FROZEN, + RNG_HITS, RNG_HOLD_EFFECT_FLINCH, RNG_INFATUATION, + RNG_LOADED_DICE, RNG_METRONOME, RNG_PARALYSIS, RNG_POISON_POINT, diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 9a9a3c3ae8..532a131cc7 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -12206,30 +12206,14 @@ static void Cmd_setmultihitcounter(void) } else { - #if B_MULTI_HIT_CHANCE >= GEN_5 - // Based on Gen 5's odds - // 35% for 2 hits - // 35% for 3 hits - // 15% for 4 hits - // 15% for 5 hits - gMultiHitCounter = Random() % 100; - if (gMultiHitCounter < 35) - gMultiHitCounter = 2; - else if (gMultiHitCounter < 35 + 35) - gMultiHitCounter = 3; - else if (gMultiHitCounter < 35 + 35 + 15) - gMultiHitCounter = 4; - else - gMultiHitCounter = 5; - #else - // 2 and 3 hits: 37.5% - // 4 and 5 hits: 12.5% - gMultiHitCounter = Random() % 4; - if (gMultiHitCounter > 1) - gMultiHitCounter = (Random() % 4) + 2; - else - gMultiHitCounter += 2; - #endif + // WARNING: These seem to be unused, see SetRandomMultiHitCounter. + #if B_MULTI_HIT_CHANCE >= GEN_5 + // 35%: 2 hits, 35%: 3 hits, 15% 4 hits, 15% 5 hits. + gMultiHitCounter = RandomWeighted(RNG_HITS, 0, 0, 7, 7, 3, 3); + #else + // 37.5%: 2 hits, 37.5%: 3 hits, 12.5% 4 hits, 12.5% 5 hits. + gMultiHitCounter = RandomWeighted(RNG_HITS, 0, 0, 3, 3, 1, 1); + #endif } } diff --git a/src/battle_util.c b/src/battle_util.c index 495f26cd50..d229bd0728 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -10899,35 +10899,19 @@ bool32 CanTargetBattler(u8 battlerAtk, u8 battlerDef, u16 move) static void SetRandomMultiHitCounter() { -#if (B_MULTI_HIT_CHANCE >= GEN_5) - // Based on Gen 5's odds - // 35% for 2 hits - // 35% for 3 hits - // 15% for 4 hits - // 15% for 5 hits - gMultiHitCounter = Random() % 100; - if (gMultiHitCounter < 35) - gMultiHitCounter = 2; - else if (gMultiHitCounter < 35 + 35) - gMultiHitCounter = 3; - else if (gMultiHitCounter < 35 + 35 + 15) - gMultiHitCounter = 4; - else - gMultiHitCounter = 5; -#else - // 2 and 3 hits: 37.5% - // 4 and 5 hits: 12.5% - gMultiHitCounter = Random() % 4; - if (gMultiHitCounter > 1) - gMultiHitCounter = (Random() % 4) + 2; - else - gMultiHitCounter += 2; -#endif - - if (gMultiHitCounter < 4 && GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_LOADED_DICE) + if (GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_LOADED_DICE) { - // If roll 4 or 5 Loaded Dice doesn't do anything. Otherwise it rolls the number of hits as 5 minus a random integer from 0 to 1 inclusive. - gMultiHitCounter = 5 - (Random() & 1); + gMultiHitCounter = RandomUniform(RNG_LOADED_DICE, 4, 5); + } + else + { +#if B_MULTI_HIT_CHANCE >= GEN_5 + // 35%: 2 hits, 35%: 3 hits, 15% 4 hits, 15% 5 hits. + gMultiHitCounter = RandomWeighted(RNG_HITS, 0, 0, 7, 7, 3, 3); +#else + // 37.5%: 2 hits, 37.5%: 3 hits, 12.5% 4 hits, 12.5% 5 hits. + gMultiHitCounter = RandomWeighted(RNG_HITS, 0, 0, 3, 3, 1, 1); +#endif } } diff --git a/test/move_effect_metronome.c b/test/move_effect_metronome.c index 7265d5aa8a..1e68603e63 100644 --- a/test/move_effect_metronome.c +++ b/test/move_effect_metronome.c @@ -56,6 +56,6 @@ SINGLE_BATTLE_TEST("Metronome's called multi-hit move hits multiple times") MESSAGE("Wobbuffet used Rock Blast!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_ROCK_BLAST, player); HP_BAR(opponent); - MESSAGE("Hit 2 time(s)!"); + MESSAGE("Hit 5 time(s)!"); } } diff --git a/test/move_effect_mirror_move.c b/test/move_effect_mirror_move.c index 2b8b9dbe90..ff21c0efa7 100644 --- a/test/move_effect_mirror_move.c +++ b/test/move_effect_mirror_move.c @@ -9,8 +9,8 @@ ASSUMPTIONS SINGLE_BATTLE_TEST("Mirror Move copies the last used move by the target") { GIVEN { - PLAYER(SPECIES_WOBBUFFET) {Speed(2);} - OPPONENT(SPECIES_WOBBUFFET) {Speed(5);} + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(opponent, MOVE_TACKLE); MOVE(player, MOVE_MIRROR_MOVE); } } SCENE { @@ -26,10 +26,10 @@ SINGLE_BATTLE_TEST("Mirror Move copies the last used move by the target") SINGLE_BATTLE_TEST("Mirror Move fails if no move was used before") { GIVEN { - PLAYER(SPECIES_WOBBUFFET) {Speed(5);} - OPPONENT(SPECIES_WOBBUFFET) {Speed(2);} + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(opponent, MOVE_TACKLE); MOVE(player, MOVE_MIRROR_MOVE); } + TURN { MOVE(player, MOVE_MIRROR_MOVE); MOVE(opponent, MOVE_TACKLE); } } SCENE { MESSAGE("Wobbuffet used Mirror Move!"); MESSAGE("The Mirror Move failed!"); @@ -44,8 +44,8 @@ SINGLE_BATTLE_TEST("Mirror Move's called powder move fails against Grass Types") ASSUME(gBattleMoves[MOVE_STUN_SPORE].flags & FLAG_POWDER); ASSUME(gSpeciesInfo[SPECIES_ODDISH].types[0] == TYPE_GRASS); ASSUME(gBattleMoves[MOVE_STUN_SPORE].effect == EFFECT_PARALYZE); - PLAYER(SPECIES_ODDISH) {Speed(5);} - OPPONENT(SPECIES_WOBBUFFET) {Speed(2);} + PLAYER(SPECIES_ODDISH); + OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(player, MOVE_STUN_SPORE); MOVE(opponent, MOVE_MIRROR_MOVE); } } SCENE { @@ -59,19 +59,18 @@ SINGLE_BATTLE_TEST("Mirror Move's called powder move fails against Grass Types") } } -// It hits first 2 times, then 5 times with the default rng seed. SINGLE_BATTLE_TEST("Mirror Move's called multi-hit move hits multiple times") { GIVEN { ASSUME(gBattleMoves[MOVE_BULLET_SEED].effect == EFFECT_MULTI_HIT); - PLAYER(SPECIES_WOBBUFFET) {Speed(5);} - OPPONENT(SPECIES_WOBBUFFET) {Speed(2);} + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(player, MOVE_BULLET_SEED); MOVE(opponent, MOVE_MIRROR_MOVE); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_SEED, player); HP_BAR(opponent); - MESSAGE("Hit 2 time(s)!"); + MESSAGE("Hit 5 time(s)!"); MESSAGE("Foe Wobbuffet used Mirror Move!"); MESSAGE("Foe Wobbuffet used Bullet Seed!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_SEED, opponent); diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index a2375aef88..9111207c8d 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -368,52 +368,40 @@ u32 RandomUniformExcept(enum RandomTag tag, u32 lo, u32 hi, bool32 (*reject)(u32 PrintTestName(); } STATE->trialRatio = Q_4_12(1) / STATE->trials; - return STATE->runTrial + lo; + + while (reject(STATE->runTrial + lo + STATE->rngTrialOffset)) + { + if (STATE->runTrial + lo + STATE->rngTrialOffset > hi) + Test_ExitWithResult(TEST_RESULT_INVALID, "RandomUniformExcept called with inconsistent reject"); + STATE->rngTrialOffset++; + } + + return STATE->runTrial + lo + STATE->rngTrialOffset; } + default_ = hi; + while (reject(default_)) + { + if (default_ == lo) + Test_ExitWithResult(TEST_RESULT_INVALID, "RandomUniformExcept rejected all values"); + default_--; + } return default_; } u32 RandomWeightedArray(enum RandomTag tag, u32 sum, u32 n, const u8 *weights) { const struct BattlerTurn *turn = NULL; - u32 default_ = n-1; + + if (sum == 0) + Test_ExitWithResult(TEST_RESULT_ERROR, "RandomWeightedArray called with zero sum"); if (gCurrentTurnActionNumber < gBattlersCount) { u32 battlerId = gBattlerByTurnOrder[gCurrentTurnActionNumber]; turn = &DATA.battleRecordTurns[gBattleResults.battleTurnCounter][battlerId]; - } - - if (turn && turn->rng.tag == tag) - { - 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_SECONDARY_EFFECT: - ASSUME(n == 2); - if (turn && turn->secondaryEffect) - return turn->secondaryEffect - 1; - default_ = TRUE; - break; - } + if (turn && turn->rng.tag == tag) + return turn->rng.value; } if (tag == STATE->rngTag) @@ -432,7 +420,38 @@ u32 RandomWeightedArray(enum RandomTag tag, u32 sum, u32 n, const u8 *weights) return STATE->runTrial; } - return default_; + switch (tag) + { + case RNG_ACCURACY: + ASSUME(n == 2); + if (turn && turn->hit) + return turn->hit - 1; + else + return TRUE; + + case RNG_CRITICAL_HIT: + ASSUME(n == 2); + if (turn && turn->criticalHit) + return turn->criticalHit - 1; + else + return FALSE; + + case RNG_SECONDARY_EFFECT: + ASSUME(n == 2); + if (turn && turn->secondaryEffect) + return turn->secondaryEffect - 1; + else + return TRUE; + + default: + while (weights[n-1] == 0) + { + if (n == 1) + Test_ExitWithResult(TEST_RESULT_ERROR, "RandomWeightedArray called with all zero weights"); + n--; + } + return n-1; + } } const void *RandomElementArray(enum RandomTag tag, const void *array, size_t size, size_t count)