diff --git a/src/data/move.ts b/src/data/move.ts index f51b719d82d..53a186d9a75 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1054,12 +1054,12 @@ export class FixedDamageAttr extends MoveAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - (args[0] as Utils.IntegerHolder).value = this.getDamage(user, target, move); + (args[0] as Utils.IntegerHolder).value = this.getDamage(user, target, move, args[1], args[2]); return true; } - getDamage(user: Pokemon, target: Pokemon, move: Move): integer { + getDamage(user: Pokemon, target: Pokemon, move: Move, extra1?: any, extra2?: any): integer { return this.damage; } } @@ -1153,8 +1153,14 @@ export class RandomLevelDamageAttr extends FixedDamageAttr { super(0); } - getDamage(user: Pokemon, target: Pokemon, move: Move): number { - return Math.max(Math.floor(user.level * (user.randSeedIntRange(50, 150) * 0.01)), 1); + getDamage(user: Pokemon, target: Pokemon, move: Move, isLow?: boolean, isHigh?: boolean): number { + if (isLow) { + return Math.max(Math.floor(user.level * (50 * 0.01)), 1); + } + if (isHigh) { + return Math.max(Math.floor(user.level * (150 * 0.01)), 1); + } + return Math.max(Math.floor(user.level * (user.randSeedIntRange(50, 150, "Random damage") * 0.01)), 1); } } @@ -1762,7 +1768,7 @@ export class MultiHitAttr extends MoveAttr { switch (this.multiHitType) { case MultiHitType._2_TO_5: { - const rand = user.randSeedInt(16); + const rand = user.randSeedInt(16, undefined, "Random number of hits for a 2-to-5-hits move"); const hitValue = new Utils.IntegerHolder(rand); applyAbAttrs(MaxMultiHitAbAttr, user, null, hitValue); if (hitValue.value >= 10) { @@ -1826,7 +1832,7 @@ export class StatusEffectAttr extends MoveEffectAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true); - const statusCheck = moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance; + const statusCheck = moveChance < 0 || moveChance === 100 || user.randSeedInt(100, undefined, "Chance to apply " + Utils.getEnumKeys(StatusEffect)[this.effect]) < moveChance; if (statusCheck) { const pokemon = this.selfTarget ? user : target; if (pokemon.status) { @@ -1915,6 +1921,7 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { return new Promise(resolve => { const rand = Phaser.Math.RND.realInRange(0, 1); + console.log("Phaser.Math.RND.realInRange(0, 1)", rand) if (rand >= this.chance) { return resolve(false); } @@ -1923,10 +1930,13 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr { const poolType = target.isPlayer() ? ModifierPoolType.PLAYER : target.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD; const highestItemTier = heldItems.map(m => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier, highestTier), 0); const tierHeldItems = heldItems.filter(m => m.type.getOrInferTier(poolType) === highestItemTier); - const stolenItem = tierHeldItems[user.randSeedInt(tierHeldItems.length)]; + const stolenItem = tierHeldItems[user.randSeedInt(tierHeldItems.length, undefined, "Selecting an item to steal")]; user.scene.tryTransferHeldItemModifier(stolenItem, user, false).then(success => { if (success) { user.scene.queueMessage(getPokemonMessage(user, ` stole\n${target.name}'s ${stolenItem.type.name}!`)); + console.log(`Stole ${stolenItem.type.name}`) + } else { + console.log(`Attempted to steal ${stolenItem.type.name} but the thief can't hold any more`) } resolve(success); }); @@ -1998,7 +2008,7 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { } if (heldItems.length) { - const removedItem = heldItems[user.randSeedInt(heldItems.length)]; + const removedItem = heldItems[user.randSeedInt(heldItems.length, undefined, "Selecting item to remove")]; // Decrease item amount and update icon !--removedItem.stackCount; @@ -2056,7 +2066,7 @@ export class EatBerryAttr extends MoveEffectAttr { if (heldBerries.length <= 0) { return false; } - this.chosenBerry = heldBerries[user.randSeedInt(heldBerries.length)]; + this.chosenBerry = heldBerries[user.randSeedInt(heldBerries.length, undefined, "Selecting a berry to eat")]; const preserve = new Utils.BooleanHolder(false); target.scene.applyModifiers(PreserveBerryModifier, target.isPlayer(), target, preserve); // check for berry pouch preservation if (!preserve.value) { @@ -2114,7 +2124,7 @@ export class StealEatBerryAttr extends EatBerryAttr { return false; } // if the target has berries, pick a random berry and steal it - this.chosenBerry = heldBerries[user.randSeedInt(heldBerries.length)]; + this.chosenBerry = heldBerries[user.randSeedInt(heldBerries.length, undefined, "Selecting a berry to steal")]; const message = i18next.t("battle:stealEatBerry", {pokemonName: user.name, targetName: target.name, berryName: this.chosenBerry.type.name}); user.scene.queueMessage(message); this.reduceBerryModifier(target); @@ -2471,7 +2481,7 @@ export class StatChangeAttr extends MoveEffectAttr { } const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true); - if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) { + if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100, undefined, "Chance to apply status condition") < moveChance) { const levels = this.getLevels(user); user.scene.unshiftPhase(new StatChangePhase(user.scene, (this.selfTarget ? user : target).getBattlerIndex(), this.selfTarget, this.stats, levels, this.showMessage)); return true; @@ -3612,7 +3622,10 @@ export class ShellSideArmCategoryAttr extends VariableMoveCategoryAttr { if (atkRatio > specialRatio) { category.value = MoveCategory.PHYSICAL; return true; - } else if (atkRatio === specialRatio && user.randSeedInt(2) === 0) { + } else if (atkRatio === specialRatio && args[1] == "SIM") { + category.value = MoveCategory.PHYSICAL; + return true; + } else if (atkRatio === specialRatio && user.randSeedInt(2, undefined, "Randomly selecting an attack type for Shell Side Arm") === 0) { category.value = MoveCategory.PHYSICAL; return true; } @@ -4080,7 +4093,7 @@ export class FrenzyAttr extends MoveEffectAttr { } if (!user.getTag(BattlerTagType.FRENZY) && !user.getMoveQueue().length) { - const turnCount = user.randSeedIntRange(1, 2); + const turnCount = user.randSeedIntRange(1, 2, "Frenzy targeting"); new Array(turnCount).fill(null).map(() => user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true })); user.addTag(BattlerTagType.FRENZY, turnCount, move.id, user.id); } else { @@ -4122,8 +4135,8 @@ export class AddBattlerTagAttr extends MoveEffectAttr { } const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true); - if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) { - return (this.selfTarget ? user : target).addTag(this.tagType, user.randSeedInt(this.turnCountMax - this.turnCountMin, this.turnCountMin), move.id, user.id); + if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100, undefined, "Chance to apply battler tag") < moveChance) { + return (this.selfTarget ? user : target).addTag(this.tagType, user.randSeedInt(this.turnCountMax - this.turnCountMin, this.turnCountMin, "Duration of effect"), move.id, user.id); } return false; @@ -4302,7 +4315,7 @@ export class ProtectAttr extends AddBattlerTagAttr { timesUsed++; } if (timesUsed) { - return !user.randSeedInt(Math.pow(3, timesUsed)); + return !user.randSeedInt(Math.pow(3, timesUsed), undefined, "Chance for Protect-like move to fail"); } return true; }); @@ -4417,7 +4430,7 @@ export class AddArenaTagAttr extends MoveEffectAttr { return false; } - if (move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance) { + if (move.chance < 0 || move.chance === 100 || user.randSeedInt(100, undefined, "Chance to add arena tag") < move.chance) { user.scene.arena.addTag(this.tagType, this.turnCount, move.id, user.id, (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY); return true; } @@ -4492,7 +4505,7 @@ export class AddArenaTrapTagHitAttr extends AddArenaTagAttr { const moveChance = this.getMoveChance(user,target,move,this.selfTarget, true); const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const tag = user.scene.arena.getTagOnSide(this.tagType, side) as ArenaTrapTag; - if ((moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance)) { + if ((moveChance < 0 || moveChance === 100 || user.randSeedInt(100, undefined, "Chance to apply trap") < moveChance)) { user.scene.arena.addTag(this.tagType, 0, move.id, user.id, side); if (!tag) { return true; @@ -4646,7 +4659,7 @@ export class RevivalBlessingAttr extends MoveEffectAttr { && user.scene.getEnemyParty().findIndex(p => p.isFainted() && !p.isBoss()) > -1) { // Selects a random fainted pokemon const faintedPokemon = user.scene.getEnemyParty().filter(p => p.isFainted() && !p.isBoss()); - const pokemon = faintedPokemon[user.randSeedInt(faintedPokemon.length)]; + const pokemon = faintedPokemon[user.randSeedInt(faintedPokemon.length, undefined, "Randomly selecting a Pokemon to revive")]; 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())); @@ -4953,7 +4966,7 @@ export class RandomMovesetMoveAttr extends OverrideMoveEffectAttr { const moveset = (!this.enemyMoveset ? user : target).getMoveset(); const moves = moveset.filter(m => !m.getMove().hasFlag(MoveFlags.IGNORE_VIRTUAL)); if (moves.length) { - const move = moves[user.randSeedInt(moves.length)]; + const move = moves[user.randSeedInt(moves.length, undefined, "Randomly selecting a known move")]; const moveIndex = moveset.findIndex(m => m.moveId === move.moveId); const moveTargets = getMoveTargets(user, move.moveId); if (!moveTargets.targets.length) { @@ -4971,7 +4984,7 @@ export class RandomMovesetMoveAttr extends OverrideMoveEffectAttr { } default: { moveTargets.targets.splice(moveTargets.targets.indexOf(user.getAlly().getBattlerIndex())); - selectTargets = [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; + selectTargets = [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length, undefined, "Randomly selecting a target")] ]; break; } } @@ -4989,7 +5002,7 @@ export class RandomMoveAttr extends OverrideMoveEffectAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { return new Promise(resolve => { const moveIds = Utils.getEnumValues(Moves).filter(m => !allMoves[m].hasFlag(MoveFlags.IGNORE_VIRTUAL) && !allMoves[m].name.endsWith(" (N)")); - const moveId = moveIds[user.randSeedInt(moveIds.length)]; + const moveId = moveIds[user.randSeedInt(moveIds.length, undefined, "Randomly selecting any valid move")]; const moveTargets = getMoveTargets(user, moveId); if (!moveTargets.targets.length) { @@ -5000,7 +5013,7 @@ export class RandomMoveAttr extends OverrideMoveEffectAttr { ? moveTargets.targets : moveTargets.targets.indexOf(target.getBattlerIndex()) > -1 ? [ target.getBattlerIndex() ] - : [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; + : [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length, undefined, "Randomly selecting a target")] ]; user.getMoveQueue().push({ move: moveId, targets: targets, ignorePP: true }); user.scene.unshiftPhase(new MovePhase(user.scene, user, targets, new PokemonMove(moveId, 0, 0, true), true)); initMoveAnim(user.scene, moveId).then(() => { @@ -5183,7 +5196,7 @@ export class CopyMoveAttr extends OverrideMoveEffectAttr { ? moveTargets.targets : moveTargets.targets.indexOf(target.getBattlerIndex()) > -1 ? [ target.getBattlerIndex() ] - : [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; + : [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length, undefined, "Randomly selecting a target for the copied move")] ]; user.getMoveQueue().push({ move: lastMove, targets: targets, ignorePP: true }); user.scene.unshiftPhase(new MovePhase(user.scene, user as PlayerPokemon, targets, new PokemonMove(lastMove, 0, 0, true), true)); @@ -5818,7 +5831,7 @@ export class ResistLastMoveTypeAttr extends MoveEffectAttr { if (!validTypes.length) { return false; } - const type = validTypes[user.randSeedInt(validTypes.length)]; + const type = validTypes[user.randSeedInt(validTypes.length, undefined, "Randomly selecting a type for Conversion2 that resists Type." + Utils.getEnumKeys(Type)[moveData.type])]; user.summonData.types = [ type ]; user.scene.queueMessage(i18next.t("battle:transformedIntoType", {pokemonName: getPokemonNameWithAffix(user), type: Utils.toReadableString(Type[type])})); user.updateInfo(); @@ -5871,7 +5884,7 @@ export function getMoveTargets(user: Pokemon, move: Moves): MoveTargetSet { multiple = moveTarget !== MoveTarget.NEAR_ENEMY; break; case MoveTarget.RANDOM_NEAR_ENEMY: - set = [ opponents[user.randSeedInt(opponents.length)] ]; + set = [ opponents[user.randSeedInt(opponents.length, undefined, "Randomly selecting an opponent to attack")] ]; break; case MoveTarget.ATTACKER: return { targets: [ -1 as BattlerIndex ], multiple: false }; diff --git a/src/phases.ts b/src/phases.ts index ebe0bb2bc76..2741e30af01 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -135,10 +135,13 @@ function findBest(scene: BattleScene, pokemon: EnemyPokemon, override?: boolean) var offset = 0 scene.getModifiers(BypassSpeedChanceModifier, true).forEach(m => { //console.log(m, m.getPokemon(this.scene), pokemon) - if (m.getPokemon(scene).isActive()) { - console.log(m.getPokemon(scene).name + " has a Quick Claw") - offset++ - } + var p = m.getPokemon(scene) + scene.getField().forEach((p2, idx) => { + if (p == p2) { + console.log(m.getPokemon(scene).name + " (Position: " + (idx + 1) + ") has a Quick Claw") + offset++ + } + }) }) scene.currentBattle.multiInt(scene, rolls, offset + 3, 65536) //console.log(rolls) diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 26749827aff..d467f74de8c 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -362,11 +362,17 @@ export default class FightUiHandler extends UiHandler { damage2.value = Math.floor(damage2.value / 2); } - const fixedDamage = new Utils.IntegerHolder(0); - MoveData.applyMoveAttrs(MoveData.FixedDamageAttr, user, target, move, fixedDamage); - if (!isTypeImmune && fixedDamage.value) { - damage1.value = fixedDamage.value; - damage2.value = fixedDamage.value; + const fixedDamage1 = new Utils.IntegerHolder(0); + const fixedDamage2 = new Utils.IntegerHolder(0); + MoveData.applyMoveAttrs(MoveData.FixedDamageAttr, user, target, move, fixedDamage1, true, false); + MoveData.applyMoveAttrs(MoveData.FixedDamageAttr, user, target, move, fixedDamage2, false, true); + if (!isTypeImmune && fixedDamage1.value) { + damage1.value = fixedDamage1.value; + isCritical = false; + result = HitResult.EFFECTIVE; + } + if (!isTypeImmune && fixedDamage2.value) { + damage2.value = fixedDamage2.value; isCritical = false; result = HitResult.EFFECTIVE; } @@ -392,13 +398,18 @@ export default class FightUiHandler extends UiHandler { } } - if (!fixedDamage.value) { + if (!fixedDamage1.value) { if (!user.isPlayer()) { this.scene.applyModifiers(EnemyDamageBoosterModifier, false, damage1); - this.scene.applyModifiers(EnemyDamageBoosterModifier, false, damage2); - } - if (!target.isPlayer()) { + } else { this.scene.applyModifiers(EnemyDamageReducerModifier, false, damage1); + } + } + + if (!fixedDamage2.value) { + if (!user.isPlayer()) { + this.scene.applyModifiers(EnemyDamageBoosterModifier, false, damage2); + } else { this.scene.applyModifiers(EnemyDamageReducerModifier, false, damage2); } } @@ -870,334 +881,4 @@ export default class FightUiHandler extends UiHandler { } this.cursorObj = null; } -} - -export function simulateAttack(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move) { - let result: HitResult; - const damage1 = new Utils.NumberHolder(0); - const damage2 = new Utils.NumberHolder(0); - const defendingSidePlayField = target.isPlayer() ? scene.getPlayerField() : scene.getEnemyField(); - - const variableCategory = new Utils.IntegerHolder(move.category); - MoveData.applyMoveAttrs(MoveData.VariableMoveCategoryAttr, user, target, move, variableCategory); - const moveCategory = variableCategory.value as MoveData.MoveCategory; - - const typeChangeMovePowerMultiplier = new Utils.NumberHolder(1); - MoveData.applyMoveAttrs(MoveData.VariableMoveTypeAttr, user, target, move); - applyPreAttackAbAttrs(MoveTypeChangeAttr, user, target, move, typeChangeMovePowerMultiplier); - const types = target.getTypes(true, true); - - const cancelled = new Utils.BooleanHolder(false); - const typeless = move.hasAttr(MoveData.TypelessAttr); - const typeMultiplier = new Utils.NumberHolder(!typeless && (moveCategory !== MoveData.MoveCategory.STATUS || move.getAttrs(MoveData.StatusMoveTypeImmunityAttr).find(attr => types.includes(attr.immuneType))) - ? target.getAttackTypeEffectiveness(move.type, user, false, false) - : 1); - MoveData.applyMoveAttrs(MoveData.VariableMoveTypeMultiplierAttr, user, target, move, typeMultiplier); - if (typeless) { - typeMultiplier.value = 1; - } - if (types.find(t => move.isTypeImmune(user, target, t))) { - typeMultiplier.value = 0; - } - - // Apply arena tags for conditional protection - if (!move.checkFlag(MoveData.MoveFlags.IGNORE_PROTECT, user, target) && !move.isAllyTarget()) { - const defendingSide = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - scene.arena.applyTagsForSide(ArenaTagType.QUICK_GUARD, defendingSide, cancelled, this, move.priority); - scene.arena.applyTagsForSide(ArenaTagType.WIDE_GUARD, defendingSide, cancelled, this, move.moveTarget); - scene.arena.applyTagsForSide(ArenaTagType.MAT_BLOCK, defendingSide, cancelled, this, move.category); - scene.arena.applyTagsForSide(ArenaTagType.CRAFTY_SHIELD, defendingSide, cancelled, this, move.category, move.moveTarget); - } - - switch (moveCategory) { - case MoveData.MoveCategory.PHYSICAL: - case MoveData.MoveCategory.SPECIAL: - const isPhysical = moveCategory === MoveData.MoveCategory.PHYSICAL; - const power = new Utils.NumberHolder(move.power); - const sourceTeraType = user.getTeraType(); - if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === move.type && power.value < 60 && move.priority <= 0 && !move.hasAttr(MoveData.MultiHitAttr) && !scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === user.id)) { - power.value = 60; - } - applyPreAttackAbAttrs(VariableMovePowerAbAttr, user, target, move, power); - - if (user.getAlly()?.hasAbilityWithAttr(AllyMoveCategoryPowerBoostAbAttr)) { - applyPreAttackAbAttrs(AllyMoveCategoryPowerBoostAbAttr, user, target, move, power); - } - - const fieldAuras = new Set( - scene.getField(true) - .map((p) => p.getAbilityAttrs(FieldMoveTypePowerBoostAbAttr) as FieldMoveTypePowerBoostAbAttr[]) - .flat(), - ); - for (const aura of fieldAuras) { - // The only relevant values are `move` and the `power` holder - aura.applyPreAttack(null, null, null, move, [power]); - } - - const alliedField: Pokemon[] = user instanceof PlayerPokemon ? scene.getPlayerField() : scene.getEnemyField(); - alliedField.forEach(p => applyPreAttackAbAttrs(UserFieldMoveTypePowerBoostAbAttr, p, user, move, power)); - - power.value *= typeChangeMovePowerMultiplier.value; - - if (!typeless) { - applyPreDefendAbAttrs(TypeImmunityAbAttr, user, target, move, cancelled, typeMultiplier); - MoveData.applyMoveAttrs(MoveData.NeutralDamageAgainstFlyingTypeMultiplierAttr, user, target, move, typeMultiplier); - } - if (!cancelled.value) { - applyPreDefendAbAttrs(MoveImmunityAbAttr, user, target, move, cancelled, typeMultiplier); - defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, user, move, cancelled, typeMultiplier)); - } - - if (cancelled.value) { - //user.stopMultiHit(target); - result = HitResult.NO_EFFECT; - } else { - const typeBoost = user.findTag(t => t instanceof TypeBoostTag && t.boostedType === move.type) as TypeBoostTag; - if (typeBoost) { - power.value *= typeBoost.boostValue; - if (typeBoost.oneUse) { - //user.removeTag(typeBoost.tagType); - } - } - const arenaAttackTypeMultiplier = new Utils.NumberHolder(scene.arena.getAttackTypeMultiplier(move.type, user.isGrounded())); - MoveData.applyMoveAttrs(MoveData.IgnoreWeatherTypeDebuffAttr, user, target, move, arenaAttackTypeMultiplier); - if (scene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() && move.type === Type.GROUND && move.moveTarget === MoveData.MoveTarget.ALL_NEAR_OTHERS) { - power.value /= 2; - } - - MoveData.applyMoveAttrs(MoveData.VariablePowerAttr, user, target, move, power); - - scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, new Utils.IntegerHolder(0), power); - if (!typeless) { - scene.arena.applyTags(WeakenMoveTypeTag, move.type, power); - scene.applyModifiers(AttackTypeBoosterModifier, user.isPlayer(), user, move.type, power); - } - if (user.getTag(HelpingHandTag)) { - power.value *= 1.5; - } - let isCritical: boolean = true; - const critOnly = new Utils.BooleanHolder(false); - const critAlways = user.getTag(BattlerTagType.ALWAYS_CRIT); - MoveData.applyMoveAttrs(MoveData.CritOnlyAttr, user, target, move, critOnly); - applyAbAttrs(ConditionalCritAbAttr, user, null, critOnly, target, move); - if (isCritical) { - const blockCrit = new Utils.BooleanHolder(false); - applyAbAttrs(BlockCritAbAttr, target, null, blockCrit); - if (blockCrit.value) { - isCritical = false; - } - } - const sourceAtk = new Utils.IntegerHolder(user.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK, target, null, false)); - const targetDef = new Utils.IntegerHolder(target.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF, user, move, false)); - const sourceAtkCrit = new Utils.IntegerHolder(user.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK, target, null, isCritical)); - const targetDefCrit = new Utils.IntegerHolder(target.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF, user, move, isCritical)); - const criticalMultiplier = new Utils.NumberHolder(isCritical ? 1.5 : 1); - applyAbAttrs(MultCritAbAttr, user, null, criticalMultiplier); - const screenMultiplier = new Utils.NumberHolder(1); - if (!isCritical) { - scene.arena.applyTagsForSide(WeakenMoveScreenTag, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, move.category, scene.currentBattle.double, screenMultiplier); - } - const isTypeImmune = (typeMultiplier.value * arenaAttackTypeMultiplier.value) === 0; - const sourceTypes = user.getTypes(); - const matchesSourceType = sourceTypes[0] === move.type || (sourceTypes.length > 1 && sourceTypes[1] === move.type); - const stabMultiplier = new Utils.NumberHolder(1); - if (sourceTeraType === Type.UNKNOWN && matchesSourceType) { - stabMultiplier.value += 0.5; - } else if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === move.type) { - stabMultiplier.value += 0.5; - } - - applyAbAttrs(StabBoostAbAttr, user, null, stabMultiplier); - - if (sourceTeraType !== Type.UNKNOWN && matchesSourceType) { - stabMultiplier.value = Math.min(stabMultiplier.value + 0.5, 2.25); - } - - MoveData.applyMoveAttrs(MoveData.VariableAtkAttr, user, target, move, sourceAtk); - MoveData.applyMoveAttrs(MoveData.VariableDefAttr, user, target, move, targetDef); - MoveData.applyMoveAttrs(MoveData.VariableAtkAttr, user, target, move, sourceAtkCrit); - MoveData.applyMoveAttrs(MoveData.VariableDefAttr, user, target, move, targetDefCrit); - - const effectPhase = scene.getCurrentPhase(); - let numTargets = 1; - if (effectPhase instanceof MoveEffectPhase) { - numTargets = effectPhase.getTargets().length; - } - const twoStrikeMultiplier = new Utils.NumberHolder(1); - applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, target, move, numTargets, new Utils.IntegerHolder(0), twoStrikeMultiplier); - - if (!isTypeImmune) { - damage1.value = Math.ceil(((((2 * user.level / 5 + 2) * power.value * sourceAtk.value / targetDef.value) / 50) + 2) * stabMultiplier.value * typeMultiplier.value * arenaAttackTypeMultiplier.value * screenMultiplier.value * twoStrikeMultiplier.value * 0.85); // low roll - damage2.value = Math.ceil(((((2 * user.level / 5 + 2) * power.value * sourceAtkCrit.value / targetDefCrit.value) / 50) + 2) * stabMultiplier.value * typeMultiplier.value * arenaAttackTypeMultiplier.value * screenMultiplier.value * twoStrikeMultiplier.value * criticalMultiplier.value); // high roll crit - if (isPhysical && user.status && user.status.effect === StatusEffect.BURN) { - if (!move.hasAttr(MoveData.BypassBurnDamageReductionAttr)) { - const burnDamageReductionCancelled = new Utils.BooleanHolder(false); - applyAbAttrs(BypassBurnDamageReductionAbAttr, user, burnDamageReductionCancelled); - if (!burnDamageReductionCancelled.value) { - damage1.value = Math.floor(damage1.value / 2); - damage2.value = Math.floor(damage2.value / 2); - } - } - } - - applyPreAttackAbAttrs(DamageBoostAbAttr, user, target, move, damage1); - applyPreAttackAbAttrs(DamageBoostAbAttr, user, target, move, damage2); - - /** - * For each {@link HitsTagAttr} the move has, doubles the damage of the move if: - * The target has a {@link BattlerTagType} that this move interacts with - * AND - * The move doubles damage when used against that tag - */ - move.getAttrs(MoveData.HitsTagAttr).filter(hta => hta.doubleDamage).forEach(hta => { - if (target.getTag(hta.tagType)) { - damage1.value *= 2; - damage2.value *= 2; - } - }); - } - - if (scene.arena.terrain?.terrainType === TerrainType.MISTY && target.isGrounded() && move.type === Type.DRAGON) { - damage1.value = Math.floor(damage1.value / 2); - damage2.value = Math.floor(damage2.value / 2); - } - - const fixedDamage = new Utils.IntegerHolder(0); - MoveData.applyMoveAttrs(MoveData.FixedDamageAttr, user, target, move, fixedDamage); - if (!isTypeImmune && fixedDamage.value) { - damage1.value = fixedDamage.value; - damage2.value = fixedDamage.value; - isCritical = false; - result = HitResult.EFFECTIVE; - } - - if (!result) { - if (!typeMultiplier.value) { - result = move.id === Moves.SHEER_COLD ? HitResult.IMMUNE : HitResult.NO_EFFECT; - } else { - const oneHitKo = new Utils.BooleanHolder(false); - MoveData.applyMoveAttrs(MoveData.OneHitKOAttr, user, target, move, oneHitKo); - if (oneHitKo.value) { - result = HitResult.ONE_HIT_KO; - isCritical = false; - damage1.value = target.hp; - damage2.value = target.hp; - } else if (typeMultiplier.value >= 2) { - result = HitResult.SUPER_EFFECTIVE; - } else if (typeMultiplier.value >= 1) { - result = HitResult.EFFECTIVE; - } else { - result = HitResult.NOT_VERY_EFFECTIVE; - } - } - } - - if (!fixedDamage.value) { - if (!user.isPlayer()) { - scene.applyModifiers(EnemyDamageBoosterModifier, false, damage1); - scene.applyModifiers(EnemyDamageBoosterModifier, false, damage2); - } - if (!target.isPlayer()) { - scene.applyModifiers(EnemyDamageReducerModifier, false, damage1); - scene.applyModifiers(EnemyDamageReducerModifier, false, damage2); - } - } - - MoveData.applyMoveAttrs(MoveData.ModifiedDamageAttr, user, target, move, damage1); - MoveData.applyMoveAttrs(MoveData.ModifiedDamageAttr, user, target, move, damage2); - applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, user, target, move, cancelled, damage1); - applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, user, target, move, cancelled, damage2); - const destinyTag = target.getTag(BattlerTagType.DESTINY_BOND); - - const oneHitKo = result === HitResult.ONE_HIT_KO; - if (damage1.value) { - if (target.getHpRatio() === 1) { - applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, target, user, move, cancelled, damage1); - } - } - if (damage2.value) { - if (target.getHpRatio() === 1) { - applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, target, user, move, cancelled, damage2); - } - } - } - break; - case MoveData.MoveCategory.STATUS: - if (!typeless) { - applyPreDefendAbAttrs(TypeImmunityAbAttr, target, user, move, cancelled, typeMultiplier); - } - if (!cancelled.value) { - applyPreDefendAbAttrs(MoveImmunityAbAttr, target, user, move, cancelled, typeMultiplier); - defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, user, move, cancelled, typeMultiplier)); - } - if (!typeMultiplier.value) { - return -1 - } - result = cancelled.value || !typeMultiplier.value ? HitResult.NO_EFFECT : HitResult.STATUS; - break; - } - return [damage1.value, damage2.value] -} - -export function calcDamage(scene: BattleScene, user: PlayerPokemon, target: Pokemon, move: PokemonMove) { - var dmgHigh = 0 - var dmgLow = 0 - // dmgLow = (((2*user.level/5 + 2) * power * myAtk / theirDef)/50 + 2) * 0.85 * modifiers - // dmgHigh = (((2*user.level/5 + 2) * power * myAtkC / theirDefC)/50 + 2) * 1.5 * modifiers - var out = this.simulateAttack(scene, user, target, move.getMove()) - var minHits = 1 - var maxHits = 1 - var mh = move.getMove().getAttrs(MoveData.MultiHitAttr) - for (var i = 0; i < mh.length; i++) { - var mh2 = mh[i] as MoveData.MultiHitAttr - switch (mh2.multiHitType) { - case MoveData.MultiHitType._2: - minHits = 2; - maxHits = 2; - case MoveData.MultiHitType._2_TO_5: - minHits = 2; - maxHits = 5; - case MoveData.MultiHitType._3: - minHits = 3; - maxHits = 3; - case MoveData.MultiHitType._10: - minHits = 10; - maxHits = 10; - case MoveData.MultiHitType.BEAT_UP: - const party = user.isPlayer() ? user.scene.getParty() : user.scene.getEnemyParty(); - // No status means the ally pokemon can contribute to Beat Up - minHits = party.reduce((total, pokemon) => { - return total + (pokemon.id === user.id ? 1 : pokemon?.status && pokemon.status.effect !== StatusEffect.NONE ? 0 : 1); - }, 0); - maxHits = minHits - } - } - var h = user.getHeldItems() - for (var i = 0; i < h.length; i++) { - if (h[i].type instanceof PokemonMultiHitModifierType) { - minHits += h[i].getStackCount() - maxHits += h[i].getStackCount() - } - } - dmgLow = out[0] * minHits - dmgHigh = out[1] * maxHits - /* - if (user.hasAbility(Abilities.PARENTAL_BOND)) { - // Second hit deals 0.25x damage - dmgLow *= 1.25 - dmgHigh *= 1.25 - } - */ - var koText = "" - if (Math.floor(dmgLow) >= target.hp) { - koText = " (KO)" - } else if (Math.ceil(dmgHigh) >= target.hp) { - var percentChance = (target.hp - dmgLow + 1) / (dmgHigh - dmgLow + 1) - koText = " (" + Math.round(percentChance * 100) + "% KO)" - } - return (Math.round(dmgLow) == Math.round(dmgHigh) ? Math.round(dmgLow).toString() : Math.round(dmgLow) + "-" + Math.round(dmgHigh)) + koText - dmgLow = Math.round((dmgLow)/target.getBattleStat(Stat.HP)*100) - dmgHigh = Math.round((dmgHigh)/target.getBattleStat(Stat.HP)*100) - return (dmgLow == dmgHigh ? dmgLow + "%" : dmgLow + "%-" + dmgHigh + "%") + koText - return "???" } \ No newline at end of file