diff --git a/public/fonts/PokePT_Wansung.woff2 b/public/fonts/PokePT_Wansung.woff2 index 02b299ef7a0..15aab3eb4f4 100644 Binary files a/public/fonts/PokePT_Wansung.woff2 and b/public/fonts/PokePT_Wansung.woff2 differ diff --git a/public/images/pokemon/back/821.png b/public/images/pokemon/back/821.png index a102a0491de..24e0edd705d 100644 Binary files a/public/images/pokemon/back/821.png and b/public/images/pokemon/back/821.png differ diff --git a/public/images/pokemon/back/shiny/821.png b/public/images/pokemon/back/shiny/821.png index 200489518c4..6d29988ed9f 100644 Binary files a/public/images/pokemon/back/shiny/821.png and b/public/images/pokemon/back/shiny/821.png differ diff --git a/public/images/pokemon/exp/821.png b/public/images/pokemon/exp/821.png index e4a73a66140..8bb00063bcb 100644 Binary files a/public/images/pokemon/exp/821.png and b/public/images/pokemon/exp/821.png differ diff --git a/public/images/pokemon/exp/953.png b/public/images/pokemon/exp/953.png index fb4255a112b..8a6ca2e7eff 100644 Binary files a/public/images/pokemon/exp/953.png and b/public/images/pokemon/exp/953.png differ diff --git a/public/images/pokemon/exp/back/821.png b/public/images/pokemon/exp/back/821.png index 80d88dce2f9..13aa5c24e83 100644 Binary files a/public/images/pokemon/exp/back/821.png and b/public/images/pokemon/exp/back/821.png differ diff --git a/public/images/pokemon/exp/back/shiny/821.png b/public/images/pokemon/exp/back/shiny/821.png index 426a1e81fea..d069d59ef72 100644 Binary files a/public/images/pokemon/exp/back/shiny/821.png and b/public/images/pokemon/exp/back/shiny/821.png differ diff --git a/public/images/pokemon/exp/shiny/821.png b/public/images/pokemon/exp/shiny/821.png index 5c03c17c793..6f6cf7f2991 100644 Binary files a/public/images/pokemon/exp/shiny/821.png and b/public/images/pokemon/exp/shiny/821.png differ diff --git a/public/images/pokemon/variant/exp/953.json b/public/images/pokemon/variant/exp/953.json index df4262f3ed2..6f2885be607 100644 --- a/public/images/pokemon/variant/exp/953.json +++ b/public/images/pokemon/variant/exp/953.json @@ -2,79 +2,35 @@ "1": { "5a4d37": "1c1e76", "766348": "323aa5", - "9d8361": "4059bd", "c5b4aa": "d3e6e6", "37332b": "104139", "9e8360": "4059bd", - "f18725": "2e8c19", - "f18625": "2e8c19", - "bb6f2a": "2f7410", "0f0f0f": "0f0f0f", "575243": "18734a", - "b8702d": "2f7410", "777462": "199e46", - "585243": "18734a", - "4f4531": "b29c3e", - "4d4530": "b29c3e", - "aea56b": "f9fba2", - "afa667": "f9fba2", - "a28e85": "c1d8db", - "a28c8c": "c1d8db", - "b64c95": "dedb64", + "afa668": "f9fba2", + "a28c88": "c1d8db", "f2f2f2": "e8eab5", - "b05f8f": "dedb64", - "776348": "323aa5", + "b45492": "dedb64", "f28625": "2e8c19", - "bc6e28": "2f7410", - "bb6e27": "2f7410", - "4d4430": "b29c3e", - "b0a766": "f9fba2", - "a18f8d": "c1d8db", - "a28f86": "c1d8db", - "b74a94": "dedb64", - "9e8461": "4059bd", - "777362": "199e46", - "ba6d27": "2f7410", - "afa567": "f9fba2", - "a18f85": "c1d8db" + "4e4530": "b29c3e", + "ba6e29": "2f7410" }, "2": { "5a4d37": "333e5f", "766348": "8c9fbf", - "9d8361": "dbedec", "c5b4aa": "39cfbc", "37332b": "261031", "9e8360": "dbedec", - "f18725": "4baecd", - "f18625": "4baecd", - "bb6f2a": "4792bd", "0f0f0f": "0f0f0f", "575243": "5e2d72", - "b8702d": "4792bd", "777462": "8358a1", - "585243": "5e2d72", - "4f4531": "534b8c", - "4d4530": "534b8c", - "aea56b": "c9dbac", - "afa667": "c9dbac", - "a28e85": "52b0b0", - "a28c8c": "52b0b0", - "b64c95": "b56c3e", + "afa668": "c9dbac", + "a28e88": "52b0b0", "f2f2f2": "d9c951", - "b05f8f": "b56c3e", - "776348": "8c9fbf", + "b45492": "b56c3e", "f28625": "4baecd", - "bc6e28": "4792bd", - "bb6e27": "4792bd", - "4d4430": "534b8c", - "b0a766": "c9dbac", - "a18f8d": "52b0b0", - "a28f86": "52b0b0", - "b74a94": "b56c3e", - "9e8461": "dbedec", - "777362": "8358a1", - "ba6d27": "4792bd", - "afa567": "c9dbac", - "a18f85": "52b0b0" + "4e4530": "534b8c", + "ba6e29": "4792bd" } -} \ No newline at end of file +} diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 4bc623773d2..9cae60da05d 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1100,7 +1100,7 @@ export default class BattleScene extends SceneBase { } else if (trainerConfigs[trainerType].hasDouble) { const doubleChance = new Utils.IntegerHolder(newWaveIndex % 10 === 0 ? 32 : 8); this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance); - playerField.forEach(p => applyAbAttrs(DoubleBattleChanceAbAttr, p, null, doubleChance)); + playerField.forEach(p => applyAbAttrs(DoubleBattleChanceAbAttr, p, null, false, doubleChance)); doubleTrainer = !Utils.randSeedInt(doubleChance.value); // Add a check that special trainers can't be double except for tate and liza - they should use the normal double chance if (trainerConfigs[trainerType].trainerTypeDouble && ![ TrainerType.TATE, TrainerType.LIZA ].includes(trainerType)) { @@ -1116,7 +1116,7 @@ export default class BattleScene extends SceneBase { if (newBattleType === BattleType.WILD && !this.gameMode.isWaveFinal(newWaveIndex)) { const doubleChance = new Utils.IntegerHolder(newWaveIndex % 10 === 0 ? 32 : 8); this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance); - playerField.forEach(p => applyAbAttrs(DoubleBattleChanceAbAttr, p, null, doubleChance)); + playerField.forEach(p => applyAbAttrs(DoubleBattleChanceAbAttr, p, null, false, doubleChance)); newDouble = !Utils.randSeedInt(doubleChance.value); } else if (newBattleType === BattleType.TRAINER) { newDouble = newTrainer?.variant === TrainerVariant.DOUBLE; @@ -1518,7 +1518,7 @@ export default class BattleScene extends SceneBase { return; } const formattedMoney = Utils.formatMoney(this.moneyFormat, this.money); - this.moneyText.setText(`₽${formattedMoney}`); + this.moneyText.setText(i18next.t("battleScene:moneyOwned", { formattedMoney })); this.fieldUI.moveAbove(this.moneyText, this.luckText); if (forceVisible) { this.moneyText.setVisible(true); @@ -2136,7 +2136,7 @@ export default class BattleScene extends SceneBase { pushMovePhase(movePhase: MovePhase, priorityOverride?: integer): void { const movePriority = new Utils.IntegerHolder(priorityOverride !== undefined ? priorityOverride : movePhase.move.getMove().priority); - applyAbAttrs(ChangeMovePriorityAbAttr, movePhase.pokemon, null, movePhase.move.getMove(), movePriority); + applyAbAttrs(ChangeMovePriorityAbAttr, movePhase.pokemon, null, false, movePhase.move.getMove(), movePriority); const lowerPriorityPhase = this.phaseQueue.find(p => p instanceof MovePhase && p.move.getMove().priority < movePriority.value); if (lowerPriorityPhase) { this.phaseQueue.splice(this.phaseQueue.indexOf(lowerPriorityPhase), 0, movePhase); diff --git a/src/data/ability.ts b/src/data/ability.ts index 9d0fd65a98a..8687971f81a 100755 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -135,7 +135,7 @@ export abstract class AbAttr { this.showAbility = showAbility; } - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder | null, args: any[]): boolean | Promise { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder | null, args: any[]): boolean | Promise { return false; } @@ -154,7 +154,7 @@ export abstract class AbAttr { } export class BlockRecoilDamageAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { cancelled.value = true; return true; @@ -170,7 +170,7 @@ export class DoubleBattleChanceAbAttr extends AbAttr { super(false); } - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { const doubleChance = (args[0] as Utils.IntegerHolder); doubleChance.value = Math.max(doubleChance.value / 2, 1); return true; @@ -178,7 +178,7 @@ export class DoubleBattleChanceAbAttr extends AbAttr { } export class PostBattleInitAbAttr extends AbAttr { - applyPostBattleInit(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise { + applyPostBattleInit(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { return false; } } @@ -192,9 +192,9 @@ export class PostBattleInitFormChangeAbAttr extends PostBattleInitAbAttr { this.formFunc = formFunc; } - applyPostBattleInit(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + applyPostBattleInit(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const formIndex = this.formFunc(pokemon); - if (formIndex !== pokemon.formIndex) { + if (formIndex !== pokemon.formIndex && !simulated) { return pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); } @@ -217,22 +217,24 @@ export class PostBattleInitStatChangeAbAttr extends PostBattleInitAbAttr { this.selfTarget = !!selfTarget; } - applyPostBattleInit(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + applyPostBattleInit(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const statChangePhases: StatChangePhase[] = []; - if (this.selfTarget) { - statChangePhases.push(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.levels)); - } else { - for (const opponent of pokemon.getOpponents()) { - statChangePhases.push(new StatChangePhase(pokemon.scene, opponent.getBattlerIndex(), false, this.stats, this.levels)); + if (!simulated) { + if (this.selfTarget) { + statChangePhases.push(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.levels)); + } else { + for (const opponent of pokemon.getOpponents()) { + statChangePhases.push(new StatChangePhase(pokemon.scene, opponent.getBattlerIndex(), false, this.stats, this.levels)); + } } - } - for (const statChangePhase of statChangePhases) { - if (!this.selfTarget && !statChangePhase.getPokemon()?.summonData) { - pokemon.scene.pushPhase(statChangePhase); - } else { // TODO: This causes the ability bar to be shown at the wrong time - pokemon.scene.unshiftPhase(statChangePhase); + for (const statChangePhase of statChangePhases) { + if (!this.selfTarget && !statChangePhase.getPokemon()?.summonData) { + pokemon.scene.pushPhase(statChangePhase); + } else { // TODO: This causes the ability bar to be shown at the wrong time + pokemon.scene.unshiftPhase(statChangePhase); + } } } @@ -243,17 +245,17 @@ export class PostBattleInitStatChangeAbAttr extends PostBattleInitAbAttr { type PreDefendAbAttrCondition = (pokemon: Pokemon, attacker: Pokemon, move: Move) => boolean; export class PreDefendAbAttr extends AbAttr { - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move | null, cancelled: Utils.BooleanHolder | null, args: any[]): boolean | Promise { + applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move | null, cancelled: Utils.BooleanHolder | null, args: any[]): boolean | Promise { return false; } } export class PreDefendFullHpEndureAbAttr extends PreDefendAbAttr { - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (pokemon.isFullHp() && - pokemon.getMaxHp() > 1 && //Checks if pokemon has wonder_guard (which forces 1hp) - (args[0] as Utils.NumberHolder).value >= pokemon.hp) { //Damage >= hp - return pokemon.addTag(BattlerTagType.STURDY, 1); + applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { + if (pokemon.isFullHp() + && pokemon.getMaxHp() > 1 //Checks if pokemon has wonder_guard (which forces 1hp) + && (args[0] as Utils.NumberHolder).value >= pokemon.hp) { //Damage >= hp + return simulated || pokemon.addTag(BattlerTagType.STURDY, 1); } return false; @@ -261,7 +263,7 @@ export class PreDefendFullHpEndureAbAttr extends PreDefendAbAttr { } export class BlockItemTheftAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { cancelled.value = true; return true; @@ -276,7 +278,7 @@ export class BlockItemTheftAbAttr extends AbAttr { } export class StabBoostAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { if ((args[0] as Utils.NumberHolder).value > 1) { (args[0] as Utils.NumberHolder).value += 0.5; return true; @@ -297,9 +299,9 @@ export class ReceivedMoveDamageMultiplierAbAttr extends PreDefendAbAttr { this.damageMultiplier = damageMultiplier; } - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { + applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { if (this.condition(pokemon, attacker, move)) { - (args[0] as Utils.NumberHolder).value = Math.floor((args[0] as Utils.NumberHolder).value * this.damageMultiplier); + (args[0] as Utils.NumberHolder).value = Utils.toDmgValue((args[0] as Utils.NumberHolder).value * this.damageMultiplier); return true; } @@ -341,7 +343,7 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr { * @param args [0] {@linkcode Utils.NumberHolder} gets set to 0 if move is immuned by an ability. * @param args [1] - Whether the move is simulated. */ - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { + applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { // Field moves should ignore immunity if ([ MoveTarget.BOTH_SIDES, MoveTarget.ENEMY_SIDE, MoveTarget.USER_SIDE ].includes(move.moveTarget)) { return false; @@ -368,9 +370,9 @@ export class AttackTypeImmunityAbAttr extends TypeImmunityAbAttr { * Type immunity abilities that do not give additional benefits (HP recovery, stat boosts, etc) are not immune to status moves of the type * Example: Levitate */ - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { + applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { if (move.category !== MoveCategory.STATUS) { - return super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args); + return super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); } return false; } @@ -381,17 +383,14 @@ export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr { super(immuneType); } - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { - const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args); + applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { + const ret = super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); if (ret) { - if (!pokemon.isFullHp()) { - const simulated = args.length > 1 && args[1]; - if (!simulated) { - const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; - pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), - Math.max(Math.floor(pokemon.getMaxHp() / 4), 1), i18next.t("abilityTriggers:typeImmunityHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); - } + if (!pokemon.isFullHp() && !simulated) { + const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; + pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), + Utils.toDmgValue(pokemon.getMaxHp() / 4), i18next.t("abilityTriggers:typeImmunityHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); } return true; } @@ -411,12 +410,11 @@ class TypeImmunityStatChangeAbAttr extends TypeImmunityAbAttr { this.levels = levels; } - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { - const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args); + applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { + const ret = super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); if (ret) { cancelled.value = true; - const simulated = args.length > 1 && args[1]; if (!simulated) { pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.levels)); } @@ -437,12 +435,11 @@ class TypeImmunityAddBattlerTagAbAttr extends TypeImmunityAbAttr { this.turnCount = turnCount; } - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { - const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args); + applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { + const ret = super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); if (ret) { cancelled.value = true; - const simulated = args.length > 1 && args[1]; if (!simulated) { pokemon.addTag(this.tagType, this.turnCount, undefined, pokemon.id); } @@ -457,7 +454,7 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr { super(null, condition); } - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { + applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { if (move instanceof AttackMove && pokemon.getAttackTypeEffectiveness(move.type, attacker) < 2) { cancelled.value = true; (args[0] as Utils.NumberHolder).value = 0; @@ -476,7 +473,7 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr { } export class PostDefendAbAttr extends AbAttr { - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise { + applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise { return false; } } @@ -500,12 +497,16 @@ export class PostDefendGulpMissileAbAttr extends PostDefendAbAttr { * @param {any[]} args - n/a * @returns Whether the effects of the ability are applied. */ - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean | Promise { + applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean | Promise { const battlerTag = pokemon.getTag(GulpMissileTag); if (!battlerTag || move.category === MoveCategory.STATUS || pokemon.getTag(SemiInvulnerableTag)) { return false; } + if (simulated) { + return true; + } + const cancelled = new Utils.BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled); @@ -525,12 +526,12 @@ export class PostDefendGulpMissileAbAttr extends PostDefendAbAttr { } export class FieldPriorityMoveImmunityAbAttr extends PreDefendAbAttr { - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { + applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { const attackPriority = new Utils.IntegerHolder(move.priority); - applyMoveAttrs(IncrementMovePriorityAttr,attacker,null,move,attackPriority); - applyAbAttrs(ChangeMovePriorityAbAttr, attacker, null, move, attackPriority); + applyMoveAttrs(IncrementMovePriorityAttr, attacker, null, move, attackPriority); + applyAbAttrs(ChangeMovePriorityAbAttr, attacker, null, simulated, move, attackPriority); - if (move.moveTarget===MoveTarget.USER || move.moveTarget===MoveTarget.NEAR_ALLY) { + if (move.moveTarget === MoveTarget.USER || move.moveTarget === MoveTarget.NEAR_ALLY) { return false; } @@ -544,7 +545,7 @@ export class FieldPriorityMoveImmunityAbAttr extends PreDefendAbAttr { } export class PostStatChangeAbAttr extends AbAttr { - applyPostStatChange(pokemon: Pokemon, statsChanged: BattleStat[], levelChanged: integer, selfTarget: boolean, args: any[]): boolean | Promise { + applyPostStatChange(pokemon: Pokemon, simulated: boolean, statsChanged: BattleStat[], levelChanged: integer, selfTarget: boolean, args: any[]): boolean | Promise { return false; } } @@ -558,7 +559,7 @@ export class MoveImmunityAbAttr extends PreDefendAbAttr { this.immuneCondition = immuneCondition; } - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { + applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { if (this.immuneCondition(pokemon, attacker, move)) { cancelled.value = true; return true; @@ -579,7 +580,7 @@ export class MoveImmunityAbAttr extends PreDefendAbAttr { * @extends PreDefendAbAttr */ export class WonderSkinAbAttr extends PreDefendAbAttr { - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { + applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { const moveAccuracy = args[0] as Utils.NumberHolder; if (move.category === MoveCategory.STATUS && moveAccuracy.value >= 50) { moveAccuracy.value = 50; @@ -600,13 +601,10 @@ export class MoveImmunityStatChangeAbAttr extends MoveImmunityAbAttr { this.levels = levels; } - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { - const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args); - if (ret) { - const simulated = args.length > 1 && args[1]; - if (!simulated) { - pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.levels)); - } + applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { + const ret = super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); + if (ret && !simulated) { + pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.levels)); } return ret; @@ -630,9 +628,11 @@ export class ReverseDrainAbAttr extends PostDefendAbAttr { * @args N/A * @returns true if healing should be reversed on a healing move, false otherwise. */ - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { if (move.hasAttr(HitHealAttr)) { - pokemon.scene.queueMessage(i18next.t("abilityTriggers:reverseDrain", { pokemonNameWithAffix: getPokemonNameWithAffix(attacker) })); + if (!simulated) { + pokemon.scene.queueMessage(i18next.t("abilityTriggers:reverseDrain", { pokemonNameWithAffix: getPokemonNameWithAffix(attacker) })); + } return true; } return false; @@ -656,8 +656,12 @@ export class PostDefendStatChangeAbAttr extends PostDefendAbAttr { this.allOthers = allOthers; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { if (this.condition(pokemon, attacker, move)) { + if (simulated) { + return true; + } + if (this.allOthers) { const otherPokemon = pokemon.getAlly() ? pokemon.getOpponents().concat([ pokemon.getAlly() ]) : pokemon.getOpponents(); for (const other of otherPokemon) { @@ -690,13 +694,15 @@ export class PostDefendHpGatedStatChangeAbAttr extends PostDefendAbAttr { this.selfTarget = selfTarget; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { const hpGateFlat: integer = Math.ceil(pokemon.getMaxHp() * this.hpGate); const lastAttackReceived = pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1]; const damageReceived = lastAttackReceived?.damage || 0; if (this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + damageReceived) > hpGateFlat)) { - pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, (this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.levels)); + if (!simulated) { + pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, (this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.levels)); + } return true; } @@ -715,11 +721,13 @@ export class PostDefendApplyArenaTrapTagAbAttr extends PostDefendAbAttr { this.tagType = tagType; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { if (this.condition(pokemon, attacker, move)) { const tag = pokemon.scene.arena.getTag(this.tagType) as ArenaTrapTag; if (!pokemon.scene.arena.getTag(this.tagType) || tag.layers < tag.maxLayers) { - pokemon.scene.arena.addTag(this.tagType, 0, undefined, pokemon.id, pokemon.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER); + if (!simulated) { + pokemon.scene.arena.addTag(this.tagType, 0, undefined, pokemon.id, pokemon.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER); + } return true; } } @@ -737,9 +745,9 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr { this.tagType = tagType; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { if (this.condition(pokemon, attacker, move)) { - if (!pokemon.getTag(this.tagType)) { + if (!pokemon.getTag(this.tagType) && !simulated) { pokemon.addTag(this.tagType, undefined, undefined, pokemon.id); pokemon.scene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name })); } @@ -750,8 +758,11 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr { } export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr { - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { if (hitResult < HitResult.NO_EFFECT) { + if (simulated) { + return true; + } const type = move.type; const pokemonTypes = pokemon.getTypes(true); if (pokemonTypes.length !== 1 || pokemonTypes[0] !== type) { @@ -781,9 +792,13 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr { this.terrainType = terrainType; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { if (hitResult < HitResult.NO_EFFECT) { - return pokemon.scene.arena.trySetTerrain(this.terrainType, true); + if (simulated) { + return pokemon.scene.arena.terrain?.terrainType !== (this.terrainType || undefined); + } else { + return pokemon.scene.arena.trySetTerrain(this.terrainType, true); + } } return false; @@ -801,10 +816,14 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr { this.effects = effects; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) { const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)]; - return attacker.trySetStatus(effect, true, pokemon); + if (simulated) { + return attacker.canSetStatus(effect, true, false, pokemon); + } else { + return attacker.trySetStatus(effect, true, pokemon); + } } return false; @@ -816,11 +835,11 @@ export class EffectSporeAbAttr extends PostDefendContactApplyStatusEffectAbAttr super(10, StatusEffect.POISON, StatusEffect.PARALYSIS, StatusEffect.SLEEP); } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { if (attacker.hasAbility(Abilities.OVERCOAT) || attacker.isOfType(Type.GRASS)) { return false; } - return super.applyPostDefend(pokemon, passive, attacker, move, hitResult, args); + return super.applyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args); } } @@ -837,9 +856,13 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr { this.turnCount = turnCount; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance) { - return attacker.addTag(this.tagType, this.turnCount, move.id, attacker.id); + if (simulated) { + return attacker.canAddTag(this.tagType); + } else { + return attacker.addTag(this.tagType, this.turnCount, move.id, attacker.id); + } } return false; @@ -857,8 +880,10 @@ export class PostDefendCritStatChangeAbAttr extends PostDefendAbAttr { this.levels = levels; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.levels)); + applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + if (!simulated) { + pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.levels)); + } return true; } @@ -877,10 +902,10 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr { this.damageRatio = damageRatio; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { - attacker.damageAndUpdate(Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER); - attacker.turnData.damageTaken += Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio)); + applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + if (!simulated && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { + attacker.damageAndUpdate(Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER); + attacker.turnData.damageTaken += Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)); return true; } @@ -910,13 +935,15 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr { this.turns = turns; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) { if (pokemon.getTag(BattlerTagType.PERISH_SONG) || attacker.getTag(BattlerTagType.PERISH_SONG)) { return false; } else { - attacker.addTag(BattlerTagType.PERISH_SONG, this.turns); - pokemon.addTag(BattlerTagType.PERISH_SONG, this.turns); + if (!simulated) { + attacker.addTag(BattlerTagType.PERISH_SONG, this.turns); + pokemon.addTag(BattlerTagType.PERISH_SONG, this.turns); + } return true; } } @@ -939,11 +966,14 @@ export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr { this.condition = condition ?? null; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { if (this.condition !== null && !this.condition(pokemon, attacker, move)) { return false; } if (!pokemon.scene.arena.weather?.isImmutable()) { + if (simulated) { + return pokemon.scene.arena.weather?.weatherType !== this.weatherType; + } return pokemon.scene.arena.trySetWeather(this.weatherType, true); } @@ -956,11 +986,13 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr { super(); } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr)) { - const tempAbilityId = attacker.getAbility().id; - attacker.summonData.ability = pokemon.getAbility().id; - pokemon.summonData.ability = tempAbilityId; + if (!simulated) { + const tempAbilityId = attacker.getAbility().id; + attacker.summonData.ability = pokemon.getAbility().id; + pokemon.summonData.ability = tempAbilityId; + } return true; } @@ -980,9 +1012,11 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr { this.ability = ability; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr) && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr)) { - attacker.summonData.ability = this.ability; + if (!simulated) { + attacker.summonData.ability = this.ability; + } return true; } @@ -1009,9 +1043,13 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr { this.chance = chance; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { if (!attacker.summonData.disabledMove) { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !attacker.isMax()) { + if (simulated) { + return true; + } + this.attacker = attacker; this.move = move; @@ -1044,9 +1082,11 @@ export class PostStatChangeStatChangeAbAttr extends PostStatChangeAbAttr { this.levels = levels; } - applyPostStatChange(pokemon: Pokemon, statsChanged: BattleStat[], levelsChanged: integer, selfTarget: boolean, args: any[]): boolean { + applyPostStatChange(pokemon: Pokemon, simulated: boolean, statsChanged: BattleStat[], levelsChanged: integer, selfTarget: boolean, args: any[]): boolean { if (this.condition(pokemon, statsChanged, levelsChanged) && !selfTarget) { - pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, (pokemon).getBattlerIndex(), true, this.statsToChange, this.levels)); + if (!simulated) { + pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, (pokemon).getBattlerIndex(), true, this.statsToChange, this.levels)); + } return true; } @@ -1055,7 +1095,7 @@ export class PostStatChangeStatChangeAbAttr extends PostStatChangeAbAttr { } export class PreAttackAbAttr extends AbAttr { - applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon | null, move: Move, args: any[]): boolean | Promise { + applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon | null, move: Move, args: any[]): boolean | Promise { return false; } } @@ -1076,7 +1116,7 @@ export class MoveEffectChanceMultiplierAbAttr extends AbAttr { * @param args [0]: {@linkcode Utils.NumberHolder} Move additional effect chance. Has to be higher than or equal to 0. * [1]: {@linkcode Moves } Move used by the ability user. */ - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { // Disable showAbility during getTargetBenefitScore this.showAbility = args[4]; if ((args[0] as Utils.NumberHolder).value <= 0 || (args[1] as Move).id === Moves.ORDER_UP) { @@ -1099,7 +1139,7 @@ export class IgnoreMoveEffectsAbAttr extends PreDefendAbAttr { /** * @param args [0]: {@linkcode Utils.NumberHolder} Move additional effect chance. */ - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { + applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { if ((args[0] as Utils.NumberHolder).value <= 0) { return false; @@ -1112,14 +1152,14 @@ export class IgnoreMoveEffectsAbAttr extends PreDefendAbAttr { } export class VariableMovePowerAbAttr extends PreAttackAbAttr { - applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean { + applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): boolean { //const power = args[0] as Utils.NumberHolder; return false; } } export class FieldPreventExplosiveMovesAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { cancelled.value = true; return true; } @@ -1156,7 +1196,7 @@ export class FieldMultiplyBattleStatAbAttr extends AbAttr { * @param args {any[]} unused * @returns true if this changed the checked stat, false otherwise. */ - applyFieldBattleStat(pokemon: Pokemon, passive: boolean, stat: Stat, statValue: Utils.NumberHolder, checkedPokemon: Pokemon, hasApplied: Utils.BooleanHolder, args: any[]): boolean { + applyFieldBattleStat(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: Stat, statValue: Utils.NumberHolder, checkedPokemon: Pokemon, hasApplied: Utils.BooleanHolder, args: any[]): boolean { if (!this.canStack && hasApplied.value) { return false; } @@ -1180,7 +1220,7 @@ export class MoveTypeChangeAttr extends PreAttackAbAttr { super(true); } - applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean { + applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): boolean { if (this.condition && this.condition(pokemon, defender, move)) { move.type = this.newType; if (args[0] && args[0] instanceof Utils.NumberHolder) { @@ -1201,7 +1241,7 @@ export class PokemonTypeChangeAbAttr extends PreAttackAbAttr { super(true); } - applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean { + applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): boolean { if ( !pokemon.isTerastallized() && move.id !== Moves.STRUGGLE && @@ -1229,9 +1269,11 @@ export class PokemonTypeChangeAbAttr extends PreAttackAbAttr { } if (pokemon.getTypes().some((t) => t !== moveCopy.type)) { - this.moveType = moveCopy.type; - pokemon.summonData.types = [moveCopy.type]; - pokemon.updateInfo(); + if (!simulated) { + this.moveType = moveCopy.type; + pokemon.summonData.types = [moveCopy.type]; + pokemon.updateInfo(); + } return true; } @@ -1307,7 +1349,7 @@ export class AddSecondStrikeAbAttr extends PreAttackAbAttr { * @param {Utils.NumberHolder} args\[2\] the damage multiplier for the current strike * @returns */ - applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean { + applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): boolean { const numTargets = args[0] as integer; const hitCount = args[1] as Utils.IntegerHolder; const multiplier = args[2] as Utils.NumberHolder; @@ -1352,7 +1394,7 @@ export class DamageBoostAbAttr extends PreAttackAbAttr { * @param args Utils.NumberHolder as damage * @returns true if the function succeeds */ - applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean { + applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): boolean { if (this.condition(pokemon, defender, move)) { const power = args[0] as Utils.NumberHolder; power.value = Math.floor(power.value * this.damageMultiplier); @@ -1373,7 +1415,7 @@ export class MovePowerBoostAbAttr extends VariableMovePowerAbAttr { this.powerMultiplier = powerMultiplier; } - applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean { + applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): boolean { if (this.condition(pokemon, defender, move)) { (args[0] as Utils.NumberHolder).value *= this.powerMultiplier; @@ -1420,7 +1462,7 @@ export class VariableMovePowerBoostAbAttr extends VariableMovePowerAbAttr { /** * @override */ - applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move, args: any[]): boolean { + applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move, args: any[]): boolean { const multiplier = this.mult(pokemon, defender, move); if (multiplier !== 1) { (args[0] as Utils.NumberHolder).value *= multiplier; @@ -1449,7 +1491,7 @@ export class FieldMovePowerBoostAbAttr extends AbAttr { this.powerMultiplier = powerMultiplier; } - applyPreAttack(pokemon: Pokemon | null, passive: boolean | null, defender: Pokemon | null, move: Move, args: any[]): boolean { + applyPreAttack(pokemon: Pokemon | null, passive: boolean | null, simulated: boolean, defender: Pokemon | null, move: Move, args: any[]): boolean { if (this.condition(pokemon, defender, move)) { (args[0] as Utils.NumberHolder).value *= this.powerMultiplier; @@ -1513,7 +1555,7 @@ export class BattleStatMultiplierAbAttr extends AbAttr { this.condition = condition ?? null; } - applyBattleStat(pokemon: Pokemon, passive: boolean, battleStat: BattleStat, statValue: Utils.NumberHolder, args: any[]): boolean | Promise { + applyBattleStat(pokemon: Pokemon, passive: boolean, simulated: boolean, battleStat: BattleStat, statValue: Utils.NumberHolder, args: any[]): boolean | Promise { const move = (args[0] as Move); if (battleStat === this.battleStat && (!this.condition || this.condition(pokemon, null, move))) { statValue.value *= this.multiplier; @@ -1539,11 +1581,11 @@ export class PostAttackAbAttr extends AbAttr { * applying the effect of any inherited class. This can be changed by providing a different {@link attackCondition} to the constructor. See {@link ConfusionOnStatusEffectAbAttr} * for an example of an effect that does not require a damaging move. */ - applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise { + applyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise { // When attackRequired is true, we require the move to be an attack move and to deal damage before checking secondary requirements. // If attackRequired is false, we always defer to the secondary requirements. if (this.attackCondition(pokemon, defender, move)) { - return this.applyPostAttackAfterMoveTypeCheck(pokemon, passive, defender, move, hitResult, args); + return this.applyPostAttackAfterMoveTypeCheck(pokemon, passive, simulated, defender, move, hitResult, args); } else { return false; } @@ -1552,7 +1594,7 @@ export class PostAttackAbAttr extends AbAttr { /** * This method is only called after {@link applyPostAttack} has already been applied. Use this for handling checks specific to the ability in question. */ - applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise { + applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise { return false; } } @@ -1566,9 +1608,9 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { this.stealCondition = stealCondition ?? null; } - applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise { + applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise { return new Promise(resolve => { - if (hitResult < HitResult.NO_EFFECT && (!this.stealCondition || this.stealCondition(pokemon, defender, move))) { + if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.stealCondition || this.stealCondition(pokemon, defender, move))) { const heldItems = this.getTargetHeldItems(defender).filter(i => i.isTransferrable); if (heldItems.length) { const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; @@ -1581,7 +1623,7 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { return; } } - resolve(false); + resolve(simulated); }); } @@ -1604,14 +1646,14 @@ export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr { this.effects = effects; } - applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { /**Status inflicted by abilities post attacking are also considered additional effects.*/ - if (!attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && pokemon !== attacker && (!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance && !pokemon.status) { + if (!attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && !simulated && pokemon !== attacker && (!this.contactRequired || move.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)]; return attacker.trySetStatus(effect, true, pokemon); } - return false; + return simulated; } } @@ -1635,11 +1677,11 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr { this.effects = effects; } - applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { /**Battler tags inflicted by abilities post attacking are also considered additional effects.*/ if (!attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && pokemon !== attacker && (!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance(attacker, pokemon, move) && !pokemon.status) { const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)]; - return attacker.addTag(effect); + return simulated || attacker.addTag(effect); } return false; @@ -1655,9 +1697,9 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { this.condition = condition ?? null; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise { + applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise { return new Promise(resolve => { - if (hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move))) { + if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move))) { const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferrable); if (heldItems.length) { const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; @@ -1670,7 +1712,7 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { return; } } - resolve(false); + resolve(simulated); }); } @@ -1681,7 +1723,7 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { } export class PostVictoryAbAttr extends AbAttr { - applyPostVictory(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise { + applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { return false; } } @@ -1697,12 +1739,13 @@ class PostVictoryStatChangeAbAttr extends PostVictoryAbAttr { this.levels = levels; } - applyPostVictory(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise { + applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { const stat = typeof this.stat === "function" ? this.stat(pokemon) : this.stat; - pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], this.levels)); - + if (!simulated) { + pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], this.levels)); + } return true; } } @@ -1716,10 +1759,12 @@ export class PostVictoryFormChangeAbAttr extends PostVictoryAbAttr { this.formFunc = formFunc; } - applyPostVictory(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise { + applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { const formIndex = this.formFunc(pokemon); if (formIndex !== pokemon.formIndex) { - pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); + if (!simulated) { + pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); + } return true; } @@ -1728,7 +1773,7 @@ export class PostVictoryFormChangeAbAttr extends PostVictoryAbAttr { } export class PostKnockOutAbAttr extends AbAttr { - applyPostKnockOut(pokemon: Pokemon, passive: boolean, knockedOut: Pokemon, args: any[]): boolean | Promise { + applyPostKnockOut(pokemon: Pokemon, passive: boolean, simulated: boolean, knockedOut: Pokemon, args: any[]): boolean | Promise { return false; } } @@ -1744,12 +1789,13 @@ export class PostKnockOutStatChangeAbAttr extends PostKnockOutAbAttr { this.levels = levels; } - applyPostKnockOut(pokemon: Pokemon, passive: boolean, knockedOut: Pokemon, args: any[]): boolean | Promise { + applyPostKnockOut(pokemon: Pokemon, passive: boolean, simulated: boolean, knockedOut: Pokemon, args: any[]): boolean | Promise { const stat = typeof this.stat === "function" ? this.stat(pokemon) : this.stat; - pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], this.levels)); - + if (!simulated) { + pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], this.levels)); + } return true; } } @@ -1759,10 +1805,12 @@ export class CopyFaintedAllyAbilityAbAttr extends PostKnockOutAbAttr { super(); } - applyPostKnockOut(pokemon: Pokemon, passive: boolean, knockedOut: Pokemon, args: any[]): boolean | Promise { + applyPostKnockOut(pokemon: Pokemon, passive: boolean, simulated: boolean, knockedOut: Pokemon, args: any[]): boolean | Promise { if (pokemon.isPlayer() === knockedOut.isPlayer() && !knockedOut.getAbility().hasAttr(UncopiableAbilityAbAttr)) { - pokemon.summonData.ability = knockedOut.getAbility().id; - pokemon.scene.queueMessage(i18next.t("abilityTriggers:copyFaintedAllyAbility", { pokemonNameWithAffix: getPokemonNameWithAffix(knockedOut), abilityName: allAbilities[knockedOut.getAbility().id].name })); + if (!simulated) { + pokemon.summonData.ability = knockedOut.getAbility().id; + pokemon.scene.queueMessage(i18next.t("abilityTriggers:copyFaintedAllyAbility", { pokemonNameWithAffix: getPokemonNameWithAffix(knockedOut), abilityName: allAbilities[knockedOut.getAbility().id].name })); + } return true; } @@ -1775,7 +1823,7 @@ export class IgnoreOpponentStatChangesAbAttr extends AbAttr { super(false); } - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]) { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]) { (args[0] as Utils.IntegerHolder).value = 0; return true; @@ -1798,7 +1846,7 @@ export class IgnoreOpponentEvasionAbAttr extends AbAttr { * @param args [0] {@linkcode Utils.IntegerHolder} of BattleStat.EVA * @returns if evasion level was successfully considered as 0 */ - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]) { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]) { (args[0] as Utils.IntegerHolder).value = 0; return true; } @@ -1809,7 +1857,7 @@ export class IntimidateImmunityAbAttr extends AbAttr { super(false); } - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { cancelled.value = true; return true; } @@ -1834,8 +1882,10 @@ export class PostIntimidateStatChangeAbAttr extends AbAttr { this.overwrites = !!overwrites; } - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { - pokemon.scene.pushPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, this.stats, this.levels)); + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + if (!simulated) { + pokemon.scene.pushPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, this.stats, this.levels)); + } cancelled.value = this.overwrites; return true; } @@ -1853,7 +1903,7 @@ export class PostSummonAbAttr extends AbAttr { * @param args Set of unique arguments needed by this attribute * @returns true if application of the ability succeeds */ - applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise { + applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { return false; } } @@ -1872,9 +1922,11 @@ export class PostSummonRemoveArenaTagAbAttr extends PostSummonAbAttr { this.arenaTags = arenaTags; } - applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise { - for (const arenaTag of this.arenaTags) { - pokemon.scene.arena.removeTag(arenaTag); + applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { + if (!simulated) { + for (const arenaTag of this.arenaTags) { + pokemon.scene.arena.removeTag(arenaTag); + } } return true; } @@ -1888,14 +1940,17 @@ export class PostSummonMessageAbAttr extends PostSummonAbAttr { this.messageFunc = messageFunc; } - applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean { - pokemon.scene.queueMessage(this.messageFunc(pokemon)); + applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + if (!simulated) { + pokemon.scene.queueMessage(this.messageFunc(pokemon)); + } + return true; } } export class PostSummonRemoveIllusionAbAttr extends PostSummonAbAttr { - applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { pokemon.scene.getField(true).map(pokemon => { pokemon.breakIllusion(); }); @@ -1913,8 +1968,10 @@ export class PostSummonUnnamedMessageAbAttr extends PostSummonAbAttr { this.message = message; } - applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean { - pokemon.scene.queueMessage(this.message); + applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + if (!simulated) { + pokemon.scene.queueMessage(this.message); + } return true; } @@ -1931,8 +1988,12 @@ export class PostSummonAddBattlerTagAbAttr extends PostSummonAbAttr { this.turnCount = turnCount; } - applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean { - return pokemon.addTag(this.tagType, this.turnCount); + applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + if (simulated) { + return pokemon.canAddTag(this.tagType); + } else { + return pokemon.addTag(this.tagType, this.turnCount); + } } } @@ -1953,7 +2014,11 @@ export class PostSummonStatChangeAbAttr extends PostSummonAbAttr { this.intimidate = !!intimidate; } - applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + if (simulated) { + return true; + } + queueShowAbility(pokemon, passive); // TODO: Better solution than manually showing the ability here if (this.selfTarget) { // we unshift the StatChangePhase to put it right after the showAbility and not at the end of the @@ -1964,8 +2029,8 @@ export class PostSummonStatChangeAbAttr extends PostSummonAbAttr { for (const opponent of pokemon.getOpponents()) { const cancelled = new Utils.BooleanHolder(false); if (this.intimidate) { - applyAbAttrs(IntimidateImmunityAbAttr, opponent, cancelled); - applyAbAttrs(PostIntimidateStatChangeAbAttr, opponent, cancelled); + applyAbAttrs(IntimidateImmunityAbAttr, opponent, cancelled, simulated); + applyAbAttrs(PostIntimidateStatChangeAbAttr, opponent, cancelled, simulated); } if (!cancelled.value) { const statChangePhase = new StatChangePhase(pokemon.scene, opponent.getBattlerIndex(), false, this.stats, this.levels); @@ -1987,11 +2052,14 @@ export class PostSummonAllyHealAbAttr extends PostSummonAbAttr { this.showAnim = showAnim; } - applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const target = pokemon.getAlly(); if (target?.isActive(true)) { - target.scene.unshiftPhase(new PokemonHealPhase(target.scene, target.getBattlerIndex(), - Math.max(Math.floor(pokemon.getMaxHp() / this.healRatio), 1), i18next.t("abilityTriggers:postSummonAllyHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(target), pokemonName: pokemon.name }), true, !this.showAnim)); + if (!simulated) { + target.scene.unshiftPhase(new PokemonHealPhase(target.scene, target.getBattlerIndex(), + Utils.toDmgValue(pokemon.getMaxHp() / this.healRatio), i18next.t("abilityTriggers:postSummonAllyHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(target), pokemonName: pokemon.name }), true, !this.showAnim)); + } + return true; } @@ -2012,14 +2080,16 @@ export class PostSummonClearAllyStatsAbAttr extends PostSummonAbAttr { super(); } - applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const target = pokemon.getAlly(); if (target?.isActive(true)) { - for (let s = 0; s < target.summonData.battleStats.length; s++) { - target.summonData.battleStats[s] = 0; - } + if (!simulated) { + for (let s = 0; s < target.summonData.battleStats.length; s++) { + target.summonData.battleStats[s] = 0; + } - target.scene.queueMessage(i18next.t("abilityTriggers:postSummonClearAllyStats", { pokemonNameWithAffix: getPokemonNameWithAffix(target) })); + target.scene.queueMessage(i18next.t("abilityTriggers:postSummonClearAllyStats", { pokemonNameWithAffix: getPokemonNameWithAffix(target) })); + } return true; } @@ -2050,7 +2120,7 @@ export class DownloadAbAttr extends PostSummonAbAttr { * @param {any[]} args N/A * @returns Returns true if ability is used successful, false if not. */ - applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { this.enemyDef = 0; this.enemySpDef = 0; this.enemyCountTally = 0; @@ -2070,7 +2140,9 @@ export class DownloadAbAttr extends PostSummonAbAttr { } if (this.enemyDef > 0 && this.enemySpDef > 0) { // only activate if there's actually an enemy to download from - pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, this.stats, 1)); + if (!simulated) { + pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, this.stats, 1)); + } return true; } @@ -2087,11 +2159,15 @@ export class PostSummonWeatherChangeAbAttr extends PostSummonAbAttr { this.weatherType = weatherType; } - applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { if ((this.weatherType === WeatherType.HEAVY_RAIN || this.weatherType === WeatherType.HARSH_SUN || this.weatherType === WeatherType.STRONG_WINDS) || !pokemon.scene.arena.weather?.isImmutable()) { - return pokemon.scene.arena.trySetWeather(this.weatherType, true); + if (simulated) { + return pokemon.scene.arena.weather?.weatherType !== this.weatherType; + } else { + return pokemon.scene.arena.trySetWeather(this.weatherType, true); + } } return false; @@ -2107,8 +2183,12 @@ export class PostSummonTerrainChangeAbAttr extends PostSummonAbAttr { this.terrainType = terrainType; } - applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean { - return pokemon.scene.arena.trySetTerrain(this.terrainType, true); + applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + if (simulated) { + return pokemon.scene.arena.terrain?.terrainType !== this.terrainType; + } else { + return pokemon.scene.arena.trySetTerrain(this.terrainType, true); + } } } @@ -2121,10 +2201,10 @@ export class PostSummonFormChangeAbAttr extends PostSummonAbAttr { this.formFunc = formFunc; } - applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const formIndex = this.formFunc(pokemon); if (formIndex !== pokemon.formIndex) { - return pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); + return simulated || pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); } return false; @@ -2136,7 +2216,7 @@ export class PostSummonCopyAbilityAbAttr extends PostSummonAbAttr { private target: Pokemon; private targetAbilityName: string; - applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const targets = pokemon.getOpponents(); if (!targets.length) { return false; @@ -2157,11 +2237,13 @@ export class PostSummonCopyAbilityAbAttr extends PostSummonAbAttr { return false; } - this.target = target!; - this.targetAbilityName = allAbilities[target!.getAbility().id].name; - pokemon.summonData.ability = target!.getAbility().id; - setAbilityRevealed(target!); - pokemon.updateInfo(); + if (!simulated) { + this.target = target!; + this.targetAbilityName = allAbilities[target!.getAbility().id].name; + pokemon.summonData.ability = target!.getAbility().id; + setAbilityRevealed(target!); + pokemon.updateInfo(); + } return true; } @@ -2198,7 +2280,7 @@ export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAtt * @param args - n/a * @returns A boolean or a promise that resolves to a boolean indicating the result of the ability application. */ - applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise { + applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { const party = pokemon instanceof PlayerPokemon ? pokemon.scene.getPlayerField() : pokemon.scene.getEnemyField(); const allowedParty = party.filter(p => p.isAllowedInBattle()); @@ -2206,14 +2288,15 @@ export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAtt return false; } - for (const pokemon of allowedParty) { - if (pokemon.status && this.statusEffect.includes(pokemon.status.effect)) { - pokemon.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); - pokemon.resetStatus(false); - pokemon.updateInfo(); + if (!simulated) { + for (const pokemon of allowedParty) { + if (pokemon.status && this.statusEffect.includes(pokemon.status.effect)) { + pokemon.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); + pokemon.resetStatus(false); + pokemon.updateInfo(); + } } } - return true; } } @@ -2221,7 +2304,7 @@ export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAtt /** Attempt to copy the stat changes on an ally pokemon */ export class PostSummonCopyAllyStatsAbAttr extends PostSummonAbAttr { - applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { if (!pokemon.scene.currentBattle.double) { return false; } @@ -2231,8 +2314,10 @@ export class PostSummonCopyAllyStatsAbAttr extends PostSummonAbAttr { return false; } - pokemon.summonData.battleStats = ally.summonData.battleStats; - pokemon.updateInfo(); + if (!simulated) { + pokemon.summonData.battleStats = ally.summonData.battleStats; + pokemon.updateInfo(); + } return true; } @@ -2250,10 +2335,10 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { super(true); } - applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const targets = pokemon.getOpponents(); - if (!targets.length) { - return false; + if (simulated || !targets.length) { + return simulated; } let target: Pokemon = targets[0]; @@ -2291,16 +2376,19 @@ export class PreSwitchOutAbAttr extends AbAttr { super(true); } - applyPreSwitchOut(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise { + applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { return false; } } export class PreSwitchOutResetStatusAbAttr extends PreSwitchOutAbAttr { - applyPreSwitchOut(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise { + applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { if (pokemon.status) { - pokemon.resetStatus(); - pokemon.updateInfo(); + if (!simulated) { + pokemon.resetStatus(); + pokemon.updateInfo(); + } + return true; } @@ -2319,7 +2407,7 @@ export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr { * @param args N/A * @returns {boolean} Returns true if the weather clears, otherwise false. */ - applyPreSwitchOut(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise { + applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { const weatherType = pokemon.scene.arena.weather?.weatherType; let turnOffWeather = false; @@ -2345,6 +2433,10 @@ export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr { break; } + if (simulated) { + return turnOffWeather; + } + if (turnOffWeather) { pokemon.scene.arena.trySetWeather(WeatherType.NONE, false); return true; @@ -2355,11 +2447,14 @@ export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr { } export class PreSwitchOutHealAbAttr extends PreSwitchOutAbAttr { - applyPreSwitchOut(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise { + applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { if (!pokemon.isFullHp()) { - const healAmount = Math.floor(pokemon.getMaxHp() * 0.33); - pokemon.heal(healAmount); - pokemon.updateInfo(); + if (!simulated) { + const healAmount = Utils.toDmgValue(pokemon.getMaxHp() * 0.33); + pokemon.heal(healAmount); + pokemon.updateInfo(); + } + return true; } @@ -2388,10 +2483,12 @@ export class PreSwitchOutFormChangeAbAttr extends PreSwitchOutAbAttr { * @param args N/A * @returns true if the form change was successful */ - applyPreSwitchOut(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise { + applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { const formIndex = this.formFunc(pokemon); if (formIndex !== pokemon.formIndex) { - pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); + if (!simulated) { + pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); + } return true; } @@ -2401,7 +2498,7 @@ export class PreSwitchOutFormChangeAbAttr extends PreSwitchOutAbAttr { } export class PreStatChangeAbAttr extends AbAttr { - applyPreStatChange(pokemon: Pokemon | null, passive: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { + applyPreStatChange(pokemon: Pokemon | null, passive: boolean, simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { return false; } } @@ -2415,7 +2512,7 @@ export class ProtectStatAbAttr extends PreStatChangeAbAttr { this.protectedStat = protectedStat; } - applyPreStatChange(pokemon: Pokemon, passive: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean { + applyPreStatChange(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean { if (Utils.isNullOrUndefined(this.protectedStat) || stat === this.protectedStat) { cancelled.value = true; return true; @@ -2459,16 +2556,20 @@ export class ConfusionOnStatusEffectAbAttr extends PostAttackAbAttr { * @param args [0] {@linkcode StatusEffect} applied by move * @returns true if defender is confused */ - applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { if (this.effects.indexOf(args[0]) > -1 && !defender.isFainted()) { - return defender.addTag(BattlerTagType.CONFUSED, pokemon.randSeedInt(3,2), move.id, defender.id); + if (simulated) { + return defender.canAddTag(BattlerTagType.CONFUSED); + } else { + return defender.addTag(BattlerTagType.CONFUSED, pokemon.randSeedInt(3,2), move.id, defender.id); + } } return false; } } export class PreSetStatusAbAttr extends AbAttr { - applyPreSetStatus(pokemon: Pokemon, passive: boolean, effect: StatusEffect | undefined, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { + applyPreSetStatus(pokemon: Pokemon, passive: boolean, simulated: boolean, effect: StatusEffect | undefined, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { return false; } } @@ -2498,7 +2599,7 @@ export class PreSetStatusEffectImmunityAbAttr extends PreSetStatusAbAttr { * @param args - n/a * @returns A boolean indicating the result of the status application. */ - applyPreSetStatus(pokemon: Pokemon, passive: boolean, effect: StatusEffect, cancelled: Utils.BooleanHolder, args: any[]): boolean { + applyPreSetStatus(pokemon: Pokemon, passive: boolean, simulated: boolean, effect: StatusEffect, cancelled: Utils.BooleanHolder, args: any[]): boolean { if (this.immuneEffects.length < 1 || this.immuneEffects.includes(effect)) { cancelled.value = true; return true; @@ -2534,7 +2635,7 @@ export class StatusEffectImmunityAbAttr extends PreSetStatusEffectImmunityAbAttr export class UserFieldStatusEffectImmunityAbAttr extends PreSetStatusEffectImmunityAbAttr { } export class PreApplyBattlerTagAbAttr extends AbAttr { - applyPreApplyBattlerTag(pokemon: Pokemon, passive: boolean, tag: BattlerTag, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { + applyPreApplyBattlerTag(pokemon: Pokemon, passive: boolean, simulated: boolean, tag: BattlerTag, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { return false; } } @@ -2552,10 +2653,12 @@ export class PreApplyBattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr { this.immuneTagType = immuneTagType; } - applyPreApplyBattlerTag(pokemon: Pokemon, passive: boolean, tag: BattlerTag, cancelled: Utils.BooleanHolder, args: any[]): boolean { + applyPreApplyBattlerTag(pokemon: Pokemon, passive: boolean, simulated: boolean, tag: BattlerTag, cancelled: Utils.BooleanHolder, args: any[]): boolean { if (tag.tagType === this.immuneTagType) { cancelled.value = true; - this.battlerTag = tag; + if (!simulated) { + this.battlerTag = tag; + } return true; } @@ -2584,14 +2687,14 @@ export class BattlerTagImmunityAbAttr extends PreApplyBattlerTagImmunityAbAttr { export class UserFieldBattlerTagImmunityAbAttr extends PreApplyBattlerTagImmunityAbAttr { } export class BlockCritAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { (args[0] as Utils.BooleanHolder).value = true; return true; } } export class BonusCritAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { (args[0] as Utils.BooleanHolder).value = true; return true; } @@ -2606,7 +2709,7 @@ export class MultCritAbAttr extends AbAttr { this.multAmount = multAmount; } - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { const critMult = args[0] as Utils.NumberHolder; if (critMult.value > 1) { critMult.value *= this.multAmount; @@ -2637,7 +2740,7 @@ export class ConditionalCritAbAttr extends AbAttr { * [1] {@linkcode Pokemon} Target. * [2] {@linkcode Move} used by ability user. */ - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { const target = (args[1] as Pokemon); const move = (args[2] as Move); if (!this.condition(pokemon,target,move)) { @@ -2650,7 +2753,7 @@ export class ConditionalCritAbAttr extends AbAttr { } export class BlockNonDirectDamageAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { cancelled.value = true; return true; } @@ -2678,7 +2781,7 @@ export class BlockStatusDamageAbAttr extends AbAttr { * @param {any[]} args N/A * @returns Returns true if status damage is blocked */ - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { if (pokemon.status && this.effects.includes(pokemon.status.effect)) { cancelled.value = true; return true; @@ -2688,7 +2791,7 @@ export class BlockStatusDamageAbAttr extends AbAttr { } export class BlockOneHitKOAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { cancelled.value = true; return true; } @@ -2714,7 +2817,7 @@ export class ChangeMovePriorityAbAttr extends AbAttr { this.changeAmount = changeAmount; } - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { if (!this.moveFunc(pokemon, args[0] as Move)) { return false; } @@ -2727,7 +2830,7 @@ export class ChangeMovePriorityAbAttr extends AbAttr { export class IgnoreContactAbAttr extends AbAttr { } export class PreWeatherEffectAbAttr extends AbAttr { - applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, weather: Weather | null, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { + applyPreWeatherEffect(pokemon: Pokemon, passive: Boolean, simulated: boolean, weather: Weather | null, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { return false; } } @@ -2743,7 +2846,7 @@ export class BlockWeatherDamageAttr extends PreWeatherDamageAbAttr { this.weatherTypes = weatherTypes; } - applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, weather: Weather, cancelled: Utils.BooleanHolder, args: any[]): boolean { + applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather, cancelled: Utils.BooleanHolder, args: any[]): boolean { if (!this.weatherTypes.length || this.weatherTypes.indexOf(weather?.weatherType) > -1) { cancelled.value = true; } @@ -2761,7 +2864,7 @@ export class SuppressWeatherEffectAbAttr extends PreWeatherEffectAbAttr { this.affectsImmutable = !!affectsImmutable; } - applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, weather: Weather, cancelled: Utils.BooleanHolder, args: any[]): boolean { + applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather, cancelled: Utils.BooleanHolder, args: any[]): boolean { if (this.affectsImmutable || weather.isImmutable()) { cancelled.value = true; return true; @@ -2868,7 +2971,7 @@ export class ForewarnAbAttr extends PostSummonAbAttr { super(true); } - applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { let maxPowerSeen = 0; let maxMove = ""; let movePower = 0; @@ -2892,7 +2995,9 @@ export class ForewarnAbAttr extends PostSummonAbAttr { } } } - pokemon.scene.queueMessage(i18next.t("abilityTriggers:forewarn", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: maxMove })); + if (!simulated) { + pokemon.scene.queueMessage(i18next.t("abilityTriggers:forewarn", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: maxMove })); + } return true; } } @@ -2902,17 +3007,19 @@ export class FriskAbAttr extends PostSummonAbAttr { super(true); } - applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean { - for (const opponent of pokemon.getOpponents()) { - pokemon.scene.queueMessage(i18next.t("abilityTriggers:frisk", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), opponentName: opponent.name, opponentAbilityName: opponent.getAbility().name })); - setAbilityRevealed(opponent); + applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + if (!simulated) { + for (const opponent of pokemon.getOpponents()) { + pokemon.scene.queueMessage(i18next.t("abilityTriggers:frisk", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), opponentName: opponent.name, opponentAbilityName: opponent.getAbility().name })); + setAbilityRevealed(opponent); + } } return true; } } export class PostWeatherChangeAbAttr extends AbAttr { - applyPostWeatherChange(pokemon: Pokemon, passive: boolean, weather: WeatherType, args: any[]): boolean { + applyPostWeatherChange(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: WeatherType, args: any[]): boolean { return false; } } @@ -2930,13 +3037,17 @@ export class PostWeatherChangeAddBattlerTagAttr extends PostWeatherChangeAbAttr this.weatherTypes = weatherTypes; } - applyPostWeatherChange(pokemon: Pokemon, passive: boolean, weather: WeatherType, args: any[]): boolean { + applyPostWeatherChange(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: WeatherType, args: any[]): boolean { console.log(this.weatherTypes.find(w => weather === w), WeatherType[weather]); if (!this.weatherTypes.find(w => weather === w)) { return false; } - return pokemon.addTag(this.tagType, this.turnCount); + if (simulated) { + return pokemon.canAddTag(this.tagType); + } else { + return pokemon.addTag(this.tagType, this.turnCount); + } } } @@ -2949,7 +3060,7 @@ export class PostWeatherLapseAbAttr extends AbAttr { this.weatherTypes = weatherTypes; } - applyPostWeatherLapse(pokemon: Pokemon, passive: boolean, weather: Weather | null, args: any[]): boolean | Promise { + applyPostWeatherLapse(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather | null, args: any[]): boolean | Promise { return false; } @@ -2967,12 +3078,14 @@ export class PostWeatherLapseHealAbAttr extends PostWeatherLapseAbAttr { this.healFactor = healFactor; } - applyPostWeatherLapse(pokemon: Pokemon, passive: boolean, weather: Weather, args: any[]): boolean { + applyPostWeatherLapse(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather, args: any[]): boolean { if (!pokemon.isFullHp()) { const scene = pokemon.scene; const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; - scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), - Math.max(Math.floor(pokemon.getMaxHp() / (16 / this.healFactor)), 1), i18next.t("abilityTriggers:postWeatherLapseHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); + if (!simulated) { + scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), + Utils.toDmgValue(pokemon.getMaxHp() / (16 / this.healFactor)), i18next.t("abilityTriggers:postWeatherLapseHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); + } return true; } @@ -2989,20 +3102,24 @@ export class PostWeatherLapseDamageAbAttr extends PostWeatherLapseAbAttr { this.damageFactor = damageFactor; } - applyPostWeatherLapse(pokemon: Pokemon, passive: boolean, weather: Weather, args: any[]): boolean { + applyPostWeatherLapse(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather, args: any[]): boolean { const scene = pokemon.scene; if (pokemon.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { return false; } - const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; - scene.queueMessage(i18next.t("abilityTriggers:postWeatherLapseDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName })); - pokemon.damageAndUpdate(Math.ceil(pokemon.getMaxHp() / (16 / this.damageFactor)), HitResult.OTHER); + + if (!simulated) { + const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; + scene.queueMessage(i18next.t("abilityTriggers:postWeatherLapseDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName })); + pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / (16 / this.damageFactor)), HitResult.OTHER); + } + return true; } } export class PostTerrainChangeAbAttr extends AbAttr { - applyPostTerrainChange(pokemon: Pokemon, passive: boolean, terrain: TerrainType, args: any[]): boolean { + applyPostTerrainChange(pokemon: Pokemon, passive: boolean, simulated: boolean, terrain: TerrainType, args: any[]): boolean { return false; } } @@ -3020,12 +3137,16 @@ export class PostTerrainChangeAddBattlerTagAttr extends PostTerrainChangeAbAttr this.terrainTypes = terrainTypes; } - applyPostTerrainChange(pokemon: Pokemon, passive: boolean, terrain: TerrainType, args: any[]): boolean { + applyPostTerrainChange(pokemon: Pokemon, passive: boolean, simulated: boolean, terrain: TerrainType, args: any[]): boolean { if (!this.terrainTypes.find(t => t === terrain)) { return false; } - return pokemon.addTag(this.tagType, this.turnCount); + if (simulated) { + return pokemon.canAddTag(this.tagType); + } else { + return pokemon.addTag(this.tagType, this.turnCount); + } } } @@ -3037,7 +3158,7 @@ function getTerrainCondition(...terrainTypes: TerrainType[]): AbAttrCondition { } export class PostTurnAbAttr extends AbAttr { - applyPostTurn(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise { + applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { return false; } } @@ -3063,13 +3184,15 @@ export class PostTurnStatusHealAbAttr extends PostTurnAbAttr { * @param {any[]} args N/A * @returns Returns true if healed from status, false if not */ - applyPostTurn(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise { + applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { if (pokemon.status && this.effects.includes(pokemon.status.effect)) { if (!pokemon.isFullHp()) { - const scene = pokemon.scene; - const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; - scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), - Math.max(Math.floor(pokemon.getMaxHp() / 8), 1), i18next.t("abilityTriggers:poisonHeal", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName }), true)); + if (!simulated) { + const scene = pokemon.scene; + const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; + scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), + Utils.toDmgValue(pokemon.getMaxHp() / 8), i18next.t("abilityTriggers:poisonHeal", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName }), true)); + } return true; } } @@ -3090,17 +3213,19 @@ export class PostTurnResetStatusAbAttr extends PostTurnAbAttr { this.allyTarget = allyTarget; } - applyPostTurn(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { if (this.allyTarget) { this.target = pokemon.getAlly(); } else { this.target = pokemon; } if (this.target?.status) { + if (!simulated) { + this.target.scene.queueMessage(getStatusEffectHealText(this.target.status?.effect, getPokemonNameWithAffix(this.target))); + this.target.resetStatus(false); + this.target.updateInfo(); + } - this.target.scene.queueMessage(getStatusEffectHealText(this.target.status?.effect, getPokemonNameWithAffix(this.target))); - this.target.resetStatus(false); - this.target.updateInfo(); return true; } @@ -3125,7 +3250,7 @@ export class PostTurnLootAbAttr extends PostTurnAbAttr { super(); } - applyPostTurn(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const pass = Phaser.Math.RND.realInRange(0, 1); // Clamp procChance to [0, 1]. Skip if didn't proc (less than pass) if (Math.max(Math.min(this.procChance(pokemon), 1), 0) < pass) { @@ -3133,7 +3258,7 @@ export class PostTurnLootAbAttr extends PostTurnAbAttr { } if (this.itemType === "EATEN_BERRIES") { - return this.createEatenBerry(pokemon); + return this.createEatenBerry(pokemon, simulated); } else { return false; } @@ -3142,15 +3267,20 @@ export class PostTurnLootAbAttr extends PostTurnAbAttr { /** * Create a new berry chosen randomly from the berries the pokemon ate this battle * @param pokemon The pokemon with this ability + * @param simulated whether the associated ability call is simulated * @returns whether a new berry was created */ - createEatenBerry(pokemon: Pokemon): boolean { + createEatenBerry(pokemon: Pokemon, simulated: boolean): boolean { const berriesEaten = pokemon.battleData.berriesEaten; if (!berriesEaten.length) { return false; } + if (simulated) { + return true; + } + const randomIdx = Utils.randSeedInt(berriesEaten.length); const chosenBerryType = berriesEaten[randomIdx]; const chosenBerry = new BerryModifierType(chosenBerryType); @@ -3184,17 +3314,17 @@ export class MoodyAbAttr extends PostTurnAbAttr { super(true); } - applyPostTurn(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const selectableStats = [BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD]; const increaseStatArray = selectableStats.filter(s => pokemon.summonData.battleStats[s] < 6); let decreaseStatArray = selectableStats.filter(s => pokemon.summonData.battleStats[s] > -6); - if (increaseStatArray.length > 0) { + if (!simulated && increaseStatArray.length > 0) { const increaseStat = increaseStatArray[Utils.randInt(increaseStatArray.length)]; decreaseStatArray = decreaseStatArray.filter(s => s !== increaseStat); pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [increaseStat], 2)); } - if (decreaseStatArray.length > 0) { + if (!simulated && decreaseStatArray.length > 0) { const decreaseStat = selectableStats[Utils.randInt(selectableStats.length)]; pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [decreaseStat], -1)); } @@ -3215,19 +3345,24 @@ export class PostTurnStatChangeAbAttr extends PostTurnAbAttr { this.levels = levels; } - applyPostTurn(pokemon: Pokemon, passive: boolean, args: any[]): boolean { - pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.levels)); + applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + if (!simulated) { + pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.levels)); + } return true; } } export class PostTurnHealAbAttr extends PostTurnAbAttr { - applyPostTurn(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { if (!pokemon.isFullHp()) { - const scene = pokemon.scene; - const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; - scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), - Math.max(Math.floor(pokemon.getMaxHp() / 16), 1), i18next.t("abilityTriggers:postTurnHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); + if (!simulated) { + const scene = pokemon.scene; + const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; + scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), + Utils.toDmgValue(pokemon.getMaxHp() / 16), i18next.t("abilityTriggers:postTurnHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); + } + return true; } @@ -3244,10 +3379,13 @@ export class PostTurnFormChangeAbAttr extends PostTurnAbAttr { this.formFunc = formFunc; } - applyPostTurn(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const formIndex = this.formFunc(pokemon); if (formIndex !== pokemon.formIndex) { - pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); + if (!simulated) { + pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); + } + return true; } @@ -3265,15 +3403,18 @@ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr { * Deals damage to all sleeping opponents equal to 1/8 of their max hp (min 1) * @param {Pokemon} pokemon Pokemon that has this ability * @param {boolean} passive N/A + * @param {boolean} simulated true if applying in a simulated call. * @param {any[]} args N/A * @returns {boolean} true if any opponents are sleeping */ - applyPostTurn(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise { + applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { let hadEffect: boolean = false; for (const opp of pokemon.getOpponents()) { if ((opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(Abilities.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { - opp.damageAndUpdate(Math.floor(Math.max(1, opp.getMaxHp() / 8)), HitResult.OTHER); - pokemon.scene.queueMessage(i18next.t("abilityTriggers:badDreams", {pokemonName: getPokemonNameWithAffix(opp)})); + if (!simulated) { + opp.damageAndUpdate(Utils.toDmgValue(opp.getMaxHp() / 8), HitResult.OTHER); + pokemon.scene.queueMessage(i18next.t("abilityTriggers:badDreams", {pokemonName: getPokemonNameWithAffix(opp)})); + } hadEffect = true; } @@ -3298,7 +3439,10 @@ export class FetchBallAbAttr extends PostTurnAbAttr { * @param args N/A * @returns true if player has used a pokeball and this pokemon is owned by the player */ - applyPostTurn(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + if (simulated) { + return false; + } const lastUsed = pokemon.scene.currentBattle.lastUsedPokeball; if (lastUsed !== null && !!pokemon.isPlayer) { pokemon.scene.pokeballCounts[lastUsed]++; @@ -3321,9 +3465,13 @@ export class PostBiomeChangeWeatherChangeAbAttr extends PostBiomeChangeAbAttr { this.weatherType = weatherType; } - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { if (!pokemon.scene.arena.weather?.isImmutable()) { - return pokemon.scene.arena.trySetWeather(this.weatherType, true); + if (simulated) { + return pokemon.scene.arena.weather?.weatherType !== this.weatherType; + } else { + return pokemon.scene.arena.trySetWeather(this.weatherType, true); + } } return false; @@ -3339,8 +3487,12 @@ export class PostBiomeChangeTerrainChangeAbAttr extends PostBiomeChangeAbAttr { this.terrainType = terrainType; } - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { - return pokemon.scene.arena.trySetTerrain(this.terrainType, true); + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + if (simulated) { + return pokemon.scene.arena.terrain?.terrainType !== this.terrainType; + } else { + return pokemon.scene.arena.trySetTerrain(this.terrainType, true); + } } } @@ -3349,7 +3501,7 @@ export class PostBiomeChangeTerrainChangeAbAttr extends PostBiomeChangeAbAttr { * @extends AbAttr */ export class PostMoveUsedAbAttr extends AbAttr { - applyPostMoveUsed(pokemon: Pokemon, move: PokemonMove, source: Pokemon, targets: BattlerIndex[], args: any[]): boolean | Promise { + applyPostMoveUsed(pokemon: Pokemon, move: PokemonMove, source: Pokemon, targets: BattlerIndex[], simulated: boolean, args: any[]): boolean | Promise { return false; } } @@ -3370,20 +3522,22 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { * * @return true if the Dancer ability was resolved */ - applyPostMoveUsed(dancer: Pokemon, move: PokemonMove, source: Pokemon, targets: BattlerIndex[], args: any[]): boolean | Promise { + applyPostMoveUsed(dancer: Pokemon, move: PokemonMove, source: Pokemon, targets: BattlerIndex[], simulated: boolean, args: any[]): boolean | Promise { // List of tags that prevent the Dancer from replicating the move const forbiddenTags = [BattlerTagType.FLYING, BattlerTagType.UNDERWATER, BattlerTagType.UNDERGROUND, BattlerTagType.HIDDEN]; // The move to replicate cannot come from the Dancer if (source.getBattlerIndex() !== dancer.getBattlerIndex() && !dancer.summonData.tags.some(tag => forbiddenTags.includes(tag.tagType))) { - // If the move is an AttackMove or a StatusMove the Dancer must replicate the move on the source of the Dance - if (move.getMove() instanceof AttackMove || move.getMove() instanceof StatusMove) { - const target = this.getTarget(dancer, source, targets); - dancer.scene.unshiftPhase(new MovePhase(dancer.scene, dancer, target, move, true)); - } else if (move.getMove() instanceof SelfStatusMove) { - // If the move is a SelfStatusMove (ie. Swords Dance) the Dancer should replicate it on itself - dancer.scene.unshiftPhase(new MovePhase(dancer.scene, dancer, [dancer.getBattlerIndex()], move, true)); + if (!simulated) { + // If the move is an AttackMove or a StatusMove the Dancer must replicate the move on the source of the Dance + if (move.getMove() instanceof AttackMove || move.getMove() instanceof StatusMove) { + const target = this.getTarget(dancer, source, targets); + dancer.scene.unshiftPhase(new MovePhase(dancer.scene, dancer, target, move, true)); + } else if (move.getMove() instanceof SelfStatusMove) { + // If the move is a SelfStatusMove (ie. Swords Dance) the Dancer should replicate it on itself + dancer.scene.unshiftPhase(new MovePhase(dancer.scene, dancer, [dancer.getBattlerIndex()], move, true)); + } } return true; } @@ -3414,7 +3568,7 @@ export class StatChangeMultiplierAbAttr extends AbAttr { this.multiplier = multiplier; } - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { (args[0] as Utils.IntegerHolder).value *= this.multiplier; return true; @@ -3422,8 +3576,10 @@ export class StatChangeMultiplierAbAttr extends AbAttr { } export class StatChangeCopyAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { - pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, (args[0] as BattleStat[]), (args[1] as integer), true, false, false)); + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { + if (!simulated) { + pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, (args[0] as BattleStat[]), (args[1] as integer), true, false, false)); + } return true; } } @@ -3433,7 +3589,7 @@ export class BypassBurnDamageReductionAbAttr extends AbAttr { super(false); } - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { cancelled.value = true; return true; @@ -3457,15 +3613,15 @@ export class ReduceBurnDamageAbAttr extends AbAttr { * @param args `[0]` {@linkcode Utils.NumberHolder} The damage value being modified * @returns `true` */ - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { - (args[0] as Utils.NumberHolder).value = Math.max(Math.floor((args[0] as Utils.NumberHolder).value * this.multiplier), 1); + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + (args[0] as Utils.NumberHolder).value = Utils.toDmgValue((args[0] as Utils.NumberHolder).value * this.multiplier); return true; } } export class DoubleBerryEffectAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { (args[0] as Utils.NumberHolder).value *= 2; return true; @@ -3473,7 +3629,7 @@ export class DoubleBerryEffectAbAttr extends AbAttr { } export class PreventBerryUseAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { cancelled.value = true; return true; @@ -3496,24 +3652,25 @@ export class HealFromBerryUseAbAttr extends AbAttr { this.healPercent = Math.max(Math.min(healPercent, 1), 0); } - apply(pokemon: Pokemon, passive: boolean, ...args: [Utils.BooleanHolder, any[]]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, ...args: [Utils.BooleanHolder, any[]]): boolean { const { name: abilityName } = passive ? pokemon.getPassiveAbility() : pokemon.getAbility(); - pokemon.scene.unshiftPhase( - new PokemonHealPhase( - pokemon.scene, - pokemon.getBattlerIndex(), - Math.max(Math.floor(pokemon.getMaxHp() * this.healPercent), 1), - i18next.t("abilityTriggers:healFromBerryUse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), - true - ) - ); - + if (!simulated) { + pokemon.scene.unshiftPhase( + new PokemonHealPhase( + pokemon.scene, + pokemon.getBattlerIndex(), + Utils.toDmgValue(pokemon.getMaxHp() * this.healPercent), + i18next.t("abilityTriggers:healFromBerryUse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), + true + ) + ); + } return true; } } export class RunSuccessAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { (args[0] as Utils.IntegerHolder).value = 256; return true; @@ -3536,7 +3693,7 @@ export class CheckTrappedAbAttr extends AbAttr { this.arenaTrapCondition = condition; } - applyCheckTrapped(pokemon: Pokemon, passive: boolean, trapped: Utils.BooleanHolder, otherPokemon: Pokemon, args: any[]): boolean | Promise { + applyCheckTrapped(pokemon: Pokemon, passive: boolean, simulated: boolean, trapped: Utils.BooleanHolder, otherPokemon: Pokemon, args: any[]): boolean | Promise { return false; } } @@ -3561,7 +3718,7 @@ export class ArenaTrapAbAttr extends CheckTrappedAbAttr { * @param args N/A * @returns if enemy Pokemon is trapped or not */ - applyCheckTrapped(pokemon: Pokemon, passive: boolean, trapped: Utils.BooleanHolder, otherPokemon: Pokemon, args: any[]): boolean { + applyCheckTrapped(pokemon: Pokemon, passive: boolean, simulated: boolean, trapped: Utils.BooleanHolder, otherPokemon: Pokemon, args: any[]): boolean { if (this.arenaTrapCondition(pokemon, otherPokemon)) { if (otherPokemon.getTypes(true).includes(Type.GHOST) || (otherPokemon.getTypes(true).includes(Type.STELLAR) && otherPokemon.getTypes().includes(Type.GHOST))) { trapped.value = false; @@ -3583,7 +3740,7 @@ export class ArenaTrapAbAttr extends CheckTrappedAbAttr { } export class MaxMultiHitAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { (args[0] as Utils.IntegerHolder).value = 0; return true; @@ -3595,15 +3752,15 @@ export class PostBattleAbAttr extends AbAttr { super(showAbility); } - applyPostBattle(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + applyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { return false; } } export class PostBattleLootAbAttr extends PostBattleAbAttr { - applyPostBattle(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + applyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const postBattleLoot = pokemon.scene.currentBattle.postBattleLoot; - if (postBattleLoot.length) { + if (!simulated && postBattleLoot.length) { const randItem = Utils.randSeedItem(postBattleLoot); //@ts-ignore - TODO see below if (pokemon.scene.tryTransferHeldItemModifier(randItem, pokemon, true, 1, true)) { // TODO: fix. This is a promise!? @@ -3618,7 +3775,7 @@ export class PostBattleLootAbAttr extends PostBattleAbAttr { } export class PostFaintAbAttr extends AbAttr { - applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + applyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { return false; } } @@ -3637,7 +3794,7 @@ export class PostFaintClearWeatherAbAttr extends PostFaintAbAttr { * @param args N/A * @returns {boolean} Returns true if the weather clears, otherwise false. */ - applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + applyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { const weatherType = pokemon.scene.arena.weather?.weatherType; let turnOffWeather = false; @@ -3663,6 +3820,10 @@ export class PostFaintClearWeatherAbAttr extends PostFaintAbAttr { break; } + if (simulated) { + return turnOffWeather; + } + if (turnOffWeather) { pokemon.scene.arena.trySetWeather(WeatherType.NONE, false); return true; @@ -3681,15 +3842,17 @@ export class PostFaintContactDamageAbAttr extends PostFaintAbAttr { this.damageRatio = damageRatio; } - applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + applyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) { const cancelled = new Utils.BooleanHolder(false); - pokemon.scene.getField(true).map(p=>applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled)); + pokemon.scene.getField(true).map(p => applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled, simulated)); if (cancelled.value || attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { return false; } - attacker.damageAndUpdate(Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER); - attacker.turnData.damageTaken += Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio)); + if (!simulated) { + attacker.damageAndUpdate(Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER); + attacker.turnData.damageTaken += Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)); + } return true; } @@ -3709,10 +3872,12 @@ export class PostFaintHPDamageAbAttr extends PostFaintAbAttr { super (); } - applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - const damage = pokemon.turnData.attacksReceived[0].damage; - attacker.damageAndUpdate((damage), HitResult.OTHER); - attacker.turnData.damageTaken += damage; + applyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + if (!simulated) { + const damage = pokemon.turnData.attacksReceived[0].damage; + attacker.damageAndUpdate((damage), HitResult.OTHER); + attacker.turnData.damageTaken += damage; + } return true; } @@ -3722,7 +3887,7 @@ export class PostFaintHPDamageAbAttr extends PostFaintAbAttr { } export class RedirectMoveAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { if (this.canRedirect(args[0] as Moves)) { const target = args[1] as Utils.IntegerHolder; const newTarget = pokemon.getBattlerIndex(); @@ -3765,9 +3930,9 @@ export class ReduceStatusEffectDurationAbAttr extends AbAttr { this.statusEffect = statusEffect; } - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { if (args[0] === this.statusEffect) { - (args[1] as Utils.IntegerHolder).value = Math.floor((args[1] as Utils.IntegerHolder).value / 2); + (args[1] as Utils.IntegerHolder).value = Utils.toDmgValue((args[1] as Utils.IntegerHolder).value / 2); return true; } @@ -3794,8 +3959,10 @@ export class FlinchStatChangeAbAttr extends FlinchEffectAbAttr { this.levels = levels; } - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { - pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.levels)); + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + if (!simulated) { + pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.levels)); + } return true; } } @@ -3803,7 +3970,7 @@ export class FlinchStatChangeAbAttr extends FlinchEffectAbAttr { export class IncreasePpAbAttr extends AbAttr { } export class ForceSwitchOutImmunityAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { cancelled.value = true; return true; } @@ -3814,7 +3981,7 @@ export class ReduceBerryUseThresholdAbAttr extends AbAttr { super(); } - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { const hpRatio = pokemon.getHpRatio(); if (args[0].value < hpRatio) { @@ -3835,7 +4002,7 @@ export class WeightMultiplierAbAttr extends AbAttr { this.multiplier = multiplier; } - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { (args[0] as Utils.NumberHolder).value *= this.multiplier; return true; @@ -3847,7 +4014,7 @@ export class SyncEncounterNatureAbAttr extends AbAttr { super(false); } - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { (args[0] as Pokemon).setNature(pokemon.getNature()); return true; @@ -3863,7 +4030,7 @@ export class MoveAbilityBypassAbAttr extends AbAttr { this.moveIgnoreFunc = moveIgnoreFunc || ((pokemon, move) => true); } - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { if (this.moveIgnoreFunc(pokemon, (args[0] as Move))) { cancelled.value = true; return true; @@ -3877,7 +4044,7 @@ export class SuppressFieldAbilitiesAbAttr extends AbAttr { super(false); } - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { const ability = (args[0] as Ability); if (!ability.hasAttr(UnsuppressableAbilityAbAttr) && !ability.hasAttr(SuppressFieldAbilitiesAbAttr)) { cancelled.value = true; @@ -3932,7 +4099,7 @@ export class IgnoreTypeImmunityAbAttr extends AbAttr { this.allowedMoveTypes = allowedMoveTypes; } - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { if (this.defenderType === (args[1] as Type) && this.allowedMoveTypes.includes(args[0] as Type)) { cancelled.value = true; return true; @@ -3955,7 +4122,7 @@ export class IgnoreTypeStatusEffectImmunityAbAttr extends AbAttr { this.defenderType = defenderType; } - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { if (this.statusEffect.includes(args[0] as StatusEffect) && this.defenderType.includes(args[1] as Type)) { cancelled.value = true; return true; @@ -3982,8 +4149,10 @@ export class MoneyAbAttr extends PostBattleAbAttr { * @param args N/A * @returns true */ - applyPostBattle(pokemon: Pokemon, passive: boolean, args: any[]): boolean { - pokemon.scene.currentBattle.moneyScattered += pokemon.scene.getWaveMoneyAmount(0.2); + applyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + if (!simulated) { + pokemon.scene.currentBattle.moneyScattered += pokemon.scene.getWaveMoneyAmount(0.2); + } return true; } } @@ -4022,11 +4191,11 @@ export class PostSummonStatChangeOnArenaAbAttr extends PostSummonStatChangeAbAtt * @param {any[]} args - Additional arguments. * @returns {boolean} - Returns true if the stat change was applied, otherwise false. */ - applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const side = pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; if (pokemon.scene.arena.getTagOnSide(this.tagType, side)) { - return super.applyPostSummon(pokemon, passive, args); + return super.applyPostSummon(pokemon, passive, simulated, args); } return false; } @@ -4064,12 +4233,14 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { * @param {any[]} args Additional arguments. * @returns {boolean} Whether the immunity was applied. */ - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { + applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { if (this.condition(pokemon, attacker, move)) { - (args[0] as Utils.NumberHolder).value = this.multiplier; - pokemon.removeTag(this.tagType); - if (this.recoilDamageFunc) { - pokemon.damageAndUpdate(this.recoilDamageFunc(pokemon), HitResult.OTHER); + if (!simulated) { + (args[0] as Utils.NumberHolder).value = this.multiplier; + pokemon.removeTag(this.tagType); + if (this.recoilDamageFunc) { + pokemon.damageAndUpdate(this.recoilDamageFunc(pokemon), HitResult.OTHER); + } } return true; } @@ -4137,7 +4308,7 @@ export class IllusionBreakAbAttr extends PostDefendAbAttr { * @param {...any} args - N/A * @returns {boolean} - Whether the illusion was destroyed. */ - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { const breakIllusion: HitResult[] = [ HitResult.EFFECTIVE, HitResult.SUPER_EFFECTIVE, HitResult.NOT_VERY_EFFECTIVE, HitResult.ONE_HIT_KO ]; if (!breakIllusion.includes(hitResult)) { @@ -4157,7 +4328,7 @@ export class IllusionPostBattleAbAttr extends PostBattleAbAttr { * @param {...any} args - N/A * @returns {boolean} - Whether the illusion was applied. */ - applyPostBattle(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + applyPostBattle(pokemon: Pokemon, passive: boolean, simulated:boolean, args: any[]): boolean { pokemon.breakIllusion(); pokemon.battleData.illusion.available = true; return true; @@ -4174,7 +4345,7 @@ export class IllusionDisableAbAttr extends PostSummonAbAttr { * @param {...any} args - N/A * @returns {boolean} */ - applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { pokemon.battleData.illusion.available = false; return true; } @@ -4205,7 +4376,10 @@ export class BypassSpeedChanceAbAttr extends AbAttr { * @param {any[]} args [0] {@linkcode Utils.BooleanHolder} set to true when the ability activated * @returns {boolean} - whether the ability was activated. */ - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + if (simulated) { + return false; + } const bypassSpeed = args[0] as Utils.BooleanHolder; if (!bypassSpeed.value && pokemon.randSeedInt(100) < this.chance) { @@ -4248,7 +4422,7 @@ export class PreventBypassSpeedChanceAbAttr extends AbAttr { * @argument {boolean} bypassSpeed - determines if a Pokemon is able to bypass speed at the moment * @argument {boolean} canCheckHeldItems - determines if a Pokemon has access to Quick Claw's effects or not */ - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { const bypassSpeed = args[0] as Utils.BooleanHolder; const canCheckHeldItems = args[1] as Utils.BooleanHolder; @@ -4270,7 +4444,7 @@ async function applyAbAttrsInternal( applyFunc: AbAttrApplyFunc, args: any[], showAbilityInstant: boolean = false, - isQuiet: boolean = false, + quiet: boolean = false, messages: string[] = [], ) { for (const passive of [false, true]) { @@ -4297,11 +4471,11 @@ async function applyAbAttrsInternal( if (pokemon.summonData && !pokemon.summonData.abilitiesApplied.includes(ability.id)) { pokemon.summonData.abilitiesApplied.push(ability.id); } - if (pokemon.battleData && !pokemon.battleData.abilitiesApplied.includes(ability.id)) { + if (pokemon.battleData && !quiet && !pokemon.battleData.abilitiesApplied.includes(ability.id)) { pokemon.battleData.abilitiesApplied.push(ability.id); } - if (attr.showAbility && !isQuiet) { + if (attr.showAbility && !quiet) { if (showAbilityInstant) { pokemon.scene.abilityBar.showAbility(pokemon, passive); } else { @@ -4309,12 +4483,12 @@ async function applyAbAttrsInternal( } } - const message = attr.getTriggerMessage(pokemon, ability.name, args); - if (message) { - if (!isQuiet) { + if (!quiet) { + const message = attr.getTriggerMessage(pokemon, ability.name, args); + if (message) { pokemon.scene.queueMessage(message); + messages.push(message); } - messages.push(message); } } } @@ -4323,34 +4497,33 @@ async function applyAbAttrsInternal( } } -export function applyAbAttrs(attrType: Constructor, pokemon: Pokemon, cancelled: Utils.BooleanHolder | null, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.apply(pokemon, passive, cancelled, args), args); +export function applyAbAttrs(attrType: Constructor, pokemon: Pokemon, cancelled: Utils.BooleanHolder | null, simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.apply(pokemon, passive, simulated, cancelled, args), args, false, simulated); } export function applyPostBattleInitAbAttrs(attrType: Constructor, - pokemon: Pokemon, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostBattleInit(pokemon, passive, args), args); + pokemon: Pokemon, simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostBattleInit(pokemon, passive, simulated, args), args, false, simulated); } export function applyPreDefendAbAttrs(attrType: Constructor, - pokemon: Pokemon, attacker: Pokemon, move: Move | null, cancelled: Utils.BooleanHolder | null, ...args: any[]): Promise { - const simulated = args.length > 1 && args[1]; - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreDefend(pokemon, passive, attacker, move, cancelled, args), args, false, simulated); + pokemon: Pokemon, attacker: Pokemon, move: Move | null, cancelled: Utils.BooleanHolder | null, simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args), args, false, simulated); } export function applyPostDefendAbAttrs(attrType: Constructor, - pokemon: Pokemon, attacker: Pokemon, move: Move, hitResult: HitResult | null, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostDefend(pokemon, passive, attacker, move, hitResult, args), args); + pokemon: Pokemon, attacker: Pokemon, move: Move, hitResult: HitResult | null, simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args), args, false, simulated); } export function applyPostMoveUsedAbAttrs(attrType: Constructor, - pokemon: Pokemon, move: PokemonMove, source: Pokemon, targets: BattlerIndex[], ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostMoveUsed(pokemon, move, source, targets, args), args); + pokemon: Pokemon, move: PokemonMove, source: Pokemon, targets: BattlerIndex[], simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostMoveUsed(pokemon, move, source, targets, simulated, args), args, false, simulated); } export function applyBattleStatMultiplierAbAttrs(attrType: Constructor, - pokemon: Pokemon, battleStat: BattleStat, statValue: Utils.NumberHolder, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyBattleStat(pokemon, passive, battleStat, statValue, args), args); + pokemon: Pokemon, battleStat: BattleStat, statValue: Utils.NumberHolder, simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyBattleStat(pokemon, passive, simulated, battleStat, statValue, args), args, false, simulated); } /** @@ -4364,33 +4537,33 @@ export function applyBattleStatMultiplierAbAttrs(attrType: Constructor, - pokemon: Pokemon, stat: Stat, statValue: Utils.NumberHolder, checkedPokemon: Pokemon, hasApplied: Utils.BooleanHolder, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyFieldBattleStat(pokemon, passive, stat, statValue, checkedPokemon, hasApplied, args), args); + pokemon: Pokemon, stat: Stat, statValue: Utils.NumberHolder, checkedPokemon: Pokemon, hasApplied: Utils.BooleanHolder, simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyFieldBattleStat(pokemon, passive, simulated, stat, statValue, checkedPokemon, hasApplied, args), args, false, simulated); } export function applyPreAttackAbAttrs(attrType: Constructor, - pokemon: Pokemon, defender: Pokemon | null, move: Move, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreAttack(pokemon, passive, defender, move, args), args); + pokemon: Pokemon, defender: Pokemon | null, move: Move, simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreAttack(pokemon, passive, simulated, defender, move, args), args, false, simulated); } export function applyPostAttackAbAttrs(attrType: Constructor, - pokemon: Pokemon, defender: Pokemon, move: Move, hitResult: HitResult | null, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostAttack(pokemon, passive, defender, move, hitResult, args), args); + pokemon: Pokemon, defender: Pokemon, move: Move, hitResult: HitResult | null, simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args), args, false, simulated); } export function applyPostKnockOutAbAttrs(attrType: Constructor, - pokemon: Pokemon, knockedOut: Pokemon, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostKnockOut(pokemon, passive, knockedOut, args), args); + pokemon: Pokemon, knockedOut: Pokemon, simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostKnockOut(pokemon, passive, simulated, knockedOut, args), args, false, simulated); } export function applyPostVictoryAbAttrs(attrType: Constructor, - pokemon: Pokemon, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostVictory(pokemon, passive, args), args); + pokemon: Pokemon, simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostVictory(pokemon, passive, simulated, args), args, false, simulated); } export function applyPostSummonAbAttrs(attrType: Constructor, - pokemon: Pokemon, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostSummon(pokemon, passive, args), args); + pokemon: Pokemon, simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostSummon(pokemon, passive, simulated, args), args, false, simulated); } export function applyPreSummonAbAttrs(attrType: Constructor, @@ -4399,69 +4572,68 @@ export function applyPreSummonAbAttrs(attrType: Constructor, } export function applyPreSwitchOutAbAttrs(attrType: Constructor, - pokemon: Pokemon, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreSwitchOut(pokemon, passive, args), args, true); + pokemon: Pokemon, simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreSwitchOut(pokemon, passive, simulated, args), args, true, simulated); } export function applyPreStatChangeAbAttrs(attrType: Constructor, - pokemon: Pokemon | null, stat: BattleStat, cancelled: Utils.BooleanHolder, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreStatChange(pokemon, passive, stat, cancelled, args), args); + pokemon: Pokemon | null, stat: BattleStat, cancelled: Utils.BooleanHolder, simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreStatChange(pokemon, passive, simulated, stat, cancelled, args), args, false, simulated); } export function applyPostStatChangeAbAttrs(attrType: Constructor, - pokemon: Pokemon, stats: BattleStat[], levels: integer, selfTarget: boolean, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostStatChange(pokemon, stats, levels, selfTarget, args), args); + pokemon: Pokemon, stats: BattleStat[], levels: integer, selfTarget: boolean, simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostStatChange(pokemon, simulated, stats, levels, selfTarget, args), args, false, simulated); } export function applyPreSetStatusAbAttrs(attrType: Constructor, - pokemon: Pokemon, effect: StatusEffect | undefined, cancelled: Utils.BooleanHolder, ...args: any[]): Promise { - const simulated = args.length > 1 && args[1]; - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreSetStatus(pokemon, passive, effect, cancelled, args), args, false, !simulated); + pokemon: Pokemon, effect: StatusEffect | undefined, cancelled: Utils.BooleanHolder, simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreSetStatus(pokemon, passive, simulated, effect, cancelled, args), args, false, simulated); } export function applyPreApplyBattlerTagAbAttrs(attrType: Constructor, - pokemon: Pokemon, tag: BattlerTag, cancelled: Utils.BooleanHolder, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreApplyBattlerTag(pokemon, passive, tag, cancelled, args), args); + pokemon: Pokemon, tag: BattlerTag, cancelled: Utils.BooleanHolder, simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args), args, false, simulated); } export function applyPreWeatherEffectAbAttrs(attrType: Constructor, - pokemon: Pokemon, weather: Weather | null, cancelled: Utils.BooleanHolder, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreWeatherEffect(pokemon, passive, weather, cancelled, args), args, true); + pokemon: Pokemon, weather: Weather | null, cancelled: Utils.BooleanHolder, simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreWeatherEffect(pokemon, passive, simulated, weather, cancelled, args), args, true, simulated); } export function applyPostTurnAbAttrs(attrType: Constructor, - pokemon: Pokemon, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostTurn(pokemon, passive, args), args); + pokemon: Pokemon, simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostTurn(pokemon, passive, simulated, args), args, false, simulated); } export function applyPostWeatherChangeAbAttrs(attrType: Constructor, - pokemon: Pokemon, weather: WeatherType, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostWeatherChange(pokemon, passive, weather, args), args); + pokemon: Pokemon, weather: WeatherType, simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostWeatherChange(pokemon, passive, simulated, weather, args), args, false, simulated); } export function applyPostWeatherLapseAbAttrs(attrType: Constructor, - pokemon: Pokemon, weather: Weather | null, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostWeatherLapse(pokemon, passive, weather, args), args); + pokemon: Pokemon, weather: Weather | null, simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostWeatherLapse(pokemon, passive, simulated, weather, args), args, false, simulated); } export function applyPostTerrainChangeAbAttrs(attrType: Constructor, - pokemon: Pokemon, terrain: TerrainType, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostTerrainChange(pokemon, passive, terrain, args), args); + pokemon: Pokemon, terrain: TerrainType, simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostTerrainChange(pokemon, passive, simulated, terrain, args), args, false, simulated); } export function applyCheckTrappedAbAttrs(attrType: Constructor, - pokemon: Pokemon, trapped: Utils.BooleanHolder, otherPokemon: Pokemon, isQuiet: boolean, messages: string[], ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyCheckTrapped(pokemon, passive, trapped, otherPokemon, args), args, false, isQuiet, messages); + pokemon: Pokemon, trapped: Utils.BooleanHolder, otherPokemon: Pokemon, messages: string[], simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyCheckTrapped(pokemon, passive, simulated, trapped, otherPokemon, args), args, false, simulated, messages); } export function applyPostBattleAbAttrs(attrType: Constructor, - pokemon: Pokemon, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostBattle(pokemon, passive, args), args); + pokemon: Pokemon, simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostBattle(pokemon, passive, simulated, args), args, false, simulated); } export function applyPostFaintAbAttrs(attrType: Constructor, - pokemon: Pokemon, attacker: Pokemon, move: Move, hitResult: HitResult, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostFaint(pokemon, passive, attacker, move, hitResult, args), args); + pokemon: Pokemon, attacker: Pokemon, move: Move, hitResult: HitResult, simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostFaint(pokemon, passive, simulated, attacker, move, hitResult, args), args, false, simulated); } function queueShowAbility(pokemon: Pokemon, passive: boolean): void { @@ -5154,7 +5326,7 @@ export function initAbilities() { .conditionalAttr(pokemon => pokemon.formIndex === 0, PostSummonAddBattlerTagAbAttr, BattlerTagType.DISGUISE, 0, false) .attr(FormBlockDamageAbAttr, (target, user, move) => !!target.getTag(BattlerTagType.DISGUISE) && target.getAttackTypeEffectiveness(move.type, user) > 0, 0, BattlerTagType.DISGUISE, (pokemon, abilityName) => i18next.t("abilityTriggers:disguiseAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }), - (pokemon) => Math.floor(pokemon.getMaxHp() / 8)) + (pokemon) => Utils.toDmgValue(pokemon.getMaxHp() / 8)) .attr(PostBattleInitFormChangeAbAttr, () => 0) .bypassFaint() .ignorable(), diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 3394df827fb..a60ea5c2981 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -269,7 +269,7 @@ const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => { if (effectPhase instanceof MoveEffectPhase) { const attacker = effectPhase.getUserPokemon()!; applyMoveAttrs(IncrementMovePriorityAttr, attacker, null, move, priority); - applyAbAttrs(ChangeMovePriorityAbAttr, attacker, null, move, priority); + applyAbAttrs(ChangeMovePriorityAbAttr, attacker, null, false, move, priority); } return priority.value > 0; }; @@ -427,7 +427,7 @@ class WishTag extends ArenaTag { if (user) { this.battlerIndex = user.getBattlerIndex(); this.triggerMessage = i18next.t("arenaTag:wishTagOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(user) }); - this.healHp = Math.max(Math.floor(user.getMaxHp() / 2), 1); + this.healHp = Utils.toDmgValue(user.getMaxHp() / 2); } else { console.warn("Failed to get source for WishTag onAdd"); } @@ -585,7 +585,7 @@ class SpikesTag extends ArenaTrapTag { if (!cancelled.value) { const damageHpRatio = 1 / (10 - 2 * this.layers); - const damage = Math.ceil(pokemon.getMaxHp() * damageHpRatio); + const damage = Utils.toDmgValue(pokemon.getMaxHp() * damageHpRatio); pokemon.scene.queueMessage(i18next.t("arenaTag:spikesActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); pokemon.damageAndUpdate(damage, HitResult.OTHER); @@ -745,7 +745,7 @@ class StealthRockTag extends ArenaTrapTag { const damageHpRatio = this.getDamageHpRatio(pokemon); if (damageHpRatio) { - const damage = Math.ceil(pokemon.getMaxHp() * damageHpRatio); + const damage = Utils.toDmgValue(pokemon.getMaxHp() * damageHpRatio); pokemon.scene.queueMessage(i18next.t("arenaTag:stealthRockActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); pokemon.damageAndUpdate(damage, HitResult.OTHER); if (pokemon.turnData) { diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index ede8d029327..8c05d296e76 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -347,7 +347,7 @@ export class ConfusedTag extends BattlerTag { if (pokemon.randSeedInt(3) === 0) { 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) * (pokemon.randSeedInt(15, 85) / 100)); + const damage = Utils.toDmgValue(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedInt(15, 85) / 100)); pokemon.scene.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself")); pokemon.damageAndUpdate(damage); pokemon.battleData.hitCount++; @@ -524,7 +524,7 @@ export class SeedTag extends BattlerTag { if (!cancelled.value) { pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, source.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.LEECH_SEED)); - const damage = pokemon.damageAndUpdate(Math.max(Math.floor(pokemon.getMaxHp() / 8), 1)); + const damage = pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 8)); const reverseDrain = pokemon.hasAbilityWithAttr(ReverseDrainAbAttr, false); pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, source.getBattlerIndex(), !reverseDrain ? damage : damage * -1, @@ -570,7 +570,7 @@ export class NightmareTag extends BattlerTag { applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); if (!cancelled.value) { - pokemon.damageAndUpdate(Math.ceil(pokemon.getMaxHp() / 4)); + pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 4)); } } @@ -714,7 +714,7 @@ export class IngrainTag extends TrappedTag { new PokemonHealPhase( pokemon.scene, pokemon.getBattlerIndex(), - Math.floor(pokemon.getMaxHp() / 16), + Utils.toDmgValue(pokemon.getMaxHp() / 16), i18next.t("battlerTags:ingrainLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), true ) @@ -777,7 +777,7 @@ export class AquaRingTag extends BattlerTag { new PokemonHealPhase( pokemon.scene, pokemon.getBattlerIndex(), - Math.floor(pokemon.getMaxHp() / 16), + Utils.toDmgValue(pokemon.getMaxHp() / 16), i18next.t("battlerTags:aquaRingLapse", { moveName: this.getMoveName(), pokemonName: getPokemonNameWithAffix(pokemon) @@ -883,7 +883,7 @@ export abstract class DamagingTrapTag extends TrappedTag { applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); if (!cancelled.value) { - pokemon.damageAndUpdate(Math.ceil(pokemon.getMaxHp() / 8)); + pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 8)); } } @@ -1067,7 +1067,7 @@ export class ContactDamageProtectedTag extends ProtectedTag { if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) { const attacker = effectPhase.getPokemon(); if (!attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { - attacker.damageAndUpdate(Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER); + attacker.damageAndUpdate(Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER); } } } @@ -1541,7 +1541,7 @@ export class SaltCuredTag extends BattlerTag { if (!cancelled.value) { const pokemonSteelOrWater = pokemon.isOfType(Type.STEEL) || pokemon.isOfType(Type.WATER); - pokemon.damageAndUpdate(Math.max(Math.floor(pokemonSteelOrWater ? pokemon.getMaxHp() / 4 : pokemon.getMaxHp() / 8), 1)); + pokemon.damageAndUpdate(Utils.toDmgValue(pokemonSteelOrWater ? pokemon.getMaxHp() / 4 : pokemon.getMaxHp() / 8)); pokemon.scene.queueMessage( i18next.t("battlerTags:saltCuredLapse", { @@ -1587,7 +1587,7 @@ export class CursedTag extends BattlerTag { applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); if (!cancelled.value) { - pokemon.damageAndUpdate(Math.max(Math.floor(pokemon.getMaxHp() / 4), 1)); + pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 4)); pokemon.scene.queueMessage(i18next.t("battlerTags:cursedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } } diff --git a/src/data/berry.ts b/src/data/berry.ts index e962219ca46..d0c9c311e16 100644 --- a/src/data/berry.ts +++ b/src/data/berry.ts @@ -36,25 +36,25 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate { return (pokemon: Pokemon) => { const threshold = new Utils.NumberHolder(0.25); const battleStat = (berryType - BerryType.LIECHI) as BattleStat; - applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, threshold); + applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); return pokemon.getHpRatio() < threshold.value && pokemon.summonData.battleStats[battleStat] < 6; }; case BerryType.LANSAT: return (pokemon: Pokemon) => { const threshold = new Utils.NumberHolder(0.25); - applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, threshold); + applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST); }; case BerryType.STARF: return (pokemon: Pokemon) => { const threshold = new Utils.NumberHolder(0.25); - applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, threshold); + applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); return pokemon.getHpRatio() < 0.25; }; case BerryType.LEPPA: return (pokemon: Pokemon) => { const threshold = new Utils.NumberHolder(0.25); - applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, threshold); + applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); return !!pokemon.getMoveset().find(m => !m?.getPpRatio()); }; } @@ -70,8 +70,8 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { if (pokemon.battleData) { pokemon.battleData.berriesEaten.push(berryType); } - const hpHealed = new Utils.NumberHolder(Math.floor(pokemon.getMaxHp() / 4)); - applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, hpHealed); + const hpHealed = new Utils.NumberHolder(Utils.toDmgValue(pokemon.getMaxHp() / 4)); + applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, hpHealed); pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), hpHealed.value, i18next.t("battle:hpHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), berryName: getBerryName(berryType) }), true)); }; @@ -97,7 +97,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { } const battleStat = (berryType - BerryType.LIECHI) as BattleStat; const statLevels = new Utils.NumberHolder(1); - applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, statLevels); + applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statLevels); pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ battleStat ], statLevels.value)); }; case BerryType.LANSAT: @@ -113,7 +113,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { pokemon.battleData.berriesEaten.push(berryType); } const statLevels = new Utils.NumberHolder(2); - applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, statLevels); + applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statLevels); pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ BattleStat.RAND ], statLevels.value)); }; case BerryType.LEPPA: diff --git a/src/data/move.ts b/src/data/move.ts index 4667336b6b5..cf670d1c1d0 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -590,7 +590,7 @@ export default class Move implements Localizable { case MoveFlags.IGNORE_ABILITIES: if (user.hasAbilityWithAttr(MoveAbilityBypassAbAttr)) { const abilityEffectsIgnored = new Utils.BooleanHolder(false); - applyAbAttrs(MoveAbilityBypassAbAttr, user, abilityEffectsIgnored, this); + applyAbAttrs(MoveAbilityBypassAbAttr, user, abilityEffectsIgnored, false, this); if (abilityEffectsIgnored.value) { return true; } @@ -686,11 +686,11 @@ export default class Move implements Localizable { * @param target {@linkcode Pokemon} The Pokémon being targeted by the move. * @returns The calculated accuracy of the move. */ - calculateBattleAccuracy(user: Pokemon, target: Pokemon) { + calculateBattleAccuracy(user: Pokemon, target: Pokemon, simulated: boolean = false) { const moveAccuracy = new Utils.NumberHolder(this.accuracy); applyMoveAttrs(VariableAccuracyAttr, user, target, this, moveAccuracy); - applyPreDefendAbAttrs(WonderSkinAbAttr, target, user, this, { value: false }, moveAccuracy); + applyPreDefendAbAttrs(WonderSkinAbAttr, target, user, this, { value: false }, simulated, moveAccuracy); if (moveAccuracy.value === -1) { return moveAccuracy.value; @@ -724,7 +724,7 @@ export default class Move implements Localizable { * @param target {@linkcode Pokemon} The Pokémon being targeted by the move. * @returns The calculated power of the move. */ - calculateBattlePower(source: Pokemon, target: Pokemon): number { + calculateBattlePower(source: Pokemon, target: Pokemon, simulated: boolean = false): number { if (this.category === MoveCategory.STATUS) { return -1; } @@ -732,17 +732,17 @@ export default class Move implements Localizable { const power = new Utils.NumberHolder(this.power); const typeChangeMovePowerMultiplier = new Utils.NumberHolder(1); - applyPreAttackAbAttrs(MoveTypeChangeAttr, source, target, this, typeChangeMovePowerMultiplier); + applyPreAttackAbAttrs(MoveTypeChangeAttr, source, target, this, simulated, typeChangeMovePowerMultiplier); const sourceTeraType = source.getTeraType(); if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr(MultiHitAttr) && !source.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { power.value = 60; } - applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, target, this, power); + applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, target, this, simulated, power); if (source.getAlly()) { - applyPreAttackAbAttrs(AllyMoveCategoryPowerBoostAbAttr, source.getAlly(), target, this, power); + applyPreAttackAbAttrs(AllyMoveCategoryPowerBoostAbAttr, source.getAlly(), target, this, simulated, power); } const fieldAuras = new Set( @@ -752,11 +752,11 @@ export default class Move implements Localizable { ); for (const aura of fieldAuras) { // The only relevant values are `move` and the `power` holder - aura.applyPreAttack(null, null, null, this, [power]); + aura.applyPreAttack(null, null, simulated, null, this, [power]); } const alliedField: Pokemon[] = source instanceof PlayerPokemon ? source.scene.getPlayerField() : source.scene.getEnemyField(); - alliedField.forEach(p => applyPreAttackAbAttrs(UserFieldMoveTypePowerBoostAbAttr, p, target, this, power)); + alliedField.forEach(p => applyPreAttackAbAttrs(UserFieldMoveTypePowerBoostAbAttr, p, target, this, simulated, power)); power.value *= typeChangeMovePowerMultiplier.value; @@ -984,9 +984,9 @@ export class MoveEffectAttr extends MoveAttr { */ getMoveChance(user: Pokemon, target: Pokemon, move: Move, selfEffect?: Boolean, showAbility?: Boolean): integer { const moveChance = new Utils.NumberHolder(move.chance); - applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, moveChance, move, target, selfEffect, showAbility); + applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, false, moveChance, move, target, selfEffect, showAbility); if (!selfEffect) { - applyPreDefendAbAttrs(IgnoreMoveEffectsAbAttr, target, user, null, null, moveChance); + applyPreDefendAbAttrs(IgnoreMoveEffectsAbAttr, target, user, null, null, false, moveChance); } return moveChance.value; } @@ -1162,7 +1162,7 @@ export class TargetHalfHpDamageAttr extends FixedDamageAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - (args[0] as Utils.IntegerHolder).value = Math.max(Math.floor(target.hp / 2), 1); + (args[0] as Utils.IntegerHolder).value = Utils.toDmgValue(target.hp / 2); return true; } @@ -1208,7 +1208,7 @@ export class CounterDamageAttr extends FixedDamageAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const damage = user.turnData.attacksReceived.filter(ar => this.moveFilter(allMoves[ar.move])).reduce((total: integer, ar: AttackMoveResult) => total + ar.damage, 0); - (args[0] as Utils.IntegerHolder).value = Math.floor(Math.max(damage * this.multiplier, 1)); + (args[0] as Utils.IntegerHolder).value = Utils.toDmgValue(damage * this.multiplier); return true; } @@ -1234,7 +1234,7 @@ export class RandomLevelDamageAttr extends FixedDamageAttr { } getDamage(user: Pokemon, target: Pokemon, move: Move): number { - return Math.max(Math.floor(user.level * (user.randSeedIntRange(50, 150) * 0.01)), 1); + return Utils.toDmgValue(user.level * (user.randSeedIntRange(50, 150) * 0.01)); } } @@ -1293,8 +1293,9 @@ export class RecoilAttr extends MoveEffectAttr { return false; } - const recoilDamage = Math.max(Math.floor((!this.useHp ? user.turnData.damageDealt : user.getMaxHp()) * this.damageRatio), - user.turnData.damageDealt ? 1 : 0); + const damageValue = (!this.useHp ? user.turnData.damageDealt : user.getMaxHp()) * this.damageRatio; + const minValue = user.turnData.damageDealt ? 1 : 0; + const recoilDamage = Utils.toDmgValue(damageValue, minValue); if (!recoilDamage) { return false; } @@ -1415,7 +1416,7 @@ export class HalfSacrificialAttr extends MoveEffectAttr { // Check to see if the Pokemon has an ability that blocks non-direct damage applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled); if (!cancelled.value) { - user.damageAndUpdate(Math.ceil(user.getMaxHp()/2), HitResult.OTHER, false, true, true); + user.damageAndUpdate(Utils.toDmgValue(user.getMaxHp()/2), HitResult.OTHER, false, true, true); user.scene.queueMessage(i18next.t("moveTriggers:cutHpPowerUpMove", {pokemonName: getPokemonNameWithAffix(user)})); // Queue recoil message } return true; @@ -1466,7 +1467,7 @@ export class HealAttr extends MoveEffectAttr { */ addHealPhase(target: Pokemon, healRatio: number) { target.scene.unshiftPhase(new PokemonHealPhase(target.scene, target.getBattlerIndex(), - Math.max(Math.floor(target.getMaxHp() * healRatio), 1), i18next.t("moveTriggers:healHp", {pokemonName: getPokemonNameWithAffix(target)}), true, !this.showAnim)); + Utils.toDmgValue(target.getMaxHp() * healRatio), i18next.t("moveTriggers:healHp", {pokemonName: getPokemonNameWithAffix(target)}), true, !this.showAnim)); } getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { @@ -1750,7 +1751,7 @@ export class HitHealAttr extends MoveEffectAttr { message = i18next.t("battle:drainMessage", {pokemonName: getPokemonNameWithAffix(target)}); } else { // Default healing formula used by draining moves like Absorb, Draining Kiss, Bitter Blade, etc. - healAmount = Math.max(Math.floor(user.turnData.currDamageDealt * this.healRatio), 1); + healAmount = Utils.toDmgValue(user.turnData.currDamageDealt * this.healRatio); message = i18next.t("battle:regainHealth", {pokemonName: getPokemonNameWithAffix(user)}); } if (reverseDrain) { @@ -1883,7 +1884,7 @@ export class MultiHitAttr extends MoveAttr { { const rand = user.randSeedInt(16); const hitValue = new Utils.IntegerHolder(rand); - applyAbAttrs(MaxMultiHitAbAttr, user, null, hitValue); + applyAbAttrs(MaxMultiHitAbAttr, user, null, false, hitValue); if (hitValue.value >= 10) { return 2; } else if (hitValue.value >= 4) { @@ -1954,7 +1955,7 @@ export class StatusEffectAttr extends MoveEffectAttr { } if ((!pokemon.status || (pokemon.status.effect === this.effect && moveChance < 0)) && pokemon.trySetStatus(this.effect, true, user, this.cureTurn)) { - applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, this.effect); + applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, false, this.effect); return true; } } @@ -2710,7 +2711,7 @@ export class CutHpStatBoostAttr extends StatChangeAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { return new Promise(resolve => { - user.damageAndUpdate(Math.floor(user.getMaxHp() / this.cutRatio), HitResult.OTHER, false, true); + user.damageAndUpdate(Utils.toDmgValue(user.getMaxHp() / this.cutRatio), HitResult.OTHER, false, true); user.updateInfo().then(() => { const ret = super.apply(user, target, move, args); if (this.messageCallback) { @@ -3190,7 +3191,7 @@ export class CompareWeightPowerAttr extends VariablePowerAttr { export class HpPowerAttr extends VariablePowerAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - (args[0] as Utils.NumberHolder).value = Math.max(Math.floor(150 * user.getHpRatio()), 1); + (args[0] as Utils.NumberHolder).value = Utils.toDmgValue(150 * user.getHpRatio()); return true; } @@ -3218,7 +3219,7 @@ export class OpponentHighHpPowerAttr extends VariablePowerAttr { * @returns true */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - (args[0] as Utils.NumberHolder).value = Math.max(Math.floor(this.maxBasePower * target.getHpRatio()), 1); + (args[0] as Utils.NumberHolder).value = Utils.toDmgValue(this.maxBasePower * target.getHpRatio()); return true; } @@ -3412,7 +3413,7 @@ export class PresentPowerAttr extends VariablePowerAttr { // If this move is multi-hit, disable all other hits user.stopMultiHit(); target.scene.unshiftPhase(new PokemonHealPhase(target.scene, target.getBattlerIndex(), - Math.max(Math.floor(target.getMaxHp() / 4), 1), i18next.t("moveTriggers:regainedHealth", {pokemonName: getPokemonNameWithAffix(target)}), true)); + Utils.toDmgValue(target.getMaxHp() / 4), i18next.t("moveTriggers:regainedHealth", {pokemonName: getPokemonNameWithAffix(target)}), true)); } return true; @@ -3784,6 +3785,30 @@ export class TeraBlastCategoryAttr extends VariableMoveCategoryAttr { } } +/** + * Increases the power of Tera Blast if the user is Terastallized into Stellar type + * @extends VariablePowerAttr + */ +export class TeraBlastPowerAttr extends VariablePowerAttr { + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + /** + * @param user {@linkcode Pokemon} Pokemon using the move + * @param target {@linkcode Pokemon} N/A + * @param move {@linkcode Move} {@linkcode Move.TERA_BLAST} + * @param {any[]} args N/A + * @returns true or false + */ + const power = args[0] as Utils.NumberHolder; + if (user.isTerastallized() && move.type === Type.STELLAR) { + //200 instead of 100 to reflect lack of stellar being 2x dmg on any type + power.value = 200; + return true; + } + + return false; + } +} + /** * Change the move category to status when used on the ally * @extends VariableMoveCategoryAttr @@ -4037,6 +4062,28 @@ export class HiddenPowerTypeAttr extends VariableMoveTypeAttr { } } +/** + * Changes the type of Tera Blast to match the user's tera type + * @extends VariableMoveTypeAttr + */ +export class TeraBlastTypeAttr extends VariableMoveTypeAttr { + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + /** + * @param user {@linkcode Pokemon} the user's type is checked + * @param target {@linkcode Pokemon} N/A + * @param move {@linkcode Move} {@linkcode Move.TeraBlastTypeAttr} + * @param {any[]} args N/A + * @returns true or false + */ + if (user.isTerastallized()) { + move.type = user.getTeraType(); //changes move type to tera type + return true; + } + + return false; + } +} + export class MatchUserTypeAttr extends VariableMoveTypeAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const userTypes = user.getTypes(true); @@ -4186,9 +4233,9 @@ const crashDamageFunc = (user: Pokemon, move: Move) => { return false; } - user.damageAndUpdate(Math.floor(user.getMaxHp() / 2), HitResult.OTHER, false, true); + user.damageAndUpdate(Utils.toDmgValue(user.getMaxHp() / 2), HitResult.OTHER, false, true); user.scene.queueMessage(i18next.t("moveTriggers:keptGoingAndCrashed", {pokemonName: getPokemonNameWithAffix(user)})); - user.turnData.damageTaken += Math.floor(user.getMaxHp() / 2); + user.turnData.damageTaken += Utils.toDmgValue(user.getMaxHp() / 2); return true; }; @@ -4438,6 +4485,39 @@ export class GulpMissileTagAttr extends MoveEffectAttr { } } +/** + * Attribute to implement Jaw Lock's linked trapping effect between the user and target + * @extends AddBattlerTagAttr + */ +export class JawLockAttr extends AddBattlerTagAttr { + constructor() { + super(BattlerTagType.TRAPPED); + } + + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + if (!super.canApply(user, target, move, args)) { + return false; + } + + // If either the user or the target already has the tag, do not apply + if (user.getTag(TrappedTag) || target.getTag(TrappedTag)) { + return false; + } + + const moveChance = this.getMoveChance(user, target, move, this.selfTarget); + if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) { + /** + * Add the tag to both the user and the target. + * The target's tag source is considered to be the user and vice versa + */ + return target.addTag(BattlerTagType.TRAPPED, 1, move.id, user.id) + && user.addTag(BattlerTagType.TRAPPED, 1, move.id, target.id); + } + + return false; + } +} + export class CurseAttr extends MoveEffectAttr { apply(user: Pokemon, target: Pokemon, move:Move, args: any[]): boolean { @@ -4865,7 +4945,7 @@ export class RevivalBlessingAttr extends MoveEffectAttr { const pokemon = faintedPokemon[user.randSeedInt(faintedPokemon.length)]; const slotIndex = user.scene.getEnemyParty().findIndex(p => pokemon.id === p.id); pokemon.resetStatus(); - pokemon.heal(Math.min(Math.max(Math.ceil(Math.floor(0.5 * pokemon.getMaxHp())), 1), pokemon.getMaxHp())); + pokemon.heal(Math.min(Utils.toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp())); user.scene.queueMessage(`${getPokemonNameWithAffix(pokemon)} was revived!`,0,true); if (user.scene.currentBattle.double && user.scene.getEnemyParty().length > 1) { @@ -7477,7 +7557,7 @@ export function initMoves() { .attr(OpponentHighHpPowerAttr, 120), new AttackMove(Moves.MAGMA_STORM, Type.FIRE, MoveCategory.SPECIAL, 100, 75, 5, -1, 0, 4) .attr(TrapAttr, BattlerTagType.MAGMA_STORM), - new StatusMove(Moves.DARK_VOID, Type.DARK, 50, 10, -1, 0, 4) + new StatusMove(Moves.DARK_VOID, Type.DARK, 80, 10, -1, 0, 4) //Accuracy from Generations 4-6 .attr(StatusEffectAttr, StatusEffect.SLEEP) .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(Moves.SEED_FLARE, Type.GRASS, MoveCategory.SPECIAL, 120, 85, 5, 40, 0, 4) @@ -8316,8 +8396,7 @@ export function initMoves() { .attr(HighCritAttr) .attr(BypassRedirectAttr), new AttackMove(Moves.JAW_LOCK, Type.DARK, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 8) - .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1, 1, false, true) - .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, true, false, 1, 1, false, true) + .attr(JawLockAttr) .bitingMove(), new SelfStatusMove(Moves.STUFF_CHEEKS, Type.NORMAL, -1, 10, -1, 0, 8) // TODO: Stuff Cheeks should not be selectable when the user does not have a berry, see wiki .attr(EatBerryAttr) @@ -8762,7 +8841,10 @@ export function initMoves() { End Unused */ new AttackMove(Moves.TERA_BLAST, Type.NORMAL, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 9) .attr(TeraBlastCategoryAttr) - .unimplemented(), + .attr(TeraBlastTypeAttr) + .attr(TeraBlastPowerAttr) + .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], -1, true, (user, target, move) => user.isTerastallized() && user.isOfType(Type.STELLAR)) + .partial(), new SelfStatusMove(Moves.SILK_TRAP, Type.BUG, -1, 10, -1, 4, 9) .attr(ProtectAttr, BattlerTagType.SILK_TRAP), new AttackMove(Moves.AXE_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 90, 10, 30, 0, 9) diff --git a/src/data/terrain.ts b/src/data/terrain.ts index e29344ffea2..d4789078af7 100644 --- a/src/data/terrain.ts +++ b/src/data/terrain.ts @@ -59,7 +59,7 @@ export class Terrain { case TerrainType.PSYCHIC: if (!move.hasAttr(ProtectAttr)) { const priority = new Utils.IntegerHolder(move.priority); - applyAbAttrs(ChangeMovePriorityAbAttr, user, null, move, priority); + applyAbAttrs(ChangeMovePriorityAbAttr, user, null, false, move, priority); // Cancels move if the move has positive priority and targets a Pokemon grounded on the Psychic Terrain return priority.value > 0 && user.getOpponents().some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded()); } diff --git a/src/field/arena.ts b/src/field/arena.ts index eb3770d61d5..2ef6ce7dab3 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -584,6 +584,10 @@ export class Arena { return this.getTagOnSide(tagType, ArenaTagSide.BOTH); } + hasTag(tagType: ArenaTagType) : boolean { + return !!this.getTag(tagType); + } + getTagOnSide(tagType: ArenaTagType | Constructor, side: ArenaTagSide): ArenaTag | undefined { return typeof(tagType) === "string" ? this.tags.find(t => t.tagType === tagType && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side)) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 4cde21420e2..ed305f76207 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -842,7 +842,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { break; } } - applyAbAttrs(IgnoreOpponentStatChangesAbAttr, opponent, null, statLevel); + applyAbAttrs(IgnoreOpponentStatChangesAbAttr, opponent, null, false, statLevel); if (move) { applyMoveAttrs(IgnoreOpponentStatChangesAttr, this, opponent, move, statLevel); } @@ -1169,12 +1169,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // this.scene potentially can be undefined for a fainted pokemon in doubles // use optional chaining to avoid runtime errors - if (forDefend && (this.getTag(GroundedTag) || this.scene?.arena.getTag(ArenaTagType.GRAVITY))) { - const flyingIndex = types.indexOf(Type.FLYING); - if (flyingIndex > -1) { - types.splice(flyingIndex, 1); - } - } if (!types.length) { // become UNKNOWN if no types are present types.push(Type.UNKNOWN); @@ -1318,10 +1312,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const suppressed = new Utils.BooleanHolder(false); this.scene.getField(true).filter(p => p !== this).map(p => { if (p.getAbility().hasAttr(SuppressFieldAbilitiesAbAttr) && p.canApplyAbility()) { - p.getAbility().getAttrs(SuppressFieldAbilitiesAbAttr).map(a => a.apply(this, false, suppressed, [ability])); + p.getAbility().getAttrs(SuppressFieldAbilitiesAbAttr).map(a => a.apply(this, false, false, suppressed, [ability])); } if (p.getPassiveAbility().hasAttr(SuppressFieldAbilitiesAbAttr) && p.canApplyAbility(true)) { - p.getPassiveAbility().getAttrs(SuppressFieldAbilitiesAbAttr).map(a => a.apply(this, true, suppressed, [ability])); + p.getPassiveAbility().getAttrs(SuppressFieldAbilitiesAbAttr).map(a => a.apply(this, true, false, suppressed, [ability])); } }); if (suppressed.value) { @@ -1373,7 +1367,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { getWeight(): number { const weight = new Utils.NumberHolder(this.species.weight); // This will trigger the ability overlay so only call this function when necessary - applyAbAttrs(WeightMultiplierAbAttr, this, null, weight); + applyAbAttrs(WeightMultiplierAbAttr, this, null, false, weight); return weight.value; } @@ -1434,10 +1428,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const cancelled = new Utils.BooleanHolder(false); applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier); if (!typeless && !ignoreAbility) { - applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, typeMultiplier, true); + applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, true, typeMultiplier); } if (!cancelled.value && !ignoreAbility) { - applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, typeMultiplier, true); + applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, true, typeMultiplier); } return (!cancelled.value ? Number(typeMultiplier.value) : 0) as TypeDamageMultiplier; @@ -1464,12 +1458,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return this.isTerastallized() ? 2 : 1; } const types = this.getTypes(true, true, undefined, useIllusion); + const arena = this.scene.arena; + + // Handle flying v ground type immunity without removing flying type so effective types are still effective + // Related to https://github.com/pagefaultgames/pokerogue/issues/524 + if (moveType === Type.GROUND && (this.isGrounded() || arena.hasTag(ArenaTagType.GRAVITY))) { + const flyingIndex = types.indexOf(Type.FLYING); + if (flyingIndex > -1) { + types.splice(flyingIndex, 1); + } + } let multiplier = types.map(defType => { if (source) { const ignoreImmunity = new Utils.BooleanHolder(false); if (source.isActive(true) && source.hasAbilityWithAttr(IgnoreTypeImmunityAbAttr)) { - applyAbAttrs(IgnoreTypeImmunityAbAttr, source, ignoreImmunity, moveType, defType); + applyAbAttrs(IgnoreTypeImmunityAbAttr, source, ignoreImmunity, false, moveType, defType); } if (ignoreImmunity.value) { return 1; @@ -1485,7 +1489,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { }).reduce((acc, cur) => acc * cur, 1) as TypeDamageMultiplier; // Handle strong winds lowering effectiveness of types super effective against pure flying - if (!ignoreStrongWinds && this.scene.arena.weather?.weatherType === WeatherType.STRONG_WINDS && !this.scene.arena.weather.isEffectSuppressed(this.scene) && this.isOfType(Type.FLYING) && getTypeDamageMultiplier(moveType, Type.FLYING) === 2) { + if (!ignoreStrongWinds && arena.weather?.weatherType === WeatherType.STRONG_WINDS && !arena.weather.isEffectSuppressed(this.scene) && this.isOfType(Type.FLYING) && getTypeDamageMultiplier(moveType, Type.FLYING) === 2) { multiplier /= 2; if (!simulated) { this.scene.queueMessage(i18next.t("weather:strongWindsEffectMessage")); @@ -2111,9 +2115,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const userAccuracyLevel = new Utils.IntegerHolder(this.summonData.battleStats[BattleStat.ACC]); const targetEvasionLevel = new Utils.IntegerHolder(target.summonData.battleStats[BattleStat.EVA]); - applyAbAttrs(IgnoreOpponentStatChangesAbAttr, target, null, userAccuracyLevel); - applyAbAttrs(IgnoreOpponentStatChangesAbAttr, this, null, targetEvasionLevel); - applyAbAttrs(IgnoreOpponentEvasionAbAttr, this, null, targetEvasionLevel); + applyAbAttrs(IgnoreOpponentStatChangesAbAttr, target, null, false, userAccuracyLevel); + applyAbAttrs(IgnoreOpponentStatChangesAbAttr, this, null, false, targetEvasionLevel); + applyAbAttrs(IgnoreOpponentEvasionAbAttr, this, null, false, targetEvasionLevel); applyMoveAttrs(IgnoreOpponentStatChangesAttr, this, target, sourceMove, targetEvasionLevel); this.scene.applyModifiers(TempBattleStatBoosterModifier, this.isPlayer(), TempBattleStat.ACC, userAccuracyLevel); @@ -2128,7 +2132,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { : 3 / (3 + Math.min(targetEvasionLevel.value - userAccuracyLevel.value, 6)); } - applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, this, BattleStat.ACC, accuracyMultiplier, sourceMove); + applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, this, BattleStat.ACC, accuracyMultiplier, false, sourceMove); const evasionMultiplier = new Utils.NumberHolder(1); applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, target, BattleStat.EVA, evasionMultiplier); @@ -2138,6 +2142,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return accuracyMultiplier.value; } + /** + * Apply the results of a move to this pokemon + * @param {Pokemon} source The pokemon using the move + * @param {PokemonMove} battlerMove The move being used + * @returns {HitResult} The result of the attack + */ apply(source: Pokemon, move: Move): HitResult { let result: HitResult; const damage = new Utils.NumberHolder(0); @@ -2173,12 +2183,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const sourceTeraType = source.getTeraType(); if (!typeless) { - applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, typeMultiplier); + applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, false, typeMultiplier); applyMoveAttrs(NeutralDamageAgainstFlyingTypeMultiplierAttr, source, this, move, typeMultiplier); } if (!cancelled.value) { - applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, typeMultiplier); - defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, move, cancelled, typeMultiplier)); + applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, false, typeMultiplier); + defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, move, cancelled, false, typeMultiplier)); } if (cancelled.value) { @@ -2201,7 +2211,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const critOnly = new Utils.BooleanHolder(false); const critAlways = source.getTag(BattlerTagType.ALWAYS_CRIT); applyMoveAttrs(CritOnlyAttr, source, this, move, critOnly); - applyAbAttrs(ConditionalCritAbAttr, source, null, critOnly, this, move); + applyAbAttrs(ConditionalCritAbAttr, source, null, false, critOnly, this, move); if (critOnly.value || critAlways) { isCritical = true; } else { @@ -2211,7 +2221,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.scene.applyModifiers(TempBattleStatBoosterModifier, source.isPlayer(), TempBattleStat.CRIT, critLevel); const bonusCrit = new Utils.BooleanHolder(false); //@ts-ignore - if (applyAbAttrs(BonusCritAbAttr, source, null, bonusCrit)) { // TODO: resolve ts-ignore. This is a promise. Checking a promise is bogus. + if (applyAbAttrs(BonusCritAbAttr, source, null, false, bonusCrit)) { // TODO: resolve ts-ignore. This is a promise. Checking a promise is bogus. if (bonusCrit.value) { critLevel.value += 1; } @@ -2229,7 +2239,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (isCritical) { const noCritTag = this.scene.arena.getTagOnSide(NoCritTag, defendingSide); const blockCrit = new Utils.BooleanHolder(false); - applyAbAttrs(BlockCritAbAttr, this, null, blockCrit); + applyAbAttrs(BlockCritAbAttr, this, null, false, blockCrit); if (noCritTag || blockCrit.value) { isCritical = false; } @@ -2237,7 +2247,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const sourceAtk = new Utils.IntegerHolder(source.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK, this, undefined, isCritical)); const targetDef = new Utils.IntegerHolder(this.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF, source, move, isCritical)); const criticalMultiplier = new Utils.NumberHolder(isCritical ? 1.5 : 1); - applyAbAttrs(MultCritAbAttr, source, null, criticalMultiplier); + applyAbAttrs(MultCritAbAttr, source, null, false, criticalMultiplier); const screenMultiplier = new Utils.NumberHolder(1); if (!isCritical) { this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, move.category, this.scene.currentBattle.double, screenMultiplier); @@ -2252,7 +2262,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { stabMultiplier.value += 0.5; } - applyAbAttrs(StabBoostAbAttr, source, null, stabMultiplier); + applyAbAttrs(StabBoostAbAttr, source, null, false, stabMultiplier); if (sourceTeraType !== Type.UNKNOWN && matchesSourceType) { stabMultiplier.value = Math.min(stabMultiplier.value + 0.5, 2.25); @@ -2270,12 +2280,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { numTargets = effectPhase.getTargets().length; } const twoStrikeMultiplier = new Utils.NumberHolder(1); - applyPreAttackAbAttrs(AddSecondStrikeAbAttr, source, this, move, numTargets, new Utils.IntegerHolder(0), twoStrikeMultiplier); + applyPreAttackAbAttrs(AddSecondStrikeAbAttr, source, this, move, false, numTargets, new Utils.IntegerHolder(0), twoStrikeMultiplier); if (!isTypeImmune) { const levelMultiplier = (2 * source.level / 5 + 2); const randomMultiplier = ((this.scene.randBattleSeedInt(16) + 85) / 100); - damage.value = Math.ceil((((levelMultiplier * power * sourceAtk.value / targetDef.value) / 50) + 2) + damage.value = Utils.toDmgValue((((levelMultiplier * power * sourceAtk.value / targetDef.value) / 50) + 2) * stabMultiplier.value * typeMultiplier.value * arenaAttackTypeMultiplier.value @@ -2289,14 +2299,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (isPhysical && source.status && source.status.effect === StatusEffect.BURN) { if (!move.hasAttr(BypassBurnDamageReductionAttr)) { const burnDamageReductionCancelled = new Utils.BooleanHolder(false); - applyAbAttrs(BypassBurnDamageReductionAbAttr, source, burnDamageReductionCancelled); + applyAbAttrs(BypassBurnDamageReductionAbAttr, source, burnDamageReductionCancelled, false); if (!burnDamageReductionCancelled.value) { - damage.value = Math.floor(damage.value / 2); + damage.value = Utils.toDmgValue(damage.value / 2); } } } - applyPreAttackAbAttrs(DamageBoostAbAttr, source, this, move, damage); + applyPreAttackAbAttrs(DamageBoostAbAttr, source, this, move, false, damage); /** * For each {@link HitsTagAttr} the move has, doubles the damage of the move if: @@ -2312,7 +2322,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if (this.scene.arena.terrain?.terrainType === TerrainType.MISTY && this.isGrounded() && move.type === Type.DRAGON) { - damage.value = Math.floor(damage.value / 2); + damage.value = Utils.toDmgValue(damage.value / 2); } const fixedDamage = new Utils.IntegerHolder(0); @@ -2354,7 +2364,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.scene.applyModifiers(EnemyDamageReducerModifier, false, damage); } - applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, this, source, move, cancelled, damage); + applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, this, source, move, cancelled, false, damage); } // This attribute may modify damage arbitrarily, so be careful about changing its order of application. @@ -2367,7 +2377,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (damage.value) { if (this.isFullHp()) { - applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, this, source, move, cancelled, damage); + applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, this, source, move, cancelled, false, damage); } else if (!this.isPlayer() && damage.value >= this.hp) { this.scene.applyModifiers(EnemyEndureChanceModifier, false, this); } @@ -2434,11 +2444,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { break; case MoveCategory.STATUS: if (!typeless) { - applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, typeMultiplier); + applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, false, typeMultiplier); } if (!cancelled.value) { - applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, typeMultiplier); - defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, move, cancelled, typeMultiplier)); + applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, false, typeMultiplier); + defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, move, cancelled, false, typeMultiplier)); } if (!typeMultiplier.value) { this.scene.queueMessage(i18next.t("battle:hitResultNoEffect", { pokemonName: getPokemonNameWithAffix(this) })); @@ -2530,6 +2540,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return maxForms.includes(this.getFormKey()) || (!!this.getFusionFormKey() && maxForms.includes(this.getFusionFormKey()!)); } + canAddTag(tagType: BattlerTagType): boolean { + if (this.getTag(tagType)) { + return false; + } + + const stubTag = new BattlerTag(tagType, 0, 0); + + const cancelled = new Utils.BooleanHolder(false); + applyPreApplyBattlerTagAbAttrs(BattlerTagImmunityAbAttr, this, stubTag, cancelled, true); + + const userField = this.getAlliedField(); + userField.forEach(pokemon => applyPreApplyBattlerTagAbAttrs(UserFieldBattlerTagImmunityAbAttr, pokemon, stubTag, cancelled, true)); + + return !cancelled.value; + } + addTag(tagType: BattlerTagType, turnCount: integer = 0, sourceMove?: Moves, sourceId?: integer): boolean { const existingTag = this.getTag(tagType); if (existingTag) { @@ -2916,7 +2942,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // Check if the source Pokemon has an ability that cancels the Poison/Toxic immunity const cancelImmunity = new Utils.BooleanHolder(false); if (sourcePokemon) { - applyAbAttrs(IgnoreTypeStatusEffectImmunityAbAttr, sourcePokemon, cancelImmunity, effect, defType); + applyAbAttrs(IgnoreTypeStatusEffectImmunityAbAttr, sourcePokemon, cancelImmunity, false, effect, defType); if (cancelImmunity.value) { return false; } @@ -2988,7 +3014,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (effect === StatusEffect.SLEEP) { statusCureTurn = new Utils.IntegerHolder(this.randSeedIntRange(2, 4)); - applyAbAttrs(ReduceStatusEffectDurationAbAttr, this, null, effect, statusCureTurn); + applyAbAttrs(ReduceStatusEffectDurationAbAttr, this, null, false, effect, statusCureTurn); this.setFrameRate(4); @@ -3622,7 +3648,7 @@ export class PlayerPokemon extends Pokemon { pokemon.resetTurnData(); pokemon.resetStatus(); - pokemon.heal(Math.min(Math.max(Math.ceil(Math.floor(0.5 * pokemon.getMaxHp())), 1), pokemon.getMaxHp())); + pokemon.heal(Math.min(Utils.toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp())); this.scene.queueMessage(`${pokemon.name} was revived!`,0,true); if (this.scene.currentBattle.double && this.scene.getParty().length > 1) { @@ -4635,7 +4661,7 @@ export class PokemonMove { } getMovePp(): integer { - return this.getMove().pp + this.ppUp * Math.max(Math.floor(this.getMove().pp / 5), 1); + return this.getMove().pp + this.ppUp * Utils.toDmgValue(this.getMove().pp / 5); } getPpRatio(): number { diff --git a/src/locales/ca_ES/battle-scene.ts b/src/locales/ca_ES/battle-scene.ts new file mode 100644 index 00000000000..573e1791724 --- /dev/null +++ b/src/locales/ca_ES/battle-scene.ts @@ -0,0 +1,5 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const battleScene: SimpleTranslationEntries = { + "moneyOwned": "₽{{formattedMoney}}" +} as const; diff --git a/src/locales/ca_ES/config.ts b/src/locales/ca_ES/config.ts index 831ab5d99f5..36aee87fc75 100644 --- a/src/locales/ca_ES/config.ts +++ b/src/locales/ca_ES/config.ts @@ -6,6 +6,7 @@ import { arenaFlyout } from "./arena-flyout"; import { arenaTag } from "./arena-tag"; import { PGFachv, PGMachv } from "./achv"; import { battle } from "./battle"; +import { battleScene } from "./battle-scene"; import { battleInfo } from "./battle-info"; import { battleMessageUiHandler } from "./battle-message-ui-handler"; import { battlerTags } from "./battler-tags"; @@ -60,6 +61,7 @@ export const caESConfig = { arenaFlyout: arenaFlyout, arenaTag: arenaTag, battle: battle, + battleScene: battleScene, battleInfo: battleInfo, battleMessageUiHandler: battleMessageUiHandler, battlePokemonForm: battlePokemonForm, diff --git a/src/locales/de/battle-scene.ts b/src/locales/de/battle-scene.ts new file mode 100644 index 00000000000..bfa96445f6c --- /dev/null +++ b/src/locales/de/battle-scene.ts @@ -0,0 +1,6 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const battleScene: SimpleTranslationEntries = { + "moneyOwned": "{{formattedMoney}} ₽" + +} as const; diff --git a/src/locales/de/battler-tags.ts b/src/locales/de/battler-tags.ts index 27d5f14c597..da0150836b0 100644 --- a/src/locales/de/battler-tags.ts +++ b/src/locales/de/battler-tags.ts @@ -69,5 +69,5 @@ export const battlerTags: SimpleTranslationEntries = { "saltCuredLapse": "{{pokemonNameWithAffix}} wurde durch {{moveName}} verletzt!", "cursedOnAdd": "{{pokemonNameWithAffix}} nimmt einen Teil seiner KP und legt einen Fluch auf {{pokemonName}}!", "cursedLapse": "{{pokemonNameWithAffix}} wurde durch den Fluch verletzt!", - "stockpilingOnAdd": "{{pokemonNameWithAffix}} stockpiled {{stockpiledCount}}!", + "stockpilingOnAdd": "{{pokemonNameWithAffix}} hortet {{stockpiledCount}}!", } as const; diff --git a/src/locales/de/config.ts b/src/locales/de/config.ts index d0779c9eec4..080c9ecc598 100644 --- a/src/locales/de/config.ts +++ b/src/locales/de/config.ts @@ -4,6 +4,7 @@ import { arenaFlyout } from "./arena-flyout"; import { arenaTag } from "./arena-tag"; import { PGFachv, PGMachv } from "./achv"; import { battle } from "./battle"; +import { battleScene } from "./battle-scene"; import { battleInfo } from "./battle-info"; import { battleMessageUiHandler } from "./battle-message-ui-handler"; import { battlerTags } from "./battler-tags"; @@ -60,6 +61,7 @@ export const deConfig = { arenaFlyout: arenaFlyout, arenaTag: arenaTag, battle: battle, + battleScene: battleScene, battleInfo: battleInfo, battleMessageUiHandler: battleMessageUiHandler, battlePokemonForm: battlePokemonForm, diff --git a/src/locales/de/starter-select-ui-handler.ts b/src/locales/de/starter-select-ui-handler.ts index 284152bbd33..c96af29a3c0 100644 --- a/src/locales/de/starter-select-ui-handler.ts +++ b/src/locales/de/starter-select-ui-handler.ts @@ -7,7 +7,7 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales"; */ export const starterSelectUiHandler: SimpleTranslationEntries = { "confirmStartTeam": "Mit diesen Pokémon losziehen?", - "confirmExit": "Do you want to exit?", + "confirmExit": "Willst du zurück?", "invalidParty": "Das ist kein gültiges Team!", "gen1": "I", "gen2": "II", @@ -28,8 +28,8 @@ export const starterSelectUiHandler: SimpleTranslationEntries = { "toggleIVs": "DVs anzeigen/verbergen", "manageMoves": "Attacken ändern", "manageNature": "Wesen ändern", - "addToFavorites": "Add to Favorites", - "removeFromFavorites": "Remove from Favorites", + "addToFavorites": "Zu Favoriten hinzufügen", + "removeFromFavorites": "Von Favoriten entfernen", "useCandies": "Bonbons verwenden", "selectNature": "Wähle das neue Wesen.", "selectMoveSwapOut": "Wähle die zu ersetzende Attacke.", diff --git a/src/locales/en/battle-scene.ts b/src/locales/en/battle-scene.ts new file mode 100644 index 00000000000..573e1791724 --- /dev/null +++ b/src/locales/en/battle-scene.ts @@ -0,0 +1,5 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const battleScene: SimpleTranslationEntries = { + "moneyOwned": "₽{{formattedMoney}}" +} as const; diff --git a/src/locales/en/config.ts b/src/locales/en/config.ts index a98dd750fbe..d456b0540cc 100644 --- a/src/locales/en/config.ts +++ b/src/locales/en/config.ts @@ -6,6 +6,7 @@ import { arenaFlyout } from "./arena-flyout"; import { arenaTag } from "./arena-tag"; import { PGFachv, PGMachv } from "./achv"; import { battle } from "./battle"; +import { battleScene } from "./battle-scene"; import { battleInfo } from "./battle-info"; import { battleMessageUiHandler } from "./battle-message-ui-handler"; import { battlerTags } from "./battler-tags"; @@ -60,6 +61,7 @@ export const enConfig = { arenaFlyout: arenaFlyout, arenaTag: arenaTag, battle: battle, + battleScene: battleScene, battleInfo: battleInfo, battleMessageUiHandler: battleMessageUiHandler, battlePokemonForm: battlePokemonForm, diff --git a/src/locales/es/battle-scene.ts b/src/locales/es/battle-scene.ts new file mode 100644 index 00000000000..995ca744302 --- /dev/null +++ b/src/locales/es/battle-scene.ts @@ -0,0 +1,5 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const battleScene: SimpleTranslationEntries = { + "moneyOwned": "{{formattedMoney}} ₽" +} as const; diff --git a/src/locales/es/config.ts b/src/locales/es/config.ts index ce9ad19aac3..6c038188da2 100644 --- a/src/locales/es/config.ts +++ b/src/locales/es/config.ts @@ -4,6 +4,7 @@ import { arenaFlyout } from "./arena-flyout"; import { arenaTag } from "./arena-tag"; import { PGFachv, PGMachv } from "./achv"; import { battle } from "./battle"; +import { battleScene } from "./battle-scene"; import { battleInfo } from "./battle-info"; import { battleMessageUiHandler } from "./battle-message-ui-handler"; import { battlerTags } from "./battler-tags"; @@ -60,6 +61,7 @@ export const esConfig = { arenaFlyout: arenaFlyout, arenaTag: arenaTag, battle: battle, + battleScene: battleScene, battleInfo: battleInfo, battleMessageUiHandler: battleMessageUiHandler, battlePokemonForm: battlePokemonForm, diff --git a/src/locales/fr/battle-scene.ts b/src/locales/fr/battle-scene.ts new file mode 100644 index 00000000000..995ca744302 --- /dev/null +++ b/src/locales/fr/battle-scene.ts @@ -0,0 +1,5 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const battleScene: SimpleTranslationEntries = { + "moneyOwned": "{{formattedMoney}} ₽" +} as const; diff --git a/src/locales/fr/config.ts b/src/locales/fr/config.ts index 246ae9a073f..a2ab67eefe0 100644 --- a/src/locales/fr/config.ts +++ b/src/locales/fr/config.ts @@ -4,6 +4,7 @@ import { arenaFlyout } from "./arena-flyout"; import { arenaTag } from "./arena-tag"; import { PGFachv, PGMachv } from "./achv"; import { battle } from "./battle"; +import { battleScene } from "./battle-scene"; import { battleInfo } from "./battle-info"; import { battleMessageUiHandler } from "./battle-message-ui-handler"; import { battlerTags } from "./battler-tags"; @@ -60,6 +61,7 @@ export const frConfig = { arenaFlyout: arenaFlyout, arenaTag: arenaTag, battle: battle, + battleScene: battleScene, battleInfo: battleInfo, battleMessageUiHandler: battleMessageUiHandler, battlePokemonForm: battlePokemonForm, diff --git a/src/locales/it/ability-trigger.ts b/src/locales/it/ability-trigger.ts index 77a7007f990..08c2e368384 100644 --- a/src/locales/it/ability-trigger.ts +++ b/src/locales/it/ability-trigger.ts @@ -26,39 +26,39 @@ export const abilityTriggers: SimpleTranslationEntries = { "postAttackStealHeldItem": "{{pokemonNameWithAffix}} ruba\n{{stolenItemType}} di {{defenderName}}!", "postDefendStealHeldItem": "{{pokemonNameWithAffix}} ruba\n{{stolenItemType}} di {{attackerName}}!", "copyFaintedAllyAbility": "L'abilità {{abilityName}} di {{pokemonNameWithAffix}} è passata all'alleato!", - "intimidateImmunity": "{{pokemonNameWithAffix}}'s {{abilityName}} prevented it from being Intimidated!", - "postSummonAllyHeal": "{{pokemonNameWithAffix}} drank down all the\nmatcha that {{pokemonName}} made!", - "postSummonClearAllyStats": "{{pokemonNameWithAffix}}'s stat changes\nwere removed!", - "postSummonTransform": "{{pokemonNameWithAffix}} transformed\ninto {{targetName}}!", - "protectStat": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents lowering its {{statName}}!", - "statusEffectImmunityWithName": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents {{statusEffectName}}!", - "statusEffectImmunity": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents status problems!", - "battlerTagImmunity": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents {{battlerTagName}}!", - "forewarn": "{{pokemonNameWithAffix}} was forewarned about {{moveName}}!", - "frisk": "{{pokemonNameWithAffix}} frisked {{opponentName}}'s {{opponentAbilityName}}!", - "postWeatherLapseHeal": "{{pokemonNameWithAffix}}'s {{abilityName}}\nrestored its HP a little!", - "postWeatherLapseDamage": "{{pokemonNameWithAffix}} is hurt\nby its {{abilityName}}!", - "postTurnLootCreateEatenBerry": "{{pokemonNameWithAffix}} harvested one {{berryName}}!", - "postTurnHeal": "{{pokemonNameWithAffix}}'s {{abilityName}}\nrestored its HP a little!", - "fetchBall": "{{pokemonNameWithAffix}} found a\n{{pokeballName}}!", - "healFromBerryUse": "{{pokemonNameWithAffix}}'s {{abilityName}}\nrestored its HP!", - "arenaTrap": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents switching!", - "postBattleLoot": "{{pokemonNameWithAffix}} picked up\n{{itemName}}!", - "postFaintContactDamage": "{{pokemonNameWithAffix}}'s {{abilityName}}\nhurt its attacker!", - "postFaintHpDamage": "{{pokemonNameWithAffix}}'s {{abilityName}}\nhurt its attacker!", - "postSummonPressure": "{{pokemonNameWithAffix}} is exerting its Pressure!", - "postSummonMoldBreaker": "{{pokemonNameWithAffix}} breaks the mold!", - "postSummonAnticipation": "{{pokemonNameWithAffix}} shuddered!", - "postSummonTurboblaze": "{{pokemonNameWithAffix}} is radiating a blazing aura!", - "postSummonTeravolt": "{{pokemonNameWithAffix}} is radiating a bursting aura!", - "postSummonDarkAura": "{{pokemonNameWithAffix}} is radiating a Dark Aura!", - "postSummonFairyAura": "{{pokemonNameWithAffix}} is radiating a Fairy Aura!", - "postSummonNeutralizingGas": "{{pokemonNameWithAffix}}'s Neutralizing Gas filled the area!", - "postSummonAsOneGlastrier": "{{pokemonNameWithAffix}} has two Abilities!", - "postSummonAsOneSpectrier": "{{pokemonNameWithAffix}} has two Abilities!", - "postSummonVesselOfRuin": "{{pokemonNameWithAffix}}'s Vessel of Ruin lowered the {{statName}}\nof all surrounding Pokémon!", - "postSummonSwordOfRuin": "{{pokemonNameWithAffix}}'s Sword of Ruin lowered the {{statName}}\nof all surrounding Pokémon!", - "postSummonTabletsOfRuin": "{{pokemonNameWithAffix}}'s Tablets of Ruin lowered the {{statName}}\nof all surrounding Pokémon!", - "postSummonBeadsOfRuin": "{{pokemonNameWithAffix}}'s Beads of Ruin lowered the {{statName}}\nof all surrounding Pokémon!", + "intimidateImmunity": "{{abilityName}} impedisce a {{pokemonNameWithAffix}} di\nessere intimidito!", + "postSummonAllyHeal": "{{pokemonNameWithAffix}} beve il\ntè che {{pokemonName}} gli ha preparato!", + "postSummonClearAllyStats": "Le statistiche di {{pokemonNameWithAffix}}\ntornano alla normalità!", + "postSummonTransform": "{{pokemonNameWithAffix}} assume le sembianze\ndi {{targetName}}!", + "protectStat": "{{abilityName}} di {{pokemonNameWithAffix}}\npreviene la riduzione del/della suo/a {{statName}}!", + "statusEffectImmunityWithName": "{{abilityName}} di {{pokemonNameWithAffix}}\nnon gli fa subire il/lo/la {{statusEffectName}}!", + "statusEffectImmunity": "{{abilityName}} di {{pokemonNameWithAffix}}\npreviene i problemi di stato!", + "battlerTagImmunity": "{{abilityName}} di {{pokemonNameWithAffix}}\npreviene {{battlerTagName}}!", + "forewarn": "{{pokemonNameWithAffix}} è stato messo in guardia da {{moveName}}!", + "frisk": "{{pokemonNameWithAffix}} perquisice {{opponentName}}\ne trova la sua abilità, {{opponentAbilityName}}!", + "postWeatherLapseHeal": "{{pokemonNameWithAffix}} recupera alcuni PS\ncon {{abilityName}}!", + "postWeatherLapseDamage": "{{pokemonNameWithAffix}} subisce danni\na causa della sua abilità, {{abilityName}}!", + "postTurnLootCreateEatenBerry": "{{pokemonNameWithAffix}} raccoglie una {{berryName}}!", + "postTurnHeal": "{{pokemonNameWithAffix}} recupera alcuni PS\ncon {{abilityName}}!", + "fetchBall": "{{pokemonNameWithAffix}} ha trovato una\n{{pokeballName}}!", + "healFromBerryUse": "{{abilityName}} di {{pokemonNameWithAffix}}\nristabilisce parte dei PS!", + "arenaTrap": "L’abilità {{abilityName}} di {{pokemonNameWithAffix}}\nimpedisce la sostituzione!", + "postBattleLoot": "{{pokemonNameWithAffix}} ha raccolto\nil/l'/lo/la {{itemName}}!", + "postFaintContactDamage": "{{abilityName}} di {{pokemonNameWithAffix}}\nferisce il Pokémon che lo ha attaccato!", + "postFaintHpDamage": "{{abilityName}} di {{pokemonNameWithAffix}}\nferisce il Pokémon che lo ha attaccato!", + "postSummonPressure": "{{pokemonNameWithAffix}} fa pressione!", + "postSummonMoldBreaker": "{{pokemonNameWithAffix}} ha l’abilità Rompiforma!", + "postSummonAnticipation": "{{pokemonNameWithAffix}} rabbrividisce!", + "postSummonTurboblaze": "{{pokemonNameWithAffix}} emana un’aura infuocata!", + "postSummonTeravolt": "{{pokemonNameWithAffix}} emana un’aura repulsiva!", + "postSummonDarkAura": "L’abilità Auratetra di {{pokemonNameWithAffix}} è attiva.", + "postSummonFairyAura": "L’abilità Aurafolletto di {{pokemonNameWithAffix}} è attiva.", + "postSummonNeutralizingGas": "Il Gas Reagente di {{pokemonNameWithAffix}}\nsi diffonde tutt’intorno!", + "postSummonAsOneGlastrier": "{{pokemonNameWithAffix}} ha due abilità!", + "postSummonAsOneSpectrier": "{{pokemonNameWithAffix}} ha due abilità!", + "postSummonVesselOfRuin": "La/l'{{statName}} dei Pokémon intorno si indebolisce a causa\ndell'abilità Vaso Nefasto di {{pokemonNameWithAffix}}!", + "postSummonSwordOfRuin": "La/l'{{statName}} dei Pokémon intorno si indebolisce a causa\ndell'abilità Spada Nefasta di {{pokemonNameWithAffix}}!", + "postSummonTabletsOfRuin": "La/l'{{statName}} dei Pokémon intorno si indebolisce a causa\ndell'abilità Amuleto Nefasto di {{pokemonNameWithAffix}}!", + "postSummonBeadsOfRuin": "La/l'{{statName}} dei Pokémon intorno si indebolisce a causa\ndell'abilità Monile Nefasto di {{pokemonNameWithAffix}}!", "preventBerryUse": "{{pokemonNameWithAffix}} non riesce a\nmangiare le bacche per l'agitazione!", } as const; diff --git a/src/locales/it/achv.ts b/src/locales/it/achv.ts index 91222b81579..756d95e6431 100644 --- a/src/locales/it/achv.ts +++ b/src/locales/it/achv.ts @@ -170,8 +170,8 @@ export const PGMachv: AchievementTranslationEntries = { description: "Vinci in modalità classica", }, "UNEVOLVED_CLASSIC_VICTORY": { - name: "Bring Your Child To Work Day", - description: "Beat the game in Classic Mode with at least one unevolved party member." + name: "Alternanza scuola-lavoro", + description: "Completa la modalità classica con almeno un membro della squadra non evoluto completamente." }, "MONO_GEN_ONE": { @@ -269,8 +269,8 @@ export const PGMachv: AchievementTranslationEntries = { name: "Follettini e follettine", }, "FRESH_START": { - name: "First Try!", - description: "Complete the Fresh Start challenge." + name: "Buona la prima!", + description: "Completa la modalità sfida 'Un nuovo inizio'." } } as const; diff --git a/src/locales/it/battle-scene.ts b/src/locales/it/battle-scene.ts new file mode 100644 index 00000000000..995ca744302 --- /dev/null +++ b/src/locales/it/battle-scene.ts @@ -0,0 +1,5 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const battleScene: SimpleTranslationEntries = { + "moneyOwned": "{{formattedMoney}} ₽" +} as const; diff --git a/src/locales/it/battle.ts b/src/locales/it/battle.ts index bd7227eacb6..347a9968e96 100644 --- a/src/locales/it/battle.ts +++ b/src/locales/it/battle.ts @@ -74,22 +74,22 @@ export const battle: SimpleTranslationEntries = { "fainted": "{{pokemonNameWithAffix}} non è più in\ngrado di combattere!", "statsAnd": "e", "stats": "statistiche", - "statRose_one": "{{pokemonNameWithAffix}}'s {{stats}} è aumentato/a!", - "statRose_other": "{{pokemonNameWithAffix}}'s {{stats}} rose!", - "statSharplyRose_one": "{{pokemonNameWithAffix}}'s {{stats}} è aumentato/a molto!", - "statSharplyRose_other": "{{pokemonNameWithAffix}}'s {{stats}} sharply rose!", - "statRoseDrastically_one": "{{pokemonNameWithAffix}}'s {{stats}} è aumentato/a drasticamente!", - "statRoseDrastically_other": "{{pokemonNameWithAffix}}'s {{stats}} rose drastically!", - "statWontGoAnyHigher_one": "{{pokemonNameWithAffix}}'s {{stats}} non può aumentare più di così!", - "statWontGoAnyHigher_other": "{{pokemonNameWithAffix}}'s {{stats}} won't go any higher!", - "statFell_one": "{{pokemonNameWithAffix}}'s {{stats}} è diminuito/a!", - "statFell_other": "{{pokemonNameWithAffix}}'s {{stats}} fell!", - "statHarshlyFell_one": "{{pokemonNameWithAffix}}'s {{stats}} è diminuito/a molto!", - "statHarshlyFell_other": "{{pokemonNameWithAffix}}'s {{stats}} harshly fell!", - "statSeverelyFell_one": "{{pokemonNameWithAffix}}'s {{stats}} è diminuito/a drasticamente!", - "statSeverelyFell_other": "{{pokemonNameWithAffix}}'s {{stats}} severely fell!", - "statWontGoAnyLower_one": "{{pokemonNameWithAffix}}'s {{stats}} non può diminuire più di così!", - "statWontGoAnyLower_other": "{{pokemonNameWithAffix}}'s {{stats}} won't go any lower!", + "statRose_one": "La statistica {{stats}} di {{pokemonNameWithAffix}} è aumentata!", + "statRose_other": "Le statistiche {{stats}} di {{pokemonNameWithAffix}} sono aumentate!", + "statSharplyRose_one": "La statistica {{stats}} di {{pokemonNameWithAffix}} è aumentata molto!", + "statSharplyRose_other": "Le statistiche {{stats}} di {{pokemonNameWithAffix}} sono aumentate molto!", + "statRoseDrastically_one": "La statistica {{stats}} di {{pokemonNameWithAffix}} è aumentata drasticamente!", + "statRoseDrastically_other": "Le statistiche {{stats}} di {{pokemonNameWithAffix}} sono aumentate drasticamente!", + "statWontGoAnyHigher_one": "La statistica {{stats}} di {{pokemonNameWithAffix}} non può aumentare di più!", + "statWontGoAnyHigher_other": "Le statistiche {{stats}} di {{pokemonNameWithAffix}} non possono aumentare di più!", + "statFell_one": "La statistica {{stats}} di {{pokemonNameWithAffix}} è diminuita!", + "statFell_other": "Le statistiche {{stats}} di {{pokemonNameWithAffix}} sono diminuite!", + "statHarshlyFell_one": "La statistica {{stats}} di {{pokemonNameWithAffix}} è diminuita molto!", + "statHarshlyFell_other": "Le statistiche {{stats}} di {{pokemonNameWithAffix}} sono diminuite molto!", + "statSeverelyFell_one": "La statistica {{stats}} di {{pokemonNameWithAffix}} è diminuita drasticamente!", + "statSeverelyFell_other": "Le statistiche {{stats}} di {{pokemonNameWithAffix}} sono diminuite drasticamente!", + "statWontGoAnyLower_one": "La statistica {{stats}} di {{pokemonNameWithAffix}} non può diminuire di più!", + "statWontGoAnyLower_other": "Le statistiche {{stats}} di {{pokemonNameWithAffix}} non possono diminuire di più!", "transformedIntoType": "{{pokemonName}} diventa\ndi tipo {{type}} type!", "retryBattle": "Vuoi riprovare dall'inizio della lotta?", "unlockedSomething": "{{unlockedThing}}\nè stato/a sbloccato/a.", diff --git a/src/locales/it/battler-tags.ts b/src/locales/it/battler-tags.ts index 7dd3ebc6fb1..518e9194521 100644 --- a/src/locales/it/battler-tags.ts +++ b/src/locales/it/battler-tags.ts @@ -1,14 +1,14 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales"; export const battlerTags: SimpleTranslationEntries = { - "trappedDesc": "trapping", - "flinchedDesc": "flinching", - "confusedDesc": "confusion", - "infatuatedDesc": "infatuation", - "seedDesc": "seeding", - "nightmareDesc": "nightmares", - "ingrainDesc": "roots", - "drowsyDesc": "drowsiness", + "trappedDesc": "intrappolando", + "flinchedDesc": "tentennando", + "confusedDesc": "confuso", + "infatuatedDesc": "infatuato", + "seedDesc": "pieno di semi", + "nightmareDesc": "incubi", + "ingrainDesc": "radici", + "drowsyDesc": "assonnato", "rechargingLapse": "{{pokemonNameWithAffix}} deve\nricaricarsi!", "trappedOnAdd": "{{pokemonNameWithAffix}} non può\npiù fuggire!", "trappedOnRemove": "{{pokemonNameWithAffix}} è stato liberato\nda {{moveName}}", diff --git a/src/locales/it/challenges.ts b/src/locales/it/challenges.ts index 784791f5425..dde5bd0d4e7 100644 --- a/src/locales/it/challenges.ts +++ b/src/locales/it/challenges.ts @@ -2,7 +2,7 @@ import { TranslationEntries } from "#app/interfaces/locales"; export const challenges: TranslationEntries = { "title": "Modificatori delle sfide", - "illegalEvolution": "{{pokemon}} changed into an ineligble pokémon\nfor this challenge!", + "illegalEvolution": "{{pokemon}} non è più utilizzabile\nsecondo le regole della sfida!", "singleGeneration": { "name": "Mono gen", "desc": "Puoi usare solo Pokémon di {{gen}} generazione.", @@ -23,8 +23,8 @@ export const challenges: TranslationEntries = { "desc_default": "Puoi usare solo Pokémon del tipo selezionato." }, "freshStart": { - "name": "Fresh Start", - "desc": "You can only use the original starters, and only as if you had just started PokéRogue.", + "name": "Un nuovo inizio", + "desc": "Puoi usare solo gli starter originali, e come se avessi appena cominciato Pokérogue.", "value.0": "Off", "value.1": "On", } diff --git a/src/locales/it/config.ts b/src/locales/it/config.ts index ceb52665796..d6265061a9f 100644 --- a/src/locales/it/config.ts +++ b/src/locales/it/config.ts @@ -4,6 +4,7 @@ import { arenaFlyout } from "./arena-flyout"; import { arenaTag } from "./arena-tag"; import { PGFachv, PGMachv } from "./achv"; import { battle } from "./battle"; +import { battleScene } from "./battle-scene"; import { battleInfo } from "./battle-info"; import { battleMessageUiHandler } from "./battle-message-ui-handler"; import { battlerTags } from "./battler-tags"; @@ -60,6 +61,7 @@ export const itConfig = { arenaFlyout: arenaFlyout, arenaTag: arenaTag, battle: battle, + battleScene: battleScene, battleInfo: battleInfo, battleMessageUiHandler: battleMessageUiHandler, battlePokemonForm: battlePokemonForm, diff --git a/src/locales/it/menu-ui-handler.ts b/src/locales/it/menu-ui-handler.ts index 0cfea8d67b7..3454de24f87 100644 --- a/src/locales/it/menu-ui-handler.ts +++ b/src/locales/it/menu-ui-handler.ts @@ -25,5 +25,5 @@ export const menuUiHandler: SimpleTranslationEntries = { "unlinkGoogle": "Scollega Google", "cancel": "Annulla", "losingProgressionWarning": "Perderai tutti i progressi dall'inizio della battaglia. Confermi?", - "noEggs": "You are not hatching\nany eggs at the moment!" + "noEggs": "Non stai schiudendo\nuova al momento!" } as const; diff --git a/src/locales/it/menu.ts b/src/locales/it/menu.ts index 9766708f7ae..3787ceb0e70 100644 --- a/src/locales/it/menu.ts +++ b/src/locales/it/menu.ts @@ -17,7 +17,7 @@ export const menu: SimpleTranslationEntries = { "username": "Nome utente", "password": "Password", "login": "Accedi", - "orUse": "Or use", + "orUse": "O usa", "register": "Registrati", "emptyUsername": "Nome utente mancante!", "invalidLoginUsername": "Nome utente non valido!", @@ -39,9 +39,9 @@ export const menu: SimpleTranslationEntries = { "weeklyRankings": "Classifica settimanale", "noRankings": "Nessuna classifica", "positionIcon": "#", - "usernameScoreboard": "Username", - "score": "Score", - "wave": "Wave", + "usernameScoreboard": "Nome utente", + "score": "Punteggio", + "wave": "Onda", "loading": "Caricamento…", "loadingAsset": "Caricamento asset: {{assetName}}", "playersOnline": "Giocatori online", diff --git a/src/locales/it/modifier-type.ts b/src/locales/it/modifier-type.ts index 6f054e4e566..0b166d268a5 100644 --- a/src/locales/it/modifier-type.ts +++ b/src/locales/it/modifier-type.ts @@ -101,7 +101,7 @@ export const modifierType: ModifierTypeTranslationEntries = { }, "TmModifierTypeWithInfo": { name: "MT{{moveId}} - {{moveName}}", - description: "Insegna {{moveName}} a un Pokémon\n(Hold C or Shift for more info).", + description: "Insegna {{moveName}} a un Pokémon\n(Tieni premuto C o Shift per maggiori informazioni).", }, "EvolutionItemModifierType": { description: "Fa evolvere determinate specie di Pokémon.", @@ -153,7 +153,7 @@ export const modifierType: ModifierTypeTranslationEntries = { "REVIVER_SEED": { name: "Revitalseme", description: "Il possessore recupera 1/2 di PS in caso di KO causato da un colpo diretto." }, - "WHITE_HERB": { name: "Erbachiara", description: "An item to be held by a Pokémon. It will restore any lowered stat in battle." }, + "WHITE_HERB": { name: "Erbachiara", description: "Strumento da dare a un Pokémon. Ripristina le statistiche ridotte in lotta." }, "ETHER": { name: "Etere" }, "MAX_ETHER": { name: "Etere max" }, diff --git a/src/locales/it/modifier.ts b/src/locales/it/modifier.ts index 810524a9e5e..94512efef0d 100644 --- a/src/locales/it/modifier.ts +++ b/src/locales/it/modifier.ts @@ -1,14 +1,14 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales"; export const modifier: SimpleTranslationEntries = { - "surviveDamageApply": "{{pokemonNameWithAffix}} hung on\nusing its {{typeName}}!", - "turnHealApply": "{{pokemonNameWithAffix}} restored a little HP using\nits {{typeName}}!", - "hitHealApply": "{{pokemonNameWithAffix}} restored a little HP using\nits {{typeName}}!", - "pokemonInstantReviveApply": "{{pokemonNameWithAffix}} was revived\nby its {{typeName}}!", - "pokemonResetNegativeStatStageApply": "{{pokemonNameWithAffix}}'s lowered stats were restored\nby its {{typeName}}!", - "moneyInterestApply": "You received interest of ₽{{moneyAmount}}\nfrom the {{typeName}}!", - "turnHeldItemTransferApply": "{{pokemonNameWithAffix}}'s {{itemName}} was absorbed\nby {{pokemonName}}'s {{typeName}}!", - "contactHeldItemTransferApply": "{{pokemonNameWithAffix}}'s {{itemName}} was snatched\nby {{pokemonName}}'s {{typeName}}!", - "enemyTurnHealApply": "{{pokemonNameWithAffix}}\nrestored some HP!", + "surviveDamageApply": "{{pokemonNameWithAffix}} resiste\ngrazie al/alla suo/a {{typeName}}!", + "turnHealApply": "{{pokemonNameWithAffix}} recupera alcuni PS con\nil/la suo/a {{typeName}}!", + "hitHealApply": "{{pokemonNameWithAffix}} recupera alcuni PS con\nil/la suo/a {{typeName}}!", + "pokemonInstantReviveApply": "{{pokemonNameWithAffix}} torna in forze\ngrazie al/alla suo/a {{typeName}}!", + "pokemonResetNegativeStatStageApply": "La riduzione alle statistiche di {{pokemonNameWithAffix}}\nviene annullata grazie al/alla suo/a {{typeName}}!", + "moneyInterestApply": "Ricevi un interesse pari a {{moneyAmount}}₽\ngrazie al/allo/a {{typeName}}!", + "turnHeldItemTransferApply": "Il/l'/lo/la {{itemName}} di {{pokemonNameWithAffix}} è stato assorbito\ndal {{typeName}} di {{pokemonName}}!", + "contactHeldItemTransferApply": "Il/l'/lo/la {{itemName}} di {{pokemonNameWithAffix}} è stato rubato\nda {{pokemonName}} con {{typeName}}!", + "enemyTurnHealApply": "{{pokemonNameWithAffix}}\nristabilisce parte dei PS!", "bypassSpeedChanceApply": "{{pokemonName}} agisce più rapidamente del normale grazie al suo {{itemName}}!", } as const; diff --git a/src/locales/it/move-trigger.ts b/src/locales/it/move-trigger.ts index 198fc269785..92ce6a76a74 100644 --- a/src/locales/it/move-trigger.ts +++ b/src/locales/it/move-trigger.ts @@ -57,10 +57,10 @@ export const moveTriggers: SimpleTranslationEntries = { "sacrificialFullRestore": "{{pokemonName}} riceve i benefici\neffetti di Curardore!", "invertStats": "Le modifiche alle statistiche di {{pokemonName}}\nvengono invertite!", "resetStats": "Tutte le modifiche alle statistiche sono state annullate!", - "statEliminated": "All stat changes were eliminated!", + "statEliminated": "Tutte le modifiche alle statistiche sono state annullate!", "faintCountdown": "{{pokemonName}}\nandrà KO dopo {{turnCount}} turni.", "copyType": "{{pokemonName}} assume il tipo\ndi {{targetPokemonName}}!", "suppressAbilities": "L’abilità di {{pokemonName}}\nperde ogni efficacia!", "swapArenaTags": "{{pokemonName}} ha invertito gli effetti attivi\nnelle due metà del campo!", - "exposedMove": "{{pokemonName}} identified\n{{targetPokemonName}}!", + "exposedMove": "{{pokemonName}} ha identificato\n{{targetPokemonName}}!", } as const; diff --git a/src/locales/it/move.ts b/src/locales/it/move.ts index a2c99218cec..1c510d4df6d 100644 --- a/src/locales/it/move.ts +++ b/src/locales/it/move.ts @@ -2915,7 +2915,7 @@ export const move: MoveTranslationEntries = { }, zippyZap: { name: "Sprintaboom", - effect: "The user attacks the target with bursts of electricity at high speed. This move always goes first and raises the user's evasiveness.", + effect: "Un attacco elettrico ad altissima velocità. Questa mossa ha priorità alta e aumenta l'elusione dell'utilizzatore.", }, splishySplash: { name: "Surfasplash", diff --git a/src/locales/ja/battle-scene.ts b/src/locales/ja/battle-scene.ts new file mode 100644 index 00000000000..d2f074416e1 --- /dev/null +++ b/src/locales/ja/battle-scene.ts @@ -0,0 +1,5 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const battleScene: SimpleTranslationEntries = { + "moneyOwned": "{{formattedMoney}}円" +} as const; diff --git a/src/locales/ja/config.ts b/src/locales/ja/config.ts index 6af79547a04..61be36c08df 100644 --- a/src/locales/ja/config.ts +++ b/src/locales/ja/config.ts @@ -4,6 +4,7 @@ import { arenaFlyout } from "./arena-flyout"; import { arenaTag } from "./arena-tag"; import { PGFachv, PGMachv } from "./achv"; import { battle } from "./battle"; +import { battleScene } from "./battle-scene"; import { battleInfo } from "./battle-info"; import { battleMessageUiHandler } from "./battle-message-ui-handler"; import { battlerTags } from "./battler-tags"; @@ -61,6 +62,7 @@ export const jaConfig = { arenaFlyout: arenaFlyout, arenaTag: arenaTag, battle: battle, + battleScene: battleScene, battleInfo: battleInfo, battleMessageUiHandler: battleMessageUiHandler, battlePokemonForm: battlePokemonForm, diff --git a/src/locales/ko/battle-scene.ts b/src/locales/ko/battle-scene.ts new file mode 100644 index 00000000000..573e1791724 --- /dev/null +++ b/src/locales/ko/battle-scene.ts @@ -0,0 +1,5 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const battleScene: SimpleTranslationEntries = { + "moneyOwned": "₽{{formattedMoney}}" +} as const; diff --git a/src/locales/ko/config.ts b/src/locales/ko/config.ts index 114950a4d35..2bc60f04bef 100644 --- a/src/locales/ko/config.ts +++ b/src/locales/ko/config.ts @@ -4,6 +4,7 @@ import { arenaFlyout } from "./arena-flyout"; import { arenaTag } from "./arena-tag"; import { PGFachv, PGMachv } from "./achv"; import { battle } from "./battle"; +import { battleScene } from "./battle-scene"; import { battleInfo } from "./battle-info"; import { battleMessageUiHandler } from "./battle-message-ui-handler"; import { battlerTags } from "./battler-tags"; @@ -60,6 +61,7 @@ export const koConfig = { arenaFlyout: arenaFlyout, arenaTag: arenaTag, battle: battle, + battleScene: battleScene, battleInfo: battleInfo, battleMessageUiHandler: battleMessageUiHandler, battlePokemonForm: battlePokemonForm, diff --git a/src/locales/ko/move-trigger.ts b/src/locales/ko/move-trigger.ts index cea60e25333..e93639689d8 100644 --- a/src/locales/ko/move-trigger.ts +++ b/src/locales/ko/move-trigger.ts @@ -8,7 +8,7 @@ export const moveTriggers: SimpleTranslationEntries = { "goingAllOutForAttack": "{{pokemonName}}[[는]]\n전력을 다하기 시작했다!", "regainedHealth": "{{pokemonName}}[[는]]\n기력을 회복했다!", "keptGoingAndCrashed": "{{pokemonName}}[[는]]\n의욕이 넘쳐서 땅에 부딪쳤다!", - "fled": "{{pokemonName}}[[는]]\N도망쳤다!", + "fled": "{{pokemonName}}[[는]]\n도망쳤다!", "cannotBeSwitchedOut": "{{pokemonName}}[[를]]\n돌아오게 할 수 없습니다!", "swappedAbilitiesWithTarget": "{{pokemonName}}[[는]]\n서로의 특성을 교체했다!", "coinsScatteredEverywhere": "돈이 주위에 흩어졌다!", diff --git a/src/locales/pt_BR/battle-scene.ts b/src/locales/pt_BR/battle-scene.ts new file mode 100644 index 00000000000..573e1791724 --- /dev/null +++ b/src/locales/pt_BR/battle-scene.ts @@ -0,0 +1,5 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const battleScene: SimpleTranslationEntries = { + "moneyOwned": "₽{{formattedMoney}}" +} as const; diff --git a/src/locales/pt_BR/config.ts b/src/locales/pt_BR/config.ts index b48fcfdc8d8..9ebb4867ae4 100644 --- a/src/locales/pt_BR/config.ts +++ b/src/locales/pt_BR/config.ts @@ -4,6 +4,7 @@ import { PGFachv, PGMachv } from "./achv"; import { arenaFlyout } from "./arena-flyout"; import { arenaTag } from "./arena-tag"; import { battle } from "./battle"; +import { battleScene } from "./battle-scene"; import { battleInfo } from "./battle-info"; import { battleMessageUiHandler } from "./battle-message-ui-handler"; import { battlerTags } from "./battler-tags"; @@ -60,6 +61,7 @@ export const ptBrConfig = { arenaFlyout: arenaFlyout, arenaTag: arenaTag, battle: battle, + battleScene: battleScene, battleInfo: battleInfo, battleMessageUiHandler: battleMessageUiHandler, battlePokemonForm: battlePokemonForm, diff --git a/src/locales/pt_BR/dialogue.ts b/src/locales/pt_BR/dialogue.ts index cb0c05fab45..969c6fc36a9 100644 --- a/src/locales/pt_BR/dialogue.ts +++ b/src/locales/pt_BR/dialogue.ts @@ -2779,6 +2779,90 @@ export const PGFdialogue: DialogueTranslationEntries = { 9: "Estou realmente cansando de batalhar… Deve haver algo novo para fazer…" } }, + "breeder": { + "encounter": { + 1: "Pokémon obedientes, Pokémon egoístas… Pokémon têm características únicas.", + 2: "Embora minha criação e comportamento sejam ruins, criei meus Pokémon bem.", + 3: "Hmm, você disciplina seus Pokémon? Mimar demais não é bom." + }, + "victory": { + 1: "É importante nutrir e treinar as características de cada Pokémon.", + 2: "Ao contrário do meu lado diabólico, esses são bons Pokémon.", + 3: "Muito elogio pode estragar tanto Pokémon quanto pessoas." + }, + "defeat": { + 1: "Você não deve ficar com raiva dos seus Pokémon, mesmo se perder uma batalha.", + 2: "Certo? Pokémon bons, né? Eu sou adequado para criar coisas.", + 3: "Não importa o quanto você ame seus Pokémon, ainda precisa discipliná-los quando se comportam mal." + } + }, + "breeder_female": { + "encounter": { + 1: "Pokémon nunca te traem. Eles retribuem todo o amor que você dá a eles.", + 2: "Quer uma dica para treinar bons Pokémon?", + 3: "Eu criei esses Pokémon muito especiais usando um método especial." + }, + "victory": { + 1: "Ugh… Não era para ser assim. Será que administrei a mistura errada?", + 2: "Como isso aconteceu com meus Pokémon… O que você está dando de comer aos seus Pokémon?", + 3: "Se eu perder, isso significa que eu estava só matando o tempo. Não machuca meu ego nem um pouco." + }, + "defeat": { + 1: "Isso prova que meus Pokémon aceitaram meu amor.", + 2: "O verdadeiro truque para treinar bons Pokémon é capturar bons Pokémon.", + 3: "Pokémon serão fortes ou fracos dependendo de como você os cria." + } + }, + "fisherman": { + "encounter": { + 1: "Anem! Você me fez perder uma fisgada!\nO que vai fazer sobre isso?", + 2: "Sai daqui! Você está assustando os Pokémon!", + 3: "Vamos ver se você consegue fisgar uma vitória!", + }, + "victory": { + 1: "Esqueça isso.", + 2: "Da próxima vez, eu vou pescar a vitória!", + 3: "Acho que subestimei a força das correntes dessa vez.", + }, + }, + "fisherman_female": { + "encounter": { + 1: "Uau! Peguei um grande!", + 2: "Linha lançada, pronta para pescar o sucesso!", + 3: "Pronta para fazer ondas!" + }, + "victory": { + 1: "Vou voltar com um anzol mais forte.", + 2: "Vou pescar a vitória na próxima vez.", + 3: "Estou só afiando meus anzóis para a revanche!" + }, + }, + "swimmer": { + "encounter": { + 1: "Hora de mergulhar!", + 2: "Vamos surfar nas ondas da vitória!", + 3: "Pronto para fazer um splash!", + }, + "victory": { + 1: "Molhado na derrota!", + 2: "Uma onda de derrota!", + 3: "De volta à praia, eu acho.", + }, + }, + "backpacker": { + "encounter": { + 1: "Prepare-se, vamos começar!", + 2: "Vamos ver se você consegue acompanhar!", + 3: "Prepare-se, desafiante!", + 4: "Passei 20 anos tentando me encontrar… Mas onde estou?" + }, + "victory": { + 1: "Dessa vez tropecei!", + 2: "Ah, acho que estou perdido.", + 3: "Caminho sem saída!", + 4: "Espere um segundo! Ei! Você não sabe quem eu sou?" + }, + }, "ace_trainer": { "encounter": { 1: "Você parece bastante confiante.", @@ -2799,6 +2883,14 @@ export const PGFdialogue: DialogueTranslationEntries = { 4: "Claro que sou forte e não perco. É importante ganhar com graça." } }, + "parasol_lady": { + "encounter": { + 1: "Hora de embelezar o campo de batalha com elegância e postura!", + }, + "victory": { + 1: "Minha elegância permanece inabalável!", + } + }, "twins": { "encounter": { 1: "Prepare-se, porque quando nos unimos, é o dobro do problema!", @@ -2816,6 +2908,18 @@ export const PGFdialogue: DialogueTranslationEntries = { 3: "Dobro de sorrisos, dobro da dança da vitória!" } }, + "cyclist": { + "encounter": { + 1: "Prepare-se para comer poeira!", + 2: "Prepare-se, desafiante! Estou prestes a te deixar para trás!", + 3: "Pé no pedal, vamos ver se você consegue acompanhar!" + }, + "victory": { + 1: "As rodas podem estar paradas, mas a determinação continua a pedalar.", + 2: "Fui mais rápido!", + 3: "O caminho para a vitória tem muitas curvas e voltas para explorar." + }, + }, "black_belt": { "encounter": { 1: "Elogio sua coragem ao me desafiar! Pois eu sou o que tem o chute mais forte!", @@ -2826,6 +2930,100 @@ export const PGFdialogue: DialogueTranslationEntries = { 2: "Hmmm… Se eu ia perder de qualquer maneira, esperava ficar totalmente destruído no processo." }, }, + "battle_girl": { + "encounter": { + 1: "Você não precisa tentar me impressionar. Você pode perder contra mim.", + }, + "victory": { + 1: "É difícil dizer adeus, mas estamos ficando sem tempo…", + }, + }, + "hiker": { + "encounter": { + 1: "Minha barriga de meia-idade me deu tanta gravidade quanto as montanhas que eu escalo!", + 2: "Herdei esse corpo ossudo dos meus pais… Sou como uma cadeia de montanhas viva…", + }, + "victory": { + 1: "Pelo menos não posso perder quando se trata de IMC!", + 2: "Não é suficiente… Nunca é suficiente. Meu colesterol ruim não está alto o suficiente…" + }, + }, + "ranger": { + "encounter": { + 1: "Quando estou cercado pela natureza, a maioria das outras coisas deixa de importar.", + 2: "Quando estou vivendo sem natureza na minha vida, às vezes sinto uma crise de ansiedade se aproximando." + }, + "victory": { + 1: "Não importa para a vastidão da natureza se eu ganhar ou perder…", + 2: "Algo assim é bastante trivial comparado aos sentimentos sufocantes da vida na cidade." + }, + "defeat": { + 1: "Ganhei a batalha. Mas a vitória não é nada comparada à vastidão da natureza…", + 2: "Tenho certeza de que como você se sente não é tão ruim se comparar aos meus ataques de ansiedade…" + } + }, + "scientist": { + "encounter": { + 1: "Minha pesquisa levará este mundo à paz e alegria.", + }, + "victory": { + 1: "Sou um gênio… Não devo perder para alguém como você…", + }, + }, + "school_kid": { + "encounter": { + 1: "Heehee. Estou confiante nos meus cálculos e análises.", + 2: "Estou ganhando o máximo de experiência que posso porque quero ser um Líder de Ginásio um dia." + }, + "victory": { + 1: "Aff… Cálculo e análise talvez não sejam páreo para o acaso…", + 2: "Até experiências difíceis e desafiadoras têm seu propósito, eu acho." + } + }, + "artist": { + "encounter": { + 1: "Eu costumava ser popular, mas agora estou acabado.", + }, + "victory": { + 1: "À medida que os tempos mudam, os valores também mudam. Percebi isso tarde demais.", + }, + }, + "guitarist": { + "encounter": { + 1: "Prepare-se para sentir o ritmo da derrota enquanto eu toco minha vitória!", + }, + "victory": { + 1: "Silenciado por agora, mas minha melodia de resiliência continuará a tocar.", + }, + }, + "worker": { + "encounter": { + 1: "Me incomoda que as pessoas sempre me entendam mal. Sou muito mais puro do que todos pensam.", + }, + "victory": { + 1: "Eu realmente não quero que minha pele queime, então quero ficar na sombra enquanto trabalho.", + }, + }, + "worker_female": { + "encounter": { + 1: `Me incomoda que as pessoas sempre me entendam mal. + $Sou muito mais pura do que todos pensam.` + }, + "victory": { + 1: "Eu realmente não quero que minha pele queime, então quero ficar na sombra enquanto trabalho." + }, + "defeat": { + 1: "Meu corpo e mente nem sempre estão necessariamente em sincronia." + } + }, + "worker_double": { + "encounter": { + 1: "Vou te mostrar que podemos te quebrar. Estamos treinando no campo!", + }, + "victory": { + 1: "Que estranho… Como isso pode ser… Não deveria ter sido superado.", + }, + }, "hex_maniac": { "encounter": { 1: "Normalmente, só escuto música clássica, mas se eu perder, acho que vou tentar um pouco de new age!", @@ -2840,6 +3038,32 @@ export const PGFdialogue: DialogueTranslationEntries = { 2: "Não fique presa na tristeza ou frustração. Você pode usar seus rancores para se motivar." } }, + "psychic": { + "encounter": { + 1: "Oi! Concentre-se!", + }, + "victory": { + 1: "Perdi minha concentração!", + }, + }, + "officer": { + "encounter": { + 1: "Prepare-se, porque a justiça está prestes a ser servida!", + 2: "Pronto para defender a lei e servir a justiça no campo de batalha!" + }, + "victory": { + 1: "O peso da justiça parece mais pesado do que nunca…", + 2: "As sombras da derrota pairam no distrito." + } + }, + "beauty": { + "encounter": { + 1: "Minha última batalha… É assim que eu gostaria que víssemos esta partida…", + }, + "victory": { + 1: "Foi divertido… Vamos ter outra última batalha algum dia…", + }, + }, "baker": { "encounter": { 1: "Espero que esteja pronta para saborear a derrota!" @@ -2848,6 +3072,26 @@ export const PGFdialogue: DialogueTranslationEntries = { 1: "Vou assar uma revanche." }, }, + "biker": { + "encounter": { + 1: "Hora de acelerar e te deixar na poeira!" + }, + "victory": { + 1: "Vou me ajustar para a próxima corrida." + }, + }, + "firebreather": { + "encounter": { + 1: "Minhas chamas irão te consumir!", + 2: "Minha alma está pegando fogo. Irei te mostrar como queima!", + 3: "Cola aqui e dá uma olhada!" + }, + "victory": { + 1: "Fui reduzido a cinzas…", + 2: "Uau! Isso foi quente!", + 3: "Ai! Queimei minha língua!" + }, + }, "sailor": { "encounter": { 1: "Mano, você vai andar na prancha se perder!", @@ -2860,6 +3104,18 @@ export const PGFdialogue: DialogueTranslationEntries = { 3: "Estou achando que quem tá enjoado sou eu..." }, }, + "archer": { + "encounter": { + 1: "Antes de você ir mais longe, vamos ver como você se sai contra nós, Equipe Rocket!", + 2: "Eu tenho recebido relatórios de que suas habilidades não são insignificantes. Vamos ver se são verdadeiros.", + 3: "Eu sou Archer, um Admin da Equipe Rocket. E não tenho piedade dos inimigos da nossa organização." + }, + "victory": { + 1: "Que vexame!", + 2: "Com minhas habilidades atuais, eu não estava à altura da tarefa, afinal.", + 3: "M-me perdoe, Giovanni... Por ser derrotado por um mero treinador..." + }, + }, "ariana": { "encounter": { 1: "Pera aí! Não podemos deixar alguém solto por aí. Isso é prejudicial para o orgulho da Equipe Rocket, entende?", @@ -2872,6 +3128,30 @@ export const PGFdialogue: DialogueTranslationEntries = { 3: "Aaaieeeee! Isso não pode estar acontecendo! Eu lutei muito, mas ainda perdi…" }, }, + "proton": { + "encounter": { + 1: "O que você quer? Se você interromper nosso trabalho, não espere misericórdia!", + 2: "O que temos aqui? Costumam me chamar de o cara mais assustador e cruel da Equipe Rocket… Eu recomendo fortemente que você não interfira nos nossos negócios!", + 3: "Eu sou Proton, um Admin da Equipe Rocket. Estou aqui para acabar com a sua intromissão!" + }, + "victory": { + 1: "A fortaleza caiu!", + 2: "Você pode ter vencido desta vez… Mas tudo o que fez foi aumentar a ira da Equipe Rocket…", + 3: "Fui derrotado… Mas não esquecerei disso!" + }, + }, + "petrel": { + "encounter": { + 1: "Muhahaha, estávamos esperando por você. Eu? Você não sabe quem eu sou? Sou eu, Giovanni. O majestoso Giovanni em pessoa! Wahahaha! ...Huh? Eu não pareço nada com Giovanni? Eu nem mesmo pareço com Giovanni? Como assim? Trabalhei tanto para imitá-lo!", + 2: "Eu sou Petrel, um Admin da Equipe Rocket. Não permitirei que você interfira em nossos planos!", + 3: "O Executivo da Rocket, Petrel, vai lidar com este intruso!" + }, + "victory": { + 1: "OK, OK. Vou te contar onde ele está.", + 2: "Eu... Eu não consegui fazer nada... Giovanni, por favor, me perdoe...", + 3: "Não, eu não posso deixar isso me afetar. Tenho que informar os outros…" + }, + }, "tabitha": { "encounter": { 1: "Hehehe! Então você veio até aqui! Mas você chegou tarde demais!", @@ -2884,6 +3164,18 @@ export const PGFdialogue: DialogueTranslationEntries = { 3: "Ahya! Como isso pode ser? Para um Admin como eu perder para uma treinadora qualquer..." }, }, + "courtney": { + "encounter": { + 1: "A coisa... A coisa que você segura... É o que... É o que nós da Equipe Magma procuramos...", + 2: "... Bem então... Deletando...", + 3: "...Ha. ...Analisando... ...Hah♪" + }, + "victory": { + 1: "... ...Mudar...o mundo.", + 2: "Como antecipado. Não antecipado. Você. Bloqueio de alvo... concluído. Iniciando... experimento. Você. Para sempre. Aha... ♪", + 3: "... De novo? Isso não foi antecipado. ...Eu sabia. Você... é interessante! ...Haha. ♪" + }, + }, "shelly": { "encounter": { 1: "Ahahahaha! Você vai se meter nos assuntos da Equipe Aqua? Você é absolutamente destemida, simplesmente ignorante ou ambos! Você é tão fofa que chega a ser nojenta! Vou te derrubar", @@ -2920,6 +3212,30 @@ export const PGFdialogue: DialogueTranslationEntries = { 3: "Derrotada... Este foi um erro caro." } }, + "jupiter": { + "encounter": { + 1: "Júpiter, Comandante da Equipe Galáctica, ao seu serviço.", + 2: "A resistência é inútil. A Equipe Galáctica prevalecerá!", + 3: "Você está tremendo... já está com medo?" + }, + "victory": { + 1: "De jeito nenhum... Eu perdi?!", + 2: "Impressionante, você tem coragem!", + 3: "Perder assim... Que embaraço." + } + }, + "saturn": { + "encounter": { + 1: "Eu sou Saturno, Comandante da Equipe Galáctica.", + 2: "Nossa missão é absoluta. Qualquer obstáculo será obliterado!", + 3: "É medo o que vejo em seus olhos?" + }, + "victory": { + 1: "Impossível... Derrotado por você?!", + 2: "Você provou ser um adversário digno.", + 3: "Derrotado... Isso é inaceitável." + } + }, "zinzolin": { "encounter": { 1: "Você poderia se tornar uma ameaça para a Equipe Plasma, então vamos eliminá-la aqui e agora!", @@ -2984,6 +3300,38 @@ export const PGFdialogue: DialogueTranslationEntries = { 5: "Você diz o que? Equipe Rocket tchau-tchau a vai-vai? Quebrado é diz você?" // Uso de gramática incorreta é proposital }, }, + "magma_grunt": { + "encounter": { + 1: "Se você se meter com a Equipe Magma, não teremos piedade!", + 2: "É melhor você não interferir em nossos planos! Estamos tornando o mundo um lugar melhor!", + 3: "Você está no caminho! A Equipe Magma não tem tempo para crianças como você!", + 4: "Espero que você tenha trazido marshmallows porque as coisas estão prestes a esquentar!", + 5: "Vamos usar o poder de um vulcão! Vai ser... explosivo! Entendeu? Heh heh!" + }, + "victory": { + 1: "Ahn? Eu perdi?!", + 2: "Não posso acreditar que perdi! Até pulei o almoço por isso.", + 3: "De jeito nenhum! Você é apenas uma criança!", + 4: "Urrrgh... Eu deveria ter me escondido em nosso esconderijo imediatamente...", + 5: "Você me venceu... Você acha que o chefe vai cortar meu salário por isso?" + }, + }, + "aqua_grunt": { + "encounter": { + 1: "Não pegamos leve com quem se mete com a Equipe Aqua, nem mesmo crianças!", + 2: "Grrr... Você tem coragem de se intrometer com a Equipe Aqua!", + 3: "Você está prestes a se molhar! E não apenas por causa dos meus Pokémon aquáticos!", + 4: "Nós, da Equipe Aqua, existimos para o bem de todos!", + 5: "Prepare-se para ser levado pelas ondas do meu... uh, Pokémon! Sim, meu Pokémon!" + }, + "victory": { + 1: "Tá de brincadeira!", + 2: "Arrgh, eu não contei que seria atrapalhado por uma criança intrometida!", + 3: "Eu perdi?! Acho que vou ter que nadar de volta para o esconderijo agora...", + 4: "Oh, cara, que desastre... O chefe vai ficar furioso...", + 5: "Você me venceu... Você acha que o chefe vai me fazer andar na prancha por isso?" + }, + }, "galactic_grunt": { "encounter": { 1: "Não mexa com a Equipe Galáctica!", @@ -3000,6 +3348,104 @@ export const PGFdialogue: DialogueTranslationEntries = { 5: "Nota para mim mesmo: praticar batalhas Pokémon, o mais rápido possível." }, }, + "plasma_grunt": { + "encounter": { + 1: "Não toleramos pessoas que pensam diferente de nós!", + 2: "Se eu ganhar de você, liberte seus Pokémon!", + 3: "Se você atrapalhar a Equipe Plasma, eu cuidarei de você!", + 4: "A Equipe Plasma vai libertar os Pokémon de humanos egoístas como você!", + 5: "Nossos penteados são de outro mundo... mas nossas habilidades de batalha? Você descobrirá em breve." + }, + "victory": { + 1: "Plasmaaaaaaaaa!", + 2: "Como eu pude perder...", + 3: "...Que Pokémon fraco, vou ter que roubar alguns melhores!", + 4: "Grandes planos são sempre interrompidos.", + 5: "Isso é ruim... Ruim ruim ruim ruim ruim ruim ruim! Ruim para a Equipe Plasma! Ou Plasruim, para abreviar!" + }, + }, + "flare_grunt": { + "encounter": { + 1: "Seus Pokémon não são páreo para a elegância da Equipe Flare.", + 2: "Espero que você tenha trazido seus óculos de sol, porque as coisas vão ficar brilhantes!", + 3: "A Equipe Flare vai purificar o mundo da imperfeição!", + 4: "Prepare-se para enfrentar o brilho da Equipe Flare!", + 5: "A moda é o mais importante para nós!" + }, + "victory": { + 1: "O futuro não parece brilhante para mim.", + 2: "Talvez haja mais na batalha do que eu pensei. De volta à prancheta.", + 3: "Gahh?! Eu perdi?!", + 4: "Mesmo na derrota, a elegância da Equipe Flare brilha.", + 5: "Você pode ter me vencido, mas quando eu perco, eu saio com estilo!" + }, + }, + "rocket_boss_giovanni_1": { + "encounter": { + 1: "Tenho que admitir, estou impressionado que tenha chegado até aqui!" + }, + "victory": { + 1: "QUÊ! Isso não é possível!" + }, + "defeat": { + 1: "Guarde minhas palavras.\nNão ser capaz de medir sua própria força mostra que você ainda é uma criança." + } + }, + "rocket_boss_giovanni_2": { + "encounter": { + 1: "Meus antigos associados precisam de mim... Você vai ficar no meu caminho?" + }, + "victory": { + 1: "Como isso é possível...?\nO precioso sonho da Equipe Rocket se tornou pouco mais que uma ilusão..." + }, + "defeat": { + 1: "A Equipe Rocket renascerá, e eu dominarei o mundo!" + } + }, + "magma_boss_maxie_1": { + "encounter": { + 1: "Eu vou te enterrar com minhas próprias mãos.\nEspero que você aprecie essa honra!" + }, + "victory": { + 1: "Ugh! Você é... bastante capaz...\nEu fiquei para trás, mas apenas por um triz..." + }, + "defeat": { + 1: "A Equipe Magma vai prevalecer!" + } + }, + "magma_boss_maxie_2": { + "encounter": { + 1: "Você é o último obstáculo entre mim e meus objetivos.\nPrepare-se para meu ataque final! Fuhahaha!" + }, + "victory": { + 1: "Isso... Isso não é... Ngh..." + }, + "defeat": { + 1: "E agora... Eu transformarei este planeta em uma terra ideal para a humanidade." + } + }, + "aqua_boss_archie_1": { + "encounter": { + 1: "Eu sou o líder da Equipe Aqua, então temo que esse seja o fim da linha para você." + }, + "victory": { + 1: "Vamos nos encontrar de novo em algum lugar. Eu vou ter certeza de lembrar desse rosto." + }, + "defeat": { + 1: "Brilhante! Nada vai parar minha equipe agora!" + } + }, + "aqua_boss_archie_2": { + "encounter": { + 1: "Estive esperando tanto tempo por este dia.\nEste é o verdadeiro poder da minha equipe!" + }, + "victory": { + 1: "Como eu suspeitava..." + }, + "defeat": { + 1: "Eu vou voltar tudo neste mundo ao seu estado puro e original!!" + } + }, "galactic_boss_cyrus_1": { "encounter": { 1: "Você foi compelida a vir aqui por tal sentimentalismo vazio\nEu farei você se arrepender de ter ouvido seu coração!" @@ -3011,6 +3457,78 @@ export const PGFdialogue: DialogueTranslationEntries = { 1: "Eu criarei meu novo mundo..." } }, + "galactic_boss_cyrus_2": { + "encounter": { + 1: "Nos encontramos novamente. Parece que nossos destinos estão entrelaçados.\nMas aqui e agora, eu finalmente quebrarei esse vínculo!" + }, + "victory": { + 1: "Como? Como? COMO?!" + }, + "defeat": { + 1: "Até logo." + } + }, + "plasma_boss_ghetsis_1": { + "encounter": { + 1: "Ninguém pode me deter! Não importa quem seja ou o que faça!" + }, + "victory": { + 1: "Como isso é possível? Eu sou o criador da Equipe Plasma! Eu sou perfeito!" + }, + "defeat": { + 1: "Eu sou o governante perfeito de um novo mundo perfeito! Mwa ha ha!" + } + }, + "plasma_boss_ghetsis_2": { + "encounter": { + 1: "Vamos! Eu quero ver sua cara depois que você perder toda a esperança!" + }, + "victory": { + 1: "Meus cálculos... Não! Meus planos cuidadosos! O mundo deveria ser meu!" + }, + "defeat": { + 1: "Kyurem! Use Absofusion!" + } + }, + "flare_boss_lysandre_1": { + "encounter": { + 1: "Você está aqui para me deter? Mostre-me em batalha." + }, + "victory": { + 1: "Você está aqui para me deter. Mas eu peço que você espere." + }, + "defeat": { + 1: "Pokémon... não devem mais existir." + } + }, + "flare_boss_lysandre_2": { + "encounter": { + 1: "O futuro que você quer, ou o futuro que eu quero... Vamos ver qual é o mais merecedor, não é mesmo?" + }, + "victory": { + 1: "Uau!" + }, + "defeat": { + 1: "Tolos sem visão continuarão a poluir este belo mundo." + } + }, + "brock": { + "encounter": { + 1: "Minha especialidade em Pokémon do tipo Pedra vai te derrubar! Vamos lá!", + 2: "Minha vontade firme como pedra vai te sobrecarregar!", + 3: "Permita-me mostrar a verdadeira força dos meus Pokémon!" + }, + "victory": { + 1: "A força dos seus Pokémon superou minhas defesas de pedra!", + 2: "O mundo é enorme! Estou feliz por ter tido a chance de batalhar com você.", + 3: "Talvez eu deva voltar a perseguir meu sonho de ser Criador de Pokémon…" + }, + "defeat": { + 1: "A melhor defesa é um bom ataque!\nEssa é a minha maneira de fazer as coisas!", + 2: "Venha estudar rochas comigo da próxima vez para aprender melhor a combatê-las!", + 3: "Hah, todas as minhas viagens pelas regiões estão valendo a pena!" + } + }, "misty": { "encounter": { 1: "Minha política é um ataque total com Pokémon do tipo Água!", @@ -3045,6 +3563,77 @@ export const PGFdialogue: DialogueTranslationEntries = { 3: "Uma batalha de Pokémon é guerra, e eu te mostrei combate em primeira mão!" } }, + "erika": { + "encounter": { + 1: "Ah, o tempo está adorável aqui…\nOh, uma batalha? Muito bem então.", + 2: "Minhas habilidades de batalha Pokémon rivalizam com minhas habilidades de arranjo de flores.", + 3: "Oh, espero que o aroma agradável dos meus Pokémon não me faça dormir de novo…", + 4: "Ver flores em um jardim é tão calmante." + }, + "victory": { + 1: "Oh! Eu concedo a derrota.", + 2: "Aquela partida foi muito agradável.", + 3: "Ah, parece que perdi…", + 4: "Oh, meu Deus." + }, + "defeat": { + 1: "Tinha medo de adormecer…", + 2: "Oh, meu Deus, parece que meus Pokémon de Grama te dominaram.", + 3: "Essa batalha foi uma experiência tão calmante.", + 4: "Oh… É só isso?" + } + }, + "janine": { + "encounter": { + 1: "Estou dominando a arte dos ataques venenosos.\nVou lutar com você hoje!", + 2: "Meu pai confia que posso me defender.\nVou provar que ele está certo!", + 3: "Minhas técnicas de ninja só perdem para as do meu pai!\nVocê consegue acompanhar?" + }, + "victory": { + 1: "Ainda preciso de treinamento… Entendi.", + 2: "Sua técnica de batalha superou a minha.", + 3: "Vou me aplicar de verdade e melhorar minhas habilidades." + }, + "defeat": { + 1: "Hehe… o veneno drenou todas as suas forças para lutar.", + 2: "Ha! Você não teve chance contra minhas habilidades superiores de ninja!", + 3: "A fé do meu pai em mim não foi mal colocada." + } + }, + "sabrina": { + "encounter": { + 1: "Através da minha habilidade psíquica, tive uma visão da sua chegada!", + 2: "Não gosto de lutar, mas se você quiser, vou mostrar meus poderes!", + 3: "Posso sentir grande ambição em você. Vou ver se não é infundada." + }, + "victory": { + 1: "Seu poder… Ele supera o que eu previa…", + 2: "Não consegui prever seu poder com precisão.", + 3: "Mesmo com meus imensos poderes psíquicos, não consigo sentir outro tão forte quanto você." + }, + "defeat": { + 1: "Essa vitória… É exatamente como previ nas minhas visões!", + 2: "Talvez fosse outra pessoa que eu sentisse um grande desejo…", + 3: "Aprimore suas habilidades antes de entrar em batalha precipitadamente.\nVocê nunca sabe o que o futuro pode reservar se fizer isso…" + } + }, + "blaine": { + "encounter": { + 1: "Hah! Espero que tenha trazido uma Cura de Queimadura!", + 2: "Meus Pokémon de Fogo vão incinerar todos os desafiantes!", + 3: "Prepare-se para brincar com fogo!" + }, + "victory": { + 1: "Queimei até não restar nada! Nem cinzas sobraram!", + 2: "Não acendi as chamas alto o suficiente?", + 3: "Estou completamente exausto… Mas isso faz minha motivação para melhorar queimar ainda mais!" + }, + "defeat": { + 1: "Meu inferno ardente não pode ser apagado!", + 2: "Meus Pokémon foram fortalecidos com o calor desta vitória!", + 3: "Hah! Minha paixão queima mais do que a sua!" + } + }, "giovanni": { "encounter": { 1: "Eu, o líder da Equipe Rocket, vou te fazer sentir um mundo de dor!", @@ -3062,6 +3651,23 @@ export const PGFdialogue: DialogueTranslationEntries = { 3: "Espero que entenda o quão tolo foi me desafiar." } }, + "roxanne": { + "encounter": { + 1: "Você poderia gentilmente demonstrar como batalha?", + 2: "Você pode aprender muitas coisas batalhando com muitos treinadores.", + 3: "Oh, você me pegou estrategizando.\nGostaria de batalhar?" + }, + "victory": { + 1: "Oh, parece que perdi.\nEu entendo.", + 2: "Parece que ainda tenho muito mais a aprender quando se trata de batalhas.", + 3: "Vou levar o que aprendi aqui hoje a sério." + }, + "defeat": { + 1: "Aprendi muitas coisas com nossa batalha.\nEspero que você também tenha aprendido.", + 2: "Espero batalhar com você novamente.\nEspero que use o que aprendeu aqui.", + 3: "Venci devido a tudo o que aprendi." + } + }, "brawly": { "encounter": { 1: "Oh cara, uma desafiante!\nVamos ver o que você pode fazer!", @@ -3096,6 +3702,40 @@ export const PGFdialogue: DialogueTranslationEntries = { 3: "Wahahahaha! Que batalha eletrizante!" } }, + "flannery": { + "encounter": { + 1: "Meus Pokémon de fogo estão prontos para queimar a concorrência!\nVamos nessa!", + 2: "Prepare-se para sentir o calor da minha determinação!\nNão vou segurar nada!", + 3: "Minhas habilidades vão incinerar você!\nPrepare-se para a batalha mais quente da sua vida!" + }, + "victory": { + 1: "Essa derrota só faz minha determinação queimar mais!", + 2: "Essa perda não apagará minhas chamas!\nEstarei de volta mais forte!", + 3: "Vou usar essa experiência para reacender meu espírito competitivo!" + }, + "defeat": { + 1: "Minhas chamas nunca se apagarão!\nSou muito apaixonada por isso!", + 2: "Você foi incrível!\nVamos fazer isso de novo algum dia!", + 3: "Que batalha ardente!\nMal posso esperar pela próxima!" + } + }, + "norman": { + "encounter": { + 1: "Você está pronto para enfrentar a força pura do meu time?\nVou te mostrar o poder do equilíbrio!", + 2: "Minha experiência em batalha vai fazer você suar!\nPrepare-se!", + 3: "Treinei meu time rigorosamente.\nVamos ver se você consegue igualar!" + }, + "victory": { + 1: "Parece que subestimei você.\nFoi uma batalha dura.", + 2: "Você é forte, mas ainda há muito para aprender.", + 3: "Essa derrota não abalará minha determinação.\nEstarei de volta mais forte!" + }, + "defeat": { + 1: "Você lutou bravamente!\nEspero batalhar com você novamente.", + 2: "Sua força é incrível!\nNão posso esperar pela nossa próxima batalha.", + 3: "Foi uma honra batalhar com você!\nAté a próxima!" + } + }, "winona": { "encounter": { 1: "Tenho sobrevoado os céus em busca de presas...\nE você é meu alvo!", @@ -3147,6 +3787,60 @@ export const PGFdialogue: DialogueTranslationEntries = { 3: "Tudo graças ao meu treinamento rigoroso com Tate.\nPosso me sincronizar com meus Pokémon." } }, + "juan": { + "encounter": { + 1: "Agora não é hora de agir timidamente.\nVamos batalhar!", + 2: "Ahahaha, você será testemunha da minha arte com Pokémon de Água!", + 3: "Um tufão se aproxima!\nVocê será capaz de me testar?", + 4: "Por favor, você será testemunha da nossa arte.\nUma grande ilusão de água esculpida por meus Pokémon e por mim!" + }, + "victory": { + 1: "Você pode ser um gênio que pode enfrentar Wallace!", + 2: "Eu me concentrei na elegância enquanto você treinava.\nÉ natural que você me derrotasse.", + 3: "Ahahaha!\nMuito bem, você venceu desta vez.", + 4: "De você, sinto o brilho brilhante da habilidade que superará tudo." + }, + "defeat": { + 1: "Meus Pokémon e eu esculpimos uma ilusão de Água e saímos vitoriosos.", + 2: "Ahahaha, eu venci, e você perdeu.", + 3: "Posso emprestar meu traje? Pode te ajudar a batalhar!\nAhahaha, estou brincando!", + 4: "Eu sou o vencedor! O que quer dizer, você perdeu." + } + }, + "crasher_wake": { + "encounter": { + 1: "Crash! Crash! Cuidado!\nDemolidor Wake… está… aqui!", + 2: "Crash! Crash! Demolidor Wake!", + 3: "Sou a onda de poder que vai te lavar!" + }, + "victory": { + 1: "Isso coloca um sorriso no meu rosto!\nGuhahaha! Foi uma explosão!", + 2: "Hunwah! Acabou e terminou!\nComo vou dizer isso...\nQuero mais! Queria batalhar muito mais!", + 3: "O QUÊ?!" + }, + "defeat": { + 1: "Siiiiim! Isso mesmo!", + 2: "Eu venci, mas quero mais! Queria batalhar muito mais!", + 3: "Até logo!" + } + }, + "falkner": { + "encounter": { + 1: "Vou mostrar o verdadeiro poder dos magníficos Pokémon pássaros!", + 2: "Ventos, fiquem comigo!", + 3: "Pai! Espero que esteja vendo minha batalha de cima!" + }, + "victory": { + 1: "Eu entendo... Vou sair graciosamente.", + 2: "Uma derrota é uma derrota. Você é realmente forte.", + 3: "...Droga! Sim, eu perdi." + }, + "defeat": { + 1: "Pai! Venci com seus amados Pokémon pássaros...", + 2: "Pokémon pássaros são os melhores afinal!", + 3: "Sinto que estou alcançando meu pai!" + } + }, "nessa": { "encounter": { 1: "Não importa que tipo de plano sua mente refinada possa estar tramando, meu parceiro e eu vamos afundá-la.", @@ -3295,6 +3989,42 @@ export const PGFdialogue: DialogueTranslationEntries = { 6: "Eu sabia que venceria!" } }, + "crispin": { + "encounter": { + 1: "Quero vencer, então é exatamente isso que vou fazer!", + 2: "Eu batalho porque quero batalhar! E sabe de uma coisa? É assim que deve ser!" + }, + "victory": { + 1: "Queria vencer... mas perdi!", + 2: "Eu perdi... porque não consegui vencer!" + }, + "defeat": { + 1: "Ei, espere um segundo. Eu acabei de vencer? Acho que acabei de vencer! Que satisfação!", + 2: "Uou! Isso foi incrível!" + } + }, + "amarys": { + "encounter": { + 1: "Quero ser a pessoa a ajudar alguém em particular. Sendo assim, não posso me dar ao luxo de perder.\n... Nossa batalha começa agora." + }, + "victory": { + 1: "Eu sou... não o suficiente, eu vejo." + }, + "defeat": { + 1: "A vitória pertence a mim. Bem lutado." + } + }, + "lacey": { + "encounter": { + 1: "Vou enfrentar você com meu time usual como membro da Elite dos Quatro." + }, + "victory": { + 1: "Foi uma excelente batalha. Estou ansiosa para o próximo desafio." + }, + "defeat": { + 1: "Fufufu... Nada mal.\nDesafiantes que derrotam a Elite dos Quatro são dignos de notar." + } + }, "drayton": { "encounter": { 1: `Cara, eu amo cadeiras. Você não ama cadeiras? Que salva-vidas. @@ -3319,6 +4049,23 @@ export const PGFdialogue: DialogueTranslationEntries = { 1: "Hohoho... De fato. Pequenas lâminas frágeis de grama conseguem quebrar até mesmo concreto." } }, + "viola": { + "encounter": { + 1: `Seja as lágrimas de frustração que seguem uma derrota ou o florescer da alegria que vem com a vitória… + $Ambos são ótimos temas para minha câmera! Fantástico! Isso vai ser simplesmente fantástico! + $Agora venha para cima de mim!`, + 2: "Minha lente está sempre focada na vitória – não vou deixar nada estragar esta foto!" + }, + "victory": { + 1: "Você e seus Pokémon me mostraram uma nova profundidade de campo! Fantástico! Simplesmente fantástico!", + 2: `O mundo que você vê através de uma lente, e o mundo que você vê com um Pokémon ao seu lado… + $O mesmo mundo pode parecer completamente diferente dependendo do seu ponto de vista.` + }, + "defeat": { + 1: "A foto do momento da minha vitória vai ser um verdadeiro sucesso!", + 2: "Sim! Tirei ótimas fotos!" + } + }, "candice": { "encounter": { 1: `Você quer desafiar a Candice? Com certeza! Eu estava esperando por alguém forte! @@ -3347,6 +4094,40 @@ export const PGFdialogue: DialogueTranslationEntries = { 1: "Sim! Meus Pokémon e eu somos perfeitamente bons!" } }, + "aaron": { + "encounter": { + 1: "Ok! Deixe-me enfrentar você!" + }, + "victory": { + 1: "Batalhar é um assunto profundo e complexo..." + }, + "defeat": { + 1: "Vencer um membro da Elite dos Quatro não é fácil." + } + }, + "cress": { + "encounter": { + 1: "Isso mesmo! Serei eu e meus estimados tipos Água que você deve enfrentar na batalha!" + }, + "victory": { + 1: "Perder? Eu? Não acredito nisso." + }, + "defeat": { + 1: "Este é o resultado apropriado quando eu sou seu oponente." + } + }, + "allister": { + "encounter": { + 1: "Sou Allister.\nA-aqui... vou eu..." + }, + "victory": { + 1: `Quase perdi minha máscara de tanto choque... Isso foi… + $Uau. Posso ver sua habilidade pelo que ela é.`, + }, + "defeat": { + 1: "I-isso foi incrível!" + } + }, "clay": { "encounter": { 1: "Harrumph! Me deixou esperando, não foi, garota? Tudo bem, hora de ver o que você pode fazer!" @@ -3369,6 +4150,68 @@ export const PGFdialogue: DialogueTranslationEntries = { "defeat": { 1: "Volte para me ver novamente, ouviu?" } + }, "tulip": { + "encounter": { + 1: "Permita-me usar minhas habilidades para deixar seus lindos Pokémon ainda mais bonitos!" + }, + "victory": { + 1: "Sua força tem uma magia que não pode ser apagada." + }, + "defeat": { + 1: "Você sabe, na minha linha de trabalho, pessoas que carecem de talento em uma área ou outra frequentemente desaparecem rapidamente - nunca mais se ouve falar delas." + } + }, + "sidney": { + "encounter": { + 1: `Gostei desse olhar que você me deu. Acho que você vai ser um bom desafio. + $Isso é ótimo! Parece muito bom! Vamos nessa! + $Você e eu, vamos curtir uma batalha que só pode acontecer aqui!`, + }, + "victory": { + 1: "E aí, gostou? Eu perdi! Mas foi divertido, então não importa." + }, + "defeat": { + 1: "Sem ressentimentos, beleza?" + } + }, + "phoebe": { + "encounter": { + 1: `Enquanto treinava, adquiri a habilidade de me comunicar com Pokémon do tipo Fantasma. + $Sim, o vínculo que desenvolvi com os Pokémon é extremamente forte. + $Então, vamos lá, tente ver se você consegue até mesmo causar dano aos meus Pokémon!`, + }, + "victory": { + 1: "Ah, droga. Eu perdi." + }, + "defeat": { + 1: "Estou ansiosa para batalhar com você de novo algum dia!" + } + }, + "glacia": { + "encounter": { + 1: `Tudo o que vi foram desafios de Treinadores fracos e seus Pokémon. + $E você? Ficaria extremamente satisfeita se pudesse dar tudo de mim contra você!`, + }, + "victory": { + 1: `Você e seus Pokémon… Como seus espíritos queimam! + $O calor consumido é esmagador. + $Não é surpresa que minhas habilidades geladas falharam em te machucar.`, + }, + "defeat": { + 1: "Uma batalha intensamente apaixonada, sem dúvida." + } + }, + "drake": { + "encounter": { + 1: `Para nós, batalhar com Pokémon como parceiros, você sabe o que é necessário? Você sabe o que precisa? + $Se não souber, nunca prevalecerá contra mim!`, + }, + "victory": { + 1: "Excelente, deve-se dizer." + }, + "defeat": { + 1: "Dei meu máximo nessa batalha!" + } }, "wallace": { "encounter": { @@ -3420,6 +4263,17 @@ export const PGFdialogue: DialogueTranslationEntries = { 1: "Estou encantada! Sim, encantada por poder esmagar você sob meu calcanhar." } }, + "hala": { + "encounter": { + 1: "O velho Hala está aqui para fazer você gritar!" + }, + "victory": { + 1: "Pude sentir o poder que você ganhou na sua jornada." + }, + "defeat": { + 1: "Haha! Que batalha deliciosa!" + } + }, "molayne": { "encounter": { 1: `Dei a posição de capitão ao meu primo Sophocles, mas estou confiante na minha habilidade. @@ -3443,6 +4297,17 @@ export const PGFdialogue: DialogueTranslationEntries = { 1: "Nahahaha! Você realmente é algo mais, garota!" } }, + "bruno": { + "encounter": { + 1: "Nós vamos te triturar com nosso poder superior! Hoo hah!" + }, + "victory": { + 1: "Por quê? Como eu poderia perder?" + }, + "defeat": { + 1: "Você pode me desafiar o quanto quiser, mas os resultados nunca vão mudar!" + } + }, "bugsy": { "encounter": { 1: "Sou Bugsy! Eu nunca perco quando se trata de Pokémon do tipo Inseto!" @@ -3454,6 +4319,30 @@ export const PGFdialogue: DialogueTranslationEntries = { 1: "Obrigado! Graças à nossa batalha, eu também pude fazer progressos na minha pesquisa!" } }, + "koga": { + "encounter": { + 1: "Fwahahahaha! Pokémon não são apenas sobre força bruta--você verá em breve!" + }, + "victory": { + 1: "Ah! Você provou seu valor!" + }, + "defeat": { + 1: "Você aprendeu a temer as técnicas do ninja?" + } + }, + "bertha": { + "encounter": { + 1: "Bem, você mostraria a esta velha senhora o quanto aprendeu?" + }, + "victory": { + 1: `Bem! Querida criança, devo dizer, isso foi muito impressionante. + $Seus Pokémon acreditaram em você e fizeram o melhor para te dar a vitória. + $Mesmo tendo perdido, me encontro com esse sorriso bobo!`, + }, + "defeat": { + 1: "Hahahahah! Parece que esta velha senhora ganhou!" + } + }, "lenora": { "encounter": { 1: "Bem, desafiadora, vou pesquisar como você batalha com os Pokémon que criou com tanto carinho!" @@ -3465,6 +4354,18 @@ export const PGFdialogue: DialogueTranslationEntries = { 1: "Ah ha ha! Se você perder, certifique-se de analisar o porquê e use esse conhecimento na próxima batalha!" } }, + "siebold": { + "encounter": { + 1: "Enquanto eu estiver vivo, continuarei em busca da culinária suprema... e dos oponentes mais fortes em batalha!" + }, + "victory": { + 1: "Guardarei minha memória de você e seus Pokémon para sempre em meu coração." + }, + "defeat": { + 1: `Nossa batalha Pokémon foi como alimento para minha alma. Isso vai me manter em frente. + $É assim que vou prestar meus respeitos a você por dar tudo de si na batalha!`, + } + }, "roxie": { "encounter": { 1: "Prepare-se! Vou arrancar algum senso de você!" @@ -3476,6 +4377,40 @@ export const PGFdialogue: DialogueTranslationEntries = { 1: "Ei, vamos lá! Seja séria! Você tem que dar mais de si!" } }, + "olivia": { + "encounter": { + 1: "Não precisa de introdução aqui. Hora de batalhar comigo, Olivia!" + }, + "victory": { + 1: "Realmente encantador… Tanto você quanto seus Pokémon…" + }, + "defeat": { + 1: "Mmm-hmm." + } + }, + "poppy": { + "encounter": { + 1: "Oooh! Você quer ter uma batalha Pokémon comigo?" + }, + "victory": { + 1: "Uagh?! Mmmuuuggghhh…" + }, + "defeat": { + 1: `Yaaay! Eu consegui! Eu der-ro-tei você! Você pode vir para… Para… Uma revanche? + $Venha para uma revanche quando quiser!`, + } + }, + "agatha": { + "encounter": { + 1: "Pokémon são para batalhas! Vou te mostrar como um verdadeiro Treinador batalha!" + }, + "victory": { + 1: "Oh meu! Você é algo especial, criança!" + }, + "defeat": { + 1: "Bahaha. É assim que uma batalha adequada é feita!" + } + }, "flint": { "encounter": { 1: "Espero que você esteja aquecida, porque aqui vem o Big Bang!" @@ -3487,6 +4422,17 @@ export const PGFdialogue: DialogueTranslationEntries = { 1: "Huh? Isso é tudo? Acho que você precisa de um pouco mais de paixão." } }, + "grimsley": { + "encounter": { + 1: "O vencedor leva tudo, e não sobra nada para o perdedor." + }, + "victory": { + 1: "Quando se perde, perde-se tudo… A próxima coisa que vou procurar será a vitória, também!" + }, + "defeat": { + 1: "Se alguém vence, a pessoa que lutou contra essa pessoa perde." + } + }, "caitlin": { "encounter": { 1: `Sou eu que apareci quando a flor se abriu. Você que estava esperando… @@ -3501,6 +4447,18 @@ export const PGFdialogue: DialogueTranslationEntries = { 1: "Aspiro a reivindicar a vitória com elegância e graça." } }, + "diantha": { + "encounter": { + 1: `Batalhar contra você e seus Pokémon, todos vocês cheios de esperança para o futuro… + $Honestamente, isso apenas me enche da energia que preciso para continuar enfrentando cada novo dia! Sim!`, + }, + "victory": { + 1: "Testemunhar os espíritos nobres de você e seus Pokémon em batalha realmente tocou meu coração…" + }, + "defeat": { + 1: "Oh, fantástico! O que achou? Minha equipe foi bem legal, né?" + } + }, "wikstrom": { "encounter": { 1: `Bem encontrado, jovem desafiadora! Verdadeiramente sou a lâmina famosa de aço endurecido, Duque Wikstrom! @@ -3525,6 +4483,18 @@ export const PGFdialogue: DialogueTranslationEntries = { 1: "Ehaha! Que vitória incrível!" } }, + "larry_elite": { + "encounter": { + 1: `Olá… Sou eu, Larry. + $Eu também sou membro da Elite dos Quatro, sim… Infelizmente para mim.`, + }, + "victory": { + 1: "Bem, isso tirou o vento debaixo das nossas asas…" + }, + "defeat": { + 1: "É hora de uma reunião com o chefe." + } + }, "lance": { "encounter": { 1: "Estive esperando por você. Permita-me testar suas habilidades.", @@ -3539,6 +4509,23 @@ export const PGFdialogue: DialogueTranslationEntries = { 2: "Não é que você seja fraca. Não se incomode com isso." } }, + "karen": { + "encounter": { + 1: "Eu sou Karen. Você gostaria de um duelo com meus Pokémon do tipo Sombrio?", + 2: "Sou diferente daqueles que você já conheceu.", + 3: "Você montou uma equipe charmosa. Nossa batalha deve ser boa." + }, + "victory": { + 1: "Não! Eu não posso vencer. Como você ficou tão forte?", + 2: "Não me desviarei do meu caminho escolhido.", + 3: "O Campeão está ansioso para te conhecer." + }, + "defeat": { + 1: "Isso era o que eu esperava.", + 2: "Bem, isso foi relativamente divertido.", + 3: "Venha me visitar a qualquer momento." + } + }, "milo": { "encounter": { 1: `Parece que você entende bem os Pokémon. @@ -3552,6 +4539,20 @@ export const PGFdialogue: DialogueTranslationEntries = { 1: "Isso realmente vai te deixar em choque e admiração." } }, + "lucian": { + "encounter": { + 1: `Só um momento, por favor. O livro que estou lendo está quase no clímax emocionante… + $O herói obteve uma espada mística e está prestes a enfrentar sua prova final… Ah, tanto faz. + $Já que você chegou tão longe, vou deixar isso de lado e batalhar com você. + $Deixe-me ver se você alcançará tanta glória quanto o herói do meu livro!`, + }, + "victory": { + 1: "Eu vejo… Parece que você me colocou em xeque-mate." + }, + "defeat": { + 1: "Tenho uma reputação a manter." + } + }, "drasna": { "encounter": { 1: `Você deve ser uma Treinadora forte. Sim, bastante forte… @@ -3564,6 +4565,29 @@ export const PGFdialogue: DialogueTranslationEntries = { 1: "Como isso é possível?" } }, + "kahili": { + "encounter": { + 1: "Então, aqui está você… Por que não vemos para quem os ventos favorecem hoje, você… ou eu?" + }, + "victory": { + 1: "É frustrante para mim como membro da Elite dos Quatro, mas parece que sua força é real." + }, + "defeat": { + 1: "Essa foi uma jogada de mestre!" + } + }, + "hassel": { + "encounter": { + 1: "Prepare-se para aprender em primeira mão como é a respiração ardente de uma batalha feroz!" + }, + "victory": { + 1: `A sorte sorriu para mim desta vez, mas… + $Julgando pelo andamento da luta, quem sabe se serei tão sortudo na próxima vez.`, + }, + "defeat": { + 1: "Essa foi uma jogada de mestre!" + } + }, "blue": { "encounter": { 1: "Você deve ser muito boa para chegar tão longe." @@ -3575,6 +4599,39 @@ export const PGFdialogue: DialogueTranslationEntries = { 1: "Viu? Meu poder é o que me trouxe até aqui." } }, + "piers": { + "encounter": { + 1: "Prepare-se para uma mosh pit comigo e minha galera! Spikemuth, é hora de roquear!" + }, + "victory": { + 1: "Eu e minha equipe demos o nosso melhor. Vamos nos encontrar novamente para uma batalha algum dia…" + }, + "defeat": { + 1: "Minha garganta está desgastada de tanto gritar… Mas essa foi uma batalha empolgante!" + } + }, + "red": { + "encounter": { + 1: "…!" + }, + "victory": { + 1: "…?" + }, + "defeat": { + 1: "…!" + } + }, + "jasmine": { + "encounter": { + 1: "Oh… Seus Pokémon são impressionantes. Acho que vou gostar disso." + }, + "victory": { + 1: "Você é realmente forte. Vou ter que me esforçar muito mais também." + }, + "defeat": { + 1: "Eu nunca esperei ganhar." + } + }, "lance_champion": { "encounter": { 1: "Ainda sou o Campeão. Não vou segurar nada." @@ -3586,6 +4643,96 @@ export const PGFdialogue: DialogueTranslationEntries = { 1: "Defendi com sucesso meu Campeonato." } }, + "steven": { + "encounter": { + 1: `Diga-me… O que você viu na sua jornada com seus Pokémon? + $O que você sentiu, encontrando tantos outros Treinadores por aí? + $Viajar por esta terra rica… Isso despertou algo dentro de você? + $Quero que você venha até mim com tudo o que aprendeu. + $Meus Pokémon e eu responderemos com tudo o que sabemos!`, + }, + "victory": { + 1: "Então eu, o Campeão, caio em derrota…" + }, + "defeat": { + 1: "Esse tempo foi bem gasto! Obrigado!" + } + }, + "cynthia": { + "encounter": { + 1: "Eu, Cynthia, aceito seu desafio! Não haverá nenhuma trégua da minha parte!" + }, + "victory": { + 1: "Não importa o quão divertida a batalha seja, ela sempre terminará algum dia…" + }, + "defeat": { + 1: "Mesmo que você perca, nunca perca o amor pelos Pokémon." + } + }, + "iris": { + "encounter": { + 1: `Sabe de uma coisa? Estou realmente ansiosa para ter batalhas sérias com Treinadores fortes! + $Quero dizer, vamos lá! Os Treinadores que chegam aqui são Treinadores que desejam a vitória com todas as fibras do seu ser! + $E eles estão batalhando ao lado de Pokémon que passaram por inúmeras batalhas difíceis! + $Se eu batalhar com pessoas assim, não só eu ficarei mais forte, meus Pokémon também! + $E nós vamos nos conhecer ainda melhor! OK! Prepare-se! + $Sou Iris, a Campeã da Liga Pokémon, e vou te derrotar!`, + }, + "victory": { + 1: "Aghhhh… Eu dei o meu melhor, mas nós perdemos…" + }, + "defeat": { + 1: "Yay! Nós vencemos!" + } + }, + "hau": { + "encounter": { + 1: `Eu me pergunto se um Treinador batalha de maneira diferente dependendo se ele é de uma região quente ou fria. + $Vamos testar isso!`, + }, + "victory": { + 1: "Isso foi incrível! Acho que entendi um pouco melhor seu estilo agora!" + }, + "defeat": { + 1: "Cara, essa foi uma batalha e tanto!" + } + }, + "geeta": { + "encounter": { + 1: `Decidi entrar na batalha mais uma vez. + $Venha agora… Mostre-me os frutos do seu treinamento.`, + }, + "victory": { + 1: "Estou ansiosa para notícias de todas as suas conquistas!" + }, + "defeat": { + 1: "Qual o problema? Isso é tudo?" + } + }, + "nemona": { + "encounter": { + 1: "Yesss! Estou tão empolgada! Hora de soltar tudo!" + }, + "victory": { + 1: "Bem, isso foi ruim, mas ainda me diverti! Eu te pego na próxima!" + }, + "defeat": { + 1: "Bem, essa foi uma ótima batalha! Frutífera, com certeza." + } + }, + "leon": { + "encounter": { + 1: "Vamos ter um tempo absolutamente campeão!" + }, + "victory": { + 1: `Meu tempo como Campeão acabou… + $Mas que tempo campeão foi! + $Obrigado pela melhor batalha que já tive!`, + }, + "defeat": { + 1: "Um tempo absolutamente campeão, foi!" + } + }, "whitney": { "encounter": { 1: "Eai! Você não acha que os Pokémon são, tipo, super fofos?" @@ -3619,6 +4766,28 @@ export const PGFdialogue: DialogueTranslationEntries = { 1: "Coma, meu adorável Vivillon!" } }, + "pryce": { + "encounter": { + 1: "A juventude sozinha não garante a vitória! Experiência é o que conta." + }, + "victory": { + 1: "Excelente! Isso foi perfeito. Tente não esquecer o que sente agora." + }, + "defeat": { + 1: "Exatamente como eu imaginei." + } + }, + "clair": { + "encounter": { + 1: "Você sabe quem eu sou? E ainda se atreve a me desafiar?" + }, + "victory": { + 1: "Eu me pergunto até onde você pode ir com seu nível de habilidade. Isso deve ser fascinante." + }, + "defeat": { + 1: "E é isso." + } + }, "maylene": { "encounter": { 1: `Vim desafiá-la agora e não vou segurar nada. @@ -3631,6 +4800,18 @@ export const PGFdialogue: DialogueTranslationEntries = { 1: "Isso foi incrível." } }, + "fantina": { + "encounter": { + 1: `Você vai me desafiar, não é? Mas eu vou ganhar. + $É o que a Líder do Ginásio de Hearthome faz, não?`, + }, + "victory": { + 1: "Você é tão incrivelmente forte. Sei porque perdi." + }, + "defeat": { + 1: "Estou tão, tão, muito feliz!" + } + }, "byron": { "encounter": { 1: `Treinadora! Você é jovem, assim como meu filho, Roark. @@ -3644,6 +4825,17 @@ export const PGFdialogue: DialogueTranslationEntries = { 1: "Gwahahaha! Como foram meus Pokémon robustos?!" } }, + "olympia": { + "encounter": { + 1: "Um costume antigo decidindo o destino de alguém. A batalha começa!" + }, + "victory": { + 1: "Crie seu próprio caminho. Não deixe nada te atrapalhar. Seu destino, seu futuro." + }, + "defeat": { + 1: "Nosso caminho está claro agora." + } + }, "volkner": { "encounter": { 1: `Já que você chegou tão longe, deve ser bastante forte… @@ -3659,6 +4851,108 @@ export const PGFdialogue: DialogueTranslationEntries = { $Isso não é o que eu queria!`, } }, + "burgh": { + "encounter": { + 1: `M'hm… Se eu ganhar esta batalha, sinto que posso desenhar um quadro diferente de qualquer outro. + $OK! Posso ouvir minha musa da batalha claramente. Vamos direto ao ponto!`, + 2: `Claro, estou realmente orgulhoso de todos os meus Pokémon! + $Bem agora… Vamos direto ao ponto!` + }, + "victory": { + 1: "Acabou? Minha musa me abandonou?", + 2: "Hmm… Acabou! Você é incrível!" + }, + "defeat": { + 1: "Uau… É bonito de alguma forma, não é…", + 2: `Às vezes ouço as pessoas dizerem que foi uma vitória feia. + $Acho que se você está dando o seu melhor, qualquer vitória é bonita.` + } + }, + "elesa": { + "encounter": { + 1: `C'est fini! Quando tenho certeza disso, sinto um choque elétrico percorrer meu corpo! + $Quero sentir essa sensação, então agora meus amados Pokémon vão fazer sua cabeça girar!`, + }, + "victory": { + 1: "Eu queria fazer sua cabeça girar, mas você me surpreendeu." + }, + "defeat": { + 1: "Isso foi insatisfatório de alguma forma… Você dará tudo de si na próxima vez?" + } + }, + "skyla": { + "encounter": { + 1: `Finalmente é hora do confronto! Isso significa a batalha Pokémon que decide quem está no topo, certo? + $Eu amo estar no topo! Porque você pode ver para sempre e sempre de lugares altos! + $Então, que tal nós nos divertirmos?`, + }, + "victory": { + 1: "Ser seu oponente na batalha é uma nova fonte de força para mim. Obrigada!" + }, + "defeat": { + 1: "Ganhar ou perder, você sempre ganha algo com uma batalha, certo?" + } + }, + "brycen": { + "encounter": { + 1: `Há também força em estar com outras pessoas e Pokémon. + $Receber o apoio deles te fortalece. Vou te mostrar esse poder!`, + }, + "victory": { + 1: "A maravilhosa combinação de você e seus Pokémon! Que amizade linda!" + }, + "defeat": { + 1: "Condições extremas realmente testam e treinam você!" + } + }, + "drayden": { + "encounter": { + 1: `O que eu quero encontrar é um jovem Treinador que possa me mostrar um futuro brilhante. + $Vamos batalhar com tudo o que temos: sua habilidade, minha experiência e o amor com que criamos nossos Pokémon!`, + }, + "victory": { + 1: "Esse sentimento intenso que me invade após uma derrota… Não sei como descrevê-lo." + }, + "defeat": { + 1: "Harrumph! Sei que sua habilidade é maior que isso!" + } + }, + "grant": { + "encounter": { + 1: `Só há uma coisa que desejo. + $Que, superando um ao outro, encontremos um caminho para alturas ainda maiores.`, + }, + "victory": { + 1: "Você é uma parede que não consigo superar!" + }, + "defeat": { + 1: `Não desista. + $Isso é tudo o que realmente importa. + $As lições mais importantes da vida são simples.`, + } + }, + "korrina": { + "encounter": { + 1: "Hora da grande aparição de Lady Korrina!" + }, + "victory": { + 1: "É o seu próprio ser que permite que seus Pokémon evoluam!" + }, + "defeat": { + 1: "Que batalha explosiva!" + } + }, + "clemont": { + "encounter": { + 1: "Oh! Estou feliz por termos nos encontrado!" + }, + "victory": { + 1: "Sua paixão pela batalha me inspira!" + }, + "defeat": { + 1: "Parece que minha Máquina Treinadora-Crescer-Forte, Mach 2 está realmente funcionando!" + } + }, "valerie": { "encounter": { 1: `Oh, se não é uma jovem Treinadora… É adorável conhecê-la assim. @@ -3732,6 +5026,42 @@ export const PGFdialogue: DialogueTranslationEntries = { 1: "Bom trabalho, eu suponho." } }, + "gordie": { + "encounter": { + 1: "Então, vamos acabar com isso." + }, + "victory": { + 1: "Eu só quero me enterrar em um buraco… Bem, acho que seria mais como cair daqui." + }, + "defeat": { + 1: "Batalhe como sempre faz, a vitória seguirá!" + } + }, + "marnie": { + "encounter": { + 1: `A verdade é que, quando tudo está dito e feito… Eu realmente só quero me tornar Campeã por mim mesma! + $Então, não leve para o pessoal quando eu chutar seu traseiro!`, + }, + "victory": { + 1: "OK, então eu perdi… Mas consegui ver muitos dos pontos bons de você e seus Pokémon!" + }, + "defeat": { + 1: "Espero que você tenha gostado das nossas táticas de batalha." + } + }, + "raihan": { + "encounter": { + 1: "Vou derrotar o Campeão, vencer todo o torneio e provar ao mundo o quão forte o grande Raihan realmente é!" + }, + "victory": { + 1: `Eu pareço bem mesmo quando perco. + $É uma verdadeira maldição. + $Acho que é hora de mais uma selfie!`, + }, + "defeat": { + 1: "Vamos tirar uma selfie para lembrar disso." + } + }, "brassius": { "encounter": { 1: "Pressuponho que você está pronta? Que nossa obra de arte colaborativa comece!" @@ -3757,6 +5087,17 @@ export const PGFdialogue: DialogueTranslationEntries = { 1: "Seus olhos são MEUS!" } }, + "larry": { + "encounter": { + 1: "Quando tudo está dito e feito, a simplicidade é mais forte." + }, + "victory": { + 1: "Uma porção de derrota, hein?" + }, + "defeat": { + 1: "Vou encerrar o dia." + } + }, "ryme": { "encounter": { 1: "Vamos lá, baby! Me agite até os ossos!" @@ -3768,6 +5109,31 @@ export const PGFdialogue: DialogueTranslationEntries = { 1: "Até mais, baby!" } }, + "grusha": { + "encounter": { + 1: "Tudo o que preciso fazer é garantir que o poder do meu Pokémon te arrependa até os ossos!" + }, + "victory": { + 1: "Sua paixão ardente... Eu meio que gosto, para ser honesto." + }, + "defeat": { + 1: "As coisas não esquentaram para você." + } + }, + "marnie_elite": { + "encounter": { + 1: "Você chegou até aqui, hein? Vamos ver se você pode lidar com meus Pokémon!", + 2: "Vou dar o meu melhor, mas não pense que vou pegar leve com você!" + }, + "victory": { + 1: "Não acredito que perdi... Mas você mereceu essa vitória. Bem feito!", + 2: "Parece que ainda tenho muito a aprender. Porém, grande batalha!" + }, + "defeat": { + 1: "Você lutou bem, mas eu tenho a vantagem! Melhor sorte na próxima vez!", + 2: "Parece que meu treinamento valeu a pena. Obrigado pela batalha!" + } + }, "nessa_elite": { "encounter": { 1: "As marés estão mudando a meu favor. Pronta para ser levada pela corrente?", @@ -3782,6 +5148,20 @@ export const PGFdialogue: DialogueTranslationEntries = { 2: "Você lutou bem, mas o poder do oceano é imparável!" } }, + "bea_elite": { + "encounter": { + 1: "Prepare-se! Meu espírito de luta brilha intensamente!", + 2: "Vamos ver se você consegue acompanhar meu ritmo implacável!" + }, + "victory": { + 1: "Sua força... É impressionante. Você realmente merece essa vitória.", + 2: "Nunca senti essa intensidade antes. Trabalho incrível!" + }, + "defeat": { + 1: "Outra vitória para meu rigoroso regime de treinamento! Bem feito!", + 2: "Você tem força, mas eu treinei mais. Grande batalha!" + } + }, "allister_elite": { "encounter": { 1: "As sombras caem... Você está pronta para enfrentar seus medos?", @@ -3810,6 +5190,32 @@ export const PGFdialogue: DialogueTranslationEntries = { 2: "Você foi pega na minha tempestade! Melhor sorte na próxima vez!" } }, + "alder": { + "encounter": { + 1: "Se prepare para uma batalha contra o Treinador mais forte de Unova!" + }, + "victory": { + 1: "Muito bem! Você certamente é um talento incomparável." + }, + "defeat": { + 1: `Um vento fresco sopra em meu coração... + $Que esforço extraordinário!` + } + }, + "kieran": { + "encounter": { + 1: `Através do trabalho duro, eu me torno cada vez mais forte! + $Eu não perco.` + }, + "victory": { + 1: `Eu não acredito... + $Que batalha divertida e emocionante!` + }, + "defeat": { + 1: `Uau, que batalha! + $Hora de você treinar ainda mais.` + } + }, "rival": { "encounter": { 1: `@c{smile}Eai, estava procurando você! Sabia que você estava ansiosa para começar, mas esperava pelo menos um tchau… diff --git a/src/locales/pt_BR/menu.ts b/src/locales/pt_BR/menu.ts index 927ccce518b..87be5d8bed0 100644 --- a/src/locales/pt_BR/menu.ts +++ b/src/locales/pt_BR/menu.ts @@ -35,11 +35,11 @@ export const menu: SimpleTranslationEntries = { "sessionSuccess": "Sessão carregada com sucesso.", "failedToLoadSession": "Não foi possível carregar os dados da sua sessão.\nEles podem estar corrompidos.", "boyOrGirl": "Você é um menino ou uma menina?", - "evolving": "Que?\n{{pokemonName}} tá evoluindo!", + "evolving": "Quê?\n{{pokemonName}} tá evoluindo!", "stoppedEvolving": "{{pokemonName}} parou de evoluir.", "pauseEvolutionsQuestion": "Gostaria de pausar evoluções para {{pokemonName}}?\nEvoluções podem ser religadas na tela de equipe.", "evolutionsPaused": "Evoluções foram paradas para {{pokemonName}}.", - "evolutionDone": "Parabéns!\nSeu {{pokemonName}} evolui para {{evolvedPokemonName}}!", + "evolutionDone": "Parabéns!\nSeu {{pokemonName}} evoluiu para {{evolvedPokemonName}}!", "dailyRankings": "Classificação Diária", "weeklyRankings": "Classificação Semanal", "noRankings": "Sem Classificação", diff --git a/src/locales/pt_BR/modifier.ts b/src/locales/pt_BR/modifier.ts index 168665205c3..eadd5c5667a 100644 --- a/src/locales/pt_BR/modifier.ts +++ b/src/locales/pt_BR/modifier.ts @@ -4,7 +4,7 @@ export const modifier: SimpleTranslationEntries = { "surviveDamageApply": "{{pokemonNameWithAffix}} aguentou o tranco\nusando sua {{typeName}}!", "turnHealApply": "{{pokemonNameWithAffix}} restaurou um pouco de PS usando\nsuas {{typeName}}!", "hitHealApply": "{{pokemonNameWithAffix}} restaurou um pouco de PS usando\nsua {{typeName}}!", - "pokemonInstantReviveApply": "{{pokemonNameWithAffix}} foi revivido\npor sua {{typeName}}!", + "pokemonInstantReviveApply": "{{pokemonNameWithAffix}} foi reanimado\npor sua {{typeName}}!", "pokemonResetNegativeStatStageApply": "Os atributos diminuídos de {{pokemonNameWithAffix}} foram\nrestaurados por seu(sua) {{typeName}}!", "moneyInterestApply": "Você recebeu um juros de ₽{{moneyAmount}}\nde sua {{typeName}}!", "turnHeldItemTransferApply": "{{itemName}} de {{pokemonNameWithAffix}} foi absorvido(a)\npelo {{typeName}} de {{pokemonName}}!", diff --git a/src/locales/pt_BR/move-trigger.ts b/src/locales/pt_BR/move-trigger.ts index 042d539338e..620f867ae9a 100644 --- a/src/locales/pt_BR/move-trigger.ts +++ b/src/locales/pt_BR/move-trigger.ts @@ -26,7 +26,7 @@ export const moveTriggers: SimpleTranslationEntries = { "soothingAromaWaftedThroughArea": "Um aroma suave se espalhou pelo ambiente!", "sprangUp": "{{pokemonName}} se levantou!", "choseDoomDesireAsDestiny": "{{pokemonName}} escolheu\no Desejo da Perdição como seu destino!", - "vanishedInstantly": "{{pokemonName}} desapareceu/nde repente!", + "vanishedInstantly": "{{pokemonName}} desapareceu\nde repente!", "tookTargetIntoSky": "{{pokemonName}} levou {{targetName}}\npara o céu!", "becameCloakedInFreezingLight": "{{pokemonName}} ficou envolto/nem uma luz congelante!", "becameCloakedInFreezingAir": "{{pokemonName}} ficou envolto/nem ar congelante!", diff --git a/src/locales/pt_BR/party-ui-handler.ts b/src/locales/pt_BR/party-ui-handler.ts index 08132b4bfc0..1f3e0fbe242 100644 --- a/src/locales/pt_BR/party-ui-handler.ts +++ b/src/locales/pt_BR/party-ui-handler.ts @@ -15,7 +15,7 @@ export const partyUiHandler: SimpleTranslationEntries = { "ALL": "Tudo", "PASS_BATON": "Passar Bastão", "UNPAUSE_EVOLUTION": "Ativar Evolução", - "REVIVE": "Reviver", + "REVIVE": "Reanimar", "RENAME": "Renomear", "choosePokemon": "Escolha um Pokémon.", diff --git a/src/locales/pt_BR/pokemon-form.ts b/src/locales/pt_BR/pokemon-form.ts index 062fc165ae0..dbe63fb7864 100644 --- a/src/locales/pt_BR/pokemon-form.ts +++ b/src/locales/pt_BR/pokemon-form.ts @@ -29,7 +29,7 @@ export const pokemonForm: SimpleTranslationEntries = { "pikachuPartner": "Parceiro", "eeveePartner": "Parceiro", // 2G - "pichuSpiky": "Spiky", + "pichuSpiky": "Orelha Espetada", "unownA": "A", "unownB": "B", "unownC": "C", @@ -74,8 +74,8 @@ export const pokemonForm: SimpleTranslationEntries = { "rotomFrost": "Congelante", "rotomFan": "Ventilador", "rotomMow": "Corte", - "giratinaAltered": "Altered", - "shayminLand": "Land", + "giratinaAltered": "Alterado", + "shayminLand": "Terrestre", // 5G "basculinRedStriped": "Listras Vermelhas", "basculinBlueStriped": "Listras Azuis", @@ -84,11 +84,11 @@ export const pokemonForm: SimpleTranslationEntries = { "deerlingSummer": "Verão", "deerlingAutumn": "Outono", "deerlingWinter": "Inverno", - "tornadusIncarnate": "Incarnate", - "thundurusIncarnate": "Incarnate", - "landorusIncarnate": "Incarnate", - "keldeoOrdinary": "Ordinary", - "meloettaAria": "Aria", + "tornadusIncarnate": "Materializado", + "thundurusIncarnate": "Materializado", + "landorusIncarnate": "Materializado", + "keldeoOrdinary": "Comum", + "meloettaAria": "Ária", // 6G "froakieBattleBond": "Vínculo de Batalha", "scatterbugMeadow": "Prado", @@ -165,11 +165,11 @@ export const pokemonForm: SimpleTranslationEntries = { "eiscueNoIce": "Descongelado", "indeedeeMale": "Macho", "indeedeeFemale": "Fêmea", - "morpekoFullBelly": "Full Belly", - "zacianHeroOfManyBattles": "Hero Of Many Battles", - "zamazentaHeroOfManyBattles": "Hero Of Many Battles", + "morpekoFullBelly": "Saciado", + "zacianHeroOfManyBattles": "Herói Veterano", + "zamazentaHeroOfManyBattles": "Herói Veterano", "zarudeDada": "Papa", - "enamorusIncarnate": "Incarnate", + "enamorusIncarnate": "Materializado", // 9G "squawkabillyGreenPlumage": "Plumas Verdes", "squawkabillyBluePlumage": "Plumas Azuis", diff --git a/src/locales/zh_CN/battle-scene.ts b/src/locales/zh_CN/battle-scene.ts new file mode 100644 index 00000000000..573e1791724 --- /dev/null +++ b/src/locales/zh_CN/battle-scene.ts @@ -0,0 +1,5 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const battleScene: SimpleTranslationEntries = { + "moneyOwned": "₽{{formattedMoney}}" +} as const; diff --git a/src/locales/zh_CN/config.ts b/src/locales/zh_CN/config.ts index 24c8b870ffa..99b4e56ffc2 100644 --- a/src/locales/zh_CN/config.ts +++ b/src/locales/zh_CN/config.ts @@ -4,6 +4,7 @@ import { arenaFlyout } from "./arena-flyout"; import { arenaTag } from "./arena-tag"; import { PGFachv, PGMachv } from "./achv"; import { battle } from "./battle"; +import { battleScene } from "./battle-scene"; import { battleInfo } from "./battle-info"; import { battleMessageUiHandler } from "./battle-message-ui-handler"; import { battlerTags } from "./battler-tags"; @@ -60,6 +61,7 @@ export const zhCnConfig = { arenaFlyout: arenaFlyout, arenaTag: arenaTag, battle: battle, + battleScene: battleScene, battleInfo: battleInfo, battleMessageUiHandler: battleMessageUiHandler, battlePokemonForm: battlePokemonForm, diff --git a/src/locales/zh_TW/battle-scene.ts b/src/locales/zh_TW/battle-scene.ts new file mode 100644 index 00000000000..573e1791724 --- /dev/null +++ b/src/locales/zh_TW/battle-scene.ts @@ -0,0 +1,5 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const battleScene: SimpleTranslationEntries = { + "moneyOwned": "₽{{formattedMoney}}" +} as const; diff --git a/src/locales/zh_TW/config.ts b/src/locales/zh_TW/config.ts index 004ed1da1ab..269ea3003b9 100644 --- a/src/locales/zh_TW/config.ts +++ b/src/locales/zh_TW/config.ts @@ -4,6 +4,7 @@ import { arenaFlyout } from "./arena-flyout"; import { arenaTag } from "./arena-tag"; import { PGFachv, PGMachv } from "./achv"; import { battle } from "./battle"; +import { battleScene } from "./battle-scene"; import { battleInfo } from "./battle-info"; import { battleMessageUiHandler } from "./battle-message-ui-handler"; import { battlerTags } from "./battler-tags"; @@ -60,6 +61,7 @@ export const zhTwConfig = { arenaFlyout: arenaFlyout, arenaTag: arenaTag, battle: battle, + battleScene: battleScene, battleInfo: battleInfo, battleMessageUiHandler: battleMessageUiHandler, battlePokemonForm: battlePokemonForm, diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 8a6598f5849..99f4540f493 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1160,7 +1160,7 @@ export class TurnHealModifier extends PokemonHeldItemModifier { if (!pokemon.isFullHp()) { const scene = pokemon.scene; scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), - Math.max(Math.floor(pokemon.getMaxHp() / 16) * this.stackCount, 1), i18next.t("modifier:turnHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), true)); + Utils.toDmgValue(pokemon.getMaxHp() / 16) * this.stackCount, i18next.t("modifier:turnHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), true)); return true; } @@ -1251,7 +1251,7 @@ export class HitHealModifier extends PokemonHeldItemModifier { if (pokemon.turnData.damageDealt && !pokemon.isFullHp()) { const scene = pokemon.scene; scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), - Math.max(Math.floor(pokemon.turnData.damageDealt / 8) * this.stackCount, 1), i18next.t("modifier:hitHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), true)); + Utils.toDmgValue(pokemon.turnData.damageDealt / 8) * this.stackCount, i18next.t("modifier:hitHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), true)); } return true; @@ -1386,7 +1386,7 @@ export class PokemonInstantReviveModifier extends PokemonHeldItemModifier { const pokemon = args[0] as Pokemon; pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), - Math.max(Math.floor(pokemon.getMaxHp() / 2), 1), i18next.t("modifier:pokemonInstantReviveApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), false, false, true)); + Utils.toDmgValue(pokemon.getMaxHp() / 2), i18next.t("modifier:pokemonInstantReviveApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), false, false, true)); pokemon.resetStatus(true, false, true); return true; diff --git a/src/phases/attempt-run-phase.ts b/src/phases/attempt-run-phase.ts index 9781ca6d360..17625c57fc6 100644 --- a/src/phases/attempt-run-phase.ts +++ b/src/phases/attempt-run-phase.ts @@ -23,7 +23,7 @@ export class AttemptRunPhase extends PokemonPhase { const enemySpeed = enemyField.reduce((total: integer, enemyPokemon: Pokemon) => total + enemyPokemon.getStat(Stat.SPD), 0) / enemyField.length; const escapeChance = new Utils.IntegerHolder((((playerPokemon.getStat(Stat.SPD) * 128) / enemySpeed) + (30 * this.scene.currentBattle.escapeAttempts++)) % 256); - applyAbAttrs(RunSuccessAbAttr, playerPokemon, null, escapeChance); + applyAbAttrs(RunSuccessAbAttr, playerPokemon, null, false, escapeChance); if (playerPokemon.randSeedInt(256) < escapeChance.value) { this.scene.playSound("flee"); diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index 5d466e5d3b6..68ede826d95 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -189,7 +189,7 @@ export class CommandPhase extends FieldPhase { const batonPass = isSwitch && args[0] as boolean; const trappedAbMessages: string[] = []; if (!batonPass) { - enemyField.forEach(enemyPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, enemyPokemon, trapped, playerPokemon, true, trappedAbMessages)); + enemyField.forEach(enemyPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, enemyPokemon, trapped, playerPokemon, trappedAbMessages, true)); } if (batonPass || (!trapTag && !trapped.value)) { this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index b39df53a149..c805df21695 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -67,7 +67,7 @@ export class EncounterPhase extends BattlePhase { battle.enemyParty[e].ivs = new Array(6).fill(31); } this.scene.getParty().slice(0, !battle.double ? 1 : 2).reverse().forEach(playerPokemon => { - applyAbAttrs(SyncEncounterNatureAbAttr, playerPokemon, null, battle.enemyParty[e]); + applyAbAttrs(SyncEncounterNatureAbAttr, playerPokemon, null, false, battle.enemyParty[e]); }); } } diff --git a/src/phases/enemy-command-phase.ts b/src/phases/enemy-command-phase.ts index d7f553681c2..0b62fcbe813 100644 --- a/src/phases/enemy-command-phase.ts +++ b/src/phases/enemy-command-phase.ts @@ -47,7 +47,7 @@ export class EnemyCommandPhase extends FieldPhase { const trapTag = enemyPokemon.findTag(t => t instanceof TrappedTag) as TrappedTag; const trapped = new Utils.BooleanHolder(false); - opponents.forEach(playerPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, playerPokemon, trapped, enemyPokemon, true, [])); + opponents.forEach(playerPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, playerPokemon, trapped, enemyPokemon, [], true)); if (!trapTag && !trapped.value) { const partyMemberScores = trainer.getPartyMemberMatchupScores(enemyPokemon.trainerSlot, true); diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index a5ac913cc5d..12018656458 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -74,7 +74,7 @@ export class MoveEffectPhase extends PokemonPhase { // Assume single target for multi hit applyMoveAttrs(MultiHitAttr, user, this.getTarget() ?? null, move, hitCount); // If Parental Bond is applicable, double the hit count - applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, targets.length, hitCount, new Utils.IntegerHolder(0)); + applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, false, targets.length, hitCount, new Utils.IntegerHolder(0)); // If Multi-Lens is applicable, multiply the hit count by 1 + the number of Multi-Lenses held by the user if (move instanceof AttackMove && !move.hasAttr(FixedDamageAttr)) { this.scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, hitCount, new Utils.IntegerHolder(0)); diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index b9656df856b..2aed0bb9495 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -90,7 +90,7 @@ export class MovePhase extends BattlePhase { : null; if (moveTarget) { const oldTarget = moveTarget.value; - this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, this.move.moveId, moveTarget)); + this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, false, this.move.moveId, moveTarget)); this.pokemon.getOpponents().forEach(p => { const redirectTag = p.getTag(CenterOfAttentionTag) as CenterOfAttentionTag; if (redirectTag && (!redirectTag.powder || (!this.pokemon.isOfType(Type.GRASS) && !this.pokemon.hasAbility(Abilities.OVERCOAT)))) { diff --git a/src/phases/post-turn-status-effect-phase.ts b/src/phases/post-turn-status-effect-phase.ts index 8b533f2e90a..47db32303a5 100644 --- a/src/phases/post-turn-status-effect-phase.ts +++ b/src/phases/post-turn-status-effect-phase.ts @@ -34,7 +34,7 @@ export class PostTurnStatusEffectPhase extends PokemonPhase { break; case StatusEffect.BURN: damage.value = Math.max(pokemon.getMaxHp() >> 4, 1); - applyAbAttrs(ReduceBurnDamageAbAttr, pokemon, null, damage); + applyAbAttrs(ReduceBurnDamageAbAttr, pokemon, null, false, damage); break; } if (damage.value) { diff --git a/src/phases/stat-change-phase.ts b/src/phases/stat-change-phase.ts index 3469cc62942..fec3da9bc9a 100644 --- a/src/phases/stat-change-phase.ts +++ b/src/phases/stat-change-phase.ts @@ -68,7 +68,7 @@ export class StatChangePhase extends PokemonPhase { const levels = new Utils.IntegerHolder(this.levels); if (!this.ignoreAbilities) { - applyAbAttrs(StatChangeMultiplierAbAttr, pokemon, null, levels); + applyAbAttrs(StatChangeMultiplierAbAttr, pokemon, null, false, levels); } const battleStats = this.getPokemon().summonData.battleStats; @@ -90,7 +90,7 @@ export class StatChangePhase extends PokemonPhase { if (levels.value > 0 && this.canBeCopied) { for (const opponent of pokemon.getOpponents()) { - applyAbAttrs(StatChangeCopyAbAttr, opponent, null, this.stats, levels.value); + applyAbAttrs(StatChangeCopyAbAttr, opponent, null, false, this.stats, levels.value); } } diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index 1320cb6235c..e4064fc784a 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -34,8 +34,8 @@ export class TurnStartPhase extends FieldPhase { this.scene.getField(true).filter(p => p.summonData).map(p => { const bypassSpeed = new Utils.BooleanHolder(false); const canCheckHeldItems = new Utils.BooleanHolder(true); - applyAbAttrs(BypassSpeedChanceAbAttr, p, null, bypassSpeed); - applyAbAttrs(PreventBypassSpeedChanceAbAttr, p, null, bypassSpeed, canCheckHeldItems); + applyAbAttrs(BypassSpeedChanceAbAttr, p, null, false, bypassSpeed); + applyAbAttrs(PreventBypassSpeedChanceAbAttr, p, null, false, bypassSpeed, canCheckHeldItems); if (canCheckHeldItems.value) { this.scene.applyModifiers(BypassSpeedChanceModifier, p.isPlayer(), p, bypassSpeed); } @@ -64,8 +64,8 @@ export class TurnStartPhase extends FieldPhase { applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, aMove, aPriority); //TODO: is the bang correct here? applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, bMove, bPriority); //TODO: is the bang correct here? - applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, aMove, aPriority); //TODO: is the bang correct here? - applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, bMove, bPriority); //TODO: is the bang correct here? + applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, false, aMove, aPriority); //TODO: is the bang correct here? + applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, false, bMove, bPriority); //TODO: is the bang correct here? if (aPriority.value !== bPriority.value) { const bracketDifference = Math.ceil(aPriority.value) - Math.ceil(bPriority.value); diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts index 33f00d22555..b786787f83e 100644 --- a/src/plugins/i18n.ts +++ b/src/plugins/i18n.ts @@ -26,6 +26,7 @@ const unicodeRanges = { kana: "U+3040-30FF", CJKCommon: "U+2E80-2EFF,U+3000-303F,U+31C0-31EF,U+3200-32FF,U+3400-4DBF,U+F900-FAFF,U+FE30-FE4F", CJKIdeograph: "U+4E00-9FFF", + specialCharacters: "U+266A,U+2605,U+2665,U+2663" //♪.★,♥,♣ }; const rangesByLanguage = { korean: [unicodeRanges.CJKCommon, unicodeRanges.hangul].join(","), @@ -34,6 +35,15 @@ const rangesByLanguage = { }; const fonts: Array = [ + // unicode (special character from PokePT) + { + face: new FontFace("emerald", "url(./fonts/PokePT_Wansung.woff2)", { unicodeRange: unicodeRanges.specialCharacters }), + }, + { + face: new FontFace("pkmnems", "url(./fonts/PokePT_Wansung.woff2)", { unicodeRange: unicodeRanges.specialCharacters }), + extraOptions: { sizeAdjust: "133%" }, + }, + // unicode (korean) { face: new FontFace("emerald", "url(./fonts/PokePT_Wansung.woff2)", { unicodeRange: rangesByLanguage.korean }), }, diff --git a/src/test/abilities/disguise.test.ts b/src/test/abilities/disguise.test.ts index 969375c397e..a22c4cb55d5 100644 --- a/src/test/abilities/disguise.test.ts +++ b/src/test/abilities/disguise.test.ts @@ -6,6 +6,7 @@ import { Species } from "#enums/species"; import { StatusEffect } from "#app/data/status-effect.js"; import { BattleStat } from "#app/data/battle-stat.js"; import { SPLASH_ONLY } from "../utils/testUtils"; +import { toDmgValue } from "#app/utils"; import { Mode } from "#app/ui/ui.js"; import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; import { MoveEndPhase } from "#app/phases/move-end-phase.js"; @@ -47,7 +48,7 @@ describe("Abilities - Disguise", () => { const mimikyu = game.scene.getEnemyPokemon()!; const maxHp = mimikyu.getMaxHp(); - const disguiseDamage = Math.floor(maxHp / 8); + const disguiseDamage = toDmgValue(maxHp / 8); expect(mimikyu.formIndex).toBe(disguisedForm); @@ -80,7 +81,7 @@ describe("Abilities - Disguise", () => { const mimikyu = game.scene.getEnemyPokemon()!; const maxHp = mimikyu.getMaxHp(); - const disguiseDamage = Math.floor(maxHp / 8); + const disguiseDamage = toDmgValue(maxHp / 8); expect(mimikyu.formIndex).toBe(disguisedForm); @@ -121,7 +122,7 @@ describe("Abilities - Disguise", () => { const mimikyu = game.scene.getPlayerPokemon()!; const maxHp = mimikyu.getMaxHp(); - const disguiseDamage = Math.floor(maxHp / 8); + const disguiseDamage = toDmgValue(maxHp / 8); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); diff --git a/src/test/abilities/heatproof.test.ts b/src/test/abilities/heatproof.test.ts index 64a45c5023f..ee6c0bb6ec9 100644 --- a/src/test/abilities/heatproof.test.ts +++ b/src/test/abilities/heatproof.test.ts @@ -8,6 +8,7 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; import { StatusEffect } from "#app/enums/status-effect.js"; +import { toDmgValue } from "#app/utils"; describe("Abilities - Heatproof", () => { let phaserGame: Phaser.Game; @@ -72,6 +73,6 @@ describe("Abilities - Heatproof", () => { await game.toNextTurn(); // Normal burn damage is /16 - expect(enemy.hp).toBe(enemy.getMaxHp() - Math.floor(enemy.getMaxHp() / 32)); + expect(enemy.hp).toBe(enemy.getMaxHp() - toDmgValue(enemy.getMaxHp() / 32)); }); }); diff --git a/src/test/abilities/parental_bond.test.ts b/src/test/abilities/parental_bond.test.ts index ef0ad7785d2..d14d5871ef7 100644 --- a/src/test/abilities/parental_bond.test.ts +++ b/src/test/abilities/parental_bond.test.ts @@ -16,6 +16,7 @@ import { DamagePhase } from "#app/phases/damage-phase.js"; import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; import { MoveEndPhase } from "#app/phases/move-end-phase.js"; import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; +import { toDmgValue } from "#app/utils"; const TIMEOUT = 20 * 1000; @@ -73,7 +74,7 @@ describe("Abilities - Parental Bond", () => { const secondStrikeDamage = enemyStartingHp - enemyPokemon.hp; expect(leadPokemon.turnData.hitCount).toBe(2); - expect(secondStrikeDamage).toBe(Math.ceil(0.25 * firstStrikeDamage)); + expect(secondStrikeDamage).toBe(toDmgValue(0.25 * firstStrikeDamage)); }, TIMEOUT ); @@ -303,7 +304,7 @@ describe("Abilities - Parental Bond", () => { // This test will time out if the user faints await game.phaseInterceptor.to(BerryPhase, false); - expect(leadPokemon.hp).toBe(Math.floor(leadPokemon.getMaxHp()/2)); + expect(leadPokemon.hp).toBe(toDmgValue(leadPokemon.getMaxHp()/2)); }, TIMEOUT ); diff --git a/src/test/abilities/sand_veil.test.ts b/src/test/abilities/sand_veil.test.ts index 010878db68d..3c5f97bd653 100644 --- a/src/test/abilities/sand_veil.test.ts +++ b/src/test/abilities/sand_veil.test.ts @@ -52,7 +52,7 @@ describe("Abilities - Sand Veil", () => { const sandVeilAttr = allAbilities[Abilities.SAND_VEIL].getAttrs(BattleStatMultiplierAbAttr)[0]; vi.spyOn(sandVeilAttr, "applyBattleStat").mockImplementation( - (pokemon, passive, battleStat, statValue, args) => { + (pokemon, passive, simulated, battleStat, statValue, args) => { if (battleStat === BattleStat.EVA && game.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) { statValue.value *= -1; // will make all attacks miss return true; diff --git a/src/test/abilities/serene_grace.test.ts b/src/test/abilities/serene_grace.test.ts index 5e4841f005a..b126bb5eb7a 100644 --- a/src/test/abilities/serene_grace.test.ts +++ b/src/test/abilities/serene_grace.test.ts @@ -67,7 +67,7 @@ describe("Abilities - Serene Grace", () => { const chance = new Utils.IntegerHolder(move.chance); console.log(move.chance + " Their ability is " + phase.getUserPokemon()!.getAbility().name); - applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, chance, move, phase.getTarget(), false); + applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getTarget(), false); expect(chance.value).toBe(30); }, 20000); @@ -99,7 +99,7 @@ describe("Abilities - Serene Grace", () => { expect(move.id).toBe(Moves.AIR_SLASH); const chance = new Utils.IntegerHolder(move.chance); - applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, chance, move, phase.getTarget(), false); + applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getTarget(), false); expect(chance.value).toBe(60); }, 20000); diff --git a/src/test/abilities/sheer_force.test.ts b/src/test/abilities/sheer_force.test.ts index 33b34124cc4..564e2040af4 100644 --- a/src/test/abilities/sheer_force.test.ts +++ b/src/test/abilities/sheer_force.test.ts @@ -69,8 +69,8 @@ describe("Abilities - Sheer Force", () => { const power = new Utils.IntegerHolder(move.power); const chance = new Utils.IntegerHolder(move.chance); - applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, chance, move, phase.getTarget(), false); - applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon()!, phase.getTarget()!, move, power); + applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getTarget(), false); + applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon()!, phase.getTarget()!, move, false, power); expect(chance.value).toBe(0); expect(power.value).toBe(move.power * 5461/4096); @@ -108,8 +108,8 @@ describe("Abilities - Sheer Force", () => { const power = new Utils.IntegerHolder(move.power); const chance = new Utils.IntegerHolder(move.chance); - applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, chance, move, phase.getTarget(), false); - applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon()!, phase.getTarget()!, move, power); + applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getTarget(), false); + applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon()!, phase.getTarget()!, move, false, power); expect(chance.value).toBe(-1); expect(power.value).toBe(move.power); @@ -147,8 +147,8 @@ describe("Abilities - Sheer Force", () => { const power = new Utils.IntegerHolder(move.power); const chance = new Utils.IntegerHolder(move.chance); - applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, chance, move, phase.getTarget(), false); - applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon()!, phase.getTarget()!, move, power); + applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getTarget(), false); + applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon()!, phase.getTarget()!, move, false, power); expect(chance.value).toBe(-1); expect(power.value).toBe(move.power); @@ -191,8 +191,8 @@ describe("Abilities - Sheer Force", () => { const target = phase.getTarget()!; const opponentType = target.getTypes()[0]; - applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, chance, move, target, false); - applyPreAttackAbAttrs(MovePowerBoostAbAttr, user, target, move, power); + applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, false, chance, move, target, false); + applyPreAttackAbAttrs(MovePowerBoostAbAttr, user, target, move, false, power); applyPostDefendAbAttrs(PostDefendTypeChangeAbAttr, target, user, move, target.apply(user, move)); expect(chance.value).toBe(0); diff --git a/src/test/abilities/shield_dust.test.ts b/src/test/abilities/shield_dust.test.ts index b40689a180a..fe6c941752c 100644 --- a/src/test/abilities/shield_dust.test.ts +++ b/src/test/abilities/shield_dust.test.ts @@ -67,8 +67,8 @@ describe("Abilities - Shield Dust", () => { expect(move.id).toBe(Moves.AIR_SLASH); const chance = new Utils.IntegerHolder(move.chance); - applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, chance, move, phase.getTarget(), false); - applyPreDefendAbAttrs(IgnoreMoveEffectsAbAttr, phase.getTarget()!, phase.getUserPokemon()!, null!, null!, chance); + applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getTarget(), false); + applyPreDefendAbAttrs(IgnoreMoveEffectsAbAttr, phase.getTarget()!, phase.getUserPokemon()!, null, null, false, chance); expect(chance.value).toBe(0); }, 20000); diff --git a/src/test/arena/arena_gravity.test.ts b/src/test/arena/arena_gravity.test.ts index 68c31258454..8fad4dde83d 100644 --- a/src/test/arena/arena_gravity.test.ts +++ b/src/test/arena/arena_gravity.test.ts @@ -1,14 +1,15 @@ -import { allMoves } from "#app/data/move.js"; -import { Abilities } from "#app/enums/abilities.js"; -import { ArenaTagType } from "#app/enums/arena-tag-type.js"; -import GameManager from "#test/utils/gameManager"; -import { getMovePosition } from "#test/utils/gameManagerUtils"; +import { allMoves } from "#app/data/move"; +import { Abilities } from "#app/enums/abilities"; +import { ArenaTagType } from "#app/enums/arena-tag-type"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase"; +import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import { getMovePosition } from "#test/utils/gameManagerUtils"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; -import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; +import { SPLASH_ONLY } from "../utils/testUtils"; describe("Arena - Gravity", () => { let phaserGame: Phaser.Game; @@ -26,14 +27,17 @@ describe("Arena - Gravity", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleType("single"); - game.override.moveset([Moves.TACKLE, Moves.GRAVITY, Moves.FISSURE]); - game.override.ability(Abilities.UNNERVE); - game.override.enemyAbility(Abilities.BALL_FETCH); - game.override.enemySpecies(Species.SHUCKLE); - game.override.enemyMoveset(new Array(4).fill(Moves.SPLASH)); + game.override + .battleType("single") + .moveset([Moves.TACKLE, Moves.GRAVITY, Moves.FISSURE]) + .ability(Abilities.UNNERVE) + .enemyAbility(Abilities.BALL_FETCH) + .enemySpecies(Species.SHUCKLE) + .enemyMoveset(SPLASH_ONLY); }); + // Reference: https://bulbapedia.bulbagarden.net/wiki/Gravity_(move) + it("non-OHKO move accuracy is multiplied by 1.67", async () => { const moveToCheck = allMoves[Moves.TACKLE]; @@ -77,4 +81,65 @@ describe("Arena - Gravity", () => { expect(moveToCheck.calculateBattleAccuracy).toHaveReturnedWith(30); }); + + describe("Against flying types", () => { + it("can be hit by ground-type moves now", async () => { + game.override + .startingLevel(5) + .enemyLevel(5) + .enemySpecies(Species.PIDGEOT) + .moveset([Moves.GRAVITY, Moves.EARTHQUAKE]); + + await game.startBattle([Species.PIKACHU]); + + const pidgeot = game.scene.getEnemyPokemon()!; + vi.spyOn(pidgeot, "getAttackTypeEffectiveness"); + + // Try earthquake on 1st turn (fails!); + game.doAttack(getMovePosition(game.scene, 0, Moves.EARTHQUAKE)); + await game.phaseInterceptor.to(TurnEndPhase); + + expect(pidgeot.getAttackTypeEffectiveness).toHaveReturnedWith(0); + + // Setup Gravity on 2nd turn + await game.toNextTurn(); + game.doAttack(getMovePosition(game.scene, 0, Moves.GRAVITY)); + await game.phaseInterceptor.to(TurnEndPhase); + + expect(game.scene.arena.getTag(ArenaTagType.GRAVITY)).toBeDefined(); + + // Use ground move on 3rd turn + await game.toNextTurn(); + game.doAttack(getMovePosition(game.scene, 0, Moves.EARTHQUAKE)); + await game.phaseInterceptor.to(TurnEndPhase); + + expect(pidgeot.getAttackTypeEffectiveness).toHaveReturnedWith(1); + }); + + it("keeps super-effective moves super-effective after using gravity", async () => { + game.override + .startingLevel(5) + .enemyLevel(5) + .enemySpecies(Species.PIDGEOT) + .moveset([Moves.GRAVITY, Moves.THUNDERBOLT]); + + await game.startBattle([Species.PIKACHU]); + + const pidgeot = game.scene.getEnemyPokemon()!; + vi.spyOn(pidgeot, "getAttackTypeEffectiveness"); + + // Setup Gravity on 1st turn + game.doAttack(getMovePosition(game.scene, 0, Moves.GRAVITY)); + await game.phaseInterceptor.to(TurnEndPhase); + + expect(game.scene.arena.getTag(ArenaTagType.GRAVITY)).toBeDefined(); + + // Use electric move on 2nd turn + await game.toNextTurn(); + game.doAttack(getMovePosition(game.scene, 0, Moves.THUNDERBOLT)); + await game.phaseInterceptor.to(TurnEndPhase); + + expect(pidgeot.getAttackTypeEffectiveness).toHaveReturnedWith(2); + }); + }); }); diff --git a/src/test/battle/damage_calculation.test.ts b/src/test/battle/damage_calculation.test.ts new file mode 100644 index 00000000000..9b13a266d33 --- /dev/null +++ b/src/test/battle/damage_calculation.test.ts @@ -0,0 +1,71 @@ +import { DamagePhase } from "#app/phases/damage-phase.js"; +import GameManager from "#test/utils/gameManager"; +import { getMovePosition } from "#test/utils/gameManagerUtils"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { ArenaTagType } from "#enums/arena-tag-type"; +import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { toDmgValue } from "#app/utils"; + +describe("Round Down and Minimun 1 test in Damage Calculation", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override.battleType("single"); + game.override.startingLevel(10); + }); + + it("When the user fails to use Jump Kick with Wonder Guard ability, the damage should be 1.", async () => { + game.override.enemySpecies(Species.GASTLY); + game.override.enemyMoveset(SPLASH_ONLY); + game.override.starterSpecies(Species.SHEDINJA); + game.override.moveset([Moves.JUMP_KICK]); + game.override.ability(Abilities.WONDER_GUARD); + + await game.startBattle(); + + const shedinja = game.scene.getPlayerPokemon()!; + + game.doAttack(getMovePosition(game.scene, 0, Moves.JUMP_KICK)); + + await game.phaseInterceptor.to(DamagePhase); + + expect(shedinja.hp).toBe(shedinja.getMaxHp() - 1); + }); + + + it("Charizard with odd HP survives Stealth Rock damage twice", async () => { + game.scene.arena.addTag(ArenaTagType.STEALTH_ROCK, 1, Moves.STEALTH_ROCK, 0); + game.override.seed("Charizard Stealth Rock test"); + game.override.enemySpecies(Species.CHARIZARD); + game.override.enemyAbility(Abilities.BLAZE); + game.override.starterSpecies(Species.PIKACHU); + game.override.enemyLevel(100); + + await game.startBattle(); + + const charizard = game.scene.getEnemyPokemon()!; + + const maxHp = charizard.getMaxHp(); + const damage_prediction = toDmgValue(charizard.getMaxHp() / 2); + const currentHp = charizard.hp; + const expectedHP = maxHp - damage_prediction; + + expect(currentHp).toBe(expectedHP); + }); +}); diff --git a/src/test/moves/belly_drum.test.ts b/src/test/moves/belly_drum.test.ts index e579a4587ad..229314c96e6 100644 --- a/src/test/moves/belly_drum.test.ts +++ b/src/test/moves/belly_drum.test.ts @@ -6,6 +6,7 @@ import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { BattleStat } from "#app/data/battle-stat"; +import { toDmgValue } from "#app/utils"; const TIMEOUT = 20 * 1000; // RATIO : HP Cost of Move @@ -44,7 +45,7 @@ describe("Moves - BELLY DRUM", () => { await game.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; - const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO); + const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO); game.doAttack(getMovePosition(game.scene, 0, Moves.BELLY_DRUM)); await game.phaseInterceptor.to(TurnEndPhase); @@ -59,7 +60,7 @@ describe("Moves - BELLY DRUM", () => { await game.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; - const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO); + const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO); // Here - BattleStat.ATK -> -3 and BattleStat.SPATK -> 6 leadPokemon.summonData.battleStats[BattleStat.ATK] = -3; @@ -95,7 +96,7 @@ describe("Moves - BELLY DRUM", () => { await game.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; - const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO); + const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO); leadPokemon.hp = hpLost - PREDAMAGE; game.doAttack(getMovePosition(game.scene, 0, Moves.BELLY_DRUM)); diff --git a/src/test/moves/clangorous_soul.test.ts b/src/test/moves/clangorous_soul.test.ts index 5b2e8b6e06d..afab4c2e9be 100644 --- a/src/test/moves/clangorous_soul.test.ts +++ b/src/test/moves/clangorous_soul.test.ts @@ -7,6 +7,7 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { BattleStat } from "#app/data/battle-stat"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { toDmgValue } from "#app/utils"; const TIMEOUT = 20 * 1000; /** HP Cost of Move */ @@ -45,7 +46,7 @@ describe("Moves - CLANGOROUS_SOUL", () => { await game.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; - const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO); + const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO); game.doAttack(getMovePosition(game.scene, 0, Moves.CLANGOROUS_SOUL)); await game.phaseInterceptor.to(TurnEndPhase); @@ -64,7 +65,7 @@ describe("Moves - CLANGOROUS_SOUL", () => { await game.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; - const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO); + const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO); //Here - BattleStat.SPD -> 0 and BattleStat.SPDEF -> 4 leadPokemon.summonData.battleStats[BattleStat.ATK] = 6; @@ -113,7 +114,7 @@ describe("Moves - CLANGOROUS_SOUL", () => { await game.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; - const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO); + const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO); leadPokemon.hp = hpLost - PREDAMAGE; game.doAttack(getMovePosition(game.scene, 0, Moves.CLANGOROUS_SOUL)); diff --git a/src/test/moves/fillet_away.test.ts b/src/test/moves/fillet_away.test.ts index fcad704ef29..fc87d600eb5 100644 --- a/src/test/moves/fillet_away.test.ts +++ b/src/test/moves/fillet_away.test.ts @@ -7,6 +7,7 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { BattleStat } from "#app/data/battle-stat"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { toDmgValue } from "#app/utils"; const TIMEOUT = 20 * 1000; /** HP Cost of Move */ @@ -45,7 +46,7 @@ describe("Moves - FILLET AWAY", () => { await game.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; - const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO); + const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO); game.doAttack(getMovePosition(game.scene, 0, Moves.FILLET_AWAY)); await game.phaseInterceptor.to(TurnEndPhase); @@ -62,7 +63,7 @@ describe("Moves - FILLET AWAY", () => { await game.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; - const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO); + const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO); //Here - BattleStat.SPD -> 0 and BattleStat.SPATK -> 3 leadPokemon.summonData.battleStats[BattleStat.ATK] = 6; @@ -103,7 +104,7 @@ describe("Moves - FILLET AWAY", () => { await game.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; - const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO); + const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO); leadPokemon.hp = hpLost - PREDAMAGE; game.doAttack(getMovePosition(game.scene, 0, Moves.FILLET_AWAY)); diff --git a/src/test/moves/jaw_lock.test.ts b/src/test/moves/jaw_lock.test.ts new file mode 100644 index 00000000000..4fe996588e4 --- /dev/null +++ b/src/test/moves/jaw_lock.test.ts @@ -0,0 +1,172 @@ +import { Abilities } from "#app/enums/abilities"; +import { BattlerTagType } from "#app/enums/battler-tag-type"; +import GameManager from "#app/test/utils/gameManager"; +import { getMovePosition } from "#app/test/utils/gameManagerUtils"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { SPLASH_ONLY } from "#app/test/utils/testUtils"; +import { BattlerIndex } from "#app/battle"; +import { FaintPhase } from "#app/phases/faint-phase"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase"; +import { TurnEndPhase } from "#app/phases/turn-end-phase"; +import { BerryPhase } from "#app/phases/berry-phase"; + +const TIMEOUT = 20 * 1000; + +describe("Moves - Jaw Lock", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + + game.override + .battleType("single") + .enemySpecies(Species.SNORLAX) + .enemyAbility(Abilities.INSOMNIA) + .enemyMoveset(SPLASH_ONLY) + .moveset([Moves.JAW_LOCK, Moves.SPLASH]) + .startingLevel(100) + .enemyLevel(100) + .disableCrits(); + }); + + it( + "should trap the move's user and target", + async () => { + await game.startBattle([ Species.BULBASAUR ]); + + const leadPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.doAttack(getMovePosition(game.scene, 0, Moves.JAW_LOCK)); + + await game.phaseInterceptor.to(MoveEffectPhase, false); + + expect(leadPokemon.getTag(BattlerTagType.TRAPPED)).toBeUndefined(); + expect(enemyPokemon.getTag(BattlerTagType.TRAPPED)).toBeUndefined(); + + await game.phaseInterceptor.to(TurnEndPhase); + + expect(leadPokemon.getTag(BattlerTagType.TRAPPED)).toBeDefined(); + expect(enemyPokemon.getTag(BattlerTagType.TRAPPED)).toBeDefined(); + }, TIMEOUT + ); + + it( + "should not trap either pokemon if the target faints", + async () => { + game.override.enemyLevel(1); + await game.startBattle([ Species.BULBASAUR ]); + + const leadPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.doAttack(getMovePosition(game.scene, 0, Moves.JAW_LOCK)); + + await game.phaseInterceptor.to(MoveEffectPhase, false); + + expect(leadPokemon.getTag(BattlerTagType.TRAPPED)).toBeUndefined(); + expect(enemyPokemon.getTag(BattlerTagType.TRAPPED)).toBeUndefined(); + + await game.phaseInterceptor.to(MoveEffectPhase); + + expect(leadPokemon.getTag(BattlerTagType.TRAPPED)).toBeUndefined(); + expect(enemyPokemon.getTag(BattlerTagType.TRAPPED)).toBeUndefined(); + + await game.phaseInterceptor.to(FaintPhase); + + expect(leadPokemon.getTag(BattlerTagType.TRAPPED)).toBeUndefined(); + expect(enemyPokemon.getTag(BattlerTagType.TRAPPED)).toBeUndefined(); + }, TIMEOUT + ); + + it( + "should only trap the user until the target faints", + async () => { + await game.startBattle([ Species.BULBASAUR ]); + + const leadPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.doAttack(getMovePosition(game.scene, 0, Moves.JAW_LOCK)); + + await game.phaseInterceptor.to(MoveEffectPhase); + + expect(leadPokemon.getTag(BattlerTagType.TRAPPED)).toBeDefined(); + expect(enemyPokemon.getTag(BattlerTagType.TRAPPED)).toBeDefined(); + + await game.phaseInterceptor.to(TurnEndPhase); + + await game.doKillOpponents(); + + expect(leadPokemon.getTag(BattlerTagType.TRAPPED)).toBeUndefined(); + }, TIMEOUT + ); + + it( + "should not trap other targets after the first target is trapped", + async () => { + game.override.battleType("double"); + + await game.startBattle([ Species.CHARMANDER, Species.BULBASAUR ]); + + const playerPokemon = game.scene.getPlayerField(); + const enemyPokemon = game.scene.getEnemyField(); + + game.doAttack(getMovePosition(game.scene, 0, Moves.JAW_LOCK)); + game.doSelectTarget(BattlerIndex.ENEMY); + + game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); + + await game.phaseInterceptor.to(MoveEffectPhase); + + expect(playerPokemon[0].getTag(BattlerTagType.TRAPPED)).toBeDefined(); + expect(enemyPokemon[0].getTag(BattlerTagType.TRAPPED)).toBeDefined(); + + await game.toNextTurn(); + + game.doAttack(getMovePosition(game.scene, 0, Moves.JAW_LOCK)); + game.doSelectTarget(BattlerIndex.ENEMY_2); + + game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); + + await game.phaseInterceptor.to(MoveEffectPhase); + + expect(enemyPokemon[1].getTag(BattlerTagType.TRAPPED)).toBeUndefined(); + expect(playerPokemon[0].getTag(BattlerTagType.TRAPPED)).toBeDefined(); + expect(playerPokemon[0].getTag(BattlerTagType.TRAPPED)?.sourceId).toBe(enemyPokemon[0].id); + }, TIMEOUT + ); + + it( + "should not trap either pokemon if the target is protected", + async () => { + game.override.enemyMoveset(Array(4).fill(Moves.PROTECT)); + + await game.startBattle([ Species.BULBASAUR ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.doAttack(getMovePosition(game.scene, 0, Moves.JAW_LOCK)); + + await game.phaseInterceptor.to(BerryPhase, false); + + expect(playerPokemon.getTag(BattlerTagType.TRAPPED)).toBeUndefined(); + expect(enemyPokemon.getTag(BattlerTagType.TRAPPED)).toBeUndefined(); + }, TIMEOUT + ); +}); diff --git a/src/test/moves/rollout.test.ts b/src/test/moves/rollout.test.ts index 1fc208c6724..cad65768a1c 100644 --- a/src/test/moves/rollout.test.ts +++ b/src/test/moves/rollout.test.ts @@ -1,13 +1,13 @@ import { allMoves } from "#app/data/move.js"; import { CommandPhase } from "#app/phases/command-phase.js"; -import GameManager from "#test/utils/gameManager"; -import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import { getMovePosition } from "#test/utils/gameManagerUtils"; +import { SPLASH_ONLY } from "#test/utils/testUtils"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { SPLASH_ONLY } from "#test/utils/testUtils"; describe("Moves - Rollout", () => { let phaserGame: Phaser.Game; @@ -29,9 +29,9 @@ describe("Moves - Rollout", () => { game.override.disableCrits(); game.override.battleType("single"); game.override.starterSpecies(Species.RATTATA); - game.override.ability(Abilities.NONE); + game.override.ability(Abilities.BALL_FETCH); game.override.enemySpecies(Species.BIDOOF); - game.override.enemyAbility(Abilities.NONE); + game.override.enemyAbility(Abilities.BALL_FETCH); game.override.startingLevel(100); game.override.enemyLevel(100); game.override.enemyMoveset(SPLASH_ONLY); diff --git a/src/test/moves/tackle.test.ts b/src/test/moves/tackle.test.ts index f442645baa9..3da8bc6f978 100644 --- a/src/test/moves/tackle.test.ts +++ b/src/test/moves/tackle.test.ts @@ -78,6 +78,6 @@ describe("Moves - Tackle", () => { await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase); const hpLost = hpOpponent - game.scene.currentBattle.enemyParty[0].hp; expect(hpLost).toBeGreaterThan(0); - expect(hpLost).toBe(4); + expect(hpLost).toBeLessThan(4); }, 20000); }); diff --git a/src/test/moves/tera_blast.test.ts b/src/test/moves/tera_blast.test.ts new file mode 100644 index 00000000000..0bd2ad24e23 --- /dev/null +++ b/src/test/moves/tera_blast.test.ts @@ -0,0 +1,106 @@ +import { allMoves } from "#app/data/move"; +import GameManager from "#test/utils/gameManager"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { Abilities } from "#app/enums/abilities"; +import { SPLASH_ONLY } from "../utils/testUtils"; +import { Type } from "#app/data/type"; +import { getMovePosition } from "../utils/gameManagerUtils"; +import { BattleStat } from "#app/data/battle-stat"; +import { Stat } from "#app/enums/stat"; +import { BattlerIndex } from "#app/battle"; +import { HitResult } from "#app/field/pokemon"; + +describe("Moves - Tera Blast", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + const moveToCheck = allMoves[Moves.TERA_BLAST]; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + + game.override + .battleType("single") + .disableCrits() + .starterSpecies(Species.FEEBAS) + .moveset([Moves.TERA_BLAST]) + .ability(Abilities.BALL_FETCH) + .startingHeldItems([{name: "TERA_SHARD", type: Type.FIRE}]) + .enemySpecies(Species.MAGIKARP) + .enemyMoveset(SPLASH_ONLY) + .enemyAbility(Abilities.BALL_FETCH) + .enemyLevel(20); + + vi.spyOn(moveToCheck, "calculateBattlePower"); + }); + + it("changes type to match user's tera type", async() => { + game.override + .enemySpecies(Species.FURRET) + .startingHeldItems([{name: "TERA_SHARD", type: Type.FIGHTING}]); + await game.startBattle(); + const enemyPokemon = game.scene.getEnemyPokemon()!; + vi.spyOn(enemyPokemon, "apply"); + + game.doAttack(getMovePosition(game.scene, 0, Moves.TERA_BLAST)); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.phaseInterceptor.to("MoveEffectPhase"); + + expect(enemyPokemon.apply).toHaveReturnedWith(HitResult.SUPER_EFFECTIVE); + }, 20000); + + it("increases power if user is Stellar tera type", async() => { + game.override.startingHeldItems([{name: "TERA_SHARD", type: Type.STELLAR}]); + const stellarTypeMultiplier = 2; + const stellarTypeDmgBonus = 20; + const basePower = moveToCheck.power; + + await game.startBattle(); + + game.doAttack(getMovePosition(game.scene, 0, Moves.TERA_BLAST)); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.phaseInterceptor.to("MoveEffectPhase"); + + expect(moveToCheck.calculateBattlePower).toHaveReturnedWith((basePower + stellarTypeDmgBonus) * stellarTypeMultiplier); + }, 20000); + + // Currently abilities are bugged and can't see when a move's category is changed + it.skip("uses the higher stat of the user's Atk and SpAtk for damage calculation", async() => { + game.override.enemyAbility(Abilities.TOXIC_DEBRIS); + await game.startBattle(); + + const playerPokemon = game.scene.getPlayerPokemon()!; + playerPokemon.stats[Stat.ATK] = 100; + playerPokemon.stats[Stat.SPATK] = 1; + + game.doAttack(getMovePosition(game.scene, 0, Moves.TERA_BLAST)); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(game.scene.getEnemyPokemon()!.battleData.abilityRevealed).toBe(true); + }, 20000); + + it("causes stat drops if user is Stellar tera type", async() => { + game.override.startingHeldItems([{name: "TERA_SHARD", type: Type.STELLAR}]); + await game.startBattle(); + + const playerPokemon = game.scene.getPlayerPokemon()!; + + game.doAttack(getMovePosition(game.scene, 0, Moves.TERA_BLAST)); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.phaseInterceptor.to("MoveEndPhase"); + + expect(playerPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(-1); + expect(playerPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-1); + }, 20000); +}); diff --git a/src/ui/target-select-ui-handler.ts b/src/ui/target-select-ui-handler.ts index 42c7fef5660..6ca580dc2b2 100644 --- a/src/ui/target-select-ui-handler.ts +++ b/src/ui/target-select-ui-handler.ts @@ -118,7 +118,7 @@ export default class TargetSelectUiHandler extends UiHandler { this.targetFlashTween = this.scene.tweens.add({ targets: this.targetsHighlighted, - key: { start: 0.55, to: 1 }, + key: { start: 1, to: 0.25 }, loop: -1, loopDelay: 150, duration: Utils.fixedInt(450), diff --git a/src/utils.ts b/src/utils.ts index c51ac2b5b0b..a9bbc93d684 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -560,3 +560,17 @@ export function capitalizeString(str: string, sep: string, lowerFirstChar: boole export function isNullOrUndefined(object: any): boolean { return null === object || undefined === object; } + +/** + * This function is used in the context of a Pokémon battle game to calculate the actual integer damage value from a float result. + * Many damage calculation formulas involve various parameters and result in float values. + * The actual damage applied to a Pokémon's HP must be an integer. + * This function helps in ensuring that by flooring the float value and enforcing a minimum damage value. + * + * @param value - The float value to convert. + * @param minValue - The minimum integer value to return. Defaults to 1. + * @returns The converted value as an integer. + */ +export function toDmgValue(value: number, minValue: number = 1) { + return Math.max(Math.floor(value), minValue); +}