From e05bf78481f4eeb1978c52eec167281efcc75791 Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Tue, 2 Jan 2024 21:31:59 -0500 Subject: [PATCH] Overhaul random logic and implement battle seed --- src/battle-phases.ts | 18 ++++---- src/battle.ts | 18 ++++++++ src/data/ability.ts | 10 ++--- src/data/battler-tag.ts | 8 ++-- src/data/move.ts | 86 ++++++++++++++++++++------------------- src/data/status-effect.ts | 8 +--- src/modifier/modifier.ts | 17 ++++---- src/pokemon.ts | 36 +++++++++++----- src/utils.ts | 16 +++----- 9 files changed, 121 insertions(+), 96 deletions(-) diff --git a/src/battle-phases.ts b/src/battle-phases.ts index 12bc9e79e96..0d9d87e8562 100644 --- a/src/battle-phases.ts +++ b/src/battle-phases.ts @@ -260,7 +260,7 @@ export abstract class FieldPhase extends BattlePhase { const aSpeed = a?.getBattleStat(Stat.SPD) || 0; const bSpeed = b?.getBattleStat(Stat.SPD) || 0; - return aSpeed < bSpeed ? 1 : aSpeed > bSpeed ? -1 : !Utils.randInt(2) ? -1 : 1; + return aSpeed < bSpeed ? 1 : aSpeed > bSpeed ? -1 : !this.scene.currentBattle.randSeedInt(2) ? -1 : 1; }); const speedReversed = new Utils.BooleanHolder(false); @@ -1692,7 +1692,7 @@ export class MovePhase extends BattlePhase { switch (this.pokemon.status.effect) { case StatusEffect.PARALYSIS: - if (Utils.randInt(4) === 0) { + if (!this.pokemon.randSeedInt(4)) { activated = true; this.cancelled = true; } @@ -1704,7 +1704,7 @@ export class MovePhase extends BattlePhase { this.cancelled = activated; break; case StatusEffect.FREEZE: - healed = Utils.randInt(5) === 0; + healed = !this.pokemon.randSeedInt(5); activated = !healed; this.cancelled = activated; break; @@ -1919,7 +1919,7 @@ export class MoveEffectPhase extends PokemonPhase { applyAbAttrs(IgnoreOpponentStatChangesAbAttr, target, null, userAccuracyLevel); applyAbAttrs(IgnoreOpponentStatChangesAbAttr, this.getUserPokemon(), null, targetEvasionLevel); this.scene.applyModifiers(TempBattleStatBoosterModifier, this.player, TempBattleStat.ACC, userAccuracyLevel); - const rand = Utils.randInt(100, 1); + const rand = this.getUserPokemon().randSeedInt(100, 1); let accuracyMultiplier = 1; if (userAccuracyLevel.value !== targetEvasionLevel.value) { accuracyMultiplier = userAccuracyLevel.value > targetEvasionLevel.value @@ -2020,9 +2020,8 @@ export class StatChangePhase extends PokemonPhase { constructor(scene: BattleScene, battlerIndex: BattlerIndex, selfTarget: boolean, stats: BattleStat[], levels: integer) { super(scene, battlerIndex); - const allStats = Utils.getEnumValues(BattleStat); this.selfTarget = selfTarget; - this.stats = stats.map(s => s !== BattleStat.RAND ? s : allStats[Utils.randInt(BattleStat.SPD + 1)]); + this.stats = stats; this.levels = levels; } @@ -2032,7 +2031,8 @@ export class StatChangePhase extends PokemonPhase { if (pokemon.isFainted()) return this.end(); - const filteredStats = this.stats.filter(stat => { + const allStats = Utils.getEnumValues(BattleStat); + const filteredStats = this.stats.map(s => s !== BattleStat.RAND ? s : allStats[pokemon.randSeedInt(BattleStat.SPD + 1)]).filter(stat => { const cancelled = new Utils.BooleanHolder(false); if (!this.selfTarget && this.levels < 0) @@ -3046,7 +3046,7 @@ export class AttemptCapturePhase extends PokemonPhase { shakeCounter.stop(); this.failCatch(shakeCount); } else if (shakeCount++ < 3) { - if (Utils.randInt(65536) < y) + if (pokemon.randSeedInt(65536) < y) this.scene.playSound('pb_move'); else { shakeCounter.stop(); @@ -3190,7 +3190,7 @@ export class AttemptRunPhase extends PokemonPhase { const escapeChance = new Utils.IntegerHolder((((playerPokemon.getStat(Stat.SPD) * 128) / enemySpeed) + (30 * this.scene.currentBattle.escapeAttempts++)) % 256); applyAbAttrs(RunSuccessAbAttr, playerPokemon, null, escapeChance); - if (Utils.randInt(256) < escapeChance.value) { + if (playerPokemon.randSeedInt(256) < escapeChance.value) { this.scene.playSound('flee'); this.scene.queueMessage('You got away safely!', null, true, 500); diff --git a/src/battle.ts b/src/battle.ts index 7f8f50a8f24..964d9bf8ed2 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -48,6 +48,8 @@ export default class Battle { public playerParticipantIds: Set = new Set(); public escapeAttempts: integer = 0; public lastMove: Moves; + public battleSeed: string; + private battleSeedState: string; constructor(waveIndex: integer, battleType: BattleType, trainer: Trainer, double: boolean) { this.waveIndex = waveIndex; @@ -61,6 +63,8 @@ export default class Battle { this.double = double; this.turn = 0; this.started = false; + this.battleSeed = Utils.randomString(16, true); + this.battleSeedState = null; } private getLevelForWave(): integer { @@ -86,6 +90,7 @@ export default class Battle { incrementTurn(scene: BattleScene): void { this.turn++; this.turnCommands = Object.fromEntries(Utils.getEnumValues(BattlerIndex).map(bt => [ bt, null ])); + this.battleSeedState = null; } addParticipant(playerPokemon: PlayerPokemon): void { @@ -120,6 +125,19 @@ export default class Battle { return null; } + + randSeedInt(range: integer, min: integer = 0): integer { + let ret: integer; + 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) ]); + ret = Utils.randSeedInt(range, min); + this.battleSeedState = Phaser.Math.RND.state(); + Phaser.Math.RND.state(state); + return ret; + } } export class FixedBattle extends Battle { diff --git a/src/data/ability.ts b/src/data/ability.ts index 716e8092302..265297093f0 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -347,8 +347,8 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr { } applyPostDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { - if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && Utils.randInt(100) < this.chance && !pokemon.status) { - const effect = this.effects.length === 1 ? this.effects[0] : this.effects[Utils.randInt(this.effects.length)]; + if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance && !pokemon.status) { + const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)]; pokemon.scene.unshiftPhase(new ObtainStatusEffectPhase(pokemon.scene, attacker.getBattlerIndex(), effect)); } @@ -370,7 +370,7 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr { } applyPostDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { - if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && Utils.randInt(100) < this.chance) + if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance) return attacker.addTag(this.tagType, this.turnCount, move.moveId, pokemon.id); return false; @@ -490,7 +490,7 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { if (hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, defender, move.getMove()))) { const heldItems = this.getTargetHeldItems(defender).filter(i => i.getTransferrable(false)); if (heldItems.length) { - const stolenItem = heldItems[Utils.randInt(heldItems.length)]; + const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; pokemon.scene.tryTransferHeldItemModifier(stolenItem, pokemon, false, false).then(success => { if (success) pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` stole\n${defender.name}'s ${stolenItem.type.name}!`)); @@ -523,7 +523,7 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { if (hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move.getMove()))) { const heldItems = this.getTargetHeldItems(attacker).filter(i => i.getTransferrable(false)); if (heldItems.length) { - const stolenItem = heldItems[Utils.randInt(heldItems.length)]; + const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; pokemon.scene.tryTransferHeldItemModifier(stolenItem, pokemon, false, false).then(success => { if (success) pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` stole\n${attacker.name}'s ${stolenItem.type.name}!`)); diff --git a/src/data/battler-tag.ts b/src/data/battler-tag.ts index 76e7cea011f..4c64b71ebcc 100644 --- a/src/data/battler-tag.ts +++ b/src/data/battler-tag.ts @@ -202,10 +202,10 @@ export class ConfusedTag extends BattlerTag { pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nconfused!')); pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION)); - if (Utils.randInt(2)) { + if (pokemon.randSeedInt(2)) { const atk = pokemon.getBattleStat(Stat.ATK); const def = pokemon.getBattleStat(Stat.DEF); - const damage = Math.ceil(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (Utils.randInt(15, 85) / 100)); + const damage = Math.ceil(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedInt(15, 85) / 100)); pokemon.scene.queueMessage('It hurt itself in its\nconfusion!'); pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex())); pokemon.damage(damage); @@ -250,7 +250,7 @@ export class InfatuatedTag extends BattlerTag { pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is in love\nwith ${pokemon.scene.getPokemonById(this.sourceId).name}!`)); pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.ATTRACT)); - if (Utils.randInt(2)) { + if (pokemon.randSeedInt(2)) { pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nimmobilized by love!')); (pokemon.scene.getCurrentPhase() as MovePhase).cancel(); } @@ -347,7 +347,7 @@ export class FrenzyTag extends BattlerTag { onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - pokemon.addTag(BattlerTagType.CONFUSED, Utils.randIntRange(1, 4) + 1); + pokemon.addTag(BattlerTagType.CONFUSED, pokemon.randSeedIntRange(2, 4)); } } diff --git a/src/data/move.ts b/src/data/move.ts index d0ad8c352e5..684ee0e1a43 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1409,7 +1409,7 @@ export class RandomLevelDamageAttr extends FixedDamageAttr { } getDamage(user: Pokemon, target: Pokemon, move: Move): number { - return Math.max(Math.floor(user.level * (Utils.randIntRange(50, 150) * 0.01)), 1); + return Math.max(Math.floor(user.level * (user.randSeedIntRange(50, 150) * 0.01)), 1); } } @@ -1584,7 +1584,7 @@ export class MultiHitAttr extends MoveAttr { switch (this.multiHitType) { case MultiHitType._2_TO_5: { - const rand = Utils.randInt(16); + const rand = user.randSeedInt(16); const hitValue = new Utils.IntegerHolder(rand); applyAbAttrs(MaxMultiHitAbAttr, user, null, hitValue); if (hitValue.value >= 10) @@ -1609,7 +1609,7 @@ export class MultiHitAttr extends MoveAttr { break; case MultiHitType._1_TO_10: { - const rand = Utils.randInt(90); + const rand = user.randSeedInt(90); const hitValue = new Utils.IntegerHolder(rand); applyAbAttrs(MaxMultiHitAbAttr, user, null, hitValue); if (hitValue.value >= 81) @@ -1658,7 +1658,7 @@ export class StatusEffectAttr extends MoveEffectAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const statusCheck = move.chance < 0 || move.chance === 100 || Utils.randInt(100) < move.chance; + const statusCheck = move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance; if (statusCheck) { const pokemon = this.selfTarget ? user : target; if (pokemon.status) { @@ -1689,7 +1689,7 @@ export class StealHeldItemAttr extends MoveEffectAttr { return new Promise(resolve => { const heldItems = this.getTargetHeldItems(target).filter(i => i.getTransferrable(false)); if (heldItems.length) { - const stolenItem = heldItems[Utils.randInt(heldItems.length)]; + const stolenItem = heldItems[user.randSeedInt(heldItems.length)]; user.scene.tryTransferHeldItemModifier(stolenItem, user, false, false).then(success => { if (success) user.scene.queueMessage(getPokemonMessage(user, ` stole\n${target.name}'s ${stolenItem.type.name}!`)); @@ -1933,7 +1933,7 @@ export class StatChangeAttr extends MoveEffectAttr { if (!super.apply(user, target, move, args) || (this.condition && !this.condition(user, target, move))) return false; - if (move.chance < 0 || move.chance === 100 || Utils.randInt(100) < move.chance) { + if (move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance) { const levels = this.getLevels(user); user.scene.unshiftPhase(new StatChangePhase(user.scene, (this.selfTarget ? user : target).getBattlerIndex(), this.selfTarget, this.stats, levels)); return true; @@ -2321,7 +2321,7 @@ export class FrenzyAttr extends MoveEffectAttr { if (!user.getMoveQueue().length) { if (!user.getTag(BattlerTagType.FRENZY)) { - const turnCount = Utils.randIntRange(3, 4); + const turnCount = user.randSeedIntRange(2, 3); new Array(turnCount).fill(null).map(() => user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true })); user.addTag(BattlerTagType.FRENZY, 1, move.id, user.id); } else { @@ -2345,14 +2345,16 @@ export const frenzyMissFunc: UserMoveCondition = (user: Pokemon, move: Move) => export class AddBattlerTagAttr extends MoveEffectAttr { public tagType: BattlerTagType; - public turnCount: integer; + public turnCountMin: integer; + public turnCountMax: integer; private failOnOverlap: boolean; - constructor(tagType: BattlerTagType, selfTarget?: boolean, turnCount?: integer, failOnOverlap?: boolean) { + constructor(tagType: BattlerTagType, selfTarget: boolean = false, failOnOverlap: boolean = false, turnCountMin: integer = 0, turnCountMax?: integer) { super(selfTarget); this.tagType = tagType; - this.turnCount = turnCount; + this.turnCountMin = turnCountMin; + this.turnCountMax = turnCountMax !== undefined ? turnCountMax : turnCountMin; this.failOnOverlap = !!failOnOverlap; } @@ -2361,8 +2363,8 @@ export class AddBattlerTagAttr extends MoveEffectAttr { return false; const chance = this.getTagChance(user, target, move); - if (chance < 0 || chance === 100 || Utils.randInt(100) < chance) { - (this.selfTarget ? user : target).addTag(this.tagType, this.turnCount, move.id, user.id); + if (chance < 0 || chance === 100 || user.randSeedInt(100) < chance) { + (this.selfTarget ? user : target).addTag(this.tagType, user.randSeedInt(this.turnCountMax - this.turnCountMin, this.turnCountMin), move.id, user.id); return true; } @@ -2454,13 +2456,13 @@ export class FlinchAttr extends AddBattlerTagAttr { export class ConfuseAttr extends AddBattlerTagAttr { constructor(selfTarget?: boolean) { - super(BattlerTagType.CONFUSED, selfTarget, Utils.randIntRange(1, 4) + 1); + super(BattlerTagType.CONFUSED, selfTarget, false, 2, 5); } } export class TrapAttr extends AddBattlerTagAttr { constructor(tagType: BattlerTagType) { - super(tagType, false, Utils.randIntRange(2, 5) + 1); + super(tagType, false, false, 3, 6); } } @@ -2472,12 +2474,12 @@ export class ProtectAttr extends AddBattlerTagAttr { getCondition(): MoveCondition { return ((user, target, move): boolean => { let timesUsed = 0; - const moveHistory = user.getLastXMoves(-1); + const moveHistory = user.getLastXMoves(); let turnMove: TurnMove; while (moveHistory.length && (turnMove = moveHistory.shift()).move === move.id && turnMove.result === MoveResult.SUCCESS) timesUsed++; if (timesUsed) - return !Utils.randInt(Math.pow(2, timesUsed)); + return !user.randSeedInt(Math.pow(2, timesUsed)); return true; }); } @@ -2485,7 +2487,7 @@ export class ProtectAttr extends AddBattlerTagAttr { export class IgnoreAccuracyAttr extends AddBattlerTagAttr { constructor() { - super(BattlerTagType.IGNORE_ACCURACY, true, 1); + super(BattlerTagType.IGNORE_ACCURACY, true, false, 1); } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { @@ -2500,14 +2502,14 @@ export class IgnoreAccuracyAttr extends AddBattlerTagAttr { export class FaintCountdownAttr extends AddBattlerTagAttr { constructor() { - super(BattlerTagType.PERISH_SONG, false, 4, true); + super(BattlerTagType.PERISH_SONG, false, true, 4); } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (!super.apply(user, target, move, args)) return false; - user.scene.queueMessage(getPokemonMessage(target, `\nwill faint in ${this.turnCount - 1} turns.`)); + user.scene.queueMessage(getPokemonMessage(target, `\nwill faint in ${this.turnCountMin - 1} turns.`)); return true; } @@ -2548,7 +2550,7 @@ export class AddArenaTagAttr extends MoveEffectAttr { if (!super.apply(user, target, move, args)) return false; - if (move.chance < 0 || move.chance === 100 || Utils.randInt(100) < move.chance) { + if (move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance) { user.scene.arena.addTag(this.tagType, this.turnCount, move.id, user.id); return true; } @@ -2689,7 +2691,7 @@ export class RandomMovesetMoveAttr extends OverrideMoveEffectAttr { const moveset = (!this.enemyMoveset ? user : target).getMoveset(); const moves = moveset.filter(m => !m.getMove().hasFlag(MoveFlags.IGNORE_VIRTUAL)); if (moves.length) { - const move = moves[Utils.randInt(moves.length)]; + const move = moves[user.randSeedInt(moves.length)]; const moveIndex = moveset.findIndex(m => m.moveId === move.moveId); const moveTargets = getMoveTargets(user, move.moveId); if (!moveTargets.targets.length) @@ -2698,7 +2700,7 @@ export class RandomMovesetMoveAttr extends OverrideMoveEffectAttr { ? moveTargets.targets : moveTargets.targets.indexOf(target.getBattlerIndex()) > -1 ? [ target.getBattlerIndex() ] - : [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ]; + : [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; user.getMoveQueue().push({ move: move.moveId, targets: targets, ignorePP: this.enemyMoveset }); user.scene.unshiftPhase(new MovePhase(user.scene, user, targets, moveset[moveIndex], true)); return true; @@ -2712,7 +2714,7 @@ export class RandomMoveAttr extends OverrideMoveEffectAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { return new Promise(resolve => { const moveIds = Utils.getEnumValues(Moves).filter(m => !allMoves[m].hasFlag(MoveFlags.IGNORE_VIRTUAL)); - const moveId = moveIds[Utils.randInt(moveIds.length)]; + const moveId = moveIds[user.randSeedInt(moveIds.length)]; const moveTargets = getMoveTargets(user, moveId); if (!moveTargets.targets.length) { @@ -2723,7 +2725,7 @@ export class RandomMoveAttr extends OverrideMoveEffectAttr { ? moveTargets.targets : moveTargets.targets.indexOf(target.getBattlerIndex()) > -1 ? [ target.getBattlerIndex() ] - : [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ]; + : [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; user.getMoveQueue().push({ move: moveId, targets: targets, ignorePP: true }); user.scene.unshiftPhase(new MovePhase(user.scene, user, targets, new PokemonMove(moveId, 0, 0, true), true)); initMoveAnim(moveId).then(() => { @@ -2760,7 +2762,7 @@ export class CopyMoveAttr extends OverrideMoveEffectAttr { ? moveTargets.targets : moveTargets.targets.indexOf(target.getBattlerIndex()) > -1 ? [ target.getBattlerIndex() ] - : [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ]; + : [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; user.getMoveQueue().push({ move: lastMove, targets: targets, ignorePP: true }); user.scene.unshiftPhase(new MovePhase(user.scene, user as PlayerPokemon, targets, new PokemonMove(lastMove, 0, 0, true), true)); @@ -2945,7 +2947,7 @@ export function getMoveTargets(user: Pokemon, move: Moves): MoveTargetSet { multiple = moveTarget !== MoveTarget.NEAR_ENEMY; break; case MoveTarget.RANDOM_NEAR_ENEMY: - set = [ opponents[Utils.randInt(opponents.length)] ]; + set = [ opponents[user.randSeedInt(opponents.length)] ]; break; case MoveTarget.ATTACKER: return { targets: [ -1 as BattlerIndex ], multiple: false }; @@ -3250,7 +3252,7 @@ export function initMoves() { new StatusMove(Moves.REFLECT, "Reflect (N)", Type.PSYCHIC, -1, 20, 74, "A wondrous wall of light is put up to reduce damage from physical attacks for five turns.", -1, 0, 1) .target(MoveTarget.USER_SIDE), new SelfStatusMove(Moves.FOCUS_ENERGY, "Focus Energy", Type.NORMAL, -1, 30, -1, "The user takes a deep breath and focuses so that critical hits land more easily.", -1, 0, 1) - .attr(AddBattlerTagAttr, BattlerTagType.CRIT_BOOST, true, undefined, true), + .attr(AddBattlerTagAttr, BattlerTagType.CRIT_BOOST, true, true), new AttackMove(Moves.BIDE, "Bide (N)", Type.NORMAL, MoveCategory.PHYSICAL, -1, -1, 10, -1, "The user endures attacks for two turns, then strikes back to cause double the damage taken.", -1, 1, 1) .ignoresVirtual() .target(MoveTarget.USER), @@ -3397,7 +3399,7 @@ export function initMoves() { new AttackMove(Moves.THIEF, "Thief", Type.DARK, MoveCategory.PHYSICAL, 60, 100, 25, 18, "The user attacks and steals the target's held item simultaneously. The user can't steal anything if it already holds an item.", -1, 0, 2) .attr(StealHeldItemAttr), new StatusMove(Moves.SPIDER_WEB, "Spider Web", Type.BUG, -1, 10, -1, "The user ensnares the target with thin, gooey silk so it can't flee from battle.", -1, 0, 2) - .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, 1, true), + .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1), new StatusMove(Moves.MIND_READER, "Mind Reader", Type.NORMAL, -1, 5, -1, "The user senses the target's movements with its mind to ensure its next attack does not miss the target.", -1, 0, 2) .attr(IgnoreAccuracyAttr), new StatusMove(Moves.NIGHTMARE, "Nightmare", Type.GHOST, 100, 15, -1, "A sleeping target sees a nightmare that inflicts some damage every turn.", -1, 0, 2) @@ -3501,7 +3503,7 @@ export function initMoves() { new AttackMove(Moves.STEEL_WING, "Steel Wing", Type.STEEL, MoveCategory.PHYSICAL, 70, 90, 25, -1, "The target is hit with wings of steel. This may also raise the user's Defense stat.", 10, 0, 2) .attr(StatChangeAttr, BattleStat.DEF, 1, true), new StatusMove(Moves.MEAN_LOOK, "Mean Look", Type.NORMAL, -1, 5, -1, "The user pins the target with a dark, arresting look. The target becomes unable to flee.", -1, 0, 2) - .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, 1, true), + .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1), new StatusMove(Moves.ATTRACT, "Attract", Type.NORMAL, 100, 15, -1, "If it is the opposite gender of the user, the target becomes infatuated and less likely to attack.", -1, 0, 2) .attr(AddBattlerTagAttr, BattlerTagType.INFATUATED) .condition((user, target, move) => user.isOppositeGender(target)), @@ -3538,7 +3540,7 @@ export function initMoves() { .attr(ForceSwitchOutAttr, true, true) .hidesUser(), new StatusMove(Moves.ENCORE, "Encore", Type.NORMAL, 100, 5, 122, "The user compels the target to keep using the move it encored for three turns.", -1, 0, 2) - .attr(AddBattlerTagAttr, BattlerTagType.ENCORE, false, undefined, true) + .attr(AddBattlerTagAttr, BattlerTagType.ENCORE, false, true) .condition((user, target, move) => new EncoreTag(move.id, user.id).canAdd(target)), new AttackMove(Moves.PURSUIT, "Pursuit (N)", Type.DARK, MoveCategory.PHYSICAL, 40, 100, 20, -1, "The power of this attack move is doubled if it's used on a target that's switching out of battle.", -1, 0, 2), new AttackMove(Moves.RAPID_SPIN, "Rapid Spin", Type.NORMAL, MoveCategory.PHYSICAL, 50, 100, 40, -1, "A spin attack that can also eliminate such moves as Bind, Wrap, and Leech Seed. This also raises the user's Speed stat.", 100, 0, 2) @@ -3642,7 +3644,7 @@ export function initMoves() { .attr(RandomMovesetMoveAttr, true) .ignoresVirtual(), new SelfStatusMove(Moves.INGRAIN, "Ingrain", Type.GRASS, -1, 20, -1, "The user lays roots that restore its HP on every turn. Because it's rooted, it can't switch out.", -1, 0, 3) - .attr(AddBattlerTagAttr, BattlerTagType.INGRAIN, true, undefined, true), + .attr(AddBattlerTagAttr, BattlerTagType.INGRAIN, true, true), new AttackMove(Moves.SUPERPOWER, "Superpower", Type.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, -1, "The user attacks the target with great power. However, this also lowers the user's Attack and Defense stats.", 100, 0, 3) .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF ], -1, true), new SelfStatusMove(Moves.MAGIC_COAT, "Magic Coat (N)", Type.PSYCHIC, -1, 15, -1, "Moves like Leech Seed and moves that inflict status conditions are blocked by a barrier and reflected back to the user of those moves.", -1, 4, 3), @@ -3650,7 +3652,7 @@ export function initMoves() { new AttackMove(Moves.REVENGE, "Revenge (N)", Type.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, -1, "This attack move's power is doubled if the user has been hurt by the opponent in the same turn.", -1, -4, 3), new AttackMove(Moves.BRICK_BREAK, "Brick Break (N)", Type.FIGHTING, MoveCategory.PHYSICAL, 75, 100, 15, 58, "The user attacks with a swift chop. It can also break barriers, such as Light Screen and Reflect.", -1, 0, 3), new StatusMove(Moves.YAWN, "Yawn", Type.NORMAL, -1, 10, -1, "The user lets loose a huge yawn that lulls the target into falling asleep on the next turn.", -1, 0, 3) - .attr(AddBattlerTagAttr, BattlerTagType.DROWSY, false, undefined, true) + .attr(AddBattlerTagAttr, BattlerTagType.DROWSY, false, true) .condition((user, target, move) => !target.status), new AttackMove(Moves.KNOCK_OFF, "Knock Off (N)", Type.DARK, MoveCategory.PHYSICAL, 65, 100, 20, -1, "The user slaps down the target's held item, and that item can't be used in that battle. The move does more damage if the target has a held item.", -1, 0, 3), new AttackMove(Moves.ENDEAVOR, "Endeavor", Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 5, -1, "This attack move cuts down the target's HP to equal the user's HP.", -1, 0, 3) @@ -3782,7 +3784,7 @@ export function initMoves() { new SelfStatusMove(Moves.IRON_DEFENSE, "Iron Defense", Type.STEEL, -1, 15, 104, "The user hardens its body's surface like iron, sharply raising its Defense stat.", -1, 0, 3) .attr(StatChangeAttr, BattleStat.DEF, 2, true), new StatusMove(Moves.BLOCK, "Block", Type.NORMAL, -1, 5, -1, "The user blocks the target's way with arms spread wide to prevent escape.", -1, 0, 3) - .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, 1, true), + .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1), new StatusMove(Moves.HOWL, "Howl", Type.NORMAL, -1, 40, -1, "The user howls loudly to raise the spirit of itself and allies. This raises their Attack stats.", -1, 0, 3) .attr(StatChangeAttr, BattleStat.ATK, 1, true) .soundBased() @@ -3833,7 +3835,7 @@ export function initMoves() { .attr(StatChangeAttr, BattleStat.SPATK, -2, true), new SelfStatusMove(Moves.ROOST, "Roost", Type.FLYING, -1, 10, -1, "The user lands and rests its body. This move restores the user's HP by up to half of its max HP.", -1, 0, 4) .attr(HealAttr, 0.5) - .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, true, 1), + .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, true, false, 1), new StatusMove(Moves.GRAVITY, "Gravity", Type.PSYCHIC, -1, 5, -1, "This move enables Flying-type Pokémon or Pokémon with the Levitate Ability to be hit by Ground-type moves. Moves that involve flying can't be used.", -1, 0, 4) .attr(AddArenaTagAttr, ArenaTagType.GRAVITY, 5) .target(MoveTarget.BOTH_SIDES), @@ -3884,7 +3886,7 @@ export function initMoves() { new SelfStatusMove(Moves.POWER_TRICK, "Power Trick (N)", Type.PSYCHIC, -1, 10, -1, "The user employs its psychic power to switch its Attack stat with its Defense stat.", -1, 0, 4), new StatusMove(Moves.GASTRO_ACID, "Gastro Acid (N)", Type.POISON, 100, 10, -1, "The user hurls up its stomach acids on the target. The fluid eliminates the effect of the target's Ability.", -1, 0, 4), new StatusMove(Moves.LUCKY_CHANT, "Lucky Chant (N)", Type.NORMAL, -1, 30, -1, "The user chants an incantation toward the sky, preventing opposing Pokémon from landing critical hits for five turns.", -1, 0, 4) - .attr(AddBattlerTagAttr, BattlerTagType.NO_CRIT, false, 5) + .attr(AddBattlerTagAttr, BattlerTagType.NO_CRIT, false, false, 5) .target(MoveTarget.USER_SIDE), new StatusMove(Moves.ME_FIRST, "Me First (N)", Type.NORMAL, -1, 20, -1, "The user cuts ahead of the target to copy and use the target's intended move with greater power. This move fails if it isn't used first.", -1, 0, 4) .ignoresVirtual() @@ -3904,7 +3906,7 @@ export function initMoves() { .target(MoveTarget.ENEMY_SIDE), new StatusMove(Moves.HEART_SWAP, "Heart Swap (N)", Type.PSYCHIC, -1, 10, -1, "The user employs its psychic power to switch stat changes with the target.", -1, 0, 4), new SelfStatusMove(Moves.AQUA_RING, "Aqua Ring", Type.WATER, -1, 20, -1, "The user envelops itself in a veil made of water. It regains some HP every turn.", -1, 0, 4) - .attr(AddBattlerTagAttr, BattlerTagType.AQUA_RING, true, undefined, true), + .attr(AddBattlerTagAttr, BattlerTagType.AQUA_RING, true, true), new SelfStatusMove(Moves.MAGNET_RISE, "Magnet Rise (N)", Type.ELECTRIC, -1, 10, -1, "The user levitates using electrically generated magnetism for five turns.", -1, 0, 4), new AttackMove(Moves.FLARE_BLITZ, "Flare Blitz", Type.FIRE, MoveCategory.PHYSICAL, 120, 100, 15, 165, "The user cloaks itself in fire and charges the target. This also damages the user quite a lot. This attack may leave the target with a burn.", 10, 0, 4) .attr(RecoilAttr) @@ -4110,7 +4112,7 @@ export function initMoves() { .ignoresProtect() .target(MoveTarget.BOTH_SIDES), new AttackMove(Moves.SMACK_DOWN, "Smack Down", Type.ROCK, MoveCategory.PHYSICAL, 50, 100, 15, -1, "The user throws a stone or similar projectile to attack the target. A flying Pokémon will fall to the ground when it's hit.", 100, 0, 5) - .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, 5) + .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 5) .makesContact(false), new AttackMove(Moves.STORM_THROW, "Storm Throw", Type.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, -1, "The user strikes the target with a fierce blow. This attack always results in a critical hit.", -1, 0, 5) .attr(CritOnlyAttr), @@ -4402,10 +4404,10 @@ export function initMoves() { new AttackMove(Moves.OBLIVION_WING, "Oblivion Wing", Type.FLYING, MoveCategory.SPECIAL, 80, 100, 10, -1, "The user absorbs its target's HP. The user's HP is restored by over half of the damage taken by the target.", -1, 0, 6) .attr(HitHealAttr, 0.75), new AttackMove(Moves.THOUSAND_ARROWS, "Thousand Arrows", Type.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, -1, "This move also hits opposing Pokémon that are in the air. Those Pokémon are knocked down to the ground.", 100, 0, 6) - .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, 5) + .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 5) .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(Moves.THOUSAND_WAVES, "Thousand Waves", Type.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, -1, "The user attacks with a wave that crawls along the ground. Those it hits can't flee from battle.", -1, 0, 6) - .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, 1, true) + .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1) .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(Moves.LANDS_WRATH, "Land's Wrath", Type.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, -1, "The user gathers the energy of the land and focuses that power on opposing Pokémon to damage them.", -1, 0, 6) .target(MoveTarget.ALL_NEAR_ENEMIES), @@ -4608,7 +4610,7 @@ export function initMoves() { new SelfStatusMove(Moves.STUFF_CHEEKS, "Stuff Cheeks (N)", Type.NORMAL, -1, 10, -1, "The user eats its held Berry, then sharply raises its Defense stat.", 100, 0, 8), new SelfStatusMove(Moves.NO_RETREAT, "No Retreat", Type.FIGHTING, -1, 5, -1, "This move raises all the user's stats but prevents the user from switching out or fleeing.", 100, 0, 8) .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, true) - .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, true, 1, true), + .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, true, true, 1), new StatusMove(Moves.TAR_SHOT, "Tar Shot (N)", Type.ROCK, 100, 15, -1, "The user pours sticky tar over the target, lowering the target's Speed stat. The target becomes weaker to Fire-type moves.", 100, 0, 8), new StatusMove(Moves.MAGIC_POWDER, "Magic Powder (N)", Type.PSYCHIC, 100, 20, -1, "The user scatters a cloud of magic powder that changes the target to Psychic type.", -1, 0, 8) .powderMove(), @@ -4617,7 +4619,7 @@ export function initMoves() { new StatusMove(Moves.TEATIME, "Teatime (N)", Type.NORMAL, -1, 10, -1, "The user has teatime with all the Pokémon in the battle. Each Pokémon eats its held Berry.", -1, 0, 8) .target(MoveTarget.ALL), new StatusMove(Moves.OCTOLOCK, "Octolock (P)", Type.FIGHTING, 100, 15, -1, "The user locks the target in and prevents it from fleeing. This move also lowers the target's Defense and Sp. Def every turn.", -1, 0, 8) - .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, 1, true), + .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1), new AttackMove(Moves.BOLT_BEAK, "Bolt Beak (N)", Type.ELECTRIC, MoveCategory.PHYSICAL, 85, 100, 10, -1, "The user stabs the target with its electrified beak. If the user attacks before the target, the power of this move is doubled.", -1, 0, 8), new AttackMove(Moves.FISHIOUS_REND, "Fishious Rend (N)", Type.WATER, MoveCategory.PHYSICAL, 85, 100, 10, -1, "The user rends the target with its hard gills. If the user attacks before the target, the power of this move is doubled.", -1, 0, 8) .bitingMove(), @@ -4965,7 +4967,7 @@ export function initMoves() { new AttackMove(Moves.HARD_PRESS, "Hard Press", Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, "The target is crushed with an arm, a claw, or the like to inflict damage. The more HP the target has left, the greater the move's power.", -1, 0, 9) .attr(OpponentHighHpPowerAttr), new StatusMove(Moves.DRAGON_CHEER, "Dragon Cheer (P)", Type.DRAGON, -1, 15, -1, "The user raises its allies' morale with a draconic cry so that their future attacks have a heightened chance of landing critical hits. This rouses Dragon types more.", 100, 0, 9) - .attr(AddBattlerTagAttr, BattlerTagType.CRIT_BOOST, false, undefined, true) + .attr(AddBattlerTagAttr, BattlerTagType.CRIT_BOOST, false, true) .target(MoveTarget.NEAR_ALLY), new AttackMove(Moves.ALLURING_VOICE, "Alluring Voice (N)", Type.FAIRY, MoveCategory.SPECIAL, 80, 100, 10, -1, "The user attacks the target using its angelic voice. This also confuses the target if its stats have been boosted during the turn.", -1, 0, 9), new AttackMove(Moves.TEMPER_FLARE, "Temper Flare (N)", Type.FIRE, MoveCategory.PHYSICAL, 75, 100, 10, -1, "Spurred by desperation, the user attacks the target. This move's power is doubled if the user's previous move failed.", -1, 0, 9), diff --git a/src/data/status-effect.ts b/src/data/status-effect.ts index cd9fa514a00..4ae09526536 100644 --- a/src/data/status-effect.ts +++ b/src/data/status-effect.ts @@ -16,14 +16,10 @@ export class Status { public turnCount: integer; public cureTurn: integer; - constructor(effect: StatusEffect, turnCount?: integer, cureTurn?: integer) { + constructor(effect: StatusEffect, turnCount: integer = 0, cureTurn?: integer) { this.effect = effect; this.turnCount = turnCount === undefined ? 0 : turnCount; - if (cureTurn === undefined) { - if (effect === StatusEffect.SLEEP) - this.cureTurn = Utils.randInt(3, 2); - } else - this.cureTurn = cureTurn; + this.cureTurn = cureTurn; } incrementTurn(): void { diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 4dd137344ff..396466a3019 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -565,7 +565,7 @@ export class SurviveDamageModifier extends PokemonHeldItemModifier { const pokemon = args[0] as Pokemon; const surviveDamage = args[1] as Utils.BooleanHolder; - if (!surviveDamage.value && Utils.randInt(10) < this.getStackCount()) { + if (!surviveDamage.value && pokemon.randSeedInt(10) < this.getStackCount()) { surviveDamage.value = true; pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` hung on\nusing its ${this.type.name}!`)); @@ -598,9 +598,10 @@ export class FlinchChanceModifier extends PokemonHeldItemModifier { } apply(args: any[]): boolean { + const pokemon = args[0] as Pokemon; const flinched = args[1] as Utils.BooleanHolder; - if (!flinched.value && Utils.randInt(10) < this.getStackCount()) { + if (!flinched.value && pokemon.randSeedInt(10) < this.getStackCount()) { flinched.value = true; return true; } @@ -741,7 +742,7 @@ export class BerryModifier extends PokemonHeldItemModifier { } const preserve = new Utils.BooleanHolder(false); - pokemon.scene.applyModifiers(PreserveBerryModifier, pokemon.isPlayer(), preserve); + pokemon.scene.applyModifiers(PreserveBerryModifier, pokemon.isPlayer(), pokemon, preserve); getBerryEffectFunc(this.berryType)(pokemon); if (!preserve.value) @@ -769,12 +770,12 @@ export class PreserveBerryModifier extends PersistentModifier { } shouldApply(args: any[]): boolean { - return super.shouldApply(args) && args[0] instanceof Utils.BooleanHolder; + return super.shouldApply(args) && args[0] instanceof Pokemon && args[1] instanceof Utils.BooleanHolder; } apply(args: any[]): boolean { - if (!(args[0] as Utils.BooleanHolder).value) - (args[0] as Utils.BooleanHolder).value = Utils.randInt(this.getMaxStackCount(null)) < this.getStackCount(); + if (!(args[1] as Utils.BooleanHolder).value) + (args[1] as Utils.BooleanHolder).value = (args[0] as Pokemon).randSeedInt(this.getMaxStackCount(null)) < this.getStackCount(); return true; } @@ -1396,7 +1397,7 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { if (!opponents.length) return false; - const targetPokemon = opponents[Utils.randInt(opponents.length)]; + const targetPokemon = opponents[pokemon.randSeedInt(opponents.length)]; const transferredItemCount = this.getTransferredItemCount(); if (!transferredItemCount) @@ -1413,7 +1414,7 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { for (let i = 0; i < transferredItemCount; i++) { if (!itemModifiers.length) break; - const randItemIndex = Utils.randInt(itemModifiers.length); + const randItemIndex = pokemon.randSeedInt(itemModifiers.length); const randItem = itemModifiers[randItemIndex]; heldItemTransferPromises.push(pokemon.scene.tryTransferHeldItemModifier(randItem, pokemon, false, false, true).then(success => { if (success) { diff --git a/src/pokemon.ts b/src/pokemon.ts index 9393208c01b..6f30ffd3761 100644 --- a/src/pokemon.ts +++ b/src/pokemon.ts @@ -132,7 +132,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.fusionShiny = dataSource.fusionShiny; this.fusionGender = dataSource.fusionGender; } else { - this.id = Utils.randSeedInt(4294967295); + this.id = Utils.randSeedInt(4294967296); this.ivs = ivs || [ Utils.binToDec(Utils.decToBin(this.id).substring(0, 5)), Utils.binToDec(Utils.decToBin(this.id).substring(5, 10)), @@ -956,7 +956,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (source.getTag(BattlerTagType.CRIT_BOOST)) critLevel.value += 2; const critChance = Math.ceil(16 / Math.pow(2, critLevel.value)); - isCritical = !source.getTag(BattlerTagType.NO_CRIT) && !(this.getAbility().hasAttr(BlockCritAbAttr)) && (critChance === 1 || !Utils.randInt(critChance)); + isCritical = !source.getTag(BattlerTagType.NO_CRIT) && !(this.getAbility().hasAttr(BlockCritAbAttr)) && (critChance === 1 || !this.scene.currentBattle.randSeedInt(critChance)); } const sourceAtk = source.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK, this); const targetDef = this.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF, source); @@ -968,7 +968,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyAbAttrs(StabBoostAbAttr, source, null, stabMultiplier); if (!isTypeImmune) { - damage.value = Math.ceil(((((2 * source.level / 5 + 2) * power.value * sourceAtk / targetDef) / 50) + 2) * stabMultiplier.value * typeMultiplier.value * weatherTypeMultiplier * ((Utils.randInt(15) + 85) / 100)) * criticalMultiplier; + damage.value = Math.ceil(((((2 * source.level / 5 + 2) * power.value * sourceAtk / targetDef) / 50) + 2) * stabMultiplier.value * typeMultiplier.value * weatherTypeMultiplier * ((this.scene.currentBattle.randSeedInt(15) + 85) / 100)) * criticalMultiplier; if (isPhysical && source.status && source.status.effect === StatusEffect.BURN) damage.value = Math.floor(damage.value / 2); move.getAttrs(HitsTagAttr).map(hta => hta as HitsTagAttr).filter(hta => hta.doubleDamage).forEach(hta => { @@ -1087,14 +1087,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return this.species.speciesId === Species.ETERNATUS && this.formIndex === 1; } - addTag(tagType: BattlerTagType, turnCount?: integer, sourceMove?: Moves, sourceId?: integer): boolean { + addTag(tagType: BattlerTagType, turnCount: integer = 0, sourceMove?: Moves, sourceId?: integer): boolean { const existingTag = this.getTag(tagType); if (existingTag) { existingTag.onOverlap(this); return false; } - const newTag = getBattlerTag(tagType, turnCount || 0, sourceMove, sourceId); + const newTag = getBattlerTag(tagType, turnCount, sourceMove, sourceId); const cancelled = new Utils.BooleanHolder(false); applyPreApplyBattlerTagAbAttrs(PreApplyBattlerTagAbAttr, this, newTag, cancelled); @@ -1387,10 +1387,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (cancelled.value) return false; - if (effect === StatusEffect.SLEEP) - this.setFrameRate(4); + let cureTurn: integer; - this.status = new Status(effect); + if (effect === StatusEffect.SLEEP) { + cureTurn = this.randSeedIntRange(2, 4); + this.setFrameRate(4); + } + + this.status = new Status(effect, 0, cureTurn); return true; } @@ -1693,6 +1697,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { fusionCanvas.remove(); } + randSeedInt(range: integer, min: integer = 0): integer { + return this.scene.currentBattle + ? this.scene.currentBattle.randSeedInt(range, min) + : Utils.randSeedInt(range, min); + } + + randSeedIntRange(min: integer, max: integer): integer { + return this.randSeedInt((max - min) + 1, min); + } + destroy(): void { this.battleInfo.destroy(); super.destroy(); @@ -1958,7 +1972,7 @@ export class EnemyPokemon extends Pokemon { } switch (this.aiType) { case AiType.RANDOM: - const moveId = movePool[Utils.randInt(movePool.length)].moveId; + const moveId = movePool[this.scene.currentBattle.randSeedInt(movePool.length)].moveId; return { move: moveId, targets: this.getNextTargets(moveId) }; case AiType.SMART_RANDOM: case AiType.SMART: @@ -1990,7 +2004,7 @@ export class EnemyPokemon extends Pokemon { }); let r = 0; if (this.aiType === AiType.SMART_RANDOM) { - while (r < sortedMovePool.length - 1 && Utils.randInt(8) >= 5) + while (r < sortedMovePool.length - 1 && this.scene.currentBattle.randSeedInt(8) >= 5) r++; } console.log(movePool.map(m => m.getName()), moveScores, r, sortedMovePool.map(m => m.getName())); @@ -2043,7 +2057,7 @@ export class EnemyPokemon extends Pokemon { return total; }, 0); - const randValue = Utils.randInt(totalWeight); + const randValue = this.scene.currentBattle.randSeedInt(totalWeight); let targetIndex: integer; thresholds.every((t, i) => { diff --git a/src/utils.ts b/src/utils.ts index c51ae2b089c..67bcf421fa5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,12 +2,12 @@ export function toReadableString(str: string): string { return str.replace(/\_/g, ' ').split(' ').map(s => `${s.slice(0, 1)}${s.slice(1).toLowerCase()}`).join(' '); } -export function randomString(length: integer) { +export function randomString(length: integer, seeded: boolean = false) { const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let result = ''; for (let i = 0; i < length; i++) { - const randomIndex = Math.floor(Math.random() * characters.length); + const randomIndex = seeded ? randSeedInt(characters.length) : Math.floor(Math.random() * characters.length); result += characters[randomIndex]; } @@ -56,17 +56,13 @@ export function padInt(value: integer, length: integer, padWith?: string): strin return valueStr; } -export function randInt(range: integer, min?: integer): integer { - if (!min) - min = 0; +export function randInt(range: integer, min: integer = 0): integer { if (range === 1) return min; return Math.floor(Math.random() * range) + min; } -export function randSeedInt(range: integer, min?: integer): integer { - if (!min) - min = 0; +export function randSeedInt(range: integer, min: integer = 0): integer { if (range === 1) return min; return Phaser.Math.RND.integerInRange(min, (range - 1) + min); @@ -162,9 +158,7 @@ export function apiFetch(path: string): Promise { }); } -export function apiPost(path: string, data?: any, contentType?: string): Promise { - if (!contentType) - contentType = 'application/json'; +export function apiPost(path: string, data?: any, contentType: string = 'application/json'): Promise { return new Promise((resolve, reject) => { const headers = { 'Accept': contentType,