Factor type immunity abilities into enemy AI

pull/16/head
Flashfyre 2024-02-28 11:34:55 -05:00
parent be405f61dd
commit fdff3a549c
6 changed files with 51 additions and 28 deletions

View File

@ -100,7 +100,7 @@ export default class Battle {
randSeedGaussForLevel(value: number): number { randSeedGaussForLevel(value: number): number {
let rand = 0; let rand = 0;
for (var i = value; i > 0; i--) for (let i = value; i > 0; i--)
rand += Phaser.Math.RND.realInRange(0, 1); rand += Phaser.Math.RND.realInRange(0, 1);
return rand / value; return rand / value;
} }

View File

@ -213,9 +213,13 @@ export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr {
const ret = super.applyPreDefend(pokemon, attacker, move, cancelled, args); const ret = super.applyPreDefend(pokemon, attacker, move, cancelled, args);
if (ret) { if (ret) {
if (pokemon.getHpRatio() < 1) if (pokemon.getHpRatio() < 1) {
const simulated = args.length > 1 && args[1];
if (!simulated) {
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), 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)); Math.max(Math.floor(pokemon.getMaxHp() / 4), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true));
}
}
return true; return true;
} }
@ -239,6 +243,8 @@ class TypeImmunityStatChangeAbAttr extends TypeImmunityAbAttr {
if (ret) { if (ret) {
cancelled.value = true; cancelled.value = true;
const simulated = args.length > 1 && args[1];
if (!simulated)
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.levels)); pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.levels));
} }
@ -262,6 +268,8 @@ class TypeImmunityAddBattlerTagAbAttr extends TypeImmunityAbAttr {
if (ret) { if (ret) {
cancelled.value = true; cancelled.value = true;
const simulated = args.length > 1 && args[1];
if (!simulated)
pokemon.addTag(this.tagType, this.turnCount, undefined, pokemon.id); pokemon.addTag(this.tagType, this.turnCount, undefined, pokemon.id);
} }
@ -275,7 +283,7 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr {
} }
applyPreDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { 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; cancelled.value = true;
(args[0] as Utils.NumberHolder).value = 0; (args[0] as Utils.NumberHolder).value = 0;
return true; return true;
@ -1103,7 +1111,7 @@ export class SyncEncounterNatureAbAttr extends AbAttr {
} }
function applyAbAttrsInternal<TAttr extends AbAttr>(attrType: { new(...args: any[]): TAttr }, function applyAbAttrsInternal<TAttr extends AbAttr>(attrType: { new(...args: any[]): TAttr },
pokemon: Pokemon, applyFunc: AbAttrApplyFunc<TAttr>, isAsync?: boolean, showAbilityInstant?: boolean): Promise<void> { pokemon: Pokemon, applyFunc: AbAttrApplyFunc<TAttr>, isAsync: boolean = false, showAbilityInstant: boolean = false, quiet: boolean = false): Promise<void> {
return new Promise(resolve => { return new Promise(resolve => {
if (!pokemon.canApplyAbility()) if (!pokemon.canApplyAbility())
return resolve(); return resolve();
@ -1126,12 +1134,13 @@ function applyAbAttrsInternal<TAttr extends AbAttr>(attrType: { new(...args: any
return applyNextAbAttr(); return applyNextAbAttr();
pokemon.scene.setPhaseQueueSplice(); pokemon.scene.setPhaseQueueSplice();
const onApplySuccess = () => { const onApplySuccess = () => {
if (attr.showAbility) { if (attr.showAbility && !quiet) {
if (showAbilityInstant) if (showAbilityInstant)
pokemon.scene.abilityBar.showAbility(pokemon); pokemon.scene.abilityBar.showAbility(pokemon);
else else
queueShowAbility(pokemon); queueShowAbility(pokemon);
} }
if (!quiet) {
const message = attr.getTriggerMessage(pokemon); const message = attr.getTriggerMessage(pokemon);
if (message) { if (message) {
if (isAsync) if (isAsync)
@ -1139,6 +1148,7 @@ function applyAbAttrsInternal<TAttr extends AbAttr>(attrType: { new(...args: any
else else
pokemon.scene.queueMessage(message); pokemon.scene.queueMessage(message);
} }
}
}; };
const result = applyFunc(attr); const result = applyFunc(attr);
if (result instanceof Promise) { if (result instanceof Promise) {
@ -1163,7 +1173,8 @@ export function applyAbAttrs(attrType: { new(...args: any[]): AbAttr }, pokemon:
export function applyPreDefendAbAttrs(attrType: { new(...args: any[]): PreDefendAbAttr }, export function applyPreDefendAbAttrs(attrType: { new(...args: any[]): PreDefendAbAttr },
pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> { pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PreDefendAbAttr>(attrType, pokemon, attr => attr.applyPreDefend(pokemon, attacker, move, cancelled, args)); const simulated = args.length > 1 && args[1];
return applyAbAttrsInternal<PreDefendAbAttr>(attrType, pokemon, attr => attr.applyPreDefend(pokemon, attacker, move, cancelled, args), false, false, simulated);
} }
export function applyPostDefendAbAttrs(attrType: { new(...args: any[]): PostDefendAbAttr }, 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.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.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) 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.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.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), new Ability(Abilities.LIBERO, "Libero (N)", "Changes the Pokémon's type to the type of the move it's about to use.", 8),

View File

@ -240,7 +240,7 @@ class StealthRockTag extends ArenaTrapTag {
} }
activateTrap(pokemon: Pokemon): boolean { activateTrap(pokemon: Pokemon): boolean {
const effectiveness = pokemon.getAttackMoveEffectiveness(Type.ROCK); const effectiveness = pokemon.getAttackTypeEffectiveness(Type.ROCK);
let damageHpRatio: number; let damageHpRatio: number;

View File

@ -282,7 +282,7 @@ export class AttackMove extends Move {
let attackScore = 0; 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; attackScore = Math.pow(effectiveness - 1, 2) * effectiveness < 1 ? -2 : 2;
if (attackScore) { if (attackScore) {
if (this.category === MoveCategory.PHYSICAL) { if (this.category === MoveCategory.PHYSICAL) {
@ -770,7 +770,7 @@ export class StatusEffectAttr extends MoveEffectAttr {
} }
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { 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;
} }
} }

View File

@ -2,7 +2,7 @@ import Phaser from 'phaser';
import BattleScene, { AnySound } from './battle-scene'; import BattleScene, { AnySound } from './battle-scene';
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from './ui/battle-info'; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from './ui/battle-info';
import { Moves } from "./data/enums/moves"; 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 { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies } from './data/pokemon-species';
import * as Utils from './utils'; import * as Utils from './utils';
import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from './data/type'; 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; 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) if (moveType === Type.STELLAR)
return this.isTerastallized() ? 2 : 1; return this.isTerastallized() ? 2 : 1;
const types = this.getTypes(true); const types = this.getTypes(true);
@ -718,12 +729,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const types = this.getTypes(true); const types = this.getTypes(true);
const enemyTypes = pokemon.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); 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 atkScore = pokemon.getAttackTypeEffectiveness(types[0]) * (outspeed ? 1.25 : 1);
let defScore = 1 / Math.max(this.getAttackMoveEffectiveness(enemyTypes[0]), 0.25); let defScore = 1 / Math.max(this.getAttackTypeEffectiveness(enemyTypes[0]), 0.25);
if (types.length > 1) if (types.length > 1)
atkScore *= pokemon.getAttackMoveEffectiveness(types[1]); atkScore *= pokemon.getAttackTypeEffectiveness(types[1]);
if (enemyTypes.length > 1) if (enemyTypes.length > 1)
defScore *= (1 / this.getAttackMoveEffectiveness(enemyTypes[1])); defScore *= (1 / this.getAttackTypeEffectiveness(enemyTypes[1]));
let hpDiffRatio = this.getHpRatio() + (1 - pokemon.getHpRatio()); let hpDiffRatio = this.getHpRatio() + (1 - pokemon.getHpRatio());
if (outspeed) if (outspeed)
hpDiffRatio = Math.min(hpDiffRatio * 1.5, 1); hpDiffRatio = Math.min(hpDiffRatio * 1.5, 1);
@ -2204,7 +2215,7 @@ export class EnemyPokemon extends Pokemon {
const target = this.scene.getField()[mt]; 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); let targetScore = move.getUserBenefitScore(this, target, move) + move.getTargetBenefitScore(this, target, move) * (mt < BattlerIndex.ENEMY === this.isPlayer() ? 1 : -1);
if (mt !== this.getBattlerIndex()) if (mt !== this.getBattlerIndex())
targetScore *= target.getAttackMoveEffectiveness(move.type); targetScore *= target.getAttackMoveEffectiveness(this, pokemonMove);
targetScores.push(targetScore); targetScores.push(targetScore);
} }

View File

@ -1,9 +1,10 @@
import { BattlerIndex } from "../battle"; import { BattlerIndex } from "../battle";
import BattleScene, { Button } from "../battle-scene"; import BattleScene, { Button } from "../battle-scene";
import { Moves, getMoveTargets } from "../data/move"; import { Moves } from "../data/enums/moves";
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 { getMoveTargets } from "../data/move";
export type TargetSelectCallback = (cursor: integer) => void; export type TargetSelectCallback = (cursor: integer) => void;