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 {
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;
}

View File

@ -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<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 => {
if (!pokemon.canApplyAbility())
return resolve();
@ -1126,18 +1134,20 @@ function applyAbAttrsInternal<TAttr extends AbAttr>(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<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 },
@ -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),

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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;