From 17c6c309e1d4747388708488b74798c31e16ad0a Mon Sep 17 00:00:00 2001 From: Jakub Hanko <60473007+JakubHanko@users.noreply.github.com> Date: Mon, 13 May 2024 08:38:36 +0200 Subject: [PATCH 1/8] Partially implement Odor Sleuth --- src/data/battler-tags.ts | 22 ++++++++++++++++++++++ src/data/enums/battler-tag-type.ts | 3 ++- src/data/move.ts | 24 ++++++++++++++++++++---- src/field/pokemon.ts | 16 ++++++++++++++-- 4 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index ba00b0906..a380be86e 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1100,6 +1100,26 @@ export class MagnetRisenTag extends TypeImmuneTag { } } +export class IdentifyTag extends BattlerTag { + public type: Type; + constructor(tagType: BattlerTagType, sourceMove: Moves, type: Type, length: number) { + super(tagType, BattlerTagLapseType.TURN_END, length, sourceMove); + this.type = type; + } + + /** + * When given a battler tag or json representing one, load the data for it. + * @param {BattlerTag | any} source A battler tag + */ + loadTag(source: BattlerTag | any): void { + super.loadTag(source); + this.type = source.type as Type; + } + lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { + return true; + } +} + export class TypeBoostTag extends BattlerTag { public boostedType: Type; public boostValue: number; @@ -1358,6 +1378,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: integer, sourc return new TypeBoostTag(tagType, sourceMove, Type.ELECTRIC, 2, true); case BattlerTagType.MAGNET_RISEN: return new MagnetRisenTag(tagType, sourceMove); + case BattlerTagType.ODOR_SLEUTH: + return new IdentifyTag(tagType, sourceMove, Type.GHOST, turnCount); case BattlerTagType.NONE: default: return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); diff --git a/src/data/enums/battler-tag-type.ts b/src/data/enums/battler-tag-type.ts index d18ccf1c5..e06e4283e 100644 --- a/src/data/enums/battler-tag-type.ts +++ b/src/data/enums/battler-tag-type.ts @@ -55,5 +55,6 @@ export enum BattlerTagType { CURSED = "CURSED", CHARGED = "CHARGED", GROUNDED = "GROUNDED", - MAGNET_RISEN = "MAGNET_RISEN" + MAGNET_RISEN = "MAGNET_RISEN", + ODOR_SLEUTH = "ODOR_SLEUTH" } diff --git a/src/data/move.ts b/src/data/move.ts index 36254320f..65c313ef3 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1772,7 +1772,21 @@ export class ResetStatsAttr extends MoveEffectAttr { target.scene.queueMessage(getPokemonMessage(target, `'s stat changes\nwere eliminated!`)); return true; - } + } +} + +export class IdentifyAttr extends MoveEffectAttr { + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + if (!super.apply(user, target, move, args)) + return false; + + target.summonData.battleStats[BattleStat.EVA] = 0; + target.updateInfo(); + + target.scene.queueMessage(`${getPokemonMessage(user, " identified\n")}${getPokemonMessage(target, "!")}`); + + return true; + } } /** @@ -4656,7 +4670,7 @@ export function initMoves() { new AttackMove(Moves.ZAP_CANNON, Type.ELECTRIC, MoveCategory.SPECIAL, 120, 50, 5, 100, 0, 2) .attr(StatusEffectAttr, StatusEffect.PARALYSIS) .ballBombMove(), - new StatusMove(Moves.FORESIGHT, Type.NORMAL, -1, 40, -1, 0, 2) + new StatusMove(Moves.FORESIGHT, Type.NORMAL, -1, 40, -1, 0, 2) // TODO .unimplemented(), new SelfStatusMove(Moves.DESTINY_BOND, Type.GHOST, -1, 5, -1, 0, 2) .ignoresProtect() @@ -5007,7 +5021,9 @@ export function initMoves() { .attr(StatChangeAttr, BattleStat.SPATK, -2, true) .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE), new StatusMove(Moves.ODOR_SLEUTH, Type.NORMAL, -1, 40, -1, 0, 3) - .unimplemented(), + .attr(IdentifyAttr) + .attr(AddBattlerTagAttr, BattlerTagType.ODOR_SLEUTH, false, false, 20) + .partial(), new AttackMove(Moves.ROCK_TOMB, Type.ROCK, MoveCategory.PHYSICAL, 60, 95, 15, 100, 0, 3) .attr(StatChangeAttr, BattleStat.SPD, -1) .makesContact(false), @@ -5114,7 +5130,7 @@ export function initMoves() { new StatusMove(Moves.GRAVITY, Type.PSYCHIC, -1, 5, -1, 0, 4) .attr(AddArenaTagAttr, ArenaTagType.GRAVITY, 5) .target(MoveTarget.BOTH_SIDES), - new StatusMove(Moves.MIRACLE_EYE, Type.PSYCHIC, -1, 40, -1, 0, 4) + new StatusMove(Moves.MIRACLE_EYE, Type.PSYCHIC, -1, 40, -1, 0, 4) // TODO .unimplemented(), new AttackMove(Moves.WAKE_UP_SLAP, Type.FIGHTING, MoveCategory.PHYSICAL, 70, 100, 10, -1, 0, 4) .attr(MovePowerMultiplierAttr, (user, target, move) => target.status?.effect === StatusEffect.SLEEP ? 2 : 1) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 3864afe20..ff4dcca5d 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -4,7 +4,7 @@ import { Variant, VariantSet, variantColorCache } from '#app/data/variant'; import { variantData } from '#app/data/variant'; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from '../ui/battle-info'; import { Moves } from "../data/enums/moves"; -import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveTypeAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr } from "../data/move"; +import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveTypeAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, IdentifyAttr } from "../data/move"; import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from '../data/pokemon-species'; import * as Utils from '../utils'; import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from '../data/type'; @@ -19,7 +19,7 @@ import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEv import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from '../data/tms'; import { DamagePhase, FaintPhase, LearnMovePhase, ObtainStatusEffectPhase, StatChangePhase, SwitchSummonPhase } from '../phases'; import { BattleStat } from '../data/battle-stat'; -import { BattlerTag, BattlerTagLapseType, EncoreTag, HelpingHandTag, HighestStatBoostTag, TypeBoostTag, getBattlerTag } from '../data/battler-tags'; +import { BattlerTag, BattlerTagLapseType, EncoreTag, HelpingHandTag, HighestStatBoostTag, IdentifyTag, TypeBoostTag, getBattlerTag } from '../data/battler-tags'; import { BattlerTagType } from "../data/enums/battler-tag-type"; import { Species } from '../data/enums/species'; import { WeatherType } from '../data/weather'; @@ -779,6 +779,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { types.splice(flyingIndex, 1); } + const identifyTags = this.findTags(t => t instanceof IdentifyTag); + console.log(identifyTags); + if (forDefend && identifyTags.length) { + identifyTags.forEach(tag => { + const type = (tag as IdentifyTag).type; + const typeIndex = types.indexOf(type); + if (typeIndex > -1) + types.splice(typeIndex, 1); + }); + } + if (!types.length) // become UNKNOWN if no types are present types.push(Type.UNKNOWN); @@ -789,6 +800,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } + console.log(types); return types; } From 4b4d3c6c8aef42f82ff29abd6683f461d28f5cfe Mon Sep 17 00:00:00 2001 From: Jakub Hanko <60473007+JakubHanko@users.noreply.github.com> Date: Mon, 13 May 2024 14:01:10 +0200 Subject: [PATCH 2/8] Fix incorrect effectiveness evaluation after Odor sleuth --- src/data/battler-tags.ts | 16 ++++++++++------ src/data/move.ts | 9 ++++----- src/field/pokemon.ts | 23 +++++++++-------------- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index a380be86e..109711429 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1100,11 +1100,14 @@ export class MagnetRisenTag extends TypeImmuneTag { } } -export class IdentifyTag extends BattlerTag { - public type: Type; - constructor(tagType: BattlerTagType, sourceMove: Moves, type: Type, length: number) { +export class ExposedTag extends BattlerTag { + public immuneType: Type; + public allowedTypes: Type[]; + + constructor(tagType: BattlerTagType, sourceMove: Moves, type: Type, allowedTypes: Type[], length: number) { super(tagType, BattlerTagLapseType.TURN_END, length, sourceMove); - this.type = type; + this.immuneType = type; + this.allowedTypes = allowedTypes; } /** @@ -1113,7 +1116,8 @@ export class IdentifyTag extends BattlerTag { */ loadTag(source: BattlerTag | any): void { super.loadTag(source); - this.type = source.type as Type; + this.immuneType = source.type as Type; + this.allowedTypes = source.allowedTypes as Type[]; } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { return true; @@ -1379,7 +1383,7 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: integer, sourc case BattlerTagType.MAGNET_RISEN: return new MagnetRisenTag(tagType, sourceMove); case BattlerTagType.ODOR_SLEUTH: - return new IdentifyTag(tagType, sourceMove, Type.GHOST, turnCount); + return new ExposedTag(tagType, sourceMove, Type.GHOST, [ Type.NORMAL, Type.FIGHTING ], turnCount); case BattlerTagType.NONE: default: return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); diff --git a/src/data/move.ts b/src/data/move.ts index 65c313ef3..500188891 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -2,7 +2,7 @@ import { Moves } from "./enums/moves"; import { ChargeAnim, MoveChargeAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims"; import { BattleEndPhase, MovePhase, NewBattlePhase, PartyStatusCurePhase, PokemonHealPhase, StatChangePhase, SwitchSummonPhase } from "../phases"; import { BattleStat, getBattleStatName } from "./battle-stat"; -import { EncoreTag } from "./battler-tags"; +import { EncoreTag, ExposedTag } from "./battler-tags"; import { BattlerTagType } from "./enums/battler-tag-type"; import { getPokemonMessage } from "../messages"; import Pokemon, { AttackMoveResult, EnemyPokemon, HitResult, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../field/pokemon"; @@ -1775,7 +1775,7 @@ export class ResetStatsAttr extends MoveEffectAttr { } } -export class IdentifyAttr extends MoveEffectAttr { +export class ExposedAttr extends MoveEffectAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (!super.apply(user, target, move, args)) return false; @@ -5021,9 +5021,8 @@ export function initMoves() { .attr(StatChangeAttr, BattleStat.SPATK, -2, true) .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE), new StatusMove(Moves.ODOR_SLEUTH, Type.NORMAL, -1, 40, -1, 0, 3) - .attr(IdentifyAttr) - .attr(AddBattlerTagAttr, BattlerTagType.ODOR_SLEUTH, false, false, 20) - .partial(), + .attr(ExposedAttr) + .attr(AddBattlerTagAttr, BattlerTagType.ODOR_SLEUTH, false, false, 20), new AttackMove(Moves.ROCK_TOMB, Type.ROCK, MoveCategory.PHYSICAL, 60, 95, 15, 100, 0, 3) .attr(StatChangeAttr, BattleStat.SPD, -1) .makesContact(false), diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index ff4dcca5d..9ffeb12fe 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -4,7 +4,7 @@ import { Variant, VariantSet, variantColorCache } from '#app/data/variant'; import { variantData } from '#app/data/variant'; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from '../ui/battle-info'; import { Moves } from "../data/enums/moves"; -import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveTypeAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, IdentifyAttr } from "../data/move"; +import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveTypeAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, ExposedAttr } from "../data/move"; import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from '../data/pokemon-species'; import * as Utils from '../utils'; import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from '../data/type'; @@ -19,7 +19,7 @@ import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEv import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from '../data/tms'; import { DamagePhase, FaintPhase, LearnMovePhase, ObtainStatusEffectPhase, StatChangePhase, SwitchSummonPhase } from '../phases'; import { BattleStat } from '../data/battle-stat'; -import { BattlerTag, BattlerTagLapseType, EncoreTag, HelpingHandTag, HighestStatBoostTag, IdentifyTag, TypeBoostTag, getBattlerTag } from '../data/battler-tags'; +import { BattlerTag, BattlerTagLapseType, EncoreTag, HelpingHandTag, HighestStatBoostTag, ExposedTag, TypeBoostTag, getBattlerTag } from '../data/battler-tags'; import { BattlerTagType } from "../data/enums/battler-tag-type"; import { Species } from '../data/enums/species'; import { WeatherType } from '../data/weather'; @@ -779,17 +779,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { types.splice(flyingIndex, 1); } - const identifyTags = this.findTags(t => t instanceof IdentifyTag); - console.log(identifyTags); - if (forDefend && identifyTags.length) { - identifyTags.forEach(tag => { - const type = (tag as IdentifyTag).type; - const typeIndex = types.indexOf(type); - if (typeIndex > -1) - types.splice(typeIndex, 1); - }); - } - if (!types.length) // become UNKNOWN if no types are present types.push(Type.UNKNOWN); @@ -800,7 +789,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - console.log(types); return types; } @@ -1457,6 +1445,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (types.find(t => move.isTypeImmune(t))) typeMultiplier.value = 0; + if ((this.getTags(ExposedTag) as ExposedTag[]).some(t => { + return t.allowedTypes.includes(move.type) + && this.getTypes(true, true).includes(t.immuneType); + })) { + typeMultiplier.value = 1; + } + switch (moveCategory) { case MoveCategory.PHYSICAL: case MoveCategory.SPECIAL: From b86a20c7b2e3692f7010438a76b31978b554a138 Mon Sep 17 00:00:00 2001 From: Jakub Hanko <60473007+JakubHanko@users.noreply.github.com> Date: Mon, 13 May 2024 14:13:34 +0200 Subject: [PATCH 3/8] Implement Miracle Eye and Foresight --- src/data/battler-tags.ts | 2 ++ src/data/move.ts | 12 +++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 109711429..1314b644a 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1384,6 +1384,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: integer, sourc return new MagnetRisenTag(tagType, sourceMove); case BattlerTagType.ODOR_SLEUTH: return new ExposedTag(tagType, sourceMove, Type.GHOST, [ Type.NORMAL, Type.FIGHTING ], turnCount); + case BattlerTagType.MIRACLE_EYE: + return new ExposedTag(tagType, sourceMove, Type.DARK, [ Type.PSYCHIC ], turnCount); case BattlerTagType.NONE: default: return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); diff --git a/src/data/move.ts b/src/data/move.ts index 500188891..2e8bb4ec4 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -16,7 +16,7 @@ import { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilit import { Abilities } from "./enums/abilities"; import { allAbilities } from './ability'; import { PokemonHeldItemModifier } from "../modifier/modifier"; -import { BattlerIndex } from "../battle"; +import Battle, { BattlerIndex } from "../battle"; import { Stat } from "./pokemon-stat"; import { TerrainType } from "./terrain"; import { SpeciesFormChangeActiveTrigger } from "./pokemon-forms"; @@ -4670,8 +4670,9 @@ export function initMoves() { new AttackMove(Moves.ZAP_CANNON, Type.ELECTRIC, MoveCategory.SPECIAL, 120, 50, 5, 100, 0, 2) .attr(StatusEffectAttr, StatusEffect.PARALYSIS) .ballBombMove(), - new StatusMove(Moves.FORESIGHT, Type.NORMAL, -1, 40, -1, 0, 2) // TODO - .unimplemented(), + new StatusMove(Moves.FORESIGHT, Type.NORMAL, -1, 40, -1, 0, 2) + .attr(ExposedAttr) + .attr(AddBattlerTagAttr, BattlerTagType.ODOR_SLEUTH, false, false, 20), new SelfStatusMove(Moves.DESTINY_BOND, Type.GHOST, -1, 5, -1, 0, 2) .ignoresProtect() .condition(failOnBossCondition) @@ -5129,8 +5130,9 @@ export function initMoves() { new StatusMove(Moves.GRAVITY, Type.PSYCHIC, -1, 5, -1, 0, 4) .attr(AddArenaTagAttr, ArenaTagType.GRAVITY, 5) .target(MoveTarget.BOTH_SIDES), - new StatusMove(Moves.MIRACLE_EYE, Type.PSYCHIC, -1, 40, -1, 0, 4) // TODO - .unimplemented(), + new StatusMove(Moves.MIRACLE_EYE, Type.PSYCHIC, -1, 40, -1, 0, 4) + .attr(ExposedAttr) + .attr(AddBattlerTagAttr, BattlerTagType.MIRACLE_EYE, false, false, 20), new AttackMove(Moves.WAKE_UP_SLAP, Type.FIGHTING, MoveCategory.PHYSICAL, 70, 100, 10, -1, 0, 4) .attr(MovePowerMultiplierAttr, (user, target, move) => target.status?.effect === StatusEffect.SLEEP ? 2 : 1) .attr(HealStatusEffectAttr, false, StatusEffect.SLEEP), From 86877dc3139a37198c2ef2d366344557ff10ad0c Mon Sep 17 00:00:00 2001 From: Jakub Hanko <60473007+JakubHanko@users.noreply.github.com> Date: Mon, 13 May 2024 16:04:25 +0200 Subject: [PATCH 4/8] Code cleanup --- src/data/battler-tags.ts | 16 +++++++++++----- src/data/enums/battler-tag-type.ts | 3 ++- src/data/move.ts | 20 +++++++++++++------- src/field/pokemon.ts | 12 +++++------- 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 1314b644a..7b6fb5b5b 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1104,8 +1104,8 @@ export class ExposedTag extends BattlerTag { public immuneType: Type; public allowedTypes: Type[]; - constructor(tagType: BattlerTagType, sourceMove: Moves, type: Type, allowedTypes: Type[], length: number) { - super(tagType, BattlerTagLapseType.TURN_END, length, sourceMove); + constructor(tagType: BattlerTagType, sourceMove: Moves, type: Type, allowedTypes: Type[]) { + super(tagType, BattlerTagLapseType.TURN_END, 1, sourceMove); this.immuneType = type; this.allowedTypes = allowedTypes; } @@ -1119,8 +1119,14 @@ export class ExposedTag extends BattlerTag { this.immuneType = source.type as Type; this.allowedTypes = source.allowedTypes as Type[]; } + lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { - return true; + return lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); + } + + ignoreImmunity(pokemon: Pokemon, moveType: Type): boolean { + return pokemon.getTypes(true, true).includes(this.immuneType) + && this.allowedTypes.includes(moveType); } } @@ -1383,9 +1389,9 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: integer, sourc case BattlerTagType.MAGNET_RISEN: return new MagnetRisenTag(tagType, sourceMove); case BattlerTagType.ODOR_SLEUTH: - return new ExposedTag(tagType, sourceMove, Type.GHOST, [ Type.NORMAL, Type.FIGHTING ], turnCount); + return new ExposedTag(tagType, sourceMove, Type.GHOST, [ Type.NORMAL, Type.FIGHTING ]); case BattlerTagType.MIRACLE_EYE: - return new ExposedTag(tagType, sourceMove, Type.DARK, [ Type.PSYCHIC ], turnCount); + return new ExposedTag(tagType, sourceMove, Type.DARK, [ Type.PSYCHIC ]); case BattlerTagType.NONE: default: return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); diff --git a/src/data/enums/battler-tag-type.ts b/src/data/enums/battler-tag-type.ts index e06e4283e..dcf7cb228 100644 --- a/src/data/enums/battler-tag-type.ts +++ b/src/data/enums/battler-tag-type.ts @@ -56,5 +56,6 @@ export enum BattlerTagType { CHARGED = "CHARGED", GROUNDED = "GROUNDED", MAGNET_RISEN = "MAGNET_RISEN", - ODOR_SLEUTH = "ODOR_SLEUTH" + ODOR_SLEUTH = "ODOR_SLEUTH", + MIRACLE_EYE = "MIRACLE_EYE" } diff --git a/src/data/move.ts b/src/data/move.ts index 2e8bb4ec4..cecbe99ae 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -2,7 +2,7 @@ import { Moves } from "./enums/moves"; import { ChargeAnim, MoveChargeAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims"; import { BattleEndPhase, MovePhase, NewBattlePhase, PartyStatusCurePhase, PokemonHealPhase, StatChangePhase, SwitchSummonPhase } from "../phases"; import { BattleStat, getBattleStatName } from "./battle-stat"; -import { EncoreTag, ExposedTag } from "./battler-tags"; +import { BattlerTag, EncoreTag, ExposedTag } from "./battler-tags"; import { BattlerTagType } from "./enums/battler-tag-type"; import { getPokemonMessage } from "../messages"; import Pokemon, { AttackMoveResult, EnemyPokemon, HitResult, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../field/pokemon"; @@ -1776,12 +1776,21 @@ export class ResetStatsAttr extends MoveEffectAttr { } export class ExposedAttr extends MoveEffectAttr { + private tagType: BattlerTagType; + + constructor(tagType: BattlerTagType) { + super(); + + this.tagType = tagType; + } + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (!super.apply(user, target, move, args)) return false; target.summonData.battleStats[BattleStat.EVA] = 0; target.updateInfo(); + target.addTag(this.tagType, 20, move.id, user.id); target.scene.queueMessage(`${getPokemonMessage(user, " identified\n")}${getPokemonMessage(target, "!")}`); @@ -4671,8 +4680,7 @@ export function initMoves() { .attr(StatusEffectAttr, StatusEffect.PARALYSIS) .ballBombMove(), new StatusMove(Moves.FORESIGHT, Type.NORMAL, -1, 40, -1, 0, 2) - .attr(ExposedAttr) - .attr(AddBattlerTagAttr, BattlerTagType.ODOR_SLEUTH, false, false, 20), + .attr(ExposedAttr, BattlerTagType.ODOR_SLEUTH), new SelfStatusMove(Moves.DESTINY_BOND, Type.GHOST, -1, 5, -1, 0, 2) .ignoresProtect() .condition(failOnBossCondition) @@ -5022,8 +5030,7 @@ export function initMoves() { .attr(StatChangeAttr, BattleStat.SPATK, -2, true) .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE), new StatusMove(Moves.ODOR_SLEUTH, Type.NORMAL, -1, 40, -1, 0, 3) - .attr(ExposedAttr) - .attr(AddBattlerTagAttr, BattlerTagType.ODOR_SLEUTH, false, false, 20), + .attr(ExposedAttr, BattlerTagType.ODOR_SLEUTH), new AttackMove(Moves.ROCK_TOMB, Type.ROCK, MoveCategory.PHYSICAL, 60, 95, 15, 100, 0, 3) .attr(StatChangeAttr, BattleStat.SPD, -1) .makesContact(false), @@ -5131,8 +5138,7 @@ export function initMoves() { .attr(AddArenaTagAttr, ArenaTagType.GRAVITY, 5) .target(MoveTarget.BOTH_SIDES), new StatusMove(Moves.MIRACLE_EYE, Type.PSYCHIC, -1, 40, -1, 0, 4) - .attr(ExposedAttr) - .attr(AddBattlerTagAttr, BattlerTagType.MIRACLE_EYE, false, false, 20), + .attr(ExposedAttr, BattlerTagType.MIRACLE_EYE), new AttackMove(Moves.WAKE_UP_SLAP, Type.FIGHTING, MoveCategory.PHYSICAL, 70, 100, 10, -1, 0, 4) .attr(MovePowerMultiplierAttr, (user, target, move) => target.status?.effect === StatusEffect.SLEEP ? 2 : 1) .attr(HealStatusEffectAttr, false, StatusEffect.SLEEP), diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 9ffeb12fe..3520d7e35 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -965,6 +965,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyAbAttrs(IgnoreTypeImmunityAbAttr, source, ignoreImmunity, moveType, defType); if (ignoreImmunity.value) return 1; + + const exposedTags = this.getTags(ExposedTag) as ExposedTag[]; + if (exposedTags.some(t => t.ignoreImmunity(this, moveType))) { + return 1; + } } return getTypeDamageMultiplier(moveType, defType); @@ -1445,13 +1450,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (types.find(t => move.isTypeImmune(t))) typeMultiplier.value = 0; - if ((this.getTags(ExposedTag) as ExposedTag[]).some(t => { - return t.allowedTypes.includes(move.type) - && this.getTypes(true, true).includes(t.immuneType); - })) { - typeMultiplier.value = 1; - } - switch (moveCategory) { case MoveCategory.PHYSICAL: case MoveCategory.SPECIAL: From 33e71605b4e84f4ffebf4a3964469eba5d067831 Mon Sep 17 00:00:00 2001 From: Jakub Hanko <60473007+JakubHanko@users.noreply.github.com> Date: Mon, 13 May 2024 23:15:49 +0200 Subject: [PATCH 5/8] Make ExposedAttr extend AddBattlerTagAttr and fail on overlap --- src/data/move.ts | 41 ++++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index ce4fa46d8..434215ae3 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1830,29 +1830,6 @@ export class ResetStatsAttr extends MoveEffectAttr { } } -export class ExposedAttr extends MoveEffectAttr { - private tagType: BattlerTagType; - - constructor(tagType: BattlerTagType) { - super(); - - this.tagType = tagType; - } - - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (!super.apply(user, target, move, args)) - return false; - - target.summonData.battleStats[BattleStat.EVA] = 0; - target.updateInfo(); - target.addTag(this.tagType, 20, move.id, user.id); - - target.scene.queueMessage(`${getPokemonMessage(user, " identified\n")}${getPokemonMessage(target, "!")}`); - - return true; - } -} - /** * Attribute used for moves which swap the user and the target's stat changes. */ @@ -3109,6 +3086,24 @@ export class FaintCountdownAttr extends AddBattlerTagAttr { } } +export class ExposedAttr extends AddBattlerTagAttr { + constructor(tagType: BattlerTagType) { + super(tagType, false, true); + } + + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + if (!super.apply(user, target, move, args)) + return false; + + target.summonData.battleStats[BattleStat.EVA] = 0; + target.updateInfo(); + + target.scene.queueMessage(`${getPokemonMessage(user, " identified\n")}${getPokemonMessage(target, "!")}`); + + return true; + } +} + export class HitsTagAttr extends MoveAttr { public tagType: BattlerTagType; public doubleDamage: boolean; From d5720fbf50e9b9c02f22c913d2885494684824ed Mon Sep 17 00:00:00 2001 From: Jakub Hanko <60473007+JakubHanko@users.noreply.github.com> Date: Tue, 14 May 2024 11:24:40 +0200 Subject: [PATCH 6/8] Make the ExposedTag not require allowed types to hit the immune type. --- src/data/battler-tags.ts | 17 +++++++---------- src/field/pokemon.ts | 2 +- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 7b6fb5b5b..2950ae1b7 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -7,7 +7,7 @@ import { StatusEffect } from "./status-effect"; import * as Utils from "../utils"; import { Moves } from "./enums/moves"; import { ChargeAttr, MoveFlags, allMoves } from "./move"; -import { Type } from "./type"; +import { getTypeDamageMultiplier, Type } from "./type"; import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs } from "./ability"; import { Abilities } from "./enums/abilities"; import { BattlerTagType } from "./enums/battler-tag-type"; @@ -1102,12 +1102,10 @@ export class MagnetRisenTag extends TypeImmuneTag { export class ExposedTag extends BattlerTag { public immuneType: Type; - public allowedTypes: Type[]; - constructor(tagType: BattlerTagType, sourceMove: Moves, type: Type, allowedTypes: Type[]) { + constructor(tagType: BattlerTagType, sourceMove: Moves, type: Type) { super(tagType, BattlerTagLapseType.TURN_END, 1, sourceMove); this.immuneType = type; - this.allowedTypes = allowedTypes; } /** @@ -1117,16 +1115,15 @@ export class ExposedTag extends BattlerTag { loadTag(source: BattlerTag | any): void { super.loadTag(source); this.immuneType = source.type as Type; - this.allowedTypes = source.allowedTypes as Type[]; } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { return lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); } - ignoreImmunity(pokemon: Pokemon, moveType: Type): boolean { - return pokemon.getTypes(true, true).includes(this.immuneType) - && this.allowedTypes.includes(moveType); + ignoreImmunity(types: Type[], moveType: Type): boolean { + return types.includes(this.immuneType) + && getTypeDamageMultiplier(moveType, this.immuneType) == 0; } } @@ -1389,9 +1386,9 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: integer, sourc case BattlerTagType.MAGNET_RISEN: return new MagnetRisenTag(tagType, sourceMove); case BattlerTagType.ODOR_SLEUTH: - return new ExposedTag(tagType, sourceMove, Type.GHOST, [ Type.NORMAL, Type.FIGHTING ]); + return new ExposedTag(tagType, sourceMove, Type.GHOST); case BattlerTagType.MIRACLE_EYE: - return new ExposedTag(tagType, sourceMove, Type.DARK, [ Type.PSYCHIC ]); + return new ExposedTag(tagType, sourceMove, Type.DARK); case BattlerTagType.NONE: default: return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 3ba9d7609..ddb84a8b5 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -967,7 +967,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return 1; const exposedTags = this.getTags(ExposedTag) as ExposedTag[]; - if (exposedTags.some(t => t.ignoreImmunity(this, moveType))) { + if (exposedTags.some(t => t.ignoreImmunity(types, moveType))) { return 1; } } From a371767be371b16d67221972c965bc5d930504ef Mon Sep 17 00:00:00 2001 From: Jakub Hanko <60473007+JakubHanko@users.noreply.github.com> Date: Tue, 14 May 2024 20:57:57 +0200 Subject: [PATCH 7/8] Rename ExposedTag into TypeImmunityIgnoreTag --- src/data/battler-tags.ts | 10 +++++----- src/data/enums/battler-tag-type.ts | 4 ++-- src/data/move.ts | 10 +++++----- src/field/pokemon.ts | 6 +++--- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 2950ae1b7..79e8397d2 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1100,7 +1100,7 @@ export class MagnetRisenTag extends TypeImmuneTag { } } -export class ExposedTag extends BattlerTag { +export class TypeImmunityIgnoreTag extends BattlerTag { public immuneType: Type; constructor(tagType: BattlerTagType, sourceMove: Moves, type: Type) { @@ -1385,10 +1385,10 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: integer, sourc return new TypeBoostTag(tagType, sourceMove, Type.ELECTRIC, 2, true); case BattlerTagType.MAGNET_RISEN: return new MagnetRisenTag(tagType, sourceMove); - case BattlerTagType.ODOR_SLEUTH: - return new ExposedTag(tagType, sourceMove, Type.GHOST); - case BattlerTagType.MIRACLE_EYE: - return new ExposedTag(tagType, sourceMove, Type.DARK); + case BattlerTagType.IGNORE_GHOST: + return new TypeImmunityIgnoreTag(tagType, sourceMove, Type.GHOST); + case BattlerTagType.IGNORE_DARK: + return new TypeImmunityIgnoreTag(tagType, sourceMove, Type.DARK); case BattlerTagType.NONE: default: return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); diff --git a/src/data/enums/battler-tag-type.ts b/src/data/enums/battler-tag-type.ts index dcf7cb228..6c16ae976 100644 --- a/src/data/enums/battler-tag-type.ts +++ b/src/data/enums/battler-tag-type.ts @@ -56,6 +56,6 @@ export enum BattlerTagType { CHARGED = "CHARGED", GROUNDED = "GROUNDED", MAGNET_RISEN = "MAGNET_RISEN", - ODOR_SLEUTH = "ODOR_SLEUTH", - MIRACLE_EYE = "MIRACLE_EYE" + IGNORE_GHOST = "IGNORE_GHOST", + IGNORE_DARK = "IGNORE_DARK" } diff --git a/src/data/move.ts b/src/data/move.ts index 434215ae3..cc1eccfcf 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -2,7 +2,7 @@ import { Moves } from "./enums/moves"; import { ChargeAnim, MoveChargeAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims"; import { BattleEndPhase, MovePhase, NewBattlePhase, PartyStatusCurePhase, PokemonHealPhase, StatChangePhase, SwitchSummonPhase } from "../phases"; import { BattleStat, getBattleStatName } from "./battle-stat"; -import { BattlerTag, EncoreTag, ExposedTag } from "./battler-tags"; +import { BattlerTag, EncoreTag, TypeImmunityIgnoreTag } from "./battler-tags"; import { BattlerTagType } from "./enums/battler-tag-type"; import { getPokemonMessage } from "../messages"; import Pokemon, { AttackMoveResult, EnemyPokemon, HitResult, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../field/pokemon"; @@ -3086,7 +3086,7 @@ export class FaintCountdownAttr extends AddBattlerTagAttr { } } -export class ExposedAttr extends AddBattlerTagAttr { +export class ExposedMoveAttr extends AddBattlerTagAttr { constructor(tagType: BattlerTagType) { super(tagType, false, true); } @@ -4730,7 +4730,7 @@ export function initMoves() { .attr(StatusEffectAttr, StatusEffect.PARALYSIS) .ballBombMove(), new StatusMove(Moves.FORESIGHT, Type.NORMAL, -1, 40, -1, 0, 2) - .attr(ExposedAttr, BattlerTagType.ODOR_SLEUTH), + .attr(ExposedMoveAttr, BattlerTagType.IGNORE_GHOST), new SelfStatusMove(Moves.DESTINY_BOND, Type.GHOST, -1, 5, -1, 0, 2) .ignoresProtect() .condition(failOnBossCondition) @@ -5081,7 +5081,7 @@ export function initMoves() { .attr(StatChangeAttr, BattleStat.SPATK, -2, true) .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE), new StatusMove(Moves.ODOR_SLEUTH, Type.NORMAL, -1, 40, -1, 0, 3) - .attr(ExposedAttr, BattlerTagType.ODOR_SLEUTH), + .attr(ExposedMoveAttr, BattlerTagType.IGNORE_GHOST), new AttackMove(Moves.ROCK_TOMB, Type.ROCK, MoveCategory.PHYSICAL, 60, 95, 15, 100, 0, 3) .attr(StatChangeAttr, BattleStat.SPD, -1) .makesContact(false), @@ -5189,7 +5189,7 @@ export function initMoves() { .attr(AddArenaTagAttr, ArenaTagType.GRAVITY, 5) .target(MoveTarget.BOTH_SIDES), new StatusMove(Moves.MIRACLE_EYE, Type.PSYCHIC, -1, 40, -1, 0, 4) - .attr(ExposedAttr, BattlerTagType.MIRACLE_EYE), + .attr(ExposedMoveAttr, BattlerTagType.IGNORE_DARK), new AttackMove(Moves.WAKE_UP_SLAP, Type.FIGHTING, MoveCategory.PHYSICAL, 70, 100, 10, -1, 0, 4) .attr(MovePowerMultiplierAttr, (user, target, move) => target.status?.effect === StatusEffect.SLEEP ? 2 : 1) .attr(HealStatusEffectAttr, false, StatusEffect.SLEEP), diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index ddb84a8b5..de11c3dd5 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -4,7 +4,7 @@ import { Variant, VariantSet, variantColorCache } from '#app/data/variant'; import { variantData } from '#app/data/variant'; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from '../ui/battle-info'; import { Moves } from "../data/enums/moves"; -import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveTypeAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, ExposedAttr, BypassBurnDamageReductionAttr } from "../data/move"; +import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveTypeAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, ExposedMoveAttr, BypassBurnDamageReductionAttr } from "../data/move"; import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from '../data/pokemon-species'; import * as Utils from '../utils'; import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from '../data/type'; @@ -19,7 +19,7 @@ import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEv import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from '../data/tms'; import { DamagePhase, FaintPhase, LearnMovePhase, ObtainStatusEffectPhase, StatChangePhase, SwitchSummonPhase } from '../phases'; import { BattleStat } from '../data/battle-stat'; -import { BattlerTag, BattlerTagLapseType, EncoreTag, HelpingHandTag, HighestStatBoostTag, ExposedTag, TypeBoostTag, getBattlerTag } from '../data/battler-tags'; +import { BattlerTag, BattlerTagLapseType, EncoreTag, HelpingHandTag, HighestStatBoostTag, TypeImmunityIgnoreTag, TypeBoostTag, getBattlerTag } from '../data/battler-tags'; import { BattlerTagType } from "../data/enums/battler-tag-type"; import { Species } from '../data/enums/species'; import { WeatherType } from '../data/weather'; @@ -966,7 +966,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (ignoreImmunity.value) return 1; - const exposedTags = this.getTags(ExposedTag) as ExposedTag[]; + const exposedTags = this.getTags(TypeImmunityIgnoreTag) as TypeImmunityIgnoreTag[]; if (exposedTags.some(t => t.ignoreImmunity(types, moveType))) { return 1; } From 865325c132131be9be29f76d7a155104c50345a7 Mon Sep 17 00:00:00 2001 From: Jakub Hanko <60473007+JakubHanko@users.noreply.github.com> Date: Tue, 14 May 2024 21:06:44 +0200 Subject: [PATCH 8/8] Add TSDoc --- src/data/move.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/data/move.ts b/src/data/move.ts index cc1eccfcf..b7641b0a4 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -3091,6 +3091,15 @@ export class ExposedMoveAttr extends AddBattlerTagAttr { super(tagType, false, true); } + /** + * Moves that reset the target's evasiveness and makes it vulnerable + * to types it is immune to. + * @param user Pokemon that used the move + * @param target The target of the move + * @param move Move with this attribute + * @param args None + * @returns true if the function succeeds + */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (!super.apply(user, target, move, args)) return false;