mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-26 01:09:29 +02:00
Updates
Changed attack damage prediction to use `Pokemon.apply()`, rather than a copy of it, so that it stays consistent with any changes to that function Added the ability to simulate `Pokemon.apply`, returning minimum and maximum damage without performing any related actions (printing text, updating battler tags, dealing damage, etc.)
This commit is contained in:
parent
afaa2bb974
commit
2de8054d5e
@ -755,7 +755,7 @@ export default class Move implements Localizable {
|
|||||||
* @param target {@linkcode Pokemon} The Pokémon being targeted by the move.
|
* @param target {@linkcode Pokemon} The Pokémon being targeted by the move.
|
||||||
* @returns The calculated power of the move.
|
* @returns The calculated power of the move.
|
||||||
*/
|
*/
|
||||||
calculateBattlePower(source: Pokemon, target: Pokemon, simulated: boolean = false): number {
|
calculateBattlePower(source: Pokemon, target: Pokemon, simulated: boolean = false, isMin: boolean = false, isMax: boolean = false): number {
|
||||||
if (this.category === MoveCategory.STATUS) {
|
if (this.category === MoveCategory.STATUS) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -770,7 +770,7 @@ export default class Move implements Localizable {
|
|||||||
power.value = 60;
|
power.value = 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, target, this, simulated, power);
|
applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, target, this, simulated, power, isMin, isMax);
|
||||||
|
|
||||||
if (source.getAlly()) {
|
if (source.getAlly()) {
|
||||||
applyPreAttackAbAttrs(AllyMoveCategoryPowerBoostAbAttr, source.getAlly(), target, this, simulated, power);
|
applyPreAttackAbAttrs(AllyMoveCategoryPowerBoostAbAttr, source.getAlly(), target, this, simulated, power);
|
||||||
@ -800,7 +800,7 @@ export default class Move implements Localizable {
|
|||||||
power.value /= 2;
|
power.value /= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
applyMoveAttrs(VariablePowerAttr, source, target, this, power);
|
applyMoveAttrs(VariablePowerAttr, source, target, this, power, isMin, isMax);
|
||||||
|
|
||||||
source.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, new Utils.IntegerHolder(0), power);
|
source.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, new Utils.IntegerHolder(0), power);
|
||||||
|
|
||||||
@ -1162,12 +1162,12 @@ export class FixedDamageAttr extends MoveAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
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);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDamage(user: Pokemon, target: Pokemon, move: Move): integer {
|
getDamage(user: Pokemon, target: Pokemon, move: Move, args: any[]): integer {
|
||||||
return this.damage;
|
return this.damage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1251,7 +1251,7 @@ export class LevelDamageAttr extends FixedDamageAttr {
|
|||||||
super(0);
|
super(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
getDamage(user: Pokemon, target: Pokemon, move: Move): number {
|
getDamage(user: Pokemon, target: Pokemon, move: Move, args?: any[]): number {
|
||||||
return user.level;
|
return user.level;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1261,11 +1261,11 @@ export class RandomLevelDamageAttr extends FixedDamageAttr {
|
|||||||
super(0);
|
super(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
getDamage(user: Pokemon, target: Pokemon, move: Move, isLow?: boolean, isHigh?: boolean): number {
|
getDamage(user: Pokemon, target: Pokemon, move: Move, args: any[]): number {
|
||||||
if (isLow) {
|
if (args[1] == "LOW") {
|
||||||
return Utils.toDmgValue(user.level * 0.5);
|
return Utils.toDmgValue(user.level * 0.5);
|
||||||
}
|
}
|
||||||
if (isHigh) {
|
if (args[1] == "HIGH") {
|
||||||
return Utils.toDmgValue(user.level * 1.5);
|
return Utils.toDmgValue(user.level * 1.5);
|
||||||
}
|
}
|
||||||
return Utils.toDmgValue(user.level * (user.randSeedIntRange(50, 150, "Random Damage") * 0.01));
|
return Utils.toDmgValue(user.level * (user.randSeedIntRange(50, 150, "Random Damage") * 0.01));
|
||||||
|
@ -2547,7 +2547,13 @@ export class ContactHeldItemTransferChanceModifier extends HeldItemTransferModif
|
|||||||
return super.getArgs().concat(this.chance * 100);
|
return super.getArgs().concat(this.chance * 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTransferredItemCount(): integer {
|
getTransferredItemCount(simulated?: "PASS" | "FAIL"): integer {
|
||||||
|
if (simulated == "PASS") {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (simulated == "FAIL") {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
return Phaser.Math.RND.realInRange(0, 1) < (this.chance * this.getStackCount()) ? 1 : 0;
|
return Phaser.Math.RND.realInRange(0, 1) < (this.chance * this.getStackCount()) ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2784,6 +2790,9 @@ export class EnemyStatusEffectHealChanceModifier extends EnemyPersistentModifier
|
|||||||
|
|
||||||
apply(args: any[]): boolean {
|
apply(args: any[]): boolean {
|
||||||
const target = (args[0] as Pokemon);
|
const target = (args[0] as Pokemon);
|
||||||
|
if (args[1]) {
|
||||||
|
return false; // No RNG rolls
|
||||||
|
}
|
||||||
if (target.status && Phaser.Math.RND.realInRange(0, 1) < (this.chance * this.getStackCount())) {
|
if (target.status && Phaser.Math.RND.realInRange(0, 1) < (this.chance * this.getStackCount())) {
|
||||||
target.scene.queueMessage(getStatusEffectHealText(target.status.effect, getPokemonNameWithAffix(target)));
|
target.scene.queueMessage(getStatusEffectHealText(target.status.effect, getPokemonNameWithAffix(target)));
|
||||||
target.resetStatus();
|
target.resetStatus();
|
||||||
@ -2824,6 +2833,10 @@ export class EnemyEndureChanceModifier extends EnemyPersistentModifier {
|
|||||||
apply(args: any[]): boolean {
|
apply(args: any[]): boolean {
|
||||||
const target = (args[0] as Pokemon);
|
const target = (args[0] as Pokemon);
|
||||||
|
|
||||||
|
if (args[1]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (target.battleData.endured || Phaser.Math.RND.realInRange(0, 1) >= (this.chance * this.getStackCount())) {
|
if (target.battleData.endured || Phaser.Math.RND.realInRange(0, 1) >= (this.chance * this.getStackCount())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -4492,7 +4492,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
* made visible to the user until the resulting {@linkcode DamagePhase}
|
* made visible to the user until the resulting {@linkcode DamagePhase}
|
||||||
* is invoked.
|
* is invoked.
|
||||||
*/
|
*/
|
||||||
const hitResult = !isProtected ? target.apply(user, move) : HitResult.NO_EFFECT;
|
const hitResult = !isProtected ? target.apply(user, move).hitResult : HitResult.NO_EFFECT;
|
||||||
|
|
||||||
/** Does {@linkcode hitResult} indicate that damage was dealt to the target? */
|
/** Does {@linkcode hitResult} indicate that damage was dealt to the target? */
|
||||||
const dealsDamage = [
|
const dealsDamage = [
|
||||||
|
@ -181,7 +181,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
* made visible to the user until the resulting {@linkcode DamagePhase}
|
* made visible to the user until the resulting {@linkcode DamagePhase}
|
||||||
* is invoked.
|
* is invoked.
|
||||||
*/
|
*/
|
||||||
const hitResult = !isProtected ? target.apply(user, move) : HitResult.NO_EFFECT;
|
const hitResult = !isProtected ? target.apply(user, move) as HitResult : HitResult.NO_EFFECT;
|
||||||
|
|
||||||
/** Does {@linkcode hitResult} indicate that damage was dealt to the target? */
|
/** Does {@linkcode hitResult} indicate that damage was dealt to the target? */
|
||||||
const dealsDamage = [
|
const dealsDamage = [
|
||||||
|
@ -18,12 +18,44 @@ import { SwitchSummonPhase } from "./switch-summon-phase";
|
|||||||
import { TurnEndPhase } from "./turn-end-phase";
|
import { TurnEndPhase } from "./turn-end-phase";
|
||||||
import { WeatherEffectPhase } from "./weather-effect-phase";
|
import { WeatherEffectPhase } from "./weather-effect-phase";
|
||||||
import * as LoggerTools from "../logger";
|
import * as LoggerTools from "../logger";
|
||||||
|
import { TurnCommand } from "#app/battle.js";
|
||||||
|
|
||||||
export class TurnStartPhase extends FieldPhase {
|
export class TurnStartPhase extends FieldPhase {
|
||||||
constructor(scene: BattleScene) {
|
constructor(scene: BattleScene) {
|
||||||
super(scene);
|
super(scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logTargets(pokemon: Pokemon, mv: PokemonMove, turnCommand: TurnCommand) {
|
||||||
|
var targets = turnCommand.targets || turnCommand.move!.targets
|
||||||
|
if (pokemon.isPlayer()) {
|
||||||
|
console.log(turnCommand.targets, turnCommand.move!.targets)
|
||||||
|
if (turnCommand.args && turnCommand.args[1] && turnCommand.args[1].isContinuing != undefined) {
|
||||||
|
console.log(mv.getName(), targets)
|
||||||
|
} else {
|
||||||
|
LoggerTools.Actions[pokemon.getBattlerIndex()] = mv.getName()
|
||||||
|
if (this.scene.currentBattle.double) {
|
||||||
|
var targIDs = ["Self", "Self", "Ally", "L", "R"]
|
||||||
|
if (pokemon.getBattlerIndex() == 1) targIDs = ["Self", "Ally", "Self", "L", "R"]
|
||||||
|
LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1])
|
||||||
|
} else {
|
||||||
|
var targIDs = ["Self", "", "", "", ""]
|
||||||
|
var myField = this.scene.getField()
|
||||||
|
if (myField[0])
|
||||||
|
targIDs[1] = myField[0].name
|
||||||
|
if (myField[1])
|
||||||
|
targIDs[2] = myField[1].name
|
||||||
|
var eField = this.scene.getEnemyField()
|
||||||
|
if (eField[0])
|
||||||
|
targIDs[3] = eField[0].name
|
||||||
|
if (eField[1])
|
||||||
|
targIDs[4] = eField[1].name
|
||||||
|
//LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1])
|
||||||
|
}
|
||||||
|
console.log(mv.getName(), targets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
@ -113,8 +145,10 @@ export class TurnStartPhase extends FieldPhase {
|
|||||||
if (pokemon.isPlayer()) {
|
if (pokemon.isPlayer()) {
|
||||||
if (turnCommand.cursor === -1) {
|
if (turnCommand.cursor === -1) {
|
||||||
this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move));//TODO: is the bang correct here?
|
this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move));//TODO: is the bang correct here?
|
||||||
|
this.logTargets(pokemon, move, turnCommand)
|
||||||
} else {
|
} else {
|
||||||
const playerPhase = new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP);//TODO: is the bang correct here?
|
const playerPhase = new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP);//TODO: is the bang correct here?
|
||||||
|
this.logTargets(pokemon, move, turnCommand)
|
||||||
this.scene.pushPhase(playerPhase);
|
this.scene.pushPhase(playerPhase);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -126,6 +160,10 @@ export class TurnStartPhase extends FieldPhase {
|
|||||||
break;
|
break;
|
||||||
case Command.POKEMON:
|
case Command.POKEMON:
|
||||||
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, pokemon.getFieldIndex(), turnCommand.cursor!, true, turnCommand.args![0] as boolean, pokemon.isPlayer()));//TODO: is the bang correct here?
|
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, pokemon.getFieldIndex(), turnCommand.cursor!, true, turnCommand.args![0] as boolean, pokemon.isPlayer()));//TODO: is the bang correct here?
|
||||||
|
if (pokemon.isPlayer()) {
|
||||||
|
// " " + LoggerTools.playerPokeName(this.scene, pokemon) +
|
||||||
|
LoggerTools.Actions[pokemon.getBattlerIndex()] = ((turnCommand.args![0] as boolean) ? "Baton" : "Switch") + " to " + LoggerTools.playerPokeName(this.scene, turnCommand.cursor!)
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Command.RUN:
|
case Command.RUN:
|
||||||
let runningPokemon = pokemon;
|
let runningPokemon = pokemon;
|
||||||
@ -163,7 +201,7 @@ export class TurnStartPhase extends FieldPhase {
|
|||||||
this.scene.pushPhase(new BerryPhase(this.scene));
|
this.scene.pushPhase(new BerryPhase(this.scene));
|
||||||
this.scene.pushPhase(new TurnEndPhase(this.scene));
|
this.scene.pushPhase(new TurnEndPhase(this.scene));
|
||||||
|
|
||||||
this.scene.arenaFlyout.updateFieldText()
|
this.scene.arenaFlyout.updateFieldText();
|
||||||
|
|
||||||
if (LoggerTools.Actions.length > 1 && !this.scene.currentBattle.double) {
|
if (LoggerTools.Actions.length > 1 && !this.scene.currentBattle.double) {
|
||||||
LoggerTools.Actions.pop() // If this is a single battle, but we somehow have two actions, delete the second
|
LoggerTools.Actions.pop() // If this is a single battle, but we somehow have two actions, delete the second
|
||||||
|
@ -9,6 +9,7 @@ import { Species } from "#enums/species";
|
|||||||
import GameManager from "#test/utils/gameManager";
|
import GameManager from "#test/utils/gameManager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
import { HitResult } from "#app/field/pokemon.js";
|
||||||
|
|
||||||
|
|
||||||
describe("Abilities - Sheer Force", () => {
|
describe("Abilities - Sheer Force", () => {
|
||||||
@ -165,7 +166,7 @@ describe("Abilities - Sheer Force", () => {
|
|||||||
|
|
||||||
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, false, chance, move, target, false);
|
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, false, chance, move, target, false);
|
||||||
applyPreAttackAbAttrs(MovePowerBoostAbAttr, user, target, move, false, power);
|
applyPreAttackAbAttrs(MovePowerBoostAbAttr, user, target, move, false, power);
|
||||||
applyPostDefendAbAttrs(PostDefendTypeChangeAbAttr, target, user, move, target.apply(user, move));
|
applyPostDefendAbAttrs(PostDefendTypeChangeAbAttr, target, user, move, (target.apply(user, move) as HitResult));
|
||||||
|
|
||||||
expect(chance.value).toBe(0);
|
expect(chance.value).toBe(0);
|
||||||
expect(power.value).toBe(move.power * 5461 / 4096);
|
expect(power.value).toBe(move.power * 5461 / 4096);
|
||||||
|
@ -5,27 +5,15 @@ import { Command } from "./command-ui-handler";
|
|||||||
import { Mode } from "./ui";
|
import { Mode } from "./ui";
|
||||||
import UiHandler from "./ui-handler";
|
import UiHandler from "./ui-handler";
|
||||||
import * as Utils from "../utils";
|
import * as Utils from "../utils";
|
||||||
import Move, * as MoveData from "../data/move";
|
import * as MoveData from "../data/move";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import {Button} from "#enums/buttons";
|
import {Button} from "#enums/buttons";
|
||||||
import { Stat } from "#app/data/pokemon-stat.js";
|
import Pokemon, { AttackData, EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon.js";
|
||||||
import { WeatherType } from "#app/data/weather.js";
|
|
||||||
import { Moves } from "#app/enums/moves.js";
|
|
||||||
import { AddSecondStrikeAbAttr, AllyMoveCategoryPowerBoostAbAttr, AlwaysHitAbAttr, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreDefendAbAttrsNoApply, BattleStatMultiplierAbAttr, BlockCritAbAttr, BypassBurnDamageReductionAbAttr, ConditionalCritAbAttr, DamageBoostAbAttr, FieldMoveTypePowerBoostAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentEvasionAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MultCritAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, StabBoostAbAttr, TypeImmunityAbAttr, UserFieldMoveTypePowerBoostAbAttr, VariableMovePowerAbAttr, WonderSkinAbAttr } from "#app/data/ability.js";
|
|
||||||
import { ArenaTagType } from "#app/enums/arena-tag-type.js";
|
|
||||||
import { ArenaTagSide, WeakenMoveScreenTag, WeakenMoveTypeTag } from "#app/data/arena-tag.js";
|
|
||||||
import { HelpingHandTag, SemiInvulnerableTag, TypeBoostTag } from "#app/data/battler-tags.js";
|
|
||||||
import { TerrainType } from "#app/data/terrain.js";
|
|
||||||
import { AttackTypeBoosterModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, TempBattleStatBoosterModifier } from "#app/modifier/modifier.js";
|
|
||||||
import { BattlerTagType } from "#app/enums/battler-tag-type.js";
|
|
||||||
import { TempBattleStat } from "#app/data/temp-battle-stat.js";
|
|
||||||
import { StatusEffect } from "#app/data/status-effect.js";
|
|
||||||
import { BattleStat } from "#app/data/battle-stat.js";
|
|
||||||
import { PokemonMultiHitModifierType } from "#app/modifier/modifier-type.js";
|
|
||||||
import { MoveCategory } from "#app/data/move.js";
|
|
||||||
import Pokemon, { EnemyPokemon, HitResult, PlayerPokemon, PokemonMove } from "#app/field/pokemon.js";
|
|
||||||
import { CommandPhase } from "#app/phases/command-phase.js";
|
import { CommandPhase } from "#app/phases/command-phase.js";
|
||||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase.js";
|
import { PokemonMultiHitModifierType } from "#app/modifier/modifier-type.js";
|
||||||
|
import { Stat, PermanentStat, PERMANENT_STATS } from "#app/enums/stat.js";
|
||||||
|
import { BaseStatModifier } from "#app/modifier/modifier.js";
|
||||||
|
import { StatusEffect } from "#app/enums/status-effect.js";
|
||||||
|
|
||||||
export default class FightUiHandler extends UiHandler {
|
export default class FightUiHandler extends UiHandler {
|
||||||
public static readonly MOVES_CONTAINER_NAME = "moves";
|
public static readonly MOVES_CONTAINER_NAME = "moves";
|
||||||
@ -171,552 +159,6 @@ export default class FightUiHandler extends UiHandler {
|
|||||||
return !this.fieldIndex ? this.cursor : this.cursor2;
|
return !this.fieldIndex ? this.cursor : this.cursor2;
|
||||||
}
|
}
|
||||||
|
|
||||||
simulateAttack(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move) {
|
|
||||||
let result: HitResult | undefined = undefined;
|
|
||||||
const damage1 = new Utils.NumberHolder(0);
|
|
||||||
const damage2 = new Utils.NumberHolder(0);
|
|
||||||
const defendingSidePlayField = target.isPlayer() ? this.scene.getPlayerField() : this.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);
|
|
||||||
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)
|
|
||||||
? 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;
|
|
||||||
this.scene.arena.applyTagsForSide(ArenaTagType.QUICK_GUARD, defendingSide, cancelled, this, move.priority);
|
|
||||||
this.scene.arena.applyTagsForSide(ArenaTagType.WIDE_GUARD, defendingSide, cancelled, this, move.moveTarget);
|
|
||||||
this.scene.arena.applyTagsForSide(ArenaTagType.MAT_BLOCK, defendingSide, cancelled, this, move.category);
|
|
||||||
this.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) && !this.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === user.id)) {
|
|
||||||
power.value = 60;
|
|
||||||
}
|
|
||||||
applyPreAttackAbAttrs(VariableMovePowerAbAttr, user, target, move, true, power);
|
|
||||||
|
|
||||||
if (user.getAlly()?.hasAbilityWithAttr(AllyMoveCategoryPowerBoostAbAttr)) {
|
|
||||||
applyPreAttackAbAttrs(AllyMoveCategoryPowerBoostAbAttr, user, target, move, true, power);
|
|
||||||
}
|
|
||||||
|
|
||||||
const fieldAuras = new Set(
|
|
||||||
this.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, true, null, move, [power]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const alliedField: Pokemon[] = user instanceof PlayerPokemon ? this.scene.getPlayerField() : this.scene.getEnemyField();
|
|
||||||
alliedField.forEach(p => applyPreAttackAbAttrs(UserFieldMoveTypePowerBoostAbAttr, p, user, move, true, power));
|
|
||||||
|
|
||||||
power.value *= typeChangeMovePowerMultiplier.value;
|
|
||||||
|
|
||||||
if (!typeless) {
|
|
||||||
if (target.hasAbilityWithAttr(TypeImmunityAbAttr)) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
applyPreDefendAbAttrsNoApply(TypeImmunityAbAttr, user, target, move, cancelled, typeMultiplier);
|
|
||||||
MoveData.applyMoveAttrs(MoveData.NeutralDamageAgainstFlyingTypeMultiplierAttr, user, target, move, typeMultiplier);
|
|
||||||
}
|
|
||||||
if (!cancelled.value) {
|
|
||||||
applyPreDefendAbAttrs(MoveImmunityAbAttr, user, target, move, cancelled, true, typeMultiplier);
|
|
||||||
defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, user, move, cancelled, true, 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(this.scene.arena.getAttackTypeMultiplier(move.type, user.isGrounded()));
|
|
||||||
MoveData.applyMoveAttrs(MoveData.IgnoreWeatherTypeDebuffAttr, user, target, move, arenaAttackTypeMultiplier);
|
|
||||||
if (this.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);
|
|
||||||
|
|
||||||
this.scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, new Utils.IntegerHolder(0), power);
|
|
||||||
if (!typeless) {
|
|
||||||
this.scene.arena.applyTags(WeakenMoveTypeTag, move.type, power);
|
|
||||||
this.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, true, critOnly, target, move);
|
|
||||||
if (isCritical) {
|
|
||||||
const blockCrit = new Utils.BooleanHolder(false);
|
|
||||||
applyAbAttrs(BlockCritAbAttr, target, null, true, blockCrit);
|
|
||||||
if (blockCrit.value) {
|
|
||||||
isCritical = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const sourceAtk = new Utils.IntegerHolder(user.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK, target, move, 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, move, 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, true, criticalMultiplier);
|
|
||||||
const screenMultiplier = new Utils.NumberHolder(1);
|
|
||||||
if (!isCritical) {
|
|
||||||
this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, move.category, this.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, true, 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 = this.scene.getCurrentPhase();
|
|
||||||
let numTargets = 1;
|
|
||||||
if (effectPhase instanceof MoveEffectPhase) {
|
|
||||||
numTargets = effectPhase.getTargets().length;
|
|
||||||
}
|
|
||||||
const twoStrikeMultiplier = new Utils.NumberHolder(1);
|
|
||||||
applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, target, move, true, 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, true, damage1);
|
|
||||||
applyPreAttackAbAttrs(DamageBoostAbAttr, user, target, move, true, 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 (this.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 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (!fixedDamage1.value) {
|
|
||||||
if (!user.isPlayer()) {
|
|
||||||
this.scene.applyModifiers(EnemyDamageBoosterModifier, false, damage1);
|
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MoveData.applyMoveAttrs(MoveData.ModifiedDamageAttr, user, target, move, damage1);
|
|
||||||
MoveData.applyMoveAttrs(MoveData.ModifiedDamageAttr, user, target, move, damage2);
|
|
||||||
applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, user, target, move, cancelled, true, damage1);
|
|
||||||
applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, user, target, move, cancelled, true, damage2);
|
|
||||||
|
|
||||||
//console.log("damage (min)", damage1.value, move.name, power.value, sourceAtk, targetDef);
|
|
||||||
//console.log("damage (max)", damage2.value, move.name, power.value, sourceAtkCrit, targetDefCrit);
|
|
||||||
|
|
||||||
// In case of fatal damage, this tag would have gotten cleared before we could lapse it.
|
|
||||||
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, true, damage1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (damage2.value) {
|
|
||||||
if (target.getHpRatio() === 1) {
|
|
||||||
applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, target, user, move, cancelled, true, damage2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case MoveData.MoveCategory.STATUS:
|
|
||||||
if (!typeless) {
|
|
||||||
applyPreDefendAbAttrsNoApply(TypeImmunityAbAttr, target, user, move, cancelled, true, typeMultiplier);
|
|
||||||
}
|
|
||||||
if (!cancelled.value) {
|
|
||||||
applyPreDefendAbAttrs(MoveImmunityAbAttr, target, user, move, cancelled, true, typeMultiplier);
|
|
||||||
defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, user, move, cancelled, true, typeMultiplier));
|
|
||||||
}
|
|
||||||
if (!typeMultiplier.value) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
result = cancelled.value || !typeMultiplier.value ? HitResult.NO_EFFECT : HitResult.STATUS;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return [damage1.value, damage2.value]
|
|
||||||
}
|
|
||||||
|
|
||||||
calculateAccuracy(user: Pokemon, target: Pokemon, move: PokemonMove) {
|
|
||||||
if (this.scene.currentBattle.double && false) {
|
|
||||||
switch (move.getMove().moveTarget) {
|
|
||||||
case MoveData.MoveTarget.USER: // Targets yourself
|
|
||||||
return -1; // Moves targeting yourself always hit
|
|
||||||
case MoveData.MoveTarget.OTHER: // Targets one Pokemon
|
|
||||||
return move.getMove().accuracy
|
|
||||||
case MoveData.MoveTarget.ALL_OTHERS: // Targets all Pokemon
|
|
||||||
return move.getMove().accuracy;
|
|
||||||
case MoveData.MoveTarget.NEAR_OTHER: // Targets a Pokemon adjacent to the user
|
|
||||||
return move.getMove().accuracy;
|
|
||||||
case MoveData.MoveTarget.ALL_NEAR_OTHERS: // Targets all Pokemon adjacent to the user
|
|
||||||
return move.getMove().accuracy;
|
|
||||||
case MoveData.MoveTarget.NEAR_ENEMY: // Targets an opponent adjacent to the user
|
|
||||||
return move.getMove().accuracy;
|
|
||||||
case MoveData.MoveTarget.ALL_NEAR_ENEMIES: // Targets all opponents adjacent to the user
|
|
||||||
return move.getMove().accuracy;
|
|
||||||
case MoveData.MoveTarget.RANDOM_NEAR_ENEMY: // Targets a random opponent adjacent to the user
|
|
||||||
return move.getMove().accuracy;
|
|
||||||
case MoveData.MoveTarget.ALL_ENEMIES: // Targets all opponents
|
|
||||||
return move.getMove().accuracy;
|
|
||||||
case MoveData.MoveTarget.ATTACKER: // Counter move
|
|
||||||
return move.getMove().accuracy;
|
|
||||||
case MoveData.MoveTarget.NEAR_ALLY: // Targets an adjacent ally
|
|
||||||
return move.getMove().accuracy;
|
|
||||||
case MoveData.MoveTarget.ALLY: // Targets an ally
|
|
||||||
return move.getMove().accuracy;
|
|
||||||
case MoveData.MoveTarget.USER_OR_NEAR_ALLY: // Targets an ally or yourself
|
|
||||||
return move.getMove().accuracy;
|
|
||||||
case MoveData.MoveTarget.USER_AND_ALLIES: // Targets all on your side
|
|
||||||
return move.getMove().accuracy;
|
|
||||||
case MoveData.MoveTarget.ALL: // Targets everyone
|
|
||||||
return move.getMove().accuracy;
|
|
||||||
case MoveData.MoveTarget.USER_SIDE: // Targets your field
|
|
||||||
return move.getMove().accuracy;
|
|
||||||
case MoveData.MoveTarget.ENEMY_SIDE: // Targets enemy field
|
|
||||||
return -1; // Moves placing entry hazards always hit
|
|
||||||
case MoveData.MoveTarget.BOTH_SIDES: // Targets the entire field
|
|
||||||
return move.getMove().accuracy;
|
|
||||||
case MoveData.MoveTarget.PARTY: // Targets all of the Player's Pokemon, including ones that aren't active
|
|
||||||
return move.getMove().accuracy;
|
|
||||||
case MoveData.MoveTarget.CURSE:
|
|
||||||
return move.getMove().accuracy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Moves targeting the user and entry hazards can't miss
|
|
||||||
if ([MoveData.MoveTarget.USER, MoveData.MoveTarget.ENEMY_SIDE].includes(move.getMove().moveTarget)) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (target == undefined) return move.getMove().accuracy;
|
|
||||||
// If either Pokemon has No Guard,
|
|
||||||
if (user.hasAbilityWithAttr(AlwaysHitAbAttr) || target.hasAbilityWithAttr(AlwaysHitAbAttr)) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
// If the user should ignore accuracy on a target, check who the user targeted last turn and see if they match
|
|
||||||
if (user.getTag(BattlerTagType.IGNORE_ACCURACY) && (user.getLastXMoves().slice(1).find(() => true)?.targets || []).indexOf(target.getBattlerIndex()) !== -1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hiddenTag = target.getTag(SemiInvulnerableTag);
|
|
||||||
if (hiddenTag && !move.getMove().getAttrs(MoveData.HitsTagAttr).some(hta => hta.tagType === hiddenTag.tagType)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
const moveAccuracy = new Utils.NumberHolder(move.getMove().accuracy);
|
|
||||||
|
|
||||||
MoveData.applyMoveAttrs(MoveData.VariableAccuracyAttr, user, target, move.getMove(), moveAccuracy);
|
|
||||||
applyPreDefendAbAttrs(WonderSkinAbAttr, target, user, move.getMove(), { value: false }, true, moveAccuracy);
|
|
||||||
|
|
||||||
if (moveAccuracy.value === -1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isOhko = move.getMove().hasAttr(MoveData.OneHitKOAccuracyAttr);
|
|
||||||
|
|
||||||
if (!isOhko) {
|
|
||||||
user.scene.applyModifiers(PokemonMoveAccuracyBoosterModifier, user.isPlayer(), user, moveAccuracy);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.scene.arena.weather?.weatherType === WeatherType.FOG) {
|
|
||||||
moveAccuracy.value = Math.floor(moveAccuracy.value * 0.9);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isOhko && this.scene.arena.getTag(ArenaTagType.GRAVITY)) {
|
|
||||||
moveAccuracy.value = Math.floor(moveAccuracy.value * 1.67);
|
|
||||||
}
|
|
||||||
|
|
||||||
const userAccuracyLevel = new Utils.IntegerHolder(user.summonData.battleStats[BattleStat.ACC]);
|
|
||||||
const targetEvasionLevel = new Utils.IntegerHolder(target.summonData.battleStats[BattleStat.EVA]);
|
|
||||||
applyAbAttrs(IgnoreOpponentStatChangesAbAttr, target, null, true, userAccuracyLevel);
|
|
||||||
applyAbAttrs(IgnoreOpponentStatChangesAbAttr, user, null, true, targetEvasionLevel);
|
|
||||||
applyAbAttrs(IgnoreOpponentEvasionAbAttr, user, null, true, targetEvasionLevel);
|
|
||||||
MoveData.applyMoveAttrs(MoveData.IgnoreOpponentStatChangesAttr, user, target, move.getMove(), targetEvasionLevel);
|
|
||||||
this.scene.applyModifiers(TempBattleStatBoosterModifier, user.isPlayer(), TempBattleStat.ACC, userAccuracyLevel);
|
|
||||||
|
|
||||||
const accuracyMultiplier = new Utils.NumberHolder(1);
|
|
||||||
if (userAccuracyLevel.value !== targetEvasionLevel.value) {
|
|
||||||
accuracyMultiplier.value = userAccuracyLevel.value > targetEvasionLevel.value
|
|
||||||
? (3 + Math.min(userAccuracyLevel.value - targetEvasionLevel.value, 6)) / 3
|
|
||||||
: 3 / (3 + Math.min(targetEvasionLevel.value - userAccuracyLevel.value, 6));
|
|
||||||
}
|
|
||||||
|
|
||||||
applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, user, BattleStat.ACC, accuracyMultiplier, true, move.getMove());
|
|
||||||
|
|
||||||
const evasionMultiplier = new Utils.NumberHolder(1);
|
|
||||||
applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, target, BattleStat.EVA, evasionMultiplier, true);
|
|
||||||
|
|
||||||
accuracyMultiplier.value /= evasionMultiplier.value;
|
|
||||||
|
|
||||||
return moveAccuracy.value * accuracyMultiplier.value
|
|
||||||
}
|
|
||||||
|
|
||||||
calcDamage(scene: BattleScene, user: PlayerPokemon, target: Pokemon, move: PokemonMove) {
|
|
||||||
/*
|
|
||||||
var power = move.getMove().power
|
|
||||||
var myAtk = 0
|
|
||||||
var theirDef = 0
|
|
||||||
var myAtkC = 0
|
|
||||||
var theirDefC = 0
|
|
||||||
switch (move.getMove().category) {
|
|
||||||
case MoveData.MoveCategory.PHYSICAL:
|
|
||||||
myAtk = user.getBattleStat(Stat.ATK, target, move.getMove())
|
|
||||||
myAtkC = user.getBattleStat(Stat.ATK, target, move.getMove(), true)
|
|
||||||
theirDef = target.getBattleStat(Stat.DEF, user, move.getMove())
|
|
||||||
theirDefC = target.getBattleStat(Stat.DEF, user, move.getMove(), true)
|
|
||||||
break;
|
|
||||||
case MoveData.MoveCategory.SPECIAL:
|
|
||||||
myAtk = user.getBattleStat(Stat.SPATK, target, move.getMove())
|
|
||||||
myAtkC = user.getBattleStat(Stat.SPATK, target, move.getMove(), true)
|
|
||||||
theirDef = target.getBattleStat(Stat.SPDEF, user, move.getMove())
|
|
||||||
theirDefC = target.getBattleStat(Stat.SPDEF, user, move.getMove(), true)
|
|
||||||
break;
|
|
||||||
case MoveData.MoveCategory.STATUS:
|
|
||||||
return "---"
|
|
||||||
}
|
|
||||||
var stabBonus = 1
|
|
||||||
var types = user.getTypes()
|
|
||||||
// Apply STAB bonus
|
|
||||||
for (var i = 0; i < types.length; i++) {
|
|
||||||
if (types[i] == move.getMove().type) {
|
|
||||||
stabBonus = 1.5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Apply Tera Type bonus
|
|
||||||
if (stabBonus == 1.5) {
|
|
||||||
// STAB
|
|
||||||
if (move.getMove().type == user.getTeraType()) {
|
|
||||||
stabBonus = 2
|
|
||||||
}
|
|
||||||
} else if (move.getMove().type == user.getTeraType()) {
|
|
||||||
stabBonus = 1.5
|
|
||||||
}
|
|
||||||
// Apply adaptability
|
|
||||||
if (stabBonus == 2) {
|
|
||||||
// Tera-STAB
|
|
||||||
if (move.getMove().type == user.getTeraType()) {
|
|
||||||
stabBonus = 2.25
|
|
||||||
}
|
|
||||||
} else if (stabBonus == 1.5) {
|
|
||||||
// STAB or Tera
|
|
||||||
if (move.getMove().type == user.getTeraType()) {
|
|
||||||
stabBonus = 2
|
|
||||||
}
|
|
||||||
} else if (move.getMove().type == user.getTeraType()) {
|
|
||||||
// Adaptability
|
|
||||||
stabBonus = 1.5
|
|
||||||
}
|
|
||||||
var weatherBonus = 1
|
|
||||||
if (this.scene.arena.weather.weatherType == WeatherType.RAIN || this.scene.arena.weather.weatherType == WeatherType.HEAVY_RAIN) {
|
|
||||||
if (move.getMove().type == Type.WATER) {
|
|
||||||
weatherBonus = 1.5
|
|
||||||
}
|
|
||||||
if (move.getMove().type == Type.FIRE) {
|
|
||||||
weatherBonus = this.scene.arena.weather.weatherType == WeatherType.HEAVY_RAIN ? 0 : 0.5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.scene.arena.weather.weatherType == WeatherType.SUNNY || this.scene.arena.weather.weatherType == WeatherType.HARSH_SUN) {
|
|
||||||
if (move.getMove().type == Type.FIRE) {
|
|
||||||
weatherBonus = 1.5
|
|
||||||
}
|
|
||||||
if (move.getMove().type == Type.WATER) {
|
|
||||||
weatherBonus = this.scene.arena.weather.weatherType == WeatherType.HARSH_SUN ? 0 : (move.moveId == Moves.HYDRO_STEAM ? 1.5 : 0.5)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var typeBonus = target.getAttackMoveEffectiveness(user, move)
|
|
||||||
var modifiers = stabBonus * weatherBonus
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
var qSuffix = ""
|
|
||||||
if (target.isBoss()) {
|
|
||||||
var bossSegs = (target as EnemyPokemon).bossSegments
|
|
||||||
//dmgLow /= bossSegs
|
|
||||||
//dmgHigh /= bossSegs
|
|
||||||
//qSuffix = "?"
|
|
||||||
}
|
|
||||||
var dmgLowP = Math.round((dmgLow)/target.getBattleStat(Stat.HP)*100)
|
|
||||||
var dmgHighP = Math.round((dmgHigh)/target.getBattleStat(Stat.HP)*100)
|
|
||||||
/*
|
|
||||||
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 = 1 - ((target.hp - dmgLow + 1) / (dmgHigh - dmgLow + 1))
|
|
||||||
koText = " (" + Math.round(percentChance * 100) + "% KO)"
|
|
||||||
}
|
|
||||||
if (target.getMoveEffectiveness(user, move.getMove(), false, true) == undefined) {
|
|
||||||
return "---"
|
|
||||||
}
|
|
||||||
if (scene.damageDisplay == "Value")
|
|
||||||
return target.getMoveEffectiveness(user, move.getMove(), false, true) + "x - " + (Math.round(dmgLow) == Math.round(dmgHigh) ? Math.round(dmgLow).toString() + qSuffix : Math.round(dmgLow) + "-" + Math.round(dmgHigh) + qSuffix) + koText
|
|
||||||
if (scene.damageDisplay == "Percent")
|
|
||||||
return target.getMoveEffectiveness(user, move.getMove(), false, true) + "x - " + (dmgLowP == dmgHighP ? dmgLowP + "%" + qSuffix : dmgLowP + "%-" + dmgHighP + "%" + qSuffix) + koText
|
|
||||||
if (scene.damageDisplay == "Off")
|
|
||||||
return target.getMoveEffectiveness(user, move.getMove(), false, true) + "x" + ((Math.floor(dmgLow) >= target.hp) ? " (KO)" : "")
|
|
||||||
}
|
|
||||||
|
|
||||||
setCursor(cursor: integer): boolean {
|
setCursor(cursor: integer): boolean {
|
||||||
const ui = this.getUi();
|
const ui = this.getUi();
|
||||||
|
|
||||||
@ -746,22 +188,17 @@ export default class FightUiHandler extends UiHandler {
|
|||||||
this.typeIcon.setTexture(textureKey, Type[moveType].toLowerCase()).setScale(0.8);
|
this.typeIcon.setTexture(textureKey, Type[moveType].toLowerCase()).setScale(0.8);
|
||||||
|
|
||||||
const moveCategory = pokemonMove.getMove().category;
|
const moveCategory = pokemonMove.getMove().category;
|
||||||
this.moveCategoryIcon.setTexture("categories", MoveCategory[moveCategory].toLowerCase()).setScale(1.0);
|
this.moveCategoryIcon.setTexture("categories", MoveData.MoveCategory[moveCategory].toLowerCase()).setScale(1.0);
|
||||||
const power = pokemonMove.getMove().power;
|
const power = pokemonMove.getMove().power;
|
||||||
const accuracy = pokemonMove.getMove().accuracy;
|
const accuracy = pokemonMove.getMove().accuracy;
|
||||||
const maxPP = pokemonMove.getMovePp();
|
const maxPP = pokemonMove.getMovePp();
|
||||||
const pp = maxPP - pokemonMove.ppUsed;
|
const pp = maxPP - pokemonMove.ppUsed;
|
||||||
|
|
||||||
const accuracy1 = this.calculateAccuracy(pokemon, this.scene.getEnemyField()[0], pokemonMove)
|
|
||||||
const accuracy2 = this.calculateAccuracy(pokemon, this.scene.getEnemyField()[1], pokemonMove)
|
|
||||||
const ppLeftStr = Utils.padInt(pp, 2, " ");
|
const ppLeftStr = Utils.padInt(pp, 2, " ");
|
||||||
const ppMaxStr = Utils.padInt(maxPP, 2, " ");
|
const ppMaxStr = Utils.padInt(maxPP, 2, " ");
|
||||||
this.ppText.setText(`${ppLeftStr}/${ppMaxStr}`);
|
this.ppText.setText(`${ppLeftStr}/${ppMaxStr}`);
|
||||||
this.powerText.setText(`${power >= 0 ? power : "---"}`);
|
this.powerText.setText(`${power >= 0 ? power : "---"}`);
|
||||||
this.accuracyText.setText(`${accuracy >= 0 ? accuracy : "---"}`);
|
this.accuracyText.setText(`${accuracy >= 0 ? accuracy : "---"}`);
|
||||||
this.accuracyText.setText(`${accuracy1 >= 0 ? Math.round(accuracy1) : "---"}`);
|
|
||||||
if (this.scene.getEnemyField()[1] != undefined)
|
|
||||||
this.accuracyText.setText(`${accuracy1 >= 0 ? Math.round(accuracy1) : "---"}/${accuracy2 >= 0 ? Math.round(accuracy2) : "---"}`);
|
|
||||||
|
|
||||||
const ppPercentLeft = pp / maxPP;
|
const ppPercentLeft = pp / maxPP;
|
||||||
|
|
||||||
@ -781,7 +218,6 @@ export default class FightUiHandler extends UiHandler {
|
|||||||
|
|
||||||
pokemon.getOpponents().forEach((opponent) => {
|
pokemon.getOpponents().forEach((opponent) => {
|
||||||
opponent.updateEffectiveness(this.getEffectivenessText(pokemon, opponent, pokemonMove));
|
opponent.updateEffectiveness(this.getEffectivenessText(pokemon, opponent, pokemonMove));
|
||||||
opponent.updateEffectiveness(this.calcDamage(this.scene, pokemon, opponent, pokemonMove));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -885,4 +321,80 @@ export default class FightUiHandler extends UiHandler {
|
|||||||
}
|
}
|
||||||
this.cursorObj = null;
|
this.cursorObj = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
calcDamage(user: PlayerPokemon, target: Pokemon, move: PokemonMove) {
|
||||||
|
var dmgHigh = 0;
|
||||||
|
var dmgLow = 0;
|
||||||
|
var out = target.apply(user, move.getMove(), true) as AttackData
|
||||||
|
dmgHigh = out.damageHigh;
|
||||||
|
dmgLow = out.damageLow;
|
||||||
|
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
|
||||||
|
var qSuffix = ""
|
||||||
|
if (target.isBoss()) {
|
||||||
|
var bossSegs = (target as EnemyPokemon).bossSegments
|
||||||
|
//dmgLow /= bossSegs
|
||||||
|
//dmgHigh /= bossSegs
|
||||||
|
//qSuffix = "?"
|
||||||
|
}
|
||||||
|
var dmgLowP = Math.round((dmgLow)/target.getMaxHp())
|
||||||
|
var dmgHighP = Math.round((dmgHigh)/target.getMaxHp())
|
||||||
|
/*
|
||||||
|
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 = Utils.rangemap(target.hp, dmgLow, dmgHigh, 0, 1)
|
||||||
|
koText = " (" + Math.round(percentChance * 100) + "% KO)"
|
||||||
|
}
|
||||||
|
if (target.getMoveEffectiveness(user, move.getMove(), false, true) == undefined) {
|
||||||
|
return "---"
|
||||||
|
}
|
||||||
|
if (this.scene.damageDisplay == "Percent")
|
||||||
|
return target.getMoveEffectiveness(user, move.getMove(), false, true) + "x - " + (dmgLowP == dmgHighP ? dmgLowP + "%" + qSuffix : dmgLowP + "%-" + dmgHighP + "%" + qSuffix) + koText
|
||||||
|
if (this.scene.damageDisplay == "Value")
|
||||||
|
return target.getMoveEffectiveness(user, move.getMove(), false, true) + "x" + ((Math.floor(dmgLow) >= target.hp) ? " (KO)" : "")
|
||||||
|
if (this.scene.damageDisplay == "Off")
|
||||||
|
return target.getMoveEffectiveness(user, move.getMove(), false, true) + "x" + ((Math.floor(dmgLow) >= target.hp) ? " (KO)" : "")
|
||||||
|
}
|
||||||
}
|
}
|
@ -40,6 +40,10 @@ export function clampInt(value: integer, min: integer, max: integer): integer {
|
|||||||
return Math.min(Math.max(value, min), max);
|
return Math.min(Math.max(value, min), max);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function rangemap(value: integer, min: integer, max: integer, outMin: integer, outMax: integer) {
|
||||||
|
return ((value - min) / (max - min)) * (outMax - outMin) + outMin
|
||||||
|
}
|
||||||
|
|
||||||
export function randGauss(stdev: number, mean: number = 0): number {
|
export function randGauss(stdev: number, mean: number = 0): number {
|
||||||
if (!stdev) {
|
if (!stdev) {
|
||||||
return 0;
|
return 0;
|
||||||
|
Loading…
Reference in New Issue
Block a user