Fix off-by-one error in some random number calls

This commit is contained in:
NightKev 2024-08-20 04:58:36 -07:00
parent e39ebb68f2
commit 1fb6656c22
9 changed files with 75 additions and 16 deletions

View File

@ -851,7 +851,7 @@ export default class BattleScene extends SceneBase {
overrideModifiers(this, false); overrideModifiers(this, false);
overrideHeldItems(this, pokemon, false); overrideHeldItems(this, pokemon, false);
if (boss && !dataSource) { if (boss && !dataSource) {
const secondaryIvs = Utils.getIvsFromId(Utils.randSeedInt(4294967295)); const secondaryIvs = Utils.getIvsFromId(Utils.randSeedInt(4294967296));
for (let s = 0; s < pokemon.ivs.length; s++) { for (let s = 0; s < pokemon.ivs.length; s++) {
pokemon.ivs[s] = Math.round(Phaser.Math.Linear(Math.min(pokemon.ivs[s], secondaryIvs[s]), Math.max(pokemon.ivs[s], secondaryIvs[s]), 0.75)); pokemon.ivs[s] = Math.round(Phaser.Math.Linear(Math.min(pokemon.ivs[s], secondaryIvs[s]), Math.max(pokemon.ivs[s], secondaryIvs[s]), 0.75));
@ -957,6 +957,7 @@ export default class BattleScene extends SceneBase {
this.offsetGym = this.gameMode.isClassic && this.getGeneratedOffsetGym(); this.offsetGym = this.gameMode.isClassic && this.getGeneratedOffsetGym();
} }
// What is the purpose of this function? Why not just use Battle.randSeedInt() directly?
randBattleSeedInt(range: integer, min: integer = 0): integer { randBattleSeedInt(range: integer, min: integer = 0): integer {
return this.currentBattle?.randSeedInt(this, range, min); return this.currentBattle?.randSeedInt(this, range, min);
} }
@ -1107,7 +1108,8 @@ export default class BattleScene extends SceneBase {
doubleTrainer = false; doubleTrainer = false;
} }
} }
newTrainer = trainerData !== undefined ? trainerData.toTrainer(this) : new Trainer(this, trainerType, doubleTrainer ? TrainerVariant.DOUBLE : Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT); const variant = doubleTrainer ? TrainerVariant.DOUBLE : (Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT);
newTrainer = trainerData !== undefined ? trainerData.toTrainer(this) : new Trainer(this, trainerType, variant);
this.field.add(newTrainer); this.field.add(newTrainer);
} }
} }
@ -2550,7 +2552,7 @@ export default class BattleScene extends SceneBase {
if (mods.length < 1) { if (mods.length < 1) {
return mods; return mods;
} }
const rand = Math.floor(Utils.randSeedInt(mods.length)); const rand = Math.floor(Utils.randSeedInt(mods.length)); // Why is there a floor() call around a number that can only be an integer?
return [mods[rand], ...shuffleModifiers(mods.filter((_, i) => i !== rand))]; return [mods[rand], ...shuffleModifiers(mods.filter((_, i) => i !== rand))];
}; };
modifiers = shuffleModifiers(modifiers); modifiers = shuffleModifiers(modifiers);

View File

@ -2452,7 +2452,7 @@ export class ConfusionOnStatusEffectAbAttr extends PostAttackAbAttr {
*/ */
applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (this.effects.indexOf(args[0]) > -1 && !defender.isFainted()) { if (this.effects.indexOf(args[0]) > -1 && !defender.isFainted()) {
return defender.addTag(BattlerTagType.CONFUSED, pokemon.randSeedInt(3,2), move.id, defender.id); return defender.addTag(BattlerTagType.CONFUSED, pokemon.randSeedIntRange(2, 5), move.id, defender.id);
} }
return false; return false;
} }

View File

@ -347,7 +347,7 @@ export class ConfusedTag extends BattlerTag {
if (pokemon.randSeedInt(3) === 0) { if (pokemon.randSeedInt(3) === 0) {
const atk = pokemon.getBattleStat(Stat.ATK); const atk = pokemon.getBattleStat(Stat.ATK);
const def = pokemon.getBattleStat(Stat.DEF); const def = pokemon.getBattleStat(Stat.DEF);
const damage = Math.ceil(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedInt(15, 85) / 100)); const damage = Math.ceil(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedIntRange(85, 100) / 100));
pokemon.scene.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself")); pokemon.scene.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself"));
pokemon.damageAndUpdate(damage); pokemon.damageAndUpdate(damage);
pokemon.battleData.hitCount++; pokemon.battleData.hitCount++;

View File

@ -4334,7 +4334,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true); const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true);
if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) { if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) {
return (this.selfTarget ? user : target).addTag(this.tagType, user.randSeedInt(this.turnCountMax - this.turnCountMin, this.turnCountMin), move.id, user.id); return (this.selfTarget ? user : target).addTag(this.tagType, user.randSeedIntRange(this.turnCountMin, this.turnCountMax), move.id, user.id);
} }
return false; return false;

View File

@ -1573,7 +1573,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}; };
this.fusionSpecies = this.scene.randomSpecies(this.scene.currentBattle?.waveIndex || 0, this.level, false, filter, true); this.fusionSpecies = this.scene.randomSpecies(this.scene.currentBattle?.waveIndex || 0, this.level, false, filter, true);
this.fusionAbilityIndex = (this.fusionSpecies.abilityHidden && hasHiddenAbility ? this.fusionSpecies.ability2 ? 2 : 1 : this.fusionSpecies.ability2 ? randAbilityIndex : 0); this.fusionAbilityIndex = (this.fusionSpecies.abilityHidden && hasHiddenAbility ? 2 : this.fusionSpecies.ability2 !== this.fusionSpecies.ability1 ? randAbilityIndex : 0);
this.fusionShiny = this.shiny; this.fusionShiny = this.shiny;
this.fusionVariant = this.variant; this.fusionVariant = this.variant;
@ -2081,7 +2081,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (!isTypeImmune) { if (!isTypeImmune) {
const levelMultiplier = (2 * source.level / 5 + 2); const levelMultiplier = (2 * source.level / 5 + 2);
const randomMultiplier = ((this.scene.randBattleSeedInt(16) + 85) / 100); const randomMultiplier = (this.randSeedIntRange(85, 100) / 100);
damage.value = Math.ceil((((levelMultiplier * power * sourceAtk.value / targetDef.value) / 50) + 2) damage.value = Math.ceil((((levelMultiplier * power * sourceAtk.value / targetDef.value) / 50) + 2)
* stabMultiplier.value * stabMultiplier.value
* typeMultiplier.value * typeMultiplier.value

View File

@ -379,16 +379,16 @@ export class MoveEffectPhase extends PokemonPhase {
return false; return false;
} }
const moveAccuracy = this.move.getMove().calculateBattleAccuracy(user!, target); // TODO: is the bang correct here? const moveAccuracy = this.move.getMove().calculateBattleAccuracy(user, target);
if (moveAccuracy === -1) { if (moveAccuracy === -1) {
return true; return true;
} }
const accuracyMultiplier = user.getAccuracyMultiplier(target, this.move.getMove()); const accuracyMultiplier = user.getAccuracyMultiplier(target, this.move.getMove());
const rand = user.randSeedInt(100, 1); const rand = user.randSeedInt(100);
return rand <= moveAccuracy * (accuracyMultiplier!); // TODO: is this bang correct? return rand < (moveAccuracy * accuracyMultiplier);
} }
/** Returns the {@linkcode Pokemon} using this phase's invoked move */ /** Returns the {@linkcode Pokemon} using this phase's invoked move */

56
src/rng.md Normal file
View File

@ -0,0 +1,56 @@
`src/field/pokemon.ts -> Pokemon`
```ts
// This calls either `BattleScene:randBattleSeedInt()` in `src/battle-scene.ts` which calls `Battle:randSeedInt()` in `src/battle.ts` which calls `randSeedInt()` in `src/utils.ts`
// or it directly calls `randSeedInt()` in `src/utils.ts`
randSeedInt(range: integer, min: integer = 0): integer {
return this.scene.currentBattle
? this.scene.randBattleSeedInt(range, min)
: Utils.randSeedInt(range, min);
}
```
`src/battle-scene.ts -> BattleScene`
```ts
// This calls `Battle:randSeedInt()` in `src/battle.ts` which calls `randSeedInt()` in `src/utils.ts`
randBattleSeedInt(range: integer, min: integer = 0): integer {
return this.currentBattle?.randSeedInt(this, range, min);
}
```
`src/battle.ts -> Battle`
```ts
// This calls `randSeedInt()` in `src/utils.ts`
randSeedInt(scene: BattleScene, range: integer, min: integer = 0): integer {
if (range <= 1) {
return min;
}
const tempRngCounter = scene.rngCounter;
const tempSeedOverride = scene.rngSeedOverride;
const state = Phaser.Math.RND.state();
if (this.battleSeedState) {
Phaser.Math.RND.state(this.battleSeedState);
} else {
Phaser.Math.RND.sow([ Utils.shiftCharCodes(this.battleSeed, this.turn << 6) ]);
console.log("Battle Seed:", this.battleSeed);
}
scene.rngCounter = this.rngCounter++;
scene.rngSeedOverride = this.battleSeed;
const ret = Utils.randSeedInt(range, min);
this.battleSeedState = Phaser.Math.RND.state();
Phaser.Math.RND.state(state);
scene.rngCounter = tempRngCounter;
scene.rngSeedOverride = tempSeedOverride;
return ret;
}
```
`src/utils.ts`
```ts
// This is the eventual endpoint of every other RSI function
export function randSeedInt(range: integer, min: integer = 0): integer {
if (range <= 1) {
return min;
}
return Phaser.Math.RND.integerInRange(min, (range - 1) + min);
}
```

View File

@ -82,6 +82,7 @@ export function randInt(range: integer, min: integer = 0): integer {
return Math.floor(Math.random() * range) + min; return Math.floor(Math.random() * range) + min;
} }
// Is this only seeded if called via `Battle:randSeedInt()`?
export function randSeedInt(range: integer, min: integer = 0): integer { export function randSeedInt(range: integer, min: integer = 0): integer {
if (range <= 1) { if (range <= 1) {
return min; return min;