From fdff3a549c9cca2f8af0624e343d2f1ce8afb218 Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Wed, 28 Feb 2024 11:34:55 -0500 Subject: [PATCH] Factor type immunity abilities into enemy AI --- src/battle.ts | 2 +- src/data/ability.ts | 43 +++++++++++++++++++----------- src/data/arena-tag.ts | 2 +- src/data/move.ts | 4 +-- src/pokemon.ts | 25 ++++++++++++----- src/ui/target-select-ui-handler.ts | 3 ++- 6 files changed, 51 insertions(+), 28 deletions(-) diff --git a/src/battle.ts b/src/battle.ts index 5652a9b8674..5d5351b65ad 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -100,7 +100,7 @@ export default class Battle { randSeedGaussForLevel(value: number): number { let rand = 0; - for (var i = value; i > 0; i--) + for (let i = value; i > 0; i--) rand += Phaser.Math.RND.realInRange(0, 1); return rand / value; } diff --git a/src/data/ability.ts b/src/data/ability.ts index e9668914624..5882a35893d 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -213,9 +213,13 @@ export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr { const ret = super.applyPreDefend(pokemon, attacker, move, cancelled, args); if (ret) { - if (pokemon.getHpRatio() < 1) - pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), - Math.max(Math.floor(pokemon.getMaxHp() / 4), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true)); + if (pokemon.getHpRatio() < 1) { + const simulated = args.length > 1 && args[1]; + if (!simulated) { + pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), + Math.max(Math.floor(pokemon.getMaxHp() / 4), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true)); + } + } return true; } @@ -239,7 +243,9 @@ class TypeImmunityStatChangeAbAttr extends TypeImmunityAbAttr { if (ret) { cancelled.value = true; - pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.levels)); + const simulated = args.length > 1 && args[1]; + if (!simulated) + pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.levels)); } return ret; @@ -262,7 +268,9 @@ class TypeImmunityAddBattlerTagAbAttr extends TypeImmunityAbAttr { if (ret) { cancelled.value = true; - pokemon.addTag(this.tagType, this.turnCount, undefined, pokemon.id); + const simulated = args.length > 1 && args[1]; + if (!simulated) + pokemon.addTag(this.tagType, this.turnCount, undefined, pokemon.id); } return ret; @@ -275,7 +283,7 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr { } applyPreDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (pokemon.getAttackMoveEffectiveness(move.getMove().type) < 2) { + if (pokemon.getAttackTypeEffectiveness(move.getMove().type) < 2) { cancelled.value = true; (args[0] as Utils.NumberHolder).value = 0; return true; @@ -1103,7 +1111,7 @@ export class SyncEncounterNatureAbAttr extends AbAttr { } function applyAbAttrsInternal(attrType: { new(...args: any[]): TAttr }, - pokemon: Pokemon, applyFunc: AbAttrApplyFunc, isAsync?: boolean, showAbilityInstant?: boolean): Promise { + pokemon: Pokemon, applyFunc: AbAttrApplyFunc, isAsync: boolean = false, showAbilityInstant: boolean = false, quiet: boolean = false): Promise { return new Promise(resolve => { if (!pokemon.canApplyAbility()) return resolve(); @@ -1126,18 +1134,20 @@ function applyAbAttrsInternal(attrType: { new(...args: any return applyNextAbAttr(); pokemon.scene.setPhaseQueueSplice(); const onApplySuccess = () => { - if (attr.showAbility) { + if (attr.showAbility && !quiet) { if (showAbilityInstant) pokemon.scene.abilityBar.showAbility(pokemon); else queueShowAbility(pokemon); } - const message = attr.getTriggerMessage(pokemon); - if (message) { - if (isAsync) - pokemon.scene.ui.showText(message, null, () => pokemon.scene.ui.showText(null, 0), null, true); - else - pokemon.scene.queueMessage(message); + if (!quiet) { + const message = attr.getTriggerMessage(pokemon); + if (message) { + if (isAsync) + pokemon.scene.ui.showText(message, null, () => pokemon.scene.ui.showText(null, 0), null, true); + else + pokemon.scene.queueMessage(message); + } } }; const result = applyFunc(attr); @@ -1163,7 +1173,8 @@ export function applyAbAttrs(attrType: { new(...args: any[]): AbAttr }, pokemon: export function applyPreDefendAbAttrs(attrType: { new(...args: any[]): PreDefendAbAttr }, pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, attr => attr.applyPreDefend(pokemon, attacker, move, cancelled, args)); + const simulated = args.length > 1 && args[1]; + return applyAbAttrsInternal(attrType, pokemon, attr => attr.applyPreDefend(pokemon, attacker, move, cancelled, args), false, false, simulated); } export function applyPostDefendAbAttrs(attrType: { new(...args: any[]): PostDefendAbAttr }, @@ -1928,7 +1939,7 @@ export function initAbilities() { new Ability(Abilities.SHADOW_SHIELD, "Shadow Shield (N)", "Reduces the amount of damage the Pokémon takes while its HP is full.", 7), new Ability(Abilities.PRISM_ARMOR, "Prism Armor (N)", "Reduces the power of supereffective attacks taken.", 7), new Ability(Abilities.NEUROFORCE, "Neuroforce", "Powers up moves that are super effective.", 7) - .attr(MovePowerBoostAbAttr, (user, target, move) => target.getAttackMoveEffectiveness(move.type) >= 2, 1.25), + .attr(MovePowerBoostAbAttr, (user, target, move) => target.getAttackTypeEffectiveness(move.type) >= 2, 1.25), new Ability(Abilities.INTREPID_SWORD, "Intrepid Sword (N)", "Boosts the Pokémon's Attack stat when the Pokémon enters a battle.", 8), new Ability(Abilities.DAUNTLESS_SHIELD, "Dauntless Shield (N)", "Boosts the Pokémon's Defense stat when the Pokémon enters a battle.", 8), new Ability(Abilities.LIBERO, "Libero (N)", "Changes the Pokémon's type to the type of the move it's about to use.", 8), diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index bff42d4eb85..bb231e80438 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -240,7 +240,7 @@ class StealthRockTag extends ArenaTrapTag { } activateTrap(pokemon: Pokemon): boolean { - const effectiveness = pokemon.getAttackMoveEffectiveness(Type.ROCK); + const effectiveness = pokemon.getAttackTypeEffectiveness(Type.ROCK); let damageHpRatio: number; diff --git a/src/data/move.ts b/src/data/move.ts index 2556446a8ba..8eda1c3bb54 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -282,7 +282,7 @@ export class AttackMove extends Move { let attackScore = 0; - const effectiveness = target.getAttackMoveEffectiveness(this.type); + const effectiveness = target.getAttackTypeEffectiveness(this.type); attackScore = Math.pow(effectiveness - 1, 2) * effectiveness < 1 ? -2 : 2; if (attackScore) { if (this.category === MoveCategory.PHYSICAL) { @@ -770,7 +770,7 @@ export class StatusEffectAttr extends MoveEffectAttr { } getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { - return !(this.selfTarget ? user : target).status && target.getAttackMoveEffectiveness(move.type) ? Math.floor(move.chance * -0.1) : 0; + return !(this.selfTarget ? user : target).status && target.getAttackTypeEffectiveness(move.type) ? Math.floor(move.chance * -0.1) : 0; } } diff --git a/src/pokemon.ts b/src/pokemon.ts index 07aa6280a95..8b91a9e92e7 100644 --- a/src/pokemon.ts +++ b/src/pokemon.ts @@ -2,7 +2,7 @@ import Phaser from 'phaser'; import BattleScene, { AnySound } from './battle-scene'; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from './ui/battle-info'; import { Moves } from "./data/enums/moves"; -import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, StatusEffectAttr } from "./data/move"; +import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, StatusEffectAttr, AttackMove } from "./data/move"; import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies } from './data/pokemon-species'; import * as Utils from './utils'; import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from './data/type'; @@ -707,7 +707,18 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return this.getTeraType() !== Type.UNKNOWN; } - getAttackMoveEffectiveness(moveType: Type): TypeDamageMultiplier { + getAttackMoveEffectiveness(source: Pokemon, move: PokemonMove): TypeDamageMultiplier { + const typeless = !!move.getMove().getAttrs(TypelessAttr).length; + const typeMultiplier = new Utils.NumberHolder(this.getAttackTypeEffectiveness(move.getMove().type)); + const cancelled = new Utils.BooleanHolder(false); + if (!typeless) + applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, typeMultiplier, true); + if (!cancelled.value) + applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, typeMultiplier, true); + return (!cancelled.value ? typeMultiplier.value : 0) as TypeDamageMultiplier; + } + + getAttackTypeEffectiveness(moveType: Type): TypeDamageMultiplier { if (moveType === Type.STELLAR) return this.isTerastallized() ? 2 : 1; const types = this.getTypes(true); @@ -718,12 +729,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const types = this.getTypes(true); const enemyTypes = pokemon.getTypes(true); const outspeed = (this.isActive(true) ? this.getBattleStat(Stat.SPD, pokemon) : this.getStat(Stat.SPD)) <= pokemon.getBattleStat(Stat.SPD, this); - let atkScore = pokemon.getAttackMoveEffectiveness(types[0]) * (outspeed ? 1.25 : 1); - let defScore = 1 / Math.max(this.getAttackMoveEffectiveness(enemyTypes[0]), 0.25); + let atkScore = pokemon.getAttackTypeEffectiveness(types[0]) * (outspeed ? 1.25 : 1); + let defScore = 1 / Math.max(this.getAttackTypeEffectiveness(enemyTypes[0]), 0.25); if (types.length > 1) - atkScore *= pokemon.getAttackMoveEffectiveness(types[1]); + atkScore *= pokemon.getAttackTypeEffectiveness(types[1]); if (enemyTypes.length > 1) - defScore *= (1 / this.getAttackMoveEffectiveness(enemyTypes[1])); + defScore *= (1 / this.getAttackTypeEffectiveness(enemyTypes[1])); let hpDiffRatio = this.getHpRatio() + (1 - pokemon.getHpRatio()); if (outspeed) hpDiffRatio = Math.min(hpDiffRatio * 1.5, 1); @@ -2204,7 +2215,7 @@ export class EnemyPokemon extends Pokemon { const target = this.scene.getField()[mt]; let targetScore = move.getUserBenefitScore(this, target, move) + move.getTargetBenefitScore(this, target, move) * (mt < BattlerIndex.ENEMY === this.isPlayer() ? 1 : -1); if (mt !== this.getBattlerIndex()) - targetScore *= target.getAttackMoveEffectiveness(move.type); + targetScore *= target.getAttackMoveEffectiveness(this, pokemonMove); targetScores.push(targetScore); } diff --git a/src/ui/target-select-ui-handler.ts b/src/ui/target-select-ui-handler.ts index 96cff1716b6..f8a7c9d28a3 100644 --- a/src/ui/target-select-ui-handler.ts +++ b/src/ui/target-select-ui-handler.ts @@ -1,9 +1,10 @@ import { BattlerIndex } from "../battle"; import BattleScene, { Button } from "../battle-scene"; -import { Moves, getMoveTargets } from "../data/move"; +import { Moves } from "../data/enums/moves"; import { Mode } from "./ui"; import UiHandler from "./ui-handler"; import * as Utils from "../utils"; +import { getMoveTargets } from "../data/move"; export type TargetSelectCallback = (cursor: integer) => void;