Implement some abilities

pull/14/head
Flashfyre 2023-12-22 01:16:56 -05:00
parent 4a575a45a9
commit 7deab1c545
5 changed files with 149 additions and 26 deletions

View File

@ -25,7 +25,7 @@ import { Gender } from "./data/gender";
import { Weather, WeatherType, getRandomWeatherType, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather";
import { TempBattleStat } from "./data/temp-battle-stat";
import { ArenaTagType, ArenaTrapTag, TrickRoomTag } from "./data/arena-tag";
import { Abilities, CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreWeatherEffectAbAttrs } from "./data/ability";
import { Abilities, CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, PostAttackAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreWeatherEffectAbAttrs } from "./data/ability";
import { Unlockables, getUnlockableName } from "./system/unlockables";
import { getBiomeKey } from "./arena";
import { BattleType, BattlerIndex, TurnCommand } from "./battle";
@ -1744,6 +1744,7 @@ export class MoveEffectPhase extends PokemonPhase {
if (!user.isPlayer() && this.move instanceof AttackMove)
user.scene.applyModifiers(EnemyAttackStatusEffectChanceModifier, false, target);
}
applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move, hitResult);
if (this.move instanceof AttackMove)
this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target.getFieldIndex());
})

View File

@ -10,6 +10,7 @@ import { StatusEffect, getStatusEffectDescriptor } from "./status-effect";
import { MoveFlags, Moves, RecoilAttr } from "./move";
import { ArenaTagType } from "./arena-tag";
import { Stat } from "./pokemon-stat";
import { PokemonHeldItemModifier } from "../modifier/modifier";
export class Ability {
public id: Abilities;
@ -96,6 +97,8 @@ export class DoubleBattleChanceAbAttr extends AbAttr {
}
}
type PreDefendAbAttrCondition = (pokemon: Pokemon, attacker: Pokemon, move: PokemonMove) => boolean;
export class PreDefendAbAttr extends AbAttr {
applyPreDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
return false;
@ -260,6 +263,29 @@ export class PostDefendAbAttr extends AbAttr {
}
}
export class MoveImmunityAbAttr extends PreDefendAbAttr {
private immuneCondition: PreDefendAbAttrCondition;
constructor(immuneCondition: PreDefendAbAttrCondition) {
super(true);
this.immuneCondition = immuneCondition;
}
applyPreDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.immuneCondition(pokemon, attacker, move)) {
cancelled.value = true;
return true;
}
return false;
}
getTriggerMessage(pokemon: Pokemon, ...args: any[]): string {
return `It doesn\'t affect ${pokemon.name}!`;
}
}
export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr {
applyPostDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (hitResult < HitResult.NO_EFFECT) {
@ -433,6 +459,35 @@ export class BattleStatMultiplierAbAttr extends AbAttr {
}
}
export class PostAttackAbAttr extends AbAttr {
applyPostAttack(pokemon: Pokemon, defender: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
return false;
}
}
export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
applyPostAttack(pokemon: Pokemon, defender: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (hitResult < HitResult.NO_EFFECT) {
const heldItems = this.getTargetHeldItems(defender).filter(i => i.getTransferrable(false));
if (heldItems.length) {
const stolenItem = heldItems[Utils.randInt(heldItems.length)];
// TODO: Add support for promises
pokemon.scene.tryTransferHeldItemModifier(stolenItem, pokemon, false, false).then(success => {
if (success)
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` stole\n${defender.name}'s ${stolenItem.type.name}!`));
});
return true;
}
}
return false;
}
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
return target.scene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& (m as PokemonHeldItemModifier).pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[];
}
}
export class IgnoreOpponentStatChangesAbAttr extends AbAttr {
constructor() {
super(false);
@ -779,9 +834,21 @@ export class PostTurnAbAttr extends AbAttr {
}
}
export class PostTurnSpeedBoostAbAttr extends PostTurnAbAttr {
export class PostTurnStatChangeAbAttr extends PostTurnAbAttr {
private stats: BattleStat[];
private levels: integer;
constructor(stats: BattleStat | BattleStat[], levels: integer) {
super(true);
this.stats = Array.isArray(stats)
? stats
: [ stats ];
this.levels = levels;
}
applyPostTurn(pokemon: Pokemon, args: any[]): boolean {
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ BattleStat.SPD ], 1));
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.levels));
return true;
}
}
@ -815,6 +882,14 @@ export class StatChangeMultiplierAbAttr extends AbAttr {
}
}
export class DoubleBerryEffectAbAttr extends AbAttr {
apply(pokemon: Pokemon, cancelled: Utils.BooleanHolder, args: any[]): boolean {
(args[0] as Utils.NumberHolder).value *= 2;
return true;
}
}
export class CheckTrappedAbAttr extends AbAttr {
applyCheckTrapped(pokemon: Pokemon, trapped: Utils.BooleanHolder, args: any[]): boolean {
return false;
@ -963,6 +1038,29 @@ export function applyPreAttackAbAttrs(attrType: { new(...args: any[]): PreAttack
pokemon.scene.clearPhaseQueueSplice();
}
export function applyPostAttackAbAttrs(attrType: { new(...args: any[]): PostAttackAbAttr },
pokemon: Pokemon, defender: Pokemon, move: PokemonMove, hitResult: HitResult, ...args: any[]): void {
if (!pokemon.canApplyAbility())
return;
const ability = pokemon.getAbility();
const attrs = ability.getAttrs(attrType) as PostAttackAbAttr[];
for (let attr of attrs) {
if (!canApplyAttr(pokemon, attr))
continue;
pokemon.scene.setPhaseQueueSplice();
if (attr.applyPostAttack(pokemon, defender, move, hitResult, args)) {
if (attr.showAbility)
queueShowAbility(pokemon);
const message = attr.getTriggerMessage(pokemon, defender, move);
if (message)
pokemon.scene.queueMessage(message);
}
}
pokemon.scene.clearPhaseQueueSplice();
}
export function applyPostSummonAbAttrs(attrType: { new(...args: any[]): PostSummonAbAttr },
pokemon: Pokemon, ...args: any[]): void {
if (!pokemon.canApplyAbility())
@ -1479,7 +1577,7 @@ export function initAbilities() {
new Ability(Abilities.DRIZZLE, "Drizzle", "The Pokémon makes it rain when it enters a battle.", 3)
.attr(PostSummonWeatherChangeAbAttr, WeatherType.RAIN),
new Ability(Abilities.SPEED_BOOST, "Speed Boost", "Its Speed stat is boosted every turn.", 3)
.attr(PostTurnSpeedBoostAbAttr),
.attr(PostTurnStatChangeAbAttr, BattleStat.SPD, 1),
new Ability(Abilities.BATTLE_ARMOR, "Battle Armor", "Hard armor protects the Pokémon from critical hits.", 3)
.attr(BlockCritAbAttr),
new Ability(Abilities.STURDY, "Sturdy (N)", "It cannot be knocked out with one hit. One-hit KO moves cannot knock it out, either.", 3),
@ -1555,7 +1653,8 @@ export function initAbilities() {
new Ability(Abilities.MAGNET_PULL, "Magnet Pull", "Prevents Steel-type Pokémon from escaping using its magnetic force.", 3)
/*.attr(ArenaTrapAbAttr)
.condition((pokemon: Pokemon) => pokemon.getOpponent()?.isOfType(Type.STEEL))*/,
new Ability(Abilities.SOUNDPROOF, "Soundproof (N)", "Soundproofing gives the Pokémon full immunity to all sound-based moves.", 3),
new Ability(Abilities.SOUNDPROOF, "Soundproof", "Soundproofing gives the Pokémon full immunity to all sound-based moves.", 3)
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.getMove().hasFlag(MoveFlags.SOUND_BASED)),
new Ability(Abilities.RAIN_DISH, "Rain Dish", "The Pokémon gradually regains HP in rain.", 3)
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.RAIN, WeatherType.HEAVY_RAIN),
new Ability(Abilities.SAND_STREAM, "Sand Stream", "The Pokémon summons a sandstorm when it enters a battle.", 3)
@ -1703,7 +1802,9 @@ export function initAbilities() {
new Ability(Abilities.FLARE_BOOST, "Flare Boost (N)", "Powers up special attacks when the Pokémon is burned.", 5),
new Ability(Abilities.HARVEST, "Harvest (N)", "May create another Berry after one is used.", 5),
new Ability(Abilities.TELEPATHY, "Telepathy (N)", "Anticipates an ally's attack and dodges it.", 5),
new Ability(Abilities.MOODY, "Moody (N)", "Raises one stat sharply and lowers another every turn.", 5),
new Ability(Abilities.MOODY, "Moody", "Raises one stat sharply and lowers another every turn.", 5)
.attr(PostTurnStatChangeAbAttr, BattleStat.RAND, 2)
.attr(PostTurnStatChangeAbAttr, BattleStat.RAND, -1),
new Ability(Abilities.OVERCOAT, "Overcoat", "Protects the Pokémon from things like sand, hail, and powder.", 5)
.attr(BlockWeatherDamageAttr),
new Ability(Abilities.POISON_TOUCH, "Poison Touch", "May poison a target when the Pokémon makes contact.", 5)
@ -1740,8 +1841,10 @@ export function initAbilities() {
new Ability(Abilities.CHEEK_POUCH, "Cheek Pouch (N)", "Restores HP as well when the Pokémon eats a Berry.", 6),
new Ability(Abilities.PROTEAN, "Protean (N)", "Changes the Pokémon's type to the type of the move it's about to use.", 6),
new Ability(Abilities.FUR_COAT, "Fur Coat (N)", "Halves the damage from physical moves.", 6),
new Ability(Abilities.MAGICIAN, "Magician (N)", "The Pokémon steals the held item of a Pokémon it hits with a move.", 6),
new Ability(Abilities.BULLETPROOF, "Bulletproof (N)", "Protects the Pokémon from some ball and bomb moves.", 6),
new Ability(Abilities.MAGICIAN, "Magician", "The Pokémon steals the held item of a Pokémon it hits with a move.", 6)
.attr(PostAttackStealHeldItemAbAttr),
new Ability(Abilities.BULLETPROOF, "Bulletproof", "Protects the Pokémon from some ball and bomb moves.", 6)
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.getMove().hasFlag(MoveFlags.BALLBOMB_MOVE)),
new Ability(Abilities.COMPETITIVE, "Competitive (N)", "Boosts the Sp. Atk stat sharply when a stat is lowered.", 6),
new Ability(Abilities.STRONG_JAW, "Strong Jaw (N)", "The Pokémon's strong jaw boosts the power of its biting moves.", 6),
new Ability(Abilities.REFRIGERATE, "Refrigerate (N)", "Normal-type moves become Ice-type moves. The power of those moves is boosted a little.", 6),
@ -1824,7 +1927,8 @@ export function initAbilities() {
new Ability(Abilities.PUNK_ROCK, "Punk Rock (N)", "Boosts the power of sound-based moves. The Pokémon also takes half the damage from these kinds of moves.", 8),
new Ability(Abilities.SAND_SPIT, "Sand Spit (N)", "The Pokémon creates a sandstorm when it's hit by an attack.", 8),
new Ability(Abilities.ICE_SCALES, "Ice Scales (N)", "The Pokémon is protected by ice scales, which halve the damage taken from special moves.", 8),
new Ability(Abilities.RIPEN, "Ripen (N)", "Ripens Berries and doubles their effect.", 8),
new Ability(Abilities.RIPEN, "Ripen", "Ripens Berries and doubles their effect.", 8)
.attr(DoubleBerryEffectAbAttr),
new Ability(Abilities.ICE_FACE, "Ice Face (N)", "The Pokémon's ice head can take a physical attack as a substitute, but the attack also changes the Pokémon's appearance. The ice will be restored when it hails.", 8),
new Ability(Abilities.POWER_SPOT, "Power Spot (N)", "Just being next to the Pokémon powers up moves.", 8),
new Ability(Abilities.MIMICRY, "Mimicry (N)", "Changes the Pokémon's type depending on the terrain.", 8),
@ -1834,7 +1938,8 @@ export function initAbilities() {
new Ability(Abilities.WANDERING_SPIRIT, "Wandering Spirit (N)", "The Pokémon exchanges Abilities with a Pokémon that hits it with a move that makes direct contact.", 8),
new Ability(Abilities.GORILLA_TACTICS, "Gorilla Tactics (N)", "Boosts the Pokémon's Attack stat but only allows the use of the first selected move.", 8),
new Ability(Abilities.NEUTRALIZING_GAS, "Neutralizing Gas (N)", "If the Pokémon with Neutralizing Gas is in the battle, the effects of all Pokémon's Abilities will be nullified or will not be triggered.", 8),
new Ability(Abilities.PASTEL_VEIL, "Pastel Veil (N)", "Protects the Pokémon and its ally Pokémon from being poisoned.", 8),
new Ability(Abilities.PASTEL_VEIL, "Pastel Veil", "Protects the Pokémon and its ally Pokémon from being poisoned.", 8)
.attr(StatusEffectImmunityAbAttr, StatusEffect.POISON),
new Ability(Abilities.HUNGER_SWITCH, "Hunger Switch (N)", "The Pokémon changes its form, alternating between its Full Belly Mode and Hangry Mode after the end of each turn.", 8),
new Ability(Abilities.QUICK_DRAW, "Quick Draw (N)", "Enables the Pokémon to move first occasionally.", 8),
new Ability(Abilities.UNSEEN_FIST, "Unseen Fist (N)", "If the Pokémon uses moves that make direct contact, it can attack the target even if the target protects itself.", 8),

View File

@ -6,6 +6,7 @@ import { BattleStat } from "./battle-stat";
import { BattlerTagType } from "./battler-tag";
import { getStatusEffectHealText } from "./status-effect";
import * as Utils from "../utils";
import { DoubleBerryEffectAbAttr, applyAbAttrs } from "./ability";
export enum BerryType {
SITRUS,
@ -77,10 +78,12 @@ export type BerryEffectFunc = (pokemon: Pokemon) => void;
export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
switch (berryType) {
case BerryType.SITRUS:
case BerryType.ENIGMA:
case BerryType.ENIGMA:
return (pokemon: Pokemon) => {
const hpHealed = new Utils.NumberHolder(Math.floor(pokemon.getMaxHp() / 4));
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, hpHealed);
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(),
Math.floor(pokemon.getMaxHp() / 4), getPokemonMessage(pokemon, `'s ${getBerryName(berryType)}\nrestored its HP!`), true));
hpHealed.value, getPokemonMessage(pokemon, `'s ${getBerryName(berryType)}\nrestored its HP!`), true));
};
case BerryType.LUM:
return (pokemon: Pokemon) => {
@ -98,13 +101,19 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
case BerryType.SALAC:
return (pokemon: Pokemon) => {
const battleStat = (berryType - BerryType.LIECHI) as BattleStat;
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ battleStat ], 1));
const statLevels = new Utils.NumberHolder(1);
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, statLevels);
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ battleStat ], statLevels.value));
};
case BerryType.LANSAT:
return (pokemon: Pokemon) => {
pokemon.addTag(BattlerTagType.CRIT_BOOST);
};
case BerryType.STARF:
return (pokemon: Pokemon) => pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ BattleStat.RAND ], 2));
return (pokemon: Pokemon) => {
const statLevels = new Utils.NumberHolder(2);
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, statLevels);
return pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ BattleStat.RAND ], statLevels.value));
};
}
}

View File

@ -1677,17 +1677,21 @@ export class StealHeldItemAttr extends MoveEffectAttr {
super(false, MoveEffectTrigger.HIT);
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const heldItems = this.getTargetHeldItems(target).filter(i => i.getTransferrable(false));
if (heldItems.length) {
const stolenItem = heldItems[Utils.randInt(heldItems.length)];
user.scene.tryTransferHeldItemModifier(stolenItem, user, false, false);
// Assumes the transfer was successful
user.scene.queueMessage(getPokemonMessage(user, ` stole\n${target.name}'s ${stolenItem.type.name}!`));
return true;
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
return new Promise<boolean>(resolve => {
const heldItems = this.getTargetHeldItems(target).filter(i => i.getTransferrable(false));
if (heldItems.length) {
const stolenItem = heldItems[Utils.randInt(heldItems.length)];
user.scene.tryTransferHeldItemModifier(stolenItem, user, false, false).then(success => {
if (success)
user.scene.queueMessage(getPokemonMessage(user, ` stole\n${target.name}'s ${stolenItem.type.name}!`));
resolve(success);
});
return;
}
return false;
resolve(false);
});
}
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {

View File

@ -22,7 +22,7 @@ import { WeatherType } from './data/weather';
import { TempBattleStat } from './data/temp-battle-stat';
import { ArenaTagType, WeakenMoveTypeTag } from './data/arena-tag';
import { Biome } from './data/biome';
import { Abilities, Ability, BattleStatMultiplierAbAttr, BlockCritAbAttr, IgnoreOpponentStatChangesAbAttr, NonSuperEffectiveImmunityAbAttr, PreApplyBattlerTagAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPostDefendAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs } from './data/ability';
import { Abilities, Ability, BattleStatMultiplierAbAttr, BlockCritAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, NonSuperEffectiveImmunityAbAttr, PreApplyBattlerTagAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPostDefendAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs } from './data/ability';
import PokemonData from './system/pokemon-data';
import { BattlerIndex } from './battle';
import { Mode } from './ui/ui';
@ -889,6 +889,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (!typeless)
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier);
if (!cancelled.value)
applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier);
if (cancelled.value)
result = HitResult.NO_EFFECT;
@ -1009,6 +1011,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
case MoveCategory.STATUS:
if (!typeless)
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier);
if (!cancelled.value)
applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier);
result = cancelled.value || !typeMultiplier.value ? HitResult.NO_EFFECT : HitResult.STATUS;
break;
}
@ -2109,8 +2113,8 @@ export enum HitResult {
EFFECTIVE = 1,
SUPER_EFFECTIVE,
NOT_VERY_EFFECTIVE,
NO_EFFECT,
ONE_HIT_KO,
NO_EFFECT,
STATUS,
FAIL,
MISS,