Implement some abilities and attract move

pull/1/head
Flashfyre 2023-05-04 12:57:55 -04:00
parent 884c5cc40f
commit d1d65708ce
8 changed files with 426 additions and 40 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 { ArenaTrapTag, TrickRoomTag } from "./data/arena-tag";
import { ArenaTrapAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreWeatherDamageAbAttr, ProtectStatAttr, SuppressWeatherEffectAbAttr, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreWeatherEffectAbAttrs } from "./data/ability";
import { CheckTrappedAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreWeatherDamageAbAttr, ProtectStatAttr, SuppressWeatherEffectAbAttr, applyCheckTrappedAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreWeatherEffectAbAttrs } from "./data/ability";
import { Unlockables, getUnlockableName } from "./system/unlockables";
export class CheckLoadPhase extends BattlePhase {
@ -678,13 +678,15 @@ export class CommandPhase extends FieldPhase {
break;
case Command.POKEMON:
const trapTag = playerPokemon.findTag(t => t instanceof TrappedTag) as TrappedTag;
const arenaTrapped = !!enemyPokemon.getAbility().hasAttr(ArenaTrapAbAttr);
const trapped = new Utils.BooleanHolder(false);
const batonPass = args[0] as boolean;
if (batonPass || (!trapTag && !arenaTrapped)) {
if (!batonPass)
applyCheckTrappedAbAttrs(CheckTrappedAbAttr, enemyPokemon, trapped);
if (batonPass || (!trapTag && !trapped.value)) {
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, cursor, true, args[0] as boolean));
success = true;
} else
this.scene.ui.showText(`${this.scene.getPokemonById(trapTag.sourceId).name}'s ${trapTag?.getMoveName() || enemyPokemon.getAbility().name}\nprevents switching!`, null, () => {
} else if (trapTag)
this.scene.ui.showText(`${this.scene.getPokemonById(trapTag.sourceId).name}'s ${trapTag.getMoveName()}\nprevents switching!`, null, () => {
this.scene.ui.showText(null, 0);
}, null, true);
break;
@ -877,7 +879,7 @@ export abstract class MovePhase extends BattlePhase {
console.log(Moves[this.move.moveId]);
const target = this.pokemon.isPlayer() ? this.scene.getEnemyPokemon() : this.scene.getPlayerPokemon();
const target = this.pokemon.getOpponent();
if (!this.followUp && this.canMove())
this.pokemon.lapseTags(BattlerTagLapseType.MOVE);
@ -1043,8 +1045,11 @@ abstract class MoveEffectPhase extends PokemonPhase {
target.addTag(BattlerTagType.FLINCHED, undefined, this.move.moveId, user.id);
}
// Charge attribute with charge effect takes all effect attributes and applies them to charge stage, so ignore them if this is present
if (!isProtected && !this.move.getMove().getAttrs(ChargeAttr).filter(ca => (ca as ChargeAttr).chargeEffect).length)
if (!isProtected && !this.move.getMove().getAttrs(ChargeAttr).filter(ca => (ca as ChargeAttr).chargeEffect).length) {
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveHitEffectAttr && (!!target.hp || (attr as MoveHitEffectAttr).selfTarget), user, target, this.move.getMove());
if (target.hp)
applyPostDefendAbAttrs(PostDefendAbAttr, user, target, this.move, result);
}
}
this.end();
});

View File

@ -1,13 +1,13 @@
import Pokemon, { MoveResult, PokemonMove } from "../pokemon";
import { Type, getTypeDamageMultiplier } from "./type";
import { Type } from "./type";
import * as Utils from "../utils";
import { BattleStat, getBattleStatName } from "./battle-stat";
import { DamagePhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase } from "../battle-phases";
import { getPokemonMessage } from "../messages";
import { Weather, WeatherType } from "./weather";
import { BattlerTagType } from "./battler-tag";
import { StatusEffect } from "./status-effect";
import { Moves, RecoilAttr, WeatherHealAttr } from "./move";
import { BattlerTag, BattlerTagType, TrappedTag } from "./battler-tag";
import { StatusEffect, getStatusEffectDescriptor } from "./status-effect";
import { MoveFlags, Moves, RecoilAttr } from "./move";
export class Ability {
public id: Abilities;
@ -212,6 +212,54 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr {
}
}
export class PostDefendAbAttr extends AbAttr {
applyPostDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, moveResult: MoveResult, args: any[]): boolean {
return false;
}
}
export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr {
private chance: integer;
private effects: StatusEffect[];
constructor(chance: integer, ...effects: StatusEffect[]) {
super();
this.chance = chance;
this.effects = effects;
}
applyPostDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, moveResult: MoveResult, args: any[]): boolean {
if (move.getMove().hasFlag(MoveFlags.MAKES_CONTACT) && Utils.randInt(100) < this.chance) {
const effect = this.effects.length === 1 ? this.effects[0] : this.effects[Utils.randInt(this.effects.length)];
return attacker.trySetStatus(effect);
}
return false;
}
}
export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr {
private chance: integer;
private tagType: BattlerTagType;
private turnCount: integer;
constructor(chance: integer, tagType: BattlerTagType, turnCount?: integer) {
super();
this.tagType = tagType;
this.chance = chance;
this.turnCount = turnCount;
}
applyPostDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, moveResult: MoveResult, args: any[]): boolean {
if (move.getMove().hasFlag(MoveFlags.MAKES_CONTACT) && Utils.randInt(100) < this.chance)
return attacker.addTag(this.tagType, this.turnCount, move.moveId, pokemon.id);
return false;
}
}
export class PreAttackAbAttr extends AbAttr {
applyPreAttack(pokemon: Pokemon, defender: Pokemon, move: PokemonMove, args: any[]): boolean {
return false;
@ -282,6 +330,29 @@ export class BattleStatMultiplierAbAttr extends AbAttr {
}
}
export class PostSummonAbAttr extends AbAttr {
applyPostSummon(pokemon: Pokemon, args: any[]) {
return false;
}
}
export class PostSummonWeatherChangeAbAttr extends PostSummonAbAttr {
private weatherType: WeatherType;
constructor(weatherType: WeatherType) {
super();
this.weatherType = weatherType;
}
applyPostSummon(pokemon: Pokemon, args: any[]): boolean {
if (!pokemon.scene.arena.weather?.isImmutable())
return pokemon.scene.arena.trySetWeather(this.weatherType, false);
return false;
}
}
export class PreStatChangeAbAttr extends AbAttr {
applyPreStatChange(pokemon: Pokemon, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean {
return false;
@ -311,9 +382,65 @@ export class ProtectStatAttr extends PreStatChangeAbAttr {
}
}
export class BlockCritAbAttr extends AbAttr { }
export class PreSetStatusAbAttr extends AbAttr {
applyPreSetStatus(pokemon: Pokemon, effect: StatusEffect, cancelled: Utils.BooleanHolder, args: any[]): boolean {
return false;
}
}
export class ArenaTrapAbAttr extends AbAttr { }
export class StatusEffectImmunityAbAttr extends PreSetStatusAbAttr {
private immuneEffects: StatusEffect[];
constructor(...immuneEffects: StatusEffect[]) {
super();
this.immuneEffects = immuneEffects;
}
applyPreSetStatus(pokemon: Pokemon, effect: StatusEffect, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (!this.immuneEffects.length || this.immuneEffects.indexOf(effect) > -1) {
cancelled.value = true;
return true;
}
return false;
}
getTriggerMessage(pokemon: Pokemon, ...args: any[]): string {
return getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nprevents ${this.immuneEffects.length ? getStatusEffectDescriptor(args[0] as StatusEffect) : 'status problems'}!`);
}
}
export class PreApplyBattlerTagAbAttr extends AbAttr {
applyPreApplyBattlerTag(pokemon: Pokemon, tag: BattlerTag, cancelled: Utils.BooleanHolder, args: any[]): boolean {
return false;
}
}
export class BattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr {
private immuneTagType: BattlerTagType;
constructor(immuneTagType: BattlerTagType) {
super();
this.immuneTagType = immuneTagType;
}
applyPreApplyBattlerTag(pokemon: Pokemon, tag: BattlerTag, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (tag.tagType === this.immuneTagType) {
cancelled.value = true;
return true;
}
return false;
}
getTriggerMessage(pokemon: Pokemon, ...args: any[]): string {
return getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nprevents ${(args[0] as BattlerTag).getDescriptor()}!`);
}
}
export class BlockCritAbAttr extends AbAttr { }
export class PreWeatherEffectAbAttr extends AbAttr {
applyPreWeatherEffect(pokemon: Pokemon, weather: Weather, cancelled: Utils.BooleanHolder, args: any[]): boolean {
@ -453,6 +580,23 @@ export class PostWeatherLapseDamageAbAttr extends PostWeatherLapseAbAttr {
}
}
export class CheckTrappedAbAttr extends AbAttr {
applyCheckTrapped(pokemon: Pokemon, trapped: Utils.BooleanHolder, args: any[]): boolean {
return false;
}
}
export class ArenaTrapAbAttr extends CheckTrappedAbAttr {
applyCheckTrapped(pokemon: Pokemon, trapped: Utils.BooleanHolder, args: any[]): boolean {
trapped.value = true;
return true;
}
getTriggerMessage(pokemon: Pokemon, ...args: any[]): string {
return getPokemonMessage(pokemon, `\'s ${pokemon.getAbility().name}\nprevents switching!`);
}
}
export function applyAbAttrs(attrType: { new(...args: any[]): AbAttr }, pokemon: Pokemon, cancelled: Utils.BooleanHolder, ...args: any[]): void {
if (!pokemon.canApplyAbility())
return;
@ -497,6 +641,28 @@ export function applyPreDefendAbAttrs(attrType: { new(...args: any[]): PreDefend
pokemon.scene.clearPhaseQueueSplice();
}
export function applyPostDefendAbAttrs(attrType: { new(...args: any[]): PostDefendAbAttr },
pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, moveResult: MoveResult, ...args: any[]): void {
if (!pokemon.canApplyAbility())
return;
const ability = pokemon.getAbility();
const attrs = ability.getAttrs(attrType) as PostDefendAbAttr[];
for (let attr of attrs) {
if (!canApplyAttr(pokemon, attr))
continue;
pokemon.scene.setPhaseQueueSplice();
if (attr.applyPostDefend(pokemon, attacker, move, moveResult, args)) {
queueShowAbility(pokemon);
const message = attr.getTriggerMessage(pokemon, attacker, move);
if (message)
pokemon.scene.queueMessage(message);
}
}
pokemon.scene.clearPhaseQueueSplice();
}
export function applyBattleStatMultiplierAbAttrs(attrType: { new(...args: any[]): BattleStatMultiplierAbAttr },
pokemon: Pokemon, battleStat: BattleStat, statValue: Utils.NumberHolder, ...args: any[]) {
if (!pokemon.canApplyAbility())
@ -542,6 +708,28 @@ export function applyPreAttackAbAttrs(attrType: { new(...args: any[]): PreAttack
pokemon.scene.clearPhaseQueueSplice();
}
export function applyPostSummonAbAttrs(attrType: { new(...args: any[]): PostSummonAbAttr },
pokemon: Pokemon, ...args: any[]): void {
if (!pokemon.canApplyAbility())
return;
const ability = pokemon.getAbility();
const attrs = ability.getAttrs(attrType) as PostSummonAbAttr[];
for (let attr of attrs) {
if (!canApplyAttr(pokemon, attr))
continue;
pokemon.scene.setPhaseQueueSplice();
if (attr.applyPostSummon(pokemon, args)) {
queueShowAbility(pokemon);
const message = attr.getTriggerMessage(pokemon);
if (message)
pokemon.scene.queueMessage(message);
}
}
pokemon.scene.clearPhaseQueueSplice();
}
export function applyPreStatChangeAbAttrs(attrType: { new(...args: any[]): PreStatChangeAbAttr },
pokemon: Pokemon, stat: BattleStat, cancelled: Utils.BooleanHolder, ...args: any[]): void {
if (!pokemon.canApplyAbility())
@ -564,6 +752,50 @@ export function applyPreStatChangeAbAttrs(attrType: { new(...args: any[]): PreSt
pokemon.scene.clearPhaseQueueSplice();
}
export function applyPreSetStatusAbAttrs(attrType: { new(...args: any[]): PreSetStatusAbAttr },
pokemon: Pokemon, effect: StatusEffect, cancelled: Utils.BooleanHolder, ...args: any[]): void {
if (!pokemon.canApplyAbility())
return;
const ability = pokemon.getAbility();
const attrs = ability.getAttrs(attrType) as PreSetStatusAbAttr[];
for (let attr of attrs) {
if (!canApplyAttr(pokemon, attr))
continue;
pokemon.scene.setPhaseQueueSplice();
if (attr.applyPreSetStatus(pokemon, effect, cancelled, args)) {
queueShowAbility(pokemon);
const message = attr.getTriggerMessage(pokemon, effect);
if (message)
pokemon.scene.queueMessage(message);
}
}
pokemon.scene.clearPhaseQueueSplice();
}
export function applyPreApplyBattlerTagAbAttrs(attrType: { new(...args: any[]): PreApplyBattlerTagAbAttr },
pokemon: Pokemon, tag: BattlerTag, cancelled: Utils.BooleanHolder, ...args: any[]): void {
if (!pokemon.canApplyAbility())
return;
const ability = pokemon.getAbility();
const attrs = ability.getAttrs(attrType) as PreApplyBattlerTagAbAttr[];
for (let attr of attrs) {
if (!canApplyAttr(pokemon, attr))
continue;
pokemon.scene.setPhaseQueueSplice();
if (attr.applyPreApplyBattlerTag(pokemon, tag, cancelled, args)) {
queueShowAbility(pokemon);
const message = attr.getTriggerMessage(pokemon, tag);
if (message)
pokemon.scene.queueMessage(message);
}
}
pokemon.scene.clearPhaseQueueSplice();
}
export function applyPreWeatherEffectAbAttrs(attrType: { new(...args: any[]): PreWeatherEffectAbAttr },
pokemon: Pokemon, weather: Weather, cancelled: Utils.BooleanHolder, ...args: any[]): void {
if (!pokemon.canApplyAbility())
@ -635,6 +867,28 @@ export function applyPostWeatherLapseAbAttrs(attrType: { new(...args: any[]): Po
pokemon.scene.clearPhaseQueueSplice();
}
export function applyCheckTrappedAbAttrs(attrType: { new(...args: any[]): CheckTrappedAbAttr },
pokemon: Pokemon, trapped: Utils.BooleanHolder, ...args: any[]): void {
if (!pokemon.canApplyAbility())
return;
const ability = pokemon.getAbility();
const attrs = ability.getAttrs(attrType) as CheckTrappedAbAttr[];
for (let attr of attrs) {
if (!canApplyAttr(pokemon, attr))
continue;
pokemon.scene.setPhaseQueueSplice();
if (attr.applyCheckTrapped(pokemon, trapped, args)) {
// Don't show ability bar because this call is asynchronous
const message = attr.getTriggerMessage(pokemon);
if (message)
pokemon.scene.ui.showText(message, null, () => pokemon.scene.ui.showText(null, 0), null, true);
}
}
pokemon.scene.clearPhaseQueueSplice();
}
function canApplyAttr(pokemon: Pokemon, attr: AbAttr): boolean {
const condition = attr.getCondition();
return !condition || condition(pokemon);
@ -841,12 +1095,16 @@ export function initAbilities() {
new Ability(Abilities.COLOR_CHANGE, "Color Change (N)", "Changes the POKéMON's type to the foe's move.", 3),
new Ability(Abilities.COMPOUND_EYES, "Compound Eyes", "The POKéMON's accuracy is boosted.", 3)
.attr(BattleStatMultiplierAbAttr, BattleStat.ACC, 1.3),
new Ability(Abilities.CUTE_CHARM, "Cute Charm (N)", "Contact with the POKéMON may cause infatuation.", 3),
new Ability(Abilities.CUTE_CHARM, "Cute Charm", "Contact with the POKéMON may cause infatuation.", 3)
.attr(PostDefendContactApplyTagChanceAbAttr, 30, BattlerTagType.INFATUATED),
new Ability(Abilities.DAMP, "Damp (N)", "Prevents the use of self-destructing moves.", 3),
new Ability(Abilities.DRIZZLE, "Drizzle (N)", "The POKéMON makes it rain when it enters a battle.", 3),
new Ability(Abilities.DROUGHT, "Drought (N)", "Turns the sunlight harsh when the POKéMON enters a battle.", 3),
new Ability(Abilities.DRIZZLE, "Drizzle", "The POKéMON makes it rain when it enters a battle.", 3)
.attr(PostSummonWeatherChangeAbAttr, WeatherType.RAIN),
new Ability(Abilities.DROUGHT, "Drought", "Turns the sunlight harsh when the POKéMON enters a battle.", 3)
.attr(PostSummonWeatherChangeAbAttr, WeatherType.SUNNY),
new Ability(Abilities.EARLY_BIRD, "Early Bird (N)", "The POKéMON awakens quickly from sleep.", 3),
new Ability(Abilities.EFFECT_SPORE, "Effect Spore (N)", "Contact may poison or cause paralysis or sleep.", 3),
new Ability(Abilities.EFFECT_SPORE, "Effect Spore", "Contact may poison or cause paralysis or sleep.", 3)
.attr(PostDefendContactApplyStatusEffectAbAttr, 10, StatusEffect.POISON, StatusEffect.PARALYSIS, StatusEffect.SLEEP),
new Ability(Abilities.FLAME_BODY, "Flame Body (N)", "Contact with the POKéMON may burn the attacker.", 3),
new Ability(Abilities.FLASH_FIRE, "Flash Fire", "It powers up FIRE-type moves if it's hit by one.", 3)
.attr(TypeImmunityAddBattlerTagAbAttr, Type.FIRE, 1, BattlerTagType.FIRE_BOOST, (pokemon: Pokemon) => !pokemon.status || pokemon.status.effect !== StatusEffect.FREEZE),
@ -857,9 +1115,12 @@ export function initAbilities() {
new Ability(Abilities.HYPER_CUTTER, "Hyper Cutter", "Prevents other POKéMON from lowering ATTACK stat.", 3)
.attr(ProtectStatAttr, BattleStat.ATK),
new Ability(Abilities.ILLUMINATE, "Illuminate (N)", "Raises the likelihood of meeting wild POKéMON.", 3),
new Ability(Abilities.IMMUNITY, "Immunity (N)", "Prevents the POKéMON from getting poisoned.", 3),
new Ability(Abilities.IMMUNITY, "Immunity", "Prevents the POKéMON from getting poisoned.", 3)
.attr(StatusEffectImmunityAbAttr, StatusEffect.POISON),
new Ability(Abilities.INNER_FOCUS, "Inner Focus (N)", "The POKéMON is protected from flinching.", 3),
new Ability(Abilities.INSOMNIA, "Insomnia (N)", "Prevents the POKéMON from falling asleep.", 3),
new Ability(Abilities.INSOMNIA, "Insomnia", "Prevents the POKéMON from falling asleep.", 3)
.attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP)
.attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY),
new Ability(Abilities.INTIMIDATE, "Intimidate (N)", "Lowers the foe's ATTACK stat.", 3),
new Ability(Abilities.KEEN_EYE, "Keen Eye", "Prevents other POKéMON from lowering accuracy.", 3)
.attr(ProtectStatAttr, BattleStat.ACC),
@ -869,15 +1130,20 @@ export function initAbilities() {
.attr(TypeImmunityStatChangeAbAttr, Type.ELECTRIC, BattleStat.SPATK, 1),
new Ability(Abilities.LIMBER, "Limber (N)", "The POKéMON is protected from paralysis.", 3),
new Ability(Abilities.LIQUID_OOZE, "Liquid Ooze (N)", "Damages attackers using any draining move.", 3),
new Ability(Abilities.MAGMA_ARMOR, "Magma Armor (N)", "Prevents the POKéMON from becoming frozen.", 3),
new Ability(Abilities.MAGNET_PULL, "Magnet Pull (N)", "Prevents STEEL-type POKéMON from escaping.", 3),
new Ability(Abilities.MAGMA_ARMOR, "Magma Armor", "Prevents the POKéMON from becoming frozen.", 3)
.attr(StatusEffectImmunityAbAttr, StatusEffect.FREEZE),
new Ability(Abilities.MAGNET_PULL, "Magnet Pull", "Prevents STEEL-type POKéMON from escaping.", 3)
.attr(ArenaTrapAbAttr)
.condition((pokemon: Pokemon) => pokemon.getOpponent()?.isOfType(Type.STEEL)),
new Ability(Abilities.MARVEL_SCALE, "Marvel Scale (N)", "Ups DEFENSE if there is a status problem.", 3),
new Ability(Abilities.MINUS, "Minus (N)", "Ups SP. ATK if another POKéMON has PLUS or MINUS.", 3),
new Ability(Abilities.NATURAL_CURE, "Natural Cure (N)", "All status problems heal when it switches out.", 3),
new Ability(Abilities.OBLIVIOUS, "Oblivious (N)", "Prevents it from becoming infatuated.", 3),
new Ability(Abilities.OBLIVIOUS, "Oblivious", "Prevents it from becoming infatuated.", 3)
.attr(BattlerTagImmunityAbAttr, BattlerTagType.INFATUATED),
new Ability(Abilities.OVERGROW, "Overgrow", "Powers up GRASS-type moves in a pinch.", 3)
.attr(LowHpMoveTypePowerBoostAbAttr, Type.GRASS),
new Ability(Abilities.OWN_TEMPO, "Own Tempo (N)", "Prevents the POKéMON from becoming confused.", 3),
new Ability(Abilities.OWN_TEMPO, "Own Tempo", "Prevents the POKéMON from becoming confused.", 3)
.attr(BattlerTagImmunityAbAttr, BattlerTagType.CONFUSED),
new Ability(Abilities.PICKUP, "Pickup (N)", "The POKéMON may pick up items.", 3),
new Ability(Abilities.PLUS, "Plus (N)", "Ups SP. ATK if another POKéMON has PLUS or MINUS.", 3),
new Ability(Abilities.POISON_POINT, "Poison Point (N)", "Contact with the POKéMON may poison the attacker.", 3),
@ -889,7 +1155,8 @@ export function initAbilities() {
.attr(BlockRecoilDamageAttr),
new Ability(Abilities.ROUGH_SKIN, "Rough Skin (N)", "Inflicts damage to the attacker on contact.", 3),
new Ability(Abilities.RUN_AWAY, "Run Away (N)", "Enables a sure getaway from wild POKéMON.", 3),
new Ability(Abilities.SAND_STREAM, "Sand Stream (N)", "The POKéMON summons a sandstorm in battle.", 3),
new Ability(Abilities.SAND_STREAM, "Sand Stream", "The POKéMON summons a sandstorm in battle.", 3)
.attr(PostSummonWeatherChangeAbAttr, WeatherType.SANDSTORM),
new Ability(Abilities.SAND_VEIL, "Sand Veil", "Boosts the POKéMON's evasion in a sandstorm.", 3)
.attr(BattleStatMultiplierAbAttr, BattleStat.EVA, 1.2)
.attr(BlockWeatherDamageAttr, WeatherType.SANDSTORM)
@ -920,12 +1187,15 @@ export function initAbilities() {
.attr(LowHpMoveTypePowerBoostAbAttr, Type.WATER),
new Ability(Abilities.TRACE, "Trace (N)", "The POKéMON copies a foe's Ability.", 3),
new Ability(Abilities.TRUANT, "Truant (N)", "POKéMON can't attack on consecutive turns.", 3),
new Ability(Abilities.VITAL_SPIRIT, "Vital Spirit (N)", "Prevents the POKéMON from falling asleep.", 3),
new Ability(Abilities.VITAL_SPIRIT, "Vital Spirit", "Prevents the POKéMON from falling asleep.", 3)
.attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP)
.attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY),
new Ability(Abilities.VOLT_ABSORB, "Volt Absorb", "Restores HP if hit by an ELECTRIC-type move.", 3)
.attr(TypeImmunityHealAbAttr, Type.ELECTRIC),
new Ability(Abilities.WATER_ABSORB, "Water Absorb", "Restores HP if hit by a WATER-type move.", 3)
.attr(TypeImmunityHealAbAttr, Type.WATER),
new Ability(Abilities.WATER_VEIL, "Water Veil (N)", "Prevents the POKéMON from getting a burn.", 3),
new Ability(Abilities.WATER_VEIL, "Water Veil", "Prevents the POKéMON from getting a burn.", 3)
.attr(StatusEffectImmunityAbAttr, StatusEffect.BURN),
new Ability(Abilities.WHITE_SMOKE, "White Smoke", "Prevents other POKéMON from lowering its stats.", 3)
.attr(ProtectStatAttr),
new Ability(Abilities.WONDER_GUARD, "Wonder Guard", "Only super effective moves will hit.", 3)
@ -953,7 +1223,9 @@ export function initAbilities() {
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.HAIL),
new Ability(Abilities.IRON_FIST, "Iron Fist (N)", "Boosts the power of punching moves.", 4),
new Ability(Abilities.KLUTZ, "Klutz (N)", "The POKéMON can't use any held items.", 4),
new Ability(Abilities.LEAF_GUARD, "Leaf Guard (N)", "Prevents problems with status in sunny weather.", 4),
new Ability(Abilities.LEAF_GUARD, "Leaf Guard", "Prevents problems with status in sunny weather.", 4)
.attr(StatusEffectImmunityAbAttr)
.condition(getWeatherCondition(WeatherType.SUNNY, WeatherType.HARSH_SUN)),
new Ability(Abilities.MAGIC_GUARD, "Magic Guard (N)", "Protects the POKéMON from indirect damage.", 4),
new Ability(Abilities.MOLD_BREAKER, "Mold Breaker (N)", "Moves can be used regardless of Abilities.", 4),
new Ability(Abilities.MOTOR_DRIVE, "Motor Drive", "Raises SPEED if hit by an ELECTRIC-type move.", 4)
@ -972,7 +1244,8 @@ export function initAbilities() {
new Ability(Abilities.SLOW_START, "Slow Start (N)", "Temporarily halves ATTACK and SPEED.", 4),
new Ability(Abilities.SNIPER, "Sniper (N)", "Powers up moves if they become critical hits.", 4),
new Ability(Abilities.SNOW_CLOAK, "Snow Cloak (N)", "Raises evasion in a hailstorm.", 4),
new Ability(Abilities.SNOW_WARNING, "Snow Warning (N)", "The POKéMON summons a hailstorm in battle.", 4),
new Ability(Abilities.SNOW_WARNING, "Snow Warning", "The POKéMON summons a hailstorm in battle.", 4)
.attr(PostSummonWeatherChangeAbAttr, WeatherType.HAIL),
new Ability(Abilities.SOLAR_POWER, "Solar Power (N)", "In sunshine, SP. ATK is boosted but HP decreases.", 4),
new Ability(Abilities.SOLID_ROCK, "Solid Rock (N)", "Reduces damage from super-effective attacks.", 4),
new Ability(Abilities.STALL, "Stall (N)", "The POKéMON moves after all other POKéMON do.", 4),

View File

@ -134,7 +134,7 @@ class SpikesTag extends ArenaTrapTag {
super.onAdd(arena);
const source = arena.scene.getPokemonById(this.sourceId);
const target = source.isPlayer() ? source.scene.getEnemyPokemon() : source.scene.getPlayerPokemon();
const target = source.getOpponent();
arena.scene.queueMessage(`${this.getMoveName()} were scattered\nall around ${target.name}'s feet!`);
}
@ -161,7 +161,7 @@ class ToxicSpikesTag extends ArenaTrapTag {
super.onAdd(arena);
const source = arena.scene.getPokemonById(this.sourceId);
const target = source.isPlayer() ? source.scene.getEnemyPokemon() : source.scene.getPlayerPokemon();
const target = source.getOpponent();
arena.scene.queueMessage(`${this.getMoveName()} were scattered\nall around ${target.name}'s feet!`);
}
@ -187,7 +187,7 @@ class StealthRockTag extends ArenaTrapTag {
super.onAdd(arena);
const source = arena.scene.getPokemonById(this.sourceId);
const target = source.isPlayer() ? source.scene.getEnemyPokemon() : source.scene.getPlayerPokemon();
const target = source.getOpponent();
arena.scene.queueMessage(`Pointed stones float in the air\naround ${target.name}!`);
}

View File

@ -7,12 +7,14 @@ import { StatusEffect } from "./status-effect";
import * as Utils from "../utils";
import { Moves, allMoves } from "./move";
import { Type } from "./type";
import { Gender } from "./gender";
export enum BattlerTagType {
NONE,
RECHARGING,
FLINCHED,
CONFUSED,
INFATUATED,
SEEDED,
NIGHTMARE,
FRENZY,
@ -77,6 +79,10 @@ export class BattlerTag {
return --this.turnCount > 0;
}
getDescriptor(): string {
return '';
}
isSourceLinked(): boolean {
return false;
}
@ -130,6 +136,10 @@ export class TrappedTag extends BattlerTag {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` was freed\nfrom ${this.getMoveName()}!`));
}
getDescriptor(): string {
return 'trapping';
}
isSourceLinked(): boolean {
return true;
}
@ -152,6 +162,10 @@ export class FlinchedTag extends BattlerTag {
return true;
}
getDescriptor(): string {
return 'flinching';
}
}
export class ConfusedTag extends BattlerTag {
@ -198,6 +212,58 @@ export class ConfusedTag extends BattlerTag {
return ret;
}
getDescriptor(): string {
return 'confusion';
}
}
export class InfatuatedTag extends BattlerTag {
constructor(sourceMove: integer, sourceId: integer) {
super(BattlerTagType.INFATUATED, BattlerTagLapseType.MOVE, 1, sourceMove, sourceId);
}
canAdd(pokemon: Pokemon): boolean {
return pokemon.isOppositeGender(pokemon.scene.getPokemonById(this.sourceId));
}
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` fell in love\nwith ${pokemon.scene.getPokemonById(this.sourceId).name}!`));
}
onOverlap(pokemon: Pokemon): void {
super.onOverlap(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nalready in love!'));
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
if (ret) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is in love\nwith ${pokemon.scene.getPokemonById(this.sourceId).name}!`));
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), CommonAnim.ATTRACT));
if (Utils.randInt(2)) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nimmobilized by love!'));
(pokemon.scene.getCurrentPhase() as MovePhase).cancel();
}
}
return ret;
}
onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' got over\nits infatuation.'));
}
getDescriptor(): string {
return 'infatuation';
}
}
export class SeedTag extends BattlerTag {
@ -225,6 +291,10 @@ export class SeedTag extends BattlerTag {
return ret;
}
getDescriptor(): string {
return 'seeding';
}
}
export class NightmareTag extends BattlerTag {
@ -258,6 +328,10 @@ export class NightmareTag extends BattlerTag {
return ret;
}
getDescriptor(): string {
return 'nightmares';
}
}
export class IngrainTag extends TrappedTag {
@ -278,6 +352,10 @@ export class IngrainTag extends TrappedTag {
getTrapMessage(pokemon: Pokemon): string {
return getPokemonMessage(pokemon, ' planted its roots!');
}
getDescriptor(): string {
return 'roots';
}
}
export class AquaRingTag extends BattlerTag {
@ -320,6 +398,10 @@ export class DrowsyTag extends BattlerTag {
return true;
}
getDescriptor(): string {
return 'drowsiness';
}
}
export abstract class DamagingTrapTag extends TrappedTag {
@ -357,7 +439,7 @@ export class BindTag extends DamagingTrapTag {
}
getTrapMessage(pokemon: Pokemon): string {
return getPokemonMessage(pokemon, ` was squeezed by\n${pokemon.scene.getPokemonById(this.sourceId)}'s ${this.getMoveName()}!`);
return getPokemonMessage(pokemon, ` was squeezed by\n${pokemon.scene.getPokemonById(this.sourceId).name}'s ${this.getMoveName()}!`);
}
}
@ -367,7 +449,7 @@ export class WrapTag extends DamagingTrapTag {
}
getTrapMessage(pokemon: Pokemon): string {
return getPokemonMessage(pokemon, ` was WRAPPED\nby ${pokemon.scene.getPokemonById(this.sourceId)}!`);
return getPokemonMessage(pokemon, ` was WRAPPED\nby ${pokemon.scene.getPokemonById(this.sourceId).name}!`);
}
}
@ -516,6 +598,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: integer, sourc
return new FlinchedTag(sourceMove);
case BattlerTagType.CONFUSED:
return new ConfusedTag(turnCount, sourceMove);
case BattlerTagType.INFATUATED:
return new InfatuatedTag(sourceMove, sourceId);
case BattlerTagType.SEEDED:
return new SeedTag(sourceId);
case BattlerTagType.NIGHTMARE:

View File

@ -55,7 +55,7 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate {
return (pokemon: Pokemon) => !!pokemon.status || !!pokemon.getTag(BattlerTagType.CONFUSED);
case BerryType.ENIGMA:
return (pokemon: Pokemon) => {
const opponent = pokemon.isPlayer() ? pokemon.scene.getEnemyPokemon() : pokemon.scene.getPlayerPokemon();
const opponent = pokemon.getOpponent();
const opponentLastMove = opponent ? opponent.getLastXMoves(1).find(() => true) : null; // TODO: Update so this works even if opponent has fainted
return opponentLastMove && opponentLastMove.turn === pokemon.scene.currentBattle?.turn - 1 && opponentLastMove.result === MoveResult.SUPER_EFFECTIVE;

View File

@ -11,6 +11,7 @@ import { WeatherType } from "./weather";
import { ArenaTagType, ArenaTrapTag } from "./arena-tag";
import { BlockRecoilDamageAttr, applyAbAttrs } from "./ability";
import { PokemonHeldItemModifier } from "../modifier/modifier";
import { Gender } from "./gender";
export enum MoveCategory {
PHYSICAL,
@ -1868,7 +1869,7 @@ export function applyFilteredMoveAttrs(attrFilter: MoveAttrFilter, user: Pokemon
export function getMoveTarget(user: Pokemon, move: Moves): Pokemon {
const moveTarget = allMoves[move].moveTarget;
const other = user.isPlayer() ? user.scene.getEnemyPokemon() : user.scene.getPlayerPokemon();
const other = user.getOpponent();
switch (moveTarget) {
case MoveTarget.USER:
@ -2364,7 +2365,9 @@ export function initMoves() {
.attr(StatChangeAttr, BattleStat.DEF, 1, true),
new StatusMove(Moves.MEAN_LOOK, "Mean Look", Type.NORMAL, -1, 5, -1, "Opponent cannot flee or switch.", -1, 0, 2)
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, 1, true),
new StatusMove(Moves.ATTRACT, "Attract (N)", Type.NORMAL, 100, 15, -1, "If opponent is the opposite gender, it's less likely to attack.", -1, 0, 2),
new StatusMove(Moves.ATTRACT, "Attract", Type.NORMAL, 100, 15, -1, "If opponent is the opposite gender, it's less likely to attack.", -1, 0, 2)
.attr(AddBattlerTagAttr, BattlerTagType.INFATUATED)
.condition((user: Pokemon, target: Pokemon, move: Move) => user.isOppositeGender(target)),
new SelfStatusMove(Moves.SLEEP_TALK, "Sleep Talk", Type.NORMAL, -1, 10, 70, "User performs one of its own moves while sleeping.", -1, 0, 2)
.attr(BypassSleepAttr)
.attr(RandomMovesetMoveAttr)

View File

@ -1009,7 +1009,7 @@ export class HeldItemTransferModifier extends PokemonHeldItemModifier {
apply(args: any[]): boolean {
const pokemon = args[0] as Pokemon;
const targetPokemon = pokemon.isPlayer() ? pokemon.scene.getEnemyPokemon() : pokemon.scene.getPlayerPokemon();
const targetPokemon = pokemon.getOpponent();
if (!targetPokemon)
return false;

View File

@ -23,7 +23,7 @@ import { WeatherType } from './data/weather';
import { TempBattleStat } from './data/temp-battle-stat';
import { WeakenMoveTypeTag } from './data/arena-tag';
import { Biome } from './data/biome';
import { Abilities, Ability, BattleStatMultiplierAbAttr, BlockCritAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, abilities, applyBattleStatMultiplierAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs } from './data/ability';
import { Abilities, Ability, BattleStatMultiplierAbAttr, BlockCritAbAttr, PreApplyBattlerTagAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, abilities, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs } from './data/ability';
import PokemonData from './system/pokemon-data';
export default abstract class Pokemon extends Phaser.GameObjects.Container {
@ -483,6 +483,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.levelExp = this.exp - getLevelTotalExp(this.level, this.getSpeciesForm().growthRate);
}
getOpponent(): Pokemon {
const ret = this.isPlayer() ? this.scene.getEnemyPokemon() : this.scene.getPlayerPokemon();
if (ret.summonData)
return ret;
return null;
}
apply(source: Pokemon, battlerMove: PokemonMove): MoveResult {
let result: MoveResult;
const move = battlerMove.getMove();
@ -600,7 +607,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (!this.hp) {
this.scene.pushPhase(new FaintPhase(this.scene, this.isPlayer()));
this.resetSummonData();
(this.isPlayer() ? this.scene.getEnemyPokemon() : this.scene.getPlayerPokemon()).resetBattleSummonData();
this.getOpponent()?.resetBattleSummonData();
}
}
@ -613,7 +620,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const newTag = getBattlerTag(tagType, turnCount || 0, sourceMove, sourceId);
if (newTag.canAdd(this)) {
const cancelled = new Utils.BooleanHolder(false);
applyPreApplyBattlerTagAbAttrs(PreApplyBattlerTagAbAttr, this, newTag, cancelled);
if (!cancelled.value && newTag.canAdd(this)) {
this.summonData.tags.push(newTag);
newTag.onAdd(this);
@ -761,6 +771,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
});
}
isOppositeGender(pokemon: Pokemon): boolean {
return this.gender !== Gender.GENDERLESS && pokemon.gender === (this.gender === Gender.MALE ? Gender.FEMALE : Gender.MALE);
}
trySetStatus(effect: StatusEffect): boolean {
if (this.status)
return false;
@ -779,6 +793,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return false;
break;
}
const cancelled = new Utils.BooleanHolder(false);
applyPreSetStatusAbAttrs(StatusEffectImmunityAbAttr, this, effect, cancelled);
if (cancelled.value)
return false;
this.status = new Status(effect);
return true;
}