From 4c3447c851731c989fc591feea0094b6bbde7fd2 Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Tue, 29 Apr 2025 09:37:28 -0400 Subject: [PATCH] Added utility function `randSeedFloat` basically just `Phaser.math.RND.realInRange(0, 1)` --- src/battle.ts | 3 ++- src/data/abilities/ability.ts | 6 +++--- src/data/moves/move.ts | 6 +++--- src/data/pokemon-species.ts | 22 ++++++++++++++-------- src/field/pokemon.ts | 4 ++-- src/modifier/modifier.ts | 24 ++++++++++++------------ src/utils/common.ts | 31 +++++++++++++++++++++++-------- 7 files changed, 59 insertions(+), 37 deletions(-) diff --git a/src/battle.ts b/src/battle.ts index 07e520d6bc0..5105418fd9c 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -8,6 +8,7 @@ import { shiftCharCodes, randSeedItem, randInt, + randSeedFloat, } from "#app/utils/common"; import Trainer, { TrainerVariant } from "./field/trainer"; import type { GameMode } from "./game-mode"; @@ -150,7 +151,7 @@ export default class Battle { randSeedGaussForLevel(value: number): number { let rand = 0; for (let i = value; i > 0; i--) { - rand += Phaser.Math.RND.realInRange(0, 1); + rand += randSeedFloat(); } return rand / value; } diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index c16b1985014..c65c858472c 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -1,5 +1,5 @@ import { HitResult, MoveResult, PlayerPokemon } from "#app/field/pokemon"; -import { BooleanHolder, NumberHolder, toDmgValue, isNullOrUndefined, randSeedItem, randSeedInt, type Constructor } from "#app/utils/common"; +import { BooleanHolder, NumberHolder, toDmgValue, isNullOrUndefined, randSeedItem, randSeedInt, type Constructor, randSeedFloat } from "#app/utils/common"; import { getPokemonNameWithAffix } from "#app/messages"; import { BattlerTagLapseType, GroundedTag } from "#app/data/battler-tags"; import { getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "#app/data/status-effect"; @@ -4071,8 +4071,8 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr { } // Clamp procChance to [0, 1]. Skip if didn't proc (less than pass) - const pass = Phaser.Math.RND.realInRange(0, 1); - return Phaser.Math.Clamp(this.procChance(pokemon), 0, 1) >= pass; + const pass = randSeedFloat(); + return Phaser.Math.Clamp(this.procChance(pokemon), 1, 0) >= pass; } override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index f8d5daf28fc..a74937ab91f 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -29,7 +29,7 @@ import { } from "../status-effect"; import { getTypeDamageMultiplier } from "../type"; import { PokemonType } from "#enums/pokemon-type"; -import { BooleanHolder, NumberHolder, isNullOrUndefined, toDmgValue, randSeedItem, randSeedInt, getEnumValues, toReadableString, type Constructor } from "#app/utils/common"; +import { BooleanHolder, NumberHolder, isNullOrUndefined, toDmgValue, randSeedItem, randSeedInt, getEnumValues, toReadableString, type Constructor, randSeedFloat } from "#app/utils/common"; import { WeatherType } from "#enums/weather-type"; import type { ArenaTrapTag } from "../arena-tag"; import { ArenaTagSide, WeakenMoveTypeTag } from "../arena-tag"; @@ -2549,8 +2549,8 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const rand = Phaser.Math.RND.realInRange(0, 1); - if (rand >= this.chance) { + const rand = randSeedFloat(); + if (rand > this.chance) { return false; } const heldItems = this.getTargetHeldItems(target).filter((i) => i.isTransferable); diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index f9198c5b9c7..ffef746d4c5 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -8,7 +8,14 @@ import type { AnySound } from "#app/battle-scene"; import { globalScene } from "#app/global-scene"; import type { GameMode } from "#app/game-mode"; import { DexAttr, type StarterMoveset } from "#app/system/game-data"; -import { isNullOrUndefined, capitalizeString, randSeedInt, randSeedGauss, randSeedItem } from "#app/utils/common"; +import { + isNullOrUndefined, + capitalizeString, + randSeedInt, + randSeedGauss, + randSeedItem, + randSeedFloat, +} from "#app/utils/common"; import { uncatchableSpecies } from "#app/data/balance/biomes"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { GrowthRate } from "#app/data/exp"; @@ -749,7 +756,7 @@ export abstract class PokemonSpeciesForm { let paletteColors: Map = new Map(); const originalRandom = Math.random; - Math.random = () => Phaser.Math.RND.realInRange(0, 1); + Math.random = randSeedFloat; globalScene.executeWithSeedOffset( () => { @@ -772,6 +779,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali readonly mythical: boolean; readonly species: string; readonly growthRate: GrowthRate; + /** The chance (as a decimal) for this Species to be male, or `null` for genderless species */ readonly malePercent: number | null; readonly genderDiffs: boolean; readonly canChangeForm: boolean; @@ -881,16 +889,14 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali /** * Pick and return a random {@linkcode Gender} for a {@linkcode Pokemon}. - * @param id The personality value of the pokemon being generated. - * @returns THe selected gender for this Pokemon, rolled based on its PID. + * @returns A randomly rolled gender based on this Species' {@linkcode malePercent}. */ - generateGender(id: number): Gender { + generateGender(): Gender { if (isNullOrUndefined(this.malePercent)) { return Gender.GENDERLESS; } - const genderChance = (id % 256) * 0.390625; - if (genderChance < this.malePercent) { + if (randSeedFloat() <= this.malePercent) { return Gender.MALE; } return Gender.FEMALE; @@ -1139,7 +1145,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali } } - if (noEvolutionChance === 1 || Phaser.Math.RND.realInRange(0, 1) < noEvolutionChance) { + if (noEvolutionChance === 1 || randSeedFloat() <= noEvolutionChance) { return this.speciesId; } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 703a813620e..e6f0fac77e6 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -55,7 +55,7 @@ import { getStarterValueFriendshipCap, speciesStarterCosts, } from "#app/data/balance/starters"; -import { NumberHolder, randSeedInt, getIvsFromId, BooleanHolder, randSeedItem, isNullOrUndefined, getEnumValues, toDmgValue, fixedInt, rgbaToInt, rgbHexToRgba, rgbToHsv, deltaRgb, isBetween, type nil, type Constructor } from "#app/utils/common"; +import { NumberHolder, randSeedInt, getIvsFromId, BooleanHolder, randSeedItem, isNullOrUndefined, getEnumValues, toDmgValue, fixedInt, rgbaToInt, rgbHexToRgba, rgbToHsv, deltaRgb, isBetween, type nil, type Constructor, randSeedFloat } from "#app/utils/common"; import type { TypeDamageMultiplier } from "#app/data/type"; import { getTypeDamageMultiplier, getTypeRgb } from "#app/data/type"; import { PokemonType } from "#enums/pokemon-type"; @@ -6046,7 +6046,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { let fusionPaletteColors: Map; const originalRandom = Math.random; - Math.random = () => Phaser.Math.RND.realInRange(0, 1); + Math.random = randSeedFloat; globalScene.executeWithSeedOffset( () => { diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index d4fa7c78e6e..b2609d12dd7 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -15,7 +15,7 @@ import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import type { VoucherType } from "#app/system/voucher"; import { Command } from "#app/ui/command-ui-handler"; import { addTextObject, TextStyle } from "#app/ui/text"; -import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, toDmgValue } from "#app/utils/common"; +import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, randSeedFloat, toDmgValue } from "#app/utils/common"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; import type { Moves } from "#enums/moves"; @@ -3365,7 +3365,7 @@ export class ContactHeldItemTransferChanceModifier extends HeldItemTransferModif } getTransferredItemCount(): number { - return Phaser.Math.RND.realInRange(0, 1) < this.chance * this.getStackCount() ? 1 : 0; + return randSeedFloat() <= this.chance * this.getStackCount() ? 1 : 0; } getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierType): string { @@ -3643,7 +3643,7 @@ export class EnemyAttackStatusEffectChanceModifier extends EnemyPersistentModifi * @returns `true` if the {@linkcode Pokemon} was affected */ override apply(enemyPokemon: Pokemon): boolean { - if (Phaser.Math.RND.realInRange(0, 1) < this.chance * this.getStackCount()) { + if (randSeedFloat() <= this.chance * this.getStackCount()) { return enemyPokemon.trySetStatus(this.effect, true); } @@ -3683,16 +3683,16 @@ export class EnemyStatusEffectHealChanceModifier extends EnemyPersistentModifier * @returns `true` if the {@linkcode Pokemon} was healed */ override apply(enemyPokemon: Pokemon): boolean { - if (enemyPokemon.status && Phaser.Math.RND.realInRange(0, 1) < this.chance * this.getStackCount()) { - globalScene.queueMessage( - getStatusEffectHealText(enemyPokemon.status.effect, getPokemonNameWithAffix(enemyPokemon)), - ); - enemyPokemon.resetStatus(); - enemyPokemon.updateInfo(); - return true; + if (!enemyPokemon.status || randSeedFloat() > this.chance * this.getStackCount()) { + return false; } - return false; + globalScene.queueMessage( + getStatusEffectHealText(enemyPokemon.status.effect, getPokemonNameWithAffix(enemyPokemon)), + ); + enemyPokemon.resetStatus(); + enemyPokemon.updateInfo(); + return true; } getMaxStackCount(): number { @@ -3771,7 +3771,7 @@ export class EnemyFusionChanceModifier extends EnemyPersistentModifier { * @returns `true` if the {@linkcode EnemyPokemon} is a fusion */ override apply(isFusion: BooleanHolder): boolean { - if (Phaser.Math.RND.realInRange(0, 1) >= this.chance * this.getStackCount()) { + if (randSeedFloat() > this.chance * this.getStackCount()) { return false; } diff --git a/src/utils/common.ts b/src/utils/common.ts index 847f8a7ecdb..4b493f637e3 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -57,8 +57,8 @@ export function randSeedGauss(stdev: number, mean = 0): number { if (!stdev) { return 0; } - const u = 1 - Phaser.Math.RND.realInRange(0, 1); - const v = Phaser.Math.RND.realInRange(0, 1); + const u = 1 - randSeedFloat(); + const v = randSeedFloat(); const z = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v); return z * stdev + mean; } @@ -87,9 +87,9 @@ export function randInt(range: number, min = 0): number { } /** - * Generates a random number using the global seed, or the current battle's seed if called via `Battle.randSeedInt` - * @param range How large of a range of random numbers to choose from. If {@linkcode range} <= 1, returns {@linkcode min} - * @param min The minimum integer to pick, default `0` + * Generate a random integer using the global seed, or the current battle's seed if called via `Battle.randSeedInt` + * @param range - How large of a range of random numbers to choose from. If {@linkcode range} <= 1, returns {@linkcode min} + * @param min - The minimum integer to pick, default `0` * @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1) */ export function randSeedInt(range: number, min = 0): number { @@ -108,6 +108,14 @@ export function randIntRange(min: number, max: number): number { return randInt(max - min, min); } +/** + * Generate and return a random real number between `0` and `1` using the global seed. + * @returns A random floating-point number between `0` and `1` + */ +export function randSeedFloat(): number { + return Phaser.Math.RND.frac(); +} + export function randItem(items: T[]): T { return items.length === 1 ? items[0] : items[randInt(items.length)]; } @@ -502,12 +510,19 @@ export function capitalizeString(str: string, sep: string, lowerFirstChar = true return null; } -export function isNullOrUndefined(object: any): object is null | undefined { - return object === null || object === undefined; +/** + * Report whether a given value is nullish (`null`/`undefined`). + * @param val - The value whose nullishness is being checked + * @returns `true` if `val` is either `null` or `undefined` + */ +export function isNullOrUndefined(val: any): val is null | undefined { + return val === null || val === undefined; } /** - * Capitalizes the first letter of a string + * Capitalize the first letter of a string. + * @param str - The string whose first letter is being capitalized + * @return The original string with its first letter capitalized */ export function capitalizeFirstLetter(str: string) { return str.charAt(0).toUpperCase() + str.slice(1);