Bugfix: Abilities check final move type instead of default move type (#1440)

* Added check for move changing type before determining if defending is immune to it because of an ability

* Remove duplicate Ability change class and added getType() function under a move

* Reworking how moves get passed into hit calc

* Fixing exceptions and overreaching changes

* reverting forwarn and dancing move back to original since they are not being changed

* fixing some small move related bugs

* Fixing file permissions after testing

* Fixing move type not resetting after individual MoveEffectPhase

* Fixing move.ts permissions (again)

* Addressing some MR feedback and adding some documentation for PokemonMove class
This commit is contained in:
td76099 2024-06-07 16:57:57 -04:00 committed by GitHub
parent 636cb9c8f2
commit 0a17c2495a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 232 additions and 251 deletions

View File

@ -235,10 +235,10 @@ export class PostBattleInitStatChangeAbAttr extends PostBattleInitAbAttr {
} }
} }
type PreDefendAbAttrCondition = (pokemon: Pokemon, attacker: Pokemon, move: PokemonMove) => boolean; type PreDefendAbAttrCondition = (pokemon: Pokemon, attacker: Pokemon, move: Move) => boolean;
export class PreDefendAbAttr extends AbAttr { export class PreDefendAbAttr extends AbAttr {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
return false; return false;
} }
} }
@ -252,7 +252,7 @@ export class PreDefendFormChangeAbAttr extends PreDefendAbAttr {
this.formFunc = formFunc; this.formFunc = formFunc;
} }
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const formIndex = this.formFunc(pokemon); const formIndex = this.formFunc(pokemon);
if (formIndex !== pokemon.formIndex) { if (formIndex !== pokemon.formIndex) {
pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false);
@ -263,7 +263,7 @@ export class PreDefendFormChangeAbAttr extends PreDefendAbAttr {
} }
} }
export class PreDefendFullHpEndureAbAttr extends PreDefendAbAttr { export class PreDefendFullHpEndureAbAttr extends PreDefendAbAttr {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (pokemon.hp === pokemon.getMaxHp() && if (pokemon.hp === pokemon.getMaxHp() &&
pokemon.getMaxHp() > 1 && //Checks if pokemon has wonder_guard (which forces 1hp) pokemon.getMaxHp() > 1 && //Checks if pokemon has wonder_guard (which forces 1hp)
(args[0] as Utils.NumberHolder).value >= pokemon.hp) { //Damage >= hp (args[0] as Utils.NumberHolder).value >= pokemon.hp) { //Damage >= hp
@ -308,8 +308,8 @@ export class ReceivedMoveDamageMultiplierAbAttr extends PreDefendAbAttr {
this.powerMultiplier = powerMultiplier; this.powerMultiplier = powerMultiplier;
} }
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.condition(pokemon, attacker, move.getMove())) { if (this.condition(pokemon, attacker, move)) {
(args[0] as Utils.NumberHolder).value *= this.powerMultiplier; (args[0] as Utils.NumberHolder).value *= this.powerMultiplier;
return true; return true;
} }
@ -329,8 +329,8 @@ export class PreDefendMovePowerToOneAbAttr extends ReceivedMoveDamageMultiplierA
super(condition, 1); super(condition, 1);
} }
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.condition(pokemon, attacker, move.getMove())) { if (this.condition(pokemon, attacker, move)) {
(args[0] as Utils.NumberHolder).value = 1; (args[0] as Utils.NumberHolder).value = 1;
return true; return true;
} }
@ -339,6 +339,12 @@ export class PreDefendMovePowerToOneAbAttr extends ReceivedMoveDamageMultiplierA
} }
} }
/**
* Determines whether a Pokemon is immune to a move because of an ability.
* @extends PreDefendAbAttr
* @see {@linkcode applyPreDefend}
* @see {@linkcode getCondition}
*/
export class TypeImmunityAbAttr extends PreDefendAbAttr { export class TypeImmunityAbAttr extends PreDefendAbAttr {
private immuneType: Type; private immuneType: Type;
private condition: AbAttrCondition; private condition: AbAttrCondition;
@ -350,8 +356,17 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr {
this.condition = condition; this.condition = condition;
} }
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { /**
if ((move.getMove() instanceof AttackMove || move.getMove().getAttrs(StatusMoveTypeImmunityAttr).find(attr => attr.immuneType === this.immuneType)) && move.getMove().type === this.immuneType) { * @param pokemon {@linkcode Pokemon} the defending Pokemon
* @param passive N/A
* @param attacker {@linkcode Pokemon} the attacking Pokemon
* @param move {@linkcode Move} the attacking move
* @param cancelled N/A
* @param args [0] {@linkcode Utils.NumberHolder} gets set to 0 if move is immuned by an ability.
* @param args [1] {@linkcode Utils.NumberHolder} type of move being defended against in case it has changed from default type
*/
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if ((move instanceof AttackMove || move.getAttrs(StatusMoveTypeImmunityAttr).find(attr => attr.immuneType === this.immuneType)) && move.type === this.immuneType) {
(args[0] as Utils.NumberHolder).value = 0; (args[0] as Utils.NumberHolder).value = 0;
return true; return true;
} }
@ -369,7 +384,7 @@ export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr {
super(immuneType); super(immuneType);
} }
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args); const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args);
if (ret) { if (ret) {
@ -399,7 +414,7 @@ class TypeImmunityStatChangeAbAttr extends TypeImmunityAbAttr {
this.levels = levels; this.levels = levels;
} }
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args); const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args);
if (ret) { if (ret) {
@ -425,7 +440,7 @@ class TypeImmunityAddBattlerTagAbAttr extends TypeImmunityAbAttr {
this.turnCount = turnCount; this.turnCount = turnCount;
} }
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args); const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args);
if (ret) { if (ret) {
@ -445,8 +460,8 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr {
super(null, condition); super(null, condition);
} }
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (move.getMove() instanceof AttackMove && pokemon.getAttackTypeEffectiveness(move.getMove().type, attacker) < 2) { if (move instanceof AttackMove && pokemon.getAttackTypeEffectiveness(move.type, attacker) < 2) {
cancelled.value = true; cancelled.value = true;
(args[0] as Utils.NumberHolder).value = 0; (args[0] as Utils.NumberHolder).value = 0;
return true; return true;
@ -461,15 +476,15 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr {
} }
export class PostDefendAbAttr extends AbAttr { export class PostDefendAbAttr extends AbAttr {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean | Promise<boolean> { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean | Promise<boolean> {
return false; return false;
} }
} }
export class PostDefendDisguiseAbAttr extends PostDefendAbAttr { export class PostDefendDisguiseAbAttr extends PostDefendAbAttr {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (pokemon.formIndex === 0 && pokemon.battleData.hitCount !== 0 && (move.getMove().category === MoveCategory.SPECIAL || move.getMove().category === MoveCategory.PHYSICAL)) { if (pokemon.formIndex === 0 && pokemon.battleData.hitCount !== 0 && (move.category === MoveCategory.SPECIAL || move.category === MoveCategory.PHYSICAL)) {
const recoilDamage = Math.ceil((pokemon.getMaxHp() / 8) - attacker.turnData.damageDealt); const recoilDamage = Math.ceil((pokemon.getMaxHp() / 8) - attacker.turnData.damageDealt);
if (!recoilDamage) { if (!recoilDamage) {
@ -494,7 +509,7 @@ export class PostDefendFormChangeAbAttr extends PostDefendAbAttr {
this.formFunc = formFunc; this.formFunc = formFunc;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
const formIndex = this.formFunc(pokemon); const formIndex = this.formFunc(pokemon);
if (formIndex !== pokemon.formIndex) { if (formIndex !== pokemon.formIndex) {
pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false);
@ -506,16 +521,16 @@ export class PostDefendFormChangeAbAttr extends PostDefendAbAttr {
} }
export class FieldPriorityMoveImmunityAbAttr extends PreDefendAbAttr { export class FieldPriorityMoveImmunityAbAttr extends PreDefendAbAttr {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const attackPriority = new Utils.IntegerHolder(move.getMove().priority); const attackPriority = new Utils.IntegerHolder(move.priority);
applyMoveAttrs(IncrementMovePriorityAttr,attacker,null,move.getMove(),attackPriority); applyMoveAttrs(IncrementMovePriorityAttr,attacker,null,move,attackPriority);
applyAbAttrs(IncrementMovePriorityAbAttr, attacker, null, move.getMove(), attackPriority); applyAbAttrs(IncrementMovePriorityAbAttr, attacker, null, move, attackPriority);
if (move.getMove().moveTarget===MoveTarget.USER || move.getMove().moveTarget===MoveTarget.NEAR_ALLY) { if (move.moveTarget===MoveTarget.USER || move.moveTarget===MoveTarget.NEAR_ALLY) {
return false; return false;
} }
if (attackPriority.value > 0 && !move.getMove().isMultiTarget()) { if (attackPriority.value > 0 && !move.isMultiTarget()) {
cancelled.value = true; cancelled.value = true;
return true; return true;
} }
@ -539,7 +554,7 @@ export class MoveImmunityAbAttr extends PreDefendAbAttr {
this.immuneCondition = immuneCondition; this.immuneCondition = immuneCondition;
} }
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.immuneCondition(pokemon, attacker, move)) { if (this.immuneCondition(pokemon, attacker, move)) {
cancelled.value = true; cancelled.value = true;
return true; return true;
@ -563,7 +578,7 @@ export class MoveImmunityStatChangeAbAttr extends MoveImmunityAbAttr {
this.levels = levels; this.levels = levels;
} }
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args); const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args);
if (ret) { if (ret) {
const simulated = args.length > 1 && args[1]; const simulated = args.length > 1 && args[1];
@ -593,8 +608,8 @@ export class ReverseDrainAbAttr extends PostDefendAbAttr {
* @args N/A * @args N/A
* @returns true if healing should be reversed on a healing move, false otherwise. * @returns true if healing should be reversed on a healing move, false otherwise.
*/ */
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (move.getMove().hasAttr(HitHealAttr)) { if (move.hasAttr(HitHealAttr)) {
pokemon.scene.queueMessage(getPokemonMessage(attacker, " sucked up the liquid ooze!")); pokemon.scene.queueMessage(getPokemonMessage(attacker, " sucked up the liquid ooze!"));
return true; return true;
} }
@ -619,8 +634,8 @@ export class PostDefendStatChangeAbAttr extends PostDefendAbAttr {
this.allOthers = allOthers; this.allOthers = allOthers;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (this.condition(pokemon, attacker, move.getMove())) { if (this.condition(pokemon, attacker, move)) {
if (this.allOthers) { if (this.allOthers) {
const otherPokemon = pokemon.getAlly() ? pokemon.getOpponents().concat([ pokemon.getAlly() ]) : pokemon.getOpponents(); const otherPokemon = pokemon.getAlly() ? pokemon.getOpponents().concat([ pokemon.getAlly() ]) : pokemon.getOpponents();
for (const other of otherPokemon) { for (const other of otherPokemon) {
@ -653,10 +668,10 @@ export class PostDefendHpGatedStatChangeAbAttr extends PostDefendAbAttr {
this.selfTarget = selfTarget; this.selfTarget = selfTarget;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
const hpGateFlat: integer = Math.ceil(pokemon.getMaxHp() * this.hpGate); const hpGateFlat: integer = Math.ceil(pokemon.getMaxHp() * this.hpGate);
const lastAttackReceived = pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1]; const lastAttackReceived = pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1];
if (this.condition(pokemon, attacker, move.getMove()) && (pokemon.hp <= hpGateFlat && (pokemon.hp + lastAttackReceived.damage) > hpGateFlat)) { if (this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + lastAttackReceived.damage) > hpGateFlat)) {
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, (this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.levels)); pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, (this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.levels));
return true; return true;
} }
@ -676,8 +691,8 @@ export class PostDefendApplyArenaTrapTagAbAttr extends PostDefendAbAttr {
this.tagType = tagType; this.tagType = tagType;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (this.condition(pokemon, attacker, move.getMove())) { if (this.condition(pokemon, attacker, move)) {
const tag = pokemon.scene.arena.getTag(this.tagType) as ArenaTrapTag; const tag = pokemon.scene.arena.getTag(this.tagType) as ArenaTrapTag;
if (!pokemon.scene.arena.getTag(this.tagType) || tag.layers < tag.maxLayers) { if (!pokemon.scene.arena.getTag(this.tagType) || tag.layers < tag.maxLayers) {
pokemon.scene.arena.addTag(this.tagType, 0, undefined, pokemon.id, pokemon.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER); pokemon.scene.arena.addTag(this.tagType, 0, undefined, pokemon.id, pokemon.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER);
@ -698,11 +713,11 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr {
this.tagType = tagType; this.tagType = tagType;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (this.condition(pokemon, attacker, move.getMove())) { if (this.condition(pokemon, attacker, move)) {
if (!pokemon.getTag(this.tagType)) { if (!pokemon.getTag(this.tagType)) {
pokemon.addTag(this.tagType, undefined, undefined, pokemon.id); pokemon.addTag(this.tagType, undefined, undefined, pokemon.id);
pokemon.scene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: pokemon.name, moveName: move.getName() })); pokemon.scene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: pokemon.name, moveName: move.name }));
} }
return true; return true;
} }
@ -711,9 +726,9 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr {
} }
export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr { export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (hitResult < HitResult.NO_EFFECT) { if (hitResult < HitResult.NO_EFFECT) {
const type = move.getMove().type; const type = move.type;
const pokemonTypes = pokemon.getTypes(true); const pokemonTypes = pokemon.getTypes(true);
if (pokemonTypes.length !== 1 || pokemonTypes[0] !== type) { if (pokemonTypes.length !== 1 || pokemonTypes[0] !== type) {
pokemon.summonData.types = [ type ]; pokemon.summonData.types = [ type ];
@ -738,7 +753,7 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr {
this.terrainType = terrainType; this.terrainType = terrainType;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (hitResult < HitResult.NO_EFFECT) { if (hitResult < HitResult.NO_EFFECT) {
return pokemon.scene.arena.trySetTerrain(this.terrainType, true); return pokemon.scene.arena.trySetTerrain(this.terrainType, true);
} }
@ -758,8 +773,8 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr {
this.effects = effects; this.effects = effects;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) {
const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)]; const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)];
return attacker.trySetStatus(effect, true, pokemon); return attacker.trySetStatus(effect, true, pokemon);
} }
@ -773,7 +788,7 @@ export class EffectSporeAbAttr extends PostDefendContactApplyStatusEffectAbAttr
super(10, StatusEffect.POISON, StatusEffect.PARALYSIS, StatusEffect.SLEEP); super(10, StatusEffect.POISON, StatusEffect.PARALYSIS, StatusEffect.SLEEP);
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (attacker.hasAbility(Abilities.OVERCOAT) || attacker.isOfType(Type.GRASS)) { if (attacker.hasAbility(Abilities.OVERCOAT) || attacker.isOfType(Type.GRASS)) {
return false; return false;
} }
@ -794,9 +809,9 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr {
this.turnCount = turnCount; this.turnCount = turnCount;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance) { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance) {
return attacker.addTag(this.tagType, this.turnCount, move.moveId, attacker.id); return attacker.addTag(this.tagType, this.turnCount, move.id, attacker.id);
} }
return false; return false;
@ -814,7 +829,7 @@ export class PostDefendCritStatChangeAbAttr extends PostDefendAbAttr {
this.levels = levels; this.levels = levels;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.levels)); pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.levels));
return true; return true;
@ -834,8 +849,8 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr {
this.damageRatio = damageRatio; this.damageRatio = damageRatio;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) {
attacker.damageAndUpdate(Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER); attacker.damageAndUpdate(Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER);
attacker.turnData.damageTaken += Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio)); attacker.turnData.damageTaken += Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio));
return true; return true;
@ -864,8 +879,8 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr {
this.turns = turns; this.turns = turns;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) {
if (pokemon.getTag(BattlerTagType.PERISH_SONG) || attacker.getTag(BattlerTagType.PERISH_SONG)) { if (pokemon.getTag(BattlerTagType.PERISH_SONG) || attacker.getTag(BattlerTagType.PERISH_SONG)) {
return false; return false;
} else { } else {
@ -891,7 +906,7 @@ export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr {
this.weatherType = weatherType; this.weatherType = weatherType;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (!pokemon.scene.arena.weather?.isImmutable()) { if (!pokemon.scene.arena.weather?.isImmutable()) {
return pokemon.scene.arena.trySetWeather(this.weatherType, true); return pokemon.scene.arena.trySetWeather(this.weatherType, true);
} }
@ -905,8 +920,8 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr {
super(); super();
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr)) { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr)) {
const tempAbilityId = attacker.getAbility().id; const tempAbilityId = attacker.getAbility().id;
attacker.summonData.ability = pokemon.getAbility().id; attacker.summonData.ability = pokemon.getAbility().id;
pokemon.summonData.ability = tempAbilityId; pokemon.summonData.ability = tempAbilityId;
@ -929,8 +944,8 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr {
this.ability = ability; this.ability = ability;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr) && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr)) { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr) && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr)) {
attacker.summonData.ability = this.ability; attacker.summonData.ability = this.ability;
return true; return true;
@ -947,7 +962,7 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr {
export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr { export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr {
private chance: integer; private chance: integer;
private attacker: Pokemon; private attacker: Pokemon;
private move: PokemonMove; private move: Move;
constructor(chance: integer) { constructor(chance: integer) {
super(); super();
@ -955,13 +970,13 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr {
this.chance = chance; this.chance = chance;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (!attacker.summonData.disabledMove) { if (!attacker.summonData.disabledMove) {
if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !attacker.isMax()) { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !attacker.isMax()) {
this.attacker = attacker; this.attacker = attacker;
this.move = move; this.move = move;
attacker.summonData.disabledMove = move.moveId; attacker.summonData.disabledMove = move.id;
attacker.summonData.disabledTurns = 4; attacker.summonData.disabledTurns = 4;
return true; return true;
} }
@ -970,7 +985,7 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr {
} }
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
return getPokemonMessage(this.attacker, `'s ${this.move.getName()}\nwas disabled!`); return getPokemonMessage(this.attacker, `'s ${this.move.name}\nwas disabled!`);
} }
} }
@ -998,49 +1013,18 @@ export class PostStatChangeStatChangeAbAttr extends PostStatChangeAbAttr {
} }
export class PreAttackAbAttr extends AbAttr { export class PreAttackAbAttr extends AbAttr {
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean | Promise<boolean> { applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> {
return false; return false;
} }
} }
export class VariableMovePowerAbAttr extends PreAttackAbAttr { export class VariableMovePowerAbAttr extends PreAttackAbAttr {
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean { applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
//const power = args[0] as Utils.NumberHolder; //const power = args[0] as Utils.NumberHolder;
return false; return false;
} }
} }
export class VariableMoveTypeAbAttr extends AbAttr {
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
//const power = args[0] as Utils.IntegerHolder;
return false;
}
}
export class MoveTypeChangePowerMultiplierAbAttr extends VariableMoveTypeAbAttr {
private matchType: Type;
private newType: Type;
private powerMultiplier: number;
constructor(matchType: Type, newType: Type, powerMultiplier: number) {
super(true);
this.matchType = matchType;
this.newType = newType;
this.powerMultiplier = powerMultiplier;
}
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const type = (args[0] as Utils.IntegerHolder);
if (type.value === this.matchType) {
type.value = this.newType;
(args[1] as Utils.NumberHolder).value *= this.powerMultiplier;
return true;
}
return false;
}
}
export class FieldPreventExplosiveMovesAbAttr extends AbAttr { export class FieldPreventExplosiveMovesAbAttr extends AbAttr {
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> { apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
cancelled.value = true; cancelled.value = true;
@ -1060,11 +1044,10 @@ export class MoveTypeChangeAttr extends PreAttackAbAttr {
this.condition = condition; this.condition = condition;
} }
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean { applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
if (this.condition(pokemon, defender, move.getMove())) { if (this.condition(pokemon, defender, move)) {
const type = (args[0] as Utils.IntegerHolder); move.type = this.newType;
type.value = this.newType; (args[0] as Utils.NumberHolder).value *= this.powerMultiplier;
(args[1] as Utils.NumberHolder).value *= this.powerMultiplier;
return true; return true;
} }
@ -1097,8 +1080,8 @@ export class DamageBoostAbAttr extends PreAttackAbAttr {
* @param args Utils.NumberHolder as damage * @param args Utils.NumberHolder as damage
* @returns true if the function succeeds * @returns true if the function succeeds
*/ */
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean { applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
if (this.condition(pokemon, defender, move.getMove())) { if (this.condition(pokemon, defender, move)) {
const power = args[0] as Utils.NumberHolder; const power = args[0] as Utils.NumberHolder;
power.value = Math.floor(power.value * this.damageMultiplier); power.value = Math.floor(power.value * this.damageMultiplier);
return true; return true;
@ -1118,8 +1101,8 @@ export class MovePowerBoostAbAttr extends VariableMovePowerAbAttr {
this.powerMultiplier = powerMultiplier; this.powerMultiplier = powerMultiplier;
} }
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean { applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
if (this.condition(pokemon, defender, move.getMove())) { if (this.condition(pokemon, defender, move)) {
(args[0] as Utils.NumberHolder).value *= this.powerMultiplier; (args[0] as Utils.NumberHolder).value *= this.powerMultiplier;
return true; return true;
@ -1165,8 +1148,8 @@ export class VariableMovePowerBoostAbAttr extends VariableMovePowerAbAttr {
/** /**
* @override * @override
*/ */
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean { applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move, args: any[]): boolean {
const multiplier = this.mult(pokemon, defender, move.getMove()); const multiplier = this.mult(pokemon, defender, move);
if (multiplier !== 1) { if (multiplier !== 1) {
(args[0] as Utils.NumberHolder).value *= multiplier; (args[0] as Utils.NumberHolder).value *= multiplier;
return true; return true;
@ -1177,7 +1160,7 @@ export class VariableMovePowerBoostAbAttr extends VariableMovePowerAbAttr {
} }
export class FieldVariableMovePowerAbAttr extends AbAttr { export class FieldVariableMovePowerAbAttr extends AbAttr {
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean { applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
//const power = args[0] as Utils.NumberHolder; //const power = args[0] as Utils.NumberHolder;
return false; return false;
} }
@ -1193,8 +1176,8 @@ export class FieldMovePowerBoostAbAttr extends FieldVariableMovePowerAbAttr {
this.powerMultiplier = powerMultiplier; this.powerMultiplier = powerMultiplier;
} }
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean { applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
if (this.condition(pokemon, defender, move.getMove())) { if (this.condition(pokemon, defender, move)) {
(args[0] as Utils.NumberHolder).value *= this.powerMultiplier; (args[0] as Utils.NumberHolder).value *= this.powerMultiplier;
return true; return true;
@ -1235,7 +1218,7 @@ export class BattleStatMultiplierAbAttr extends AbAttr {
} }
export class PostAttackAbAttr extends AbAttr { export class PostAttackAbAttr extends AbAttr {
applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean | Promise<boolean> { applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean | Promise<boolean> {
return false; return false;
} }
} }
@ -1249,9 +1232,9 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
this.condition = condition; this.condition = condition;
} }
applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): Promise<boolean> { applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> {
return new Promise<boolean>(resolve => { return new Promise<boolean>(resolve => {
if (hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, defender, move.getMove()))) { if (hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, defender, move))) {
const heldItems = this.getTargetHeldItems(defender).filter(i => i.getTransferrable(false)); const heldItems = this.getTargetHeldItems(defender).filter(i => i.getTransferrable(false));
if (heldItems.length) { if (heldItems.length) {
const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)];
@ -1287,8 +1270,8 @@ export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr {
this.effects = effects; this.effects = effects;
} }
applyPostAttack(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { applyPostAttack(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (pokemon !== attacker && (!this.contactRequired || move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance && !pokemon.status) { if (pokemon !== attacker && (!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance && !pokemon.status) {
const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)]; const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)];
return attacker.trySetStatus(effect, true, pokemon); return attacker.trySetStatus(effect, true, pokemon);
} }
@ -1305,11 +1288,11 @@ export class PostAttackContactApplyStatusEffectAbAttr extends PostAttackApplySta
export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr { export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr {
private contactRequired: boolean; private contactRequired: boolean;
private chance: (user: Pokemon, target: Pokemon, move: PokemonMove) => integer; private chance: (user: Pokemon, target: Pokemon, move: Move) => integer;
private effects: BattlerTagType[]; private effects: BattlerTagType[];
constructor(contactRequired: boolean, chance: (user: Pokemon, target: Pokemon, move: PokemonMove) => integer, ...effects: BattlerTagType[]) { constructor(contactRequired: boolean, chance: (user: Pokemon, target: Pokemon, move: Move) => integer, ...effects: BattlerTagType[]) {
super(); super();
this.contactRequired = contactRequired; this.contactRequired = contactRequired;
@ -1317,8 +1300,8 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr {
this.effects = effects; this.effects = effects;
} }
applyPostAttack(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { applyPostAttack(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (pokemon !== attacker && (!this.contactRequired || move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance(attacker, pokemon, move) && !pokemon.status) { if (pokemon !== attacker && (!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance(attacker, pokemon, move) && !pokemon.status) {
const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)]; const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)];
@ -1338,9 +1321,9 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
this.condition = condition; this.condition = condition;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): Promise<boolean> { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> {
return new Promise<boolean>(resolve => { return new Promise<boolean>(resolve => {
if (hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move.getMove()))) { if (hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move))) {
const heldItems = this.getTargetHeldItems(attacker).filter(i => i.getTransferrable(false)); const heldItems = this.getTargetHeldItems(attacker).filter(i => i.getTransferrable(false));
if (heldItems.length) { if (heldItems.length) {
const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)];
@ -2002,9 +1985,9 @@ export class ConfusionOnStatusEffectAbAttr extends PostAttackAbAttr {
* @param args [0] {@linkcode StatusEffect} applied by move * @param args [0] {@linkcode StatusEffect} applied by move
* @returns true if defender is confused * @returns true if defender is confused
*/ */
applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (this.effects.indexOf(args[0]) > -1 && !defender.isFainted()) { if (this.effects.indexOf(args[0]) > -1 && !defender.isFainted()) {
return defender.addTag(BattlerTagType.CONFUSED, pokemon.randSeedInt(3,2), move.moveId, defender.id); return defender.addTag(BattlerTagType.CONFUSED, pokemon.randSeedInt(3,2), move.id, defender.id);
} }
return false; return false;
} }
@ -3028,7 +3011,7 @@ export class PostBattleLootAbAttr extends PostBattleAbAttr {
} }
export class PostFaintAbAttr extends AbAttr { export class PostFaintAbAttr extends AbAttr {
applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
return false; return false;
} }
} }
@ -3091,8 +3074,8 @@ export class PostFaintContactDamageAbAttr extends PostFaintAbAttr {
this.damageRatio = damageRatio; this.damageRatio = damageRatio;
} }
applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) {
const cancelled = new Utils.BooleanHolder(false); const cancelled = new Utils.BooleanHolder(false);
pokemon.scene.getField(true).map(p=>applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled)); pokemon.scene.getField(true).map(p=>applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled));
if (cancelled.value) { if (cancelled.value) {
@ -3119,7 +3102,7 @@ export class PostFaintHPDamageAbAttr extends PostFaintAbAttr {
super (); super ();
} }
applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
const damage = pokemon.turnData.attacksReceived[0].damage; const damage = pokemon.turnData.attacksReceived[0].damage;
attacker.damageAndUpdate((damage), HitResult.OTHER); attacker.damageAndUpdate((damage), HitResult.OTHER);
attacker.turnData.damageTaken += damage; attacker.turnData.damageTaken += damage;
@ -3566,13 +3549,13 @@ export function applyPostBattleInitAbAttrs(attrType: { new(...args: any[]): Post
} }
export function applyPreDefendAbAttrs(attrType: { new(...args: any[]): PreDefendAbAttr }, export function applyPreDefendAbAttrs(attrType: { new(...args: any[]): PreDefendAbAttr },
pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> { pokemon: Pokemon, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
const simulated = args.length > 1 && args[1]; const simulated = args.length > 1 && args[1];
return applyAbAttrsInternal<PreDefendAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreDefend(pokemon, passive, attacker, move, cancelled, args), args, false, false, simulated); return applyAbAttrsInternal<PreDefendAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreDefend(pokemon, passive, attacker, move, cancelled, args), args, false, false, simulated);
} }
export function applyPostDefendAbAttrs(attrType: { new(...args: any[]): PostDefendAbAttr }, export function applyPostDefendAbAttrs(attrType: { new(...args: any[]): PostDefendAbAttr },
pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, ...args: any[]): Promise<void> { pokemon: Pokemon, attacker: Pokemon, move: Move, hitResult: HitResult, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostDefendAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostDefend(pokemon, passive, attacker, move, hitResult, args), args); return applyAbAttrsInternal<PostDefendAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostDefend(pokemon, passive, attacker, move, hitResult, args), args);
} }
@ -3587,12 +3570,12 @@ export function applyBattleStatMultiplierAbAttrs(attrType: { new(...args: any[])
} }
export function applyPreAttackAbAttrs(attrType: { new(...args: any[]): PreAttackAbAttr }, export function applyPreAttackAbAttrs(attrType: { new(...args: any[]): PreAttackAbAttr },
pokemon: Pokemon, defender: Pokemon, move: PokemonMove, ...args: any[]): Promise<void> { pokemon: Pokemon, defender: Pokemon, move: Move, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PreAttackAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreAttack(pokemon, passive, defender, move, args), args); return applyAbAttrsInternal<PreAttackAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreAttack(pokemon, passive, defender, move, args), args);
} }
export function applyPostAttackAbAttrs(attrType: { new(...args: any[]): PostAttackAbAttr }, export function applyPostAttackAbAttrs(attrType: { new(...args: any[]): PostAttackAbAttr },
pokemon: Pokemon, defender: Pokemon, move: PokemonMove, hitResult: HitResult, ...args: any[]): Promise<void> { pokemon: Pokemon, defender: Pokemon, move: Move, hitResult: HitResult, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostAttackAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostAttack(pokemon, passive, defender, move, hitResult, args), args); return applyAbAttrsInternal<PostAttackAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostAttack(pokemon, passive, defender, move, hitResult, args), args);
} }
@ -3673,7 +3656,7 @@ export function applyPostBattleAbAttrs(attrType: { new(...args: any[]): PostBatt
} }
export function applyPostFaintAbAttrs(attrType: { new(...args: any[]): PostFaintAbAttr }, export function applyPostFaintAbAttrs(attrType: { new(...args: any[]): PostFaintAbAttr },
pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, ...args: any[]): Promise<void> { pokemon: Pokemon, attacker: Pokemon, move: Move, hitResult: HitResult, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostFaintAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostFaint(pokemon, passive, attacker, move, hitResult, args), args); return applyAbAttrsInternal<PostFaintAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostFaint(pokemon, passive, attacker, move, hitResult, args), args);
} }
@ -3692,7 +3675,7 @@ export const allAbilities = [ new Ability(Abilities.NONE, 3) ];
export function initAbilities() { export function initAbilities() {
allAbilities.push( allAbilities.push(
new Ability(Abilities.STENCH, 3) new Ability(Abilities.STENCH, 3)
.attr(PostAttackApplyBattlerTagAbAttr, false, (user, target, move) => (move.getMove().category !== MoveCategory.STATUS && !move.getMove().hasAttr(FlinchAttr)) ? 10 : 0, BattlerTagType.FLINCHED), .attr(PostAttackApplyBattlerTagAbAttr, false, (user, target, move) => (move.category !== MoveCategory.STATUS && !move.hasAttr(FlinchAttr)) ? 10 : 0, BattlerTagType.FLINCHED),
new Ability(Abilities.DRIZZLE, 3) new Ability(Abilities.DRIZZLE, 3)
.attr(PostSummonWeatherChangeAbAttr, WeatherType.RAIN) .attr(PostSummonWeatherChangeAbAttr, WeatherType.RAIN)
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.RAIN), .attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.RAIN),
@ -3829,7 +3812,7 @@ export function initAbilities() {
return false; return false;
}), }),
new Ability(Abilities.SOUNDPROOF, 3) new Ability(Abilities.SOUNDPROOF, 3)
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.getMove().hasFlag(MoveFlags.SOUND_BASED)) .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.SOUND_BASED))
.ignorable(), .ignorable(),
new Ability(Abilities.RAIN_DISH, 3) new Ability(Abilities.RAIN_DISH, 3)
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.RAIN, WeatherType.HEAVY_RAIN) .attr(PostWeatherLapseHealAbAttr, 1, WeatherType.RAIN, WeatherType.HEAVY_RAIN)
@ -4115,13 +4098,13 @@ export function initAbilities() {
) )
.partial(), .partial(),
new Ability(Abilities.TELEPATHY, 5) new Ability(Abilities.TELEPATHY, 5)
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon.getAlly() === attacker && move.getMove() instanceof AttackMove) .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon.getAlly() === attacker && move instanceof AttackMove)
.ignorable(), .ignorable(),
new Ability(Abilities.MOODY, 5) new Ability(Abilities.MOODY, 5)
.attr(MoodyAbAttr), .attr(MoodyAbAttr),
new Ability(Abilities.OVERCOAT, 5) new Ability(Abilities.OVERCOAT, 5)
.attr(BlockWeatherDamageAttr) .attr(BlockWeatherDamageAttr)
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.getMove().hasFlag(MoveFlags.POWDER_MOVE)) .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.POWDER_MOVE))
.ignorable(), .ignorable(),
new Ability(Abilities.POISON_TOUCH, 5) new Ability(Abilities.POISON_TOUCH, 5)
.attr(PostAttackContactApplyStatusEffectAbAttr, 30, StatusEffect.POISON), .attr(PostAttackContactApplyStatusEffectAbAttr, 30, StatusEffect.POISON),
@ -4210,14 +4193,14 @@ export function initAbilities() {
new Ability(Abilities.MAGICIAN, 6) new Ability(Abilities.MAGICIAN, 6)
.attr(PostAttackStealHeldItemAbAttr), .attr(PostAttackStealHeldItemAbAttr),
new Ability(Abilities.BULLETPROOF, 6) new Ability(Abilities.BULLETPROOF, 6)
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.getMove().hasFlag(MoveFlags.BALLBOMB_MOVE)) .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.BALLBOMB_MOVE))
.ignorable(), .ignorable(),
new Ability(Abilities.COMPETITIVE, 6) new Ability(Abilities.COMPETITIVE, 6)
.attr(PostStatChangeStatChangeAbAttr, (target, statsChanged, levels) => levels < 0, [BattleStat.SPATK], 2), .attr(PostStatChangeStatChangeAbAttr, (target, statsChanged, levels) => levels < 0, [BattleStat.SPATK], 2),
new Ability(Abilities.STRONG_JAW, 6) new Ability(Abilities.STRONG_JAW, 6)
.attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.BITING_MOVE), 1.5), .attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.BITING_MOVE), 1.5),
new Ability(Abilities.REFRIGERATE, 6) new Ability(Abilities.REFRIGERATE, 6)
.attr(MoveTypeChangePowerMultiplierAbAttr, Type.NORMAL, Type.ICE, 1.2), .attr(MoveTypeChangeAttr, Type.ICE, 1.2, (user, target, move) => move.type === Type.NORMAL),
new Ability(Abilities.SWEET_VEIL, 6) new Ability(Abilities.SWEET_VEIL, 6)
.attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP) .attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP)
.attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY) .attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY)
@ -4240,11 +4223,11 @@ export function initAbilities() {
new Ability(Abilities.TOUGH_CLAWS, 6) new Ability(Abilities.TOUGH_CLAWS, 6)
.attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), 1.3), .attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), 1.3),
new Ability(Abilities.PIXILATE, 6) new Ability(Abilities.PIXILATE, 6)
.attr(MoveTypeChangePowerMultiplierAbAttr, Type.NORMAL, Type.FAIRY, 1.2), .attr(MoveTypeChangeAttr, Type.FAIRY, 1.2, (user, target, move) => move.type === Type.NORMAL),
new Ability(Abilities.GOOEY, 6) new Ability(Abilities.GOOEY, 6)
.attr(PostDefendStatChangeAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), BattleStat.SPD, -1, false), .attr(PostDefendStatChangeAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), BattleStat.SPD, -1, false),
new Ability(Abilities.AERILATE, 6) new Ability(Abilities.AERILATE, 6)
.attr(MoveTypeChangePowerMultiplierAbAttr, Type.NORMAL, Type.FLYING, 1.2), .attr(MoveTypeChangeAttr, Type.FLYING, 1.2, (user, target, move) => move.type === Type.NORMAL),
new Ability(Abilities.PARENTAL_BOND, 6) new Ability(Abilities.PARENTAL_BOND, 6)
.unimplemented(), .unimplemented(),
new Ability(Abilities.DARK_AURA, 6) new Ability(Abilities.DARK_AURA, 6)
@ -4314,7 +4297,7 @@ export function initAbilities() {
new Ability(Abilities.TRIAGE, 7) new Ability(Abilities.TRIAGE, 7)
.attr(IncrementMovePriorityAbAttr, (pokemon, move) => move.hasFlag(MoveFlags.TRIAGE_MOVE), 3), .attr(IncrementMovePriorityAbAttr, (pokemon, move) => move.hasFlag(MoveFlags.TRIAGE_MOVE), 3),
new Ability(Abilities.GALVANIZE, 7) new Ability(Abilities.GALVANIZE, 7)
.attr(MoveTypeChangePowerMultiplierAbAttr, Type.NORMAL, Type.ELECTRIC, 1.2), .attr(MoveTypeChangeAttr, Type.ELECTRIC, 1.2, (user, target, move) => move.type === Type.NORMAL),
new Ability(Abilities.SURGE_SURFER, 7) new Ability(Abilities.SURGE_SURFER, 7)
.conditionalAttr(getTerrainCondition(TerrainType.ELECTRIC), BattleStatMultiplierAbAttr, BattleStat.SPD, 2), .conditionalAttr(getTerrainCondition(TerrainType.ELECTRIC), BattleStatMultiplierAbAttr, BattleStat.SPD, 2),
new Ability(Abilities.SCHOOLING, 7) new Ability(Abilities.SCHOOLING, 7)
@ -4567,7 +4550,7 @@ export function initAbilities() {
.attr(TypeImmunityStatChangeAbAttr, Type.FIRE, BattleStat.DEF, 2) .attr(TypeImmunityStatChangeAbAttr, Type.FIRE, BattleStat.DEF, 2)
.ignorable(), .ignorable(),
new Ability(Abilities.WIND_RIDER, 9) new Ability(Abilities.WIND_RIDER, 9)
.attr(MoveImmunityStatChangeAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.getMove().hasFlag(MoveFlags.WIND_MOVE) && move.getMove().category !== MoveCategory.STATUS, BattleStat.ATK, 1) .attr(MoveImmunityStatChangeAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.WIND_MOVE) && move.category !== MoveCategory.STATUS, BattleStat.ATK, 1)
.attr(PostSummonStatChangeOnArenaAbAttr, ArenaTagType.TAILWIND) .attr(PostSummonStatChangeOnArenaAbAttr, ArenaTagType.TAILWIND)
.ignorable(), .ignorable(),
new Ability(Abilities.GUARD_DOG, 9) new Ability(Abilities.GUARD_DOG, 9)
@ -4607,7 +4590,7 @@ export function initAbilities() {
.attr(NoTransformAbilityAbAttr) .attr(NoTransformAbilityAbAttr)
.partial(), // While setting the tag, the getbattlestat should ignore all modifiers to stats except stat stages .partial(), // While setting the tag, the getbattlestat should ignore all modifiers to stats except stat stages
new Ability(Abilities.GOOD_AS_GOLD, 9) new Ability(Abilities.GOOD_AS_GOLD, 9)
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.getMove().category === MoveCategory.STATUS) .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.category === MoveCategory.STATUS)
.ignorable() .ignorable()
.partial(), .partial(),
new Ability(Abilities.VESSEL_OF_RUIN, 9) new Ability(Abilities.VESSEL_OF_RUIN, 9)

View File

@ -99,6 +99,7 @@ export default class Move implements Localizable {
public id: Moves; public id: Moves;
public name: string; public name: string;
public type: Type; public type: Type;
public defaultType: Type;
public category: MoveCategory; public category: MoveCategory;
public moveTarget: MoveTarget; public moveTarget: MoveTarget;
public power: integer; public power: integer;
@ -118,6 +119,7 @@ export default class Move implements Localizable {
this.nameAppend = ""; this.nameAppend = "";
this.type = type; this.type = type;
this.defaultType = type;
this.category = category; this.category = category;
this.moveTarget = defaultMoveTarget; this.moveTarget = defaultMoveTarget;
this.power = power; this.power = power;
@ -1629,7 +1631,7 @@ export class StatusEffectAttr extends MoveEffectAttr {
} }
if ((!pokemon.status || (pokemon.status.effect === this.effect && move.chance < 0)) if ((!pokemon.status || (pokemon.status.effect === this.effect && move.chance < 0))
&& pokemon.trySetStatus(this.effect, true, user, this.cureTurn)) { && pokemon.trySetStatus(this.effect, true, user, this.cureTurn)) {
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, new PokemonMove(move.id), null,this.effect); applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null,this.effect);
return true; return true;
} }
} }
@ -3323,23 +3325,22 @@ export class TechnoBlastTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.GENESECT)) { if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.GENESECT)) {
const form = user.species.speciesId === Species.GENESECT ? user.formIndex : user.fusionSpecies.formIndex; const form = user.species.speciesId === Species.GENESECT ? user.formIndex : user.fusionSpecies.formIndex;
const type = (args[0] as Utils.IntegerHolder);
switch (form) { switch (form) {
case 1: // Shock Drive case 1: // Shock Drive
type.value = Type.ELECTRIC; move.type = Type.ELECTRIC;
break; break;
case 2: // Burn Drive case 2: // Burn Drive
type.value = Type.FIRE; move.type = Type.FIRE;
break; break;
case 3: // Chill Drive case 3: // Chill Drive
type.value = Type.ICE; move.type = Type.ICE;
break; break;
case 4: // Douse Drive case 4: // Douse Drive
type.value = Type.WATER; move.type = Type.WATER;
break; break;
default: default:
type.value = Type.NORMAL; move.type = Type.NORMAL;
break; break;
} }
return true; return true;
@ -3353,14 +3354,13 @@ export class AuraWheelTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.MORPEKO)) { if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.MORPEKO)) {
const form = user.species.speciesId === Species.MORPEKO ? user.formIndex : user.fusionSpecies.formIndex; const form = user.species.speciesId === Species.MORPEKO ? user.formIndex : user.fusionSpecies.formIndex;
const type = (args[0] as Utils.IntegerHolder);
switch (form) { switch (form) {
case 1: // Hangry Mode case 1: // Hangry Mode
type.value = Type.DARK; move.type = Type.DARK;
break; break;
default: // Full Belly Mode default: // Full Belly Mode
type.value = Type.ELECTRIC; move.type = Type.ELECTRIC;
break; break;
} }
return true; return true;
@ -3374,17 +3374,16 @@ export class RagingBullTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.PALDEA_TAUROS)) { if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.PALDEA_TAUROS)) {
const form = user.species.speciesId === Species.PALDEA_TAUROS ? user.formIndex : user.fusionSpecies.formIndex; const form = user.species.speciesId === Species.PALDEA_TAUROS ? user.formIndex : user.fusionSpecies.formIndex;
const type = (args[0] as Utils.IntegerHolder);
switch (form) { switch (form) {
case 1: // Blaze breed case 1: // Blaze breed
type.value = Type.FIRE; move.type = Type.FIRE;
break; break;
case 2: // Aqua breed case 2: // Aqua breed
type.value = Type.WATER; move.type = Type.WATER;
break; break;
default: default:
type.value = Type.FIGHTING; move.type = Type.FIGHTING;
break; break;
} }
return true; return true;
@ -3398,32 +3397,31 @@ export class IvyCudgelTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.OGERPON)) { if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.OGERPON)) {
const form = user.species.speciesId === Species.OGERPON ? user.formIndex : user.fusionSpecies.formIndex; const form = user.species.speciesId === Species.OGERPON ? user.formIndex : user.fusionSpecies.formIndex;
const type = (args[0] as Utils.IntegerHolder);
switch (form) { switch (form) {
case 1: // Wellspring Mask case 1: // Wellspring Mask
type.value = Type.WATER; move.type = Type.WATER;
break; break;
case 2: // Hearthflame Mask case 2: // Hearthflame Mask
type.value = Type.FIRE; move.type = Type.FIRE;
break; break;
case 3: // Cornerstone Mask case 3: // Cornerstone Mask
type.value = Type.ROCK; move.type = Type.ROCK;
break; break;
case 4: // Teal Mask Tera case 4: // Teal Mask Tera
type.value = Type.GRASS; move.type = Type.GRASS;
break; break;
case 5: // Wellspring Mask Tera case 5: // Wellspring Mask Tera
type.value = Type.WATER; move.type = Type.WATER;
break; break;
case 6: // Hearthflame Mask Tera case 6: // Hearthflame Mask Tera
type.value = Type.FIRE; move.type = Type.FIRE;
break; break;
case 7: // Cornerstone Mask Tera case 7: // Cornerstone Mask Tera
type.value = Type.ROCK; move.type = Type.ROCK;
break; break;
default: default:
type.value = Type.GRASS; move.type = Type.GRASS;
break; break;
} }
return true; return true;
@ -3436,23 +3434,21 @@ export class IvyCudgelTypeAttr extends VariableMoveTypeAttr {
export class WeatherBallTypeAttr extends VariableMoveTypeAttr { export class WeatherBallTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (!user.scene.arena.weather?.isEffectSuppressed(user.scene)) { if (!user.scene.arena.weather?.isEffectSuppressed(user.scene)) {
const type = (args[0] as Utils.IntegerHolder);
switch (user.scene.arena.weather?.weatherType) { switch (user.scene.arena.weather?.weatherType) {
case WeatherType.SUNNY: case WeatherType.SUNNY:
case WeatherType.HARSH_SUN: case WeatherType.HARSH_SUN:
type.value = Type.FIRE; move.type = Type.FIRE;
break; break;
case WeatherType.RAIN: case WeatherType.RAIN:
case WeatherType.HEAVY_RAIN: case WeatherType.HEAVY_RAIN:
type.value = Type.WATER; move.type = Type.WATER;
break; break;
case WeatherType.SANDSTORM: case WeatherType.SANDSTORM:
type.value = Type.ROCK; move.type = Type.ROCK;
break; break;
case WeatherType.HAIL: case WeatherType.HAIL:
case WeatherType.SNOW: case WeatherType.SNOW:
type.value = Type.ICE; move.type = Type.ICE;
break; break;
default: default:
return false; return false;
@ -3484,20 +3480,18 @@ export class TerrainPulseTypeAttr extends VariableMoveTypeAttr {
} }
const currentTerrain = user.scene.arena.getTerrainType(); const currentTerrain = user.scene.arena.getTerrainType();
const type = (args[0] as Utils.IntegerHolder);
switch (currentTerrain) { switch (currentTerrain) {
case TerrainType.MISTY: case TerrainType.MISTY:
type.value = Type.FAIRY; move.type = Type.FAIRY;
break; break;
case TerrainType.ELECTRIC: case TerrainType.ELECTRIC:
type.value = Type.ELECTRIC; move.type = Type.ELECTRIC;
break; break;
case TerrainType.GRASSY: case TerrainType.GRASSY:
type.value = Type.GRASS; move.type = Type.GRASS;
break; break;
case TerrainType.PSYCHIC: case TerrainType.PSYCHIC:
type.value = Type.PSYCHIC; move.type = Type.PSYCHIC;
break; break;
default: default:
return false; return false;
@ -3508,8 +3502,6 @@ export class TerrainPulseTypeAttr extends VariableMoveTypeAttr {
export class HiddenPowerTypeAttr extends VariableMoveTypeAttr { export class HiddenPowerTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const type = (args[0] as Utils.IntegerHolder);
const iv_val = Math.floor(((user.ivs[Stat.HP] & 1) const iv_val = Math.floor(((user.ivs[Stat.HP] & 1)
+(user.ivs[Stat.ATK] & 1) * 2 +(user.ivs[Stat.ATK] & 1) * 2
+(user.ivs[Stat.DEF] & 1) * 4 +(user.ivs[Stat.DEF] & 1) * 4
@ -3517,7 +3509,7 @@ export class HiddenPowerTypeAttr extends VariableMoveTypeAttr {
+(user.ivs[Stat.SPATK] & 1) * 16 +(user.ivs[Stat.SPATK] & 1) * 16
+(user.ivs[Stat.SPDEF] & 1) * 32) * 15/63); +(user.ivs[Stat.SPDEF] & 1) * 32) * 15/63);
type.value = [ move.type = [
Type.FIGHTING, Type.FLYING, Type.POISON, Type.GROUND, Type.FIGHTING, Type.FLYING, Type.POISON, Type.GROUND,
Type.ROCK, Type.BUG, Type.GHOST, Type.STEEL, Type.ROCK, Type.BUG, Type.GHOST, Type.STEEL,
Type.FIRE, Type.WATER, Type.GRASS, Type.ELECTRIC, Type.FIRE, Type.WATER, Type.GRASS, Type.ELECTRIC,
@ -3529,16 +3521,14 @@ export class HiddenPowerTypeAttr extends VariableMoveTypeAttr {
export class MatchUserTypeAttr extends VariableMoveTypeAttr { export class MatchUserTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const type = (args[0] as Utils.IntegerHolder);
const userTypes = user.getTypes(true); const userTypes = user.getTypes(true);
if (userTypes.includes(Type.STELLAR)) { // will not change to stellar type if (userTypes.includes(Type.STELLAR)) { // will not change to stellar type
const nonTeraTypes = user.getTypes(); const nonTeraTypes = user.getTypes();
type.value = nonTeraTypes[0]; move.type = nonTeraTypes[0];
return true; return true;
} else if (userTypes.length > 0) { } else if (userTypes.length > 0) {
type.value = userTypes[0]; move.type = userTypes[0];
return true; return true;
} else { } else {
return false; return false;
@ -4345,7 +4335,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
// Check if the move category is not STATUS or if the switch out condition is not met // Check if the move category is not STATUS or if the switch out condition is not met
if (!this.getSwitchOutCondition()(user, target, move)) { if (!this.getSwitchOutCondition()(user, target, move)) {
//Apply effects before switch out i.e. poison point, flame body, etc //Apply effects before switch out i.e. poison point, flame body, etc
applyPostDefendAbAttrs(PostDefendContactApplyStatusEffectAbAttr, target, user, new PokemonMove(move.id), null); applyPostDefendAbAttrs(PostDefendContactApplyStatusEffectAbAttr, target, user, move, null);
return resolve(false); return resolve(false);
} }

View File

@ -4,7 +4,7 @@ import { Variant, VariantSet, variantColorCache } from "#app/data/variant";
import { variantData } from "#app/data/variant"; import { variantData } from "#app/data/variant";
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "../ui/battle-info"; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "../ui/battle-info";
import { Moves } from "../data/enums/moves"; import { Moves } from "../data/enums/moves";
import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveTypeAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, MoveFlags } from "../data/move"; import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, VariableMoveTypeAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, MoveFlags } from "../data/move";
import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species"; import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type"; import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type";
@ -27,7 +27,7 @@ import { TempBattleStat } from "../data/temp-battle-stat";
import { ArenaTagSide, WeakenMoveScreenTag, WeakenMoveTypeTag } from "../data/arena-tag"; import { ArenaTagSide, WeakenMoveScreenTag, WeakenMoveTypeTag } from "../data/arena-tag";
import { ArenaTagType } from "../data/enums/arena-tag-type"; import { ArenaTagType } from "../data/enums/arena-tag-type";
import { Biome } from "../data/enums/biome"; import { Biome } from "../data/enums/biome";
import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, FieldVariableMovePowerAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, VariableMoveTypeAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr } from "../data/ability"; import { Ability, AbAttr, BattleStatMultiplierAbAttr, MoveTypeChangeAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, FieldVariableMovePowerAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr } from "../data/ability";
import { Abilities } from "#app/data/enums/abilities"; import { Abilities } from "#app/data/enums/abilities";
import PokemonData from "../system/pokemon-data"; import PokemonData from "../system/pokemon-data";
import { BattlerIndex } from "../battle"; import { BattlerIndex } from "../battle";
@ -1057,9 +1057,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return !this.isOfType(Type.FLYING, true) && !this.hasAbility(Abilities.LEVITATE); return !this.isOfType(Type.FLYING, true) && !this.hasAbility(Abilities.LEVITATE);
} }
getAttackMoveEffectiveness(source: Pokemon, move: PokemonMove): TypeDamageMultiplier { getAttackMoveEffectiveness(source: Pokemon, pokemonMove: PokemonMove): TypeDamageMultiplier {
const typeless = move.getMove().hasAttr(TypelessAttr); const move = pokemonMove.getMove();
const typeMultiplier = new Utils.NumberHolder(this.getAttackTypeEffectiveness(move.getMove().type, source)); const typeless = move.hasAttr(TypelessAttr);
const typeMultiplier = new Utils.NumberHolder(this.getAttackTypeEffectiveness(move.type, source));
const cancelled = new Utils.BooleanHolder(false); const cancelled = new Utils.BooleanHolder(false);
if (!typeless) { if (!typeless) {
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, typeMultiplier, true); applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, typeMultiplier, true);
@ -1620,9 +1621,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return (this.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField())[this.getFieldIndex() ? 0 : 1]; return (this.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField())[this.getFieldIndex() ? 0 : 1];
} }
apply(source: Pokemon, battlerMove: PokemonMove): HitResult { apply(source: Pokemon, move: Move): HitResult {
let result: HitResult; let result: HitResult;
const move = battlerMove.getMove();
const damage = new Utils.NumberHolder(0); const damage = new Utils.NumberHolder(0);
const defendingSidePlayField = this.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField(); const defendingSidePlayField = this.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField();
@ -1630,19 +1630,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyMoveAttrs(VariableMoveCategoryAttr, source, this, move, variableCategory); applyMoveAttrs(VariableMoveCategoryAttr, source, this, move, variableCategory);
const moveCategory = variableCategory.value as MoveCategory; const moveCategory = variableCategory.value as MoveCategory;
const variableType = new Utils.IntegerHolder(move.type);
const typeChangeMovePowerMultiplier = new Utils.NumberHolder(1); const typeChangeMovePowerMultiplier = new Utils.NumberHolder(1);
applyMoveAttrs(VariableMoveTypeAttr, source, this, move, variableType); applyMoveAttrs(VariableMoveTypeAttr, source, this, move);
// 2nd argument is for MoveTypeChangePowerMultiplierAbAttr applyPreAttackAbAttrs(MoveTypeChangeAttr, source, this, move, typeChangeMovePowerMultiplier);
applyAbAttrs(VariableMoveTypeAbAttr, source, null, variableType, typeChangeMovePowerMultiplier);
applyPreAttackAbAttrs(MoveTypeChangeAttr, source, this, battlerMove, variableType, typeChangeMovePowerMultiplier);
const type = variableType.value as Type;
const types = this.getTypes(true, true); const types = this.getTypes(true, true);
const cancelled = new Utils.BooleanHolder(false); const cancelled = new Utils.BooleanHolder(false);
const typeless = move.hasAttr(TypelessAttr); const typeless = move.hasAttr(TypelessAttr);
const typeMultiplier = new Utils.NumberHolder(!typeless && (moveCategory !== MoveCategory.STATUS || move.getAttrs(StatusMoveTypeImmunityAttr).find(attr => types.includes(attr.immuneType))) const typeMultiplier = new Utils.NumberHolder(!typeless && (moveCategory !== MoveCategory.STATUS || move.getAttrs(StatusMoveTypeImmunityAttr).find(attr => types.includes(attr.immuneType)))
? this.getAttackTypeEffectiveness(type, source) ? this.getAttackTypeEffectiveness(move.type, source)
: 1); : 1);
applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier); applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier);
if (typeless) { if (typeless) {
@ -1667,44 +1663,44 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const isPhysical = moveCategory === MoveCategory.PHYSICAL; const isPhysical = moveCategory === MoveCategory.PHYSICAL;
const power = new Utils.NumberHolder(move.power); const power = new Utils.NumberHolder(move.power);
const sourceTeraType = source.getTeraType(); const sourceTeraType = source.getTeraType();
if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === type && power.value < 60 && move.priority <= 0 && !move.hasAttr(MultiHitAttr) && !this.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === move.type && power.value < 60 && move.priority <= 0 && !move.hasAttr(MultiHitAttr) && !this.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
power.value = 60; power.value = 60;
} }
applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, this, battlerMove, power); applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, this, move, power);
this.scene.getField(true).map(p => applyPreAttackAbAttrs(FieldVariableMovePowerAbAttr, this, source, battlerMove, power)); this.scene.getField(true).map(p => applyPreAttackAbAttrs(FieldVariableMovePowerAbAttr, this, source, move, power));
applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, this, source, battlerMove, cancelled, power); applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, this, source, move, cancelled, power);
power.value *= typeChangeMovePowerMultiplier.value; power.value *= typeChangeMovePowerMultiplier.value;
if (!typeless) { if (!typeless) {
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier); applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, typeMultiplier);
} }
if (!cancelled.value) { if (!cancelled.value) {
applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier); applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, typeMultiplier);
defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, battlerMove, cancelled, typeMultiplier)); defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, move, cancelled, typeMultiplier));
} }
if (cancelled.value) { if (cancelled.value) {
result = HitResult.NO_EFFECT; result = HitResult.NO_EFFECT;
} else { } else {
const typeBoost = source.findTag(t => t instanceof TypeBoostTag && (t as TypeBoostTag).boostedType === type) as TypeBoostTag; const typeBoost = source.findTag(t => t instanceof TypeBoostTag && t.boostedType === move.type) as TypeBoostTag;
if (typeBoost) { if (typeBoost) {
power.value *= typeBoost.boostValue; power.value *= typeBoost.boostValue;
if (typeBoost.oneUse) { if (typeBoost.oneUse) {
source.removeTag(typeBoost.tagType); source.removeTag(typeBoost.tagType);
} }
} }
const arenaAttackTypeMultiplier = new Utils.NumberHolder(this.scene.arena.getAttackTypeMultiplier(type, source.isGrounded())); const arenaAttackTypeMultiplier = new Utils.NumberHolder(this.scene.arena.getAttackTypeMultiplier(move.type, source.isGrounded()));
applyMoveAttrs(IgnoreWeatherTypeDebuffAttr, source, this, move, arenaAttackTypeMultiplier); applyMoveAttrs(IgnoreWeatherTypeDebuffAttr, source, this, move, arenaAttackTypeMultiplier);
if (this.scene.arena.getTerrainType() === TerrainType.GRASSY && this.isGrounded() && type === Type.GROUND && move.moveTarget === MoveTarget.ALL_NEAR_OTHERS) { if (this.scene.arena.getTerrainType() === TerrainType.GRASSY && this.isGrounded() && move.type === Type.GROUND && move.moveTarget === MoveTarget.ALL_NEAR_OTHERS) {
power.value /= 2; power.value /= 2;
} }
applyMoveAttrs(VariablePowerAttr, source, this, move, power); applyMoveAttrs(VariablePowerAttr, source, this, move, power);
this.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, new Utils.IntegerHolder(0), power); this.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, new Utils.IntegerHolder(0), power);
if (!typeless) { if (!typeless) {
this.scene.arena.applyTags(WeakenMoveTypeTag, type, power); this.scene.arena.applyTags(WeakenMoveTypeTag, move.type, power);
this.scene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, type, power); this.scene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, move.type, power);
} }
if (source.getTag(HelpingHandTag)) { if (source.getTag(HelpingHandTag)) {
power.value *= 1.5; power.value *= 1.5;
@ -1749,11 +1745,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
const isTypeImmune = (typeMultiplier.value * arenaAttackTypeMultiplier.value) === 0; const isTypeImmune = (typeMultiplier.value * arenaAttackTypeMultiplier.value) === 0;
const sourceTypes = source.getTypes(); const sourceTypes = source.getTypes();
const matchesSourceType = sourceTypes[0] === type || (sourceTypes.length > 1 && sourceTypes[1] === type); const matchesSourceType = sourceTypes[0] === move.type || (sourceTypes.length > 1 && sourceTypes[1] === move.type);
const stabMultiplier = new Utils.NumberHolder(1); const stabMultiplier = new Utils.NumberHolder(1);
if (sourceTeraType === Type.UNKNOWN && matchesSourceType) { if (sourceTeraType === Type.UNKNOWN && matchesSourceType) {
stabMultiplier.value += 0.5; stabMultiplier.value += 0.5;
} else if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === type) { } else if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === move.type) {
stabMultiplier.value += 0.5; stabMultiplier.value += 0.5;
} }
@ -1778,7 +1774,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
} }
applyPreAttackAbAttrs(DamageBoostAbAttr, source, this, battlerMove, damage); applyPreAttackAbAttrs(DamageBoostAbAttr, source, this, move, damage);
/** /**
* For each {@link HitsTagAttr} the move has, doubles the damage of the move if: * For each {@link HitsTagAttr} the move has, doubles the damage of the move if:
@ -1793,7 +1789,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}); });
} }
if (this.scene.arena.terrain?.terrainType === TerrainType.MISTY && this.isGrounded() && type === Type.DRAGON) { if (this.scene.arena.terrain?.terrainType === TerrainType.MISTY && this.isGrounded() && move.type === Type.DRAGON) {
damage.value = Math.floor(damage.value / 2); damage.value = Math.floor(damage.value / 2);
} }
@ -1848,7 +1844,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const oneHitKo = result === HitResult.ONE_HIT_KO; const oneHitKo = result === HitResult.ONE_HIT_KO;
if (damage.value) { if (damage.value) {
if (this.getHpRatio() === 1) { if (this.getHpRatio() === 1) {
applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, this, source, battlerMove, cancelled, damage); applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, this, source, move, cancelled, damage);
} else if (!this.isPlayer() && damage.value >= this.hp) { } else if (!this.isPlayer() && damage.value >= this.hp) {
this.scene.applyModifiers(EnemyEndureChanceModifier, false, this); this.scene.applyModifiers(EnemyEndureChanceModifier, false, this);
} }
@ -1913,11 +1909,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
break; break;
case MoveCategory.STATUS: case MoveCategory.STATUS:
if (!typeless) { if (!typeless) {
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier); applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, typeMultiplier);
} }
if (!cancelled.value) { if (!cancelled.value) {
applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier); applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, typeMultiplier);
defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, battlerMove, cancelled, typeMultiplier)); defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, move, cancelled, typeMultiplier));
} }
if (!typeMultiplier.value) { if (!typeMultiplier.value) {
this.scene.queueMessage(i18next.t("battle:hitResultNoEffect", { pokemonName: this.name })); this.scene.queueMessage(i18next.t("battle:hitResultNoEffect", { pokemonName: this.name }));
@ -3381,10 +3377,6 @@ export class EnemyPokemon extends Pokemon {
const pokemonMove = movePool[m]; const pokemonMove = movePool[m];
const move = pokemonMove.getMove(); const move = pokemonMove.getMove();
const variableType = new Utils.IntegerHolder(move.type);
applyAbAttrs(VariableMoveTypeAbAttr, this, null, variableType);
const moveType = variableType.value as Type;
let moveScore = moveScores[m]; let moveScore = moveScores[m];
const targetScores: integer[] = []; const targetScores: integer[] = [];
@ -3402,12 +3394,12 @@ export class EnemyPokemon extends Pokemon {
const effectiveness = target.getAttackMoveEffectiveness(this, pokemonMove); const effectiveness = target.getAttackMoveEffectiveness(this, pokemonMove);
if (target.isPlayer() !== this.isPlayer()) { if (target.isPlayer() !== this.isPlayer()) {
targetScore *= effectiveness; targetScore *= effectiveness;
if (this.isOfType(moveType)) { if (this.isOfType(move.type)) {
targetScore *= 1.5; targetScore *= 1.5;
} }
} else if (effectiveness) { } else if (effectiveness) {
targetScore /= effectiveness; targetScore /= effectiveness;
if (this.isOfType(moveType)) { if (this.isOfType(move.type)) {
targetScore /= 1.5; targetScore /= 1.5;
} }
} }
@ -3789,6 +3781,19 @@ export enum HitResult {
export type DamageResult = HitResult.EFFECTIVE | HitResult.SUPER_EFFECTIVE | HitResult.NOT_VERY_EFFECTIVE | HitResult.ONE_HIT_KO | HitResult.OTHER; export type DamageResult = HitResult.EFFECTIVE | HitResult.SUPER_EFFECTIVE | HitResult.NOT_VERY_EFFECTIVE | HitResult.ONE_HIT_KO | HitResult.OTHER;
/**
* Wrapper class for the {@linkcode Move} class for Pokemon to interact with.
* These are the moves assigned to a {@linkcode Pokemon} object.
* It links to {@linkcode Move} class via the move ID.
* Compared to {@linkcode Move}, this class also tracks if a move has received.
* PP Ups, amount of PP used, and things like that.
* @see {@linkcode isUsable} - checks if move is disabled, out of PP, or not implemented.
* @see {@linkcode getMove} - returns {@linkcode Move} object by looking it up via ID.
* @see {@linkcode usePp} - removes a point of PP from the move.
* @see {@linkcode getMovePp} - returns amount of PP a move currently has.
* @see {@linkcode getPpRatio} - returns the current PP amount / max PP amount.
* @see {@linkcode getName} - returns name of {@linkcode Move}.
**/
export class PokemonMove { export class PokemonMove {
public moveId: Moves; public moveId: Moves;
public ppUsed: integer; public ppUsed: integer;

View File

@ -2710,9 +2710,10 @@ export class MoveEffectPhase extends PokemonPhase {
} }
const overridden = new Utils.BooleanHolder(false); const overridden = new Utils.BooleanHolder(false);
const move = this.move.getMove();
// Assume single target for override // Assume single target for override
applyMoveAttrs(OverrideMoveEffectAttr, user, this.getTarget(), this.move.getMove(), overridden, this.move.virtual).then(() => { applyMoveAttrs(OverrideMoveEffectAttr, user, this.getTarget(), move, overridden, this.move.virtual).then(() => {
if (overridden.value) { if (overridden.value) {
return this.end(); return this.end();
@ -2723,8 +2724,8 @@ export class MoveEffectPhase extends PokemonPhase {
if (user.turnData.hitsLeft === undefined) { if (user.turnData.hitsLeft === undefined) {
const hitCount = new Utils.IntegerHolder(1); const hitCount = new Utils.IntegerHolder(1);
// Assume single target for multi hit // Assume single target for multi hit
applyMoveAttrs(MultiHitAttr, user, this.getTarget(), this.move.getMove(), hitCount); applyMoveAttrs(MultiHitAttr, user, this.getTarget(), move, hitCount);
if (this.move.getMove() instanceof AttackMove && !this.move.getMove().hasAttr(FixedDamageAttr)) { if (move instanceof AttackMove && !move.hasAttr(FixedDamageAttr)) {
this.scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, hitCount, new Utils.IntegerHolder(0)); this.scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, hitCount, new Utils.IntegerHolder(0));
} }
user.turnData.hitsLeft = user.turnData.hitCount = hitCount.value; user.turnData.hitsLeft = user.turnData.hitCount = hitCount.value;
@ -2735,13 +2736,13 @@ export class MoveEffectPhase extends PokemonPhase {
const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ])); const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ]));
const activeTargets = targets.map(t => t.isActive(true)); const activeTargets = targets.map(t => t.isActive(true));
if (!activeTargets.length || (!this.move.getMove().hasAttr(VariableTargetAttr) && !this.move.getMove().isMultiTarget() && !targetHitChecks[this.targets[0]])) { if (!activeTargets.length || (!move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]])) {
user.turnData.hitCount = 1; user.turnData.hitCount = 1;
user.turnData.hitsLeft = 1; user.turnData.hitsLeft = 1;
if (activeTargets.length) { if (activeTargets.length) {
this.scene.queueMessage(getPokemonMessage(user, "'s\nattack missed!")); this.scene.queueMessage(getPokemonMessage(user, "'s\nattack missed!"));
moveHistoryEntry.result = MoveResult.MISS; moveHistoryEntry.result = MoveResult.MISS;
applyMoveAttrs(MissEffectAttr, user, null, this.move.getMove()); applyMoveAttrs(MissEffectAttr, user, null, move);
} else { } else {
this.scene.queueMessage(i18next.t("battle:attackFailed")); this.scene.queueMessage(i18next.t("battle:attackFailed"));
moveHistoryEntry.result = MoveResult.FAIL; moveHistoryEntry.result = MoveResult.FAIL;
@ -2752,7 +2753,7 @@ export class MoveEffectPhase extends PokemonPhase {
const applyAttrs: Promise<void>[] = []; const applyAttrs: Promise<void>[] = [];
// Move animation only needs one target // Move animation only needs one target
new MoveAnim(this.move.getMove().id as Moves, user, this.getTarget()?.getBattlerIndex()).play(this.scene, () => { new MoveAnim(move.id as Moves, user, this.getTarget()?.getBattlerIndex()).play(this.scene, () => {
for (const target of targets) { for (const target of targets) {
if (!targetHitChecks[target.getBattlerIndex()]) { if (!targetHitChecks[target.getBattlerIndex()]) {
user.turnData.hitCount = 1; user.turnData.hitCount = 1;
@ -2761,31 +2762,31 @@ export class MoveEffectPhase extends PokemonPhase {
if (moveHistoryEntry.result === MoveResult.PENDING) { if (moveHistoryEntry.result === MoveResult.PENDING) {
moveHistoryEntry.result = MoveResult.MISS; moveHistoryEntry.result = MoveResult.MISS;
} }
applyMoveAttrs(MissEffectAttr, user, null, this.move.getMove()); applyMoveAttrs(MissEffectAttr, user, null, move);
continue; continue;
} }
const isProtected = !this.move.getMove().hasFlag(MoveFlags.IGNORE_PROTECT) && target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType)); const isProtected = !move.hasFlag(MoveFlags.IGNORE_PROTECT) && target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType));
const firstHit = moveHistoryEntry.result !== MoveResult.SUCCESS; const firstHit = moveHistoryEntry.result !== MoveResult.SUCCESS;
moveHistoryEntry.result = MoveResult.SUCCESS; moveHistoryEntry.result = MoveResult.SUCCESS;
const hitResult = !isProtected ? target.apply(user, this.move) : HitResult.NO_EFFECT; const hitResult = !isProtected ? target.apply(user, move) : HitResult.NO_EFFECT;
this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger); this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger);
applyAttrs.push(new Promise(resolve => { applyAttrs.push(new Promise(resolve => {
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit), applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit),
user, target, this.move.getMove()).then(() => { user, target, move).then(() => {
if (hitResult !== HitResult.FAIL) { if (hitResult !== HitResult.FAIL) {
const chargeEffect = !!this.move.getMove().getAttrs(ChargeAttr).find(ca => ca.usedChargeEffect(user, this.getTarget(), this.move.getMove())); const chargeEffect = !!move.getAttrs(ChargeAttr).find(ca => ca.usedChargeEffect(user, this.getTarget(), move));
// Charge attribute with charge effect takes all effect attributes and applies them to charge stage, so ignore them if this is present // Charge attribute with charge effect takes all effect attributes and applies them to charge stage, so ignore them if this is present
Utils.executeIf(!chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY Utils.executeIf(!chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY
&& (attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit), user, target, this.move.getMove())).then(() => { && attr.selfTarget && (!attr.firstHitOnly || firstHit), user, target, move)).then(() => {
if (hitResult !== HitResult.NO_EFFECT) { if (hitResult !== HitResult.NO_EFFECT) {
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY
&& !(attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit), user, target, this.move.getMove()).then(() => { && !attr.selfTarget && (!attr.firstHitOnly || firstHit), user, target, move).then(() => {
if (hitResult < HitResult.NO_EFFECT) { if (hitResult < HitResult.NO_EFFECT) {
const flinched = new Utils.BooleanHolder(false); const flinched = new Utils.BooleanHolder(false);
user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched); user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched);
@ -2793,15 +2794,15 @@ export class MoveEffectPhase extends PokemonPhase {
target.addTag(BattlerTagType.FLINCHED, undefined, this.move.moveId, user.id); target.addTag(BattlerTagType.FLINCHED, undefined, this.move.moveId, user.id);
} }
} }
Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT && (!attr.firstHitOnly || firstHit), Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.HIT && (!attr.firstHitOnly || firstHit),
user, target, this.move.getMove()).then(() => { user, target, move).then(() => {
return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move, hitResult).then(() => { return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, move, hitResult).then(() => {
if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) { if (!user.isPlayer() && move instanceof AttackMove) {
user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target); user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target);
} }
})).then(() => { })).then(() => {
applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move, hitResult).then(() => { applyPostAttackAbAttrs(PostAttackAbAttr, user, target, move, hitResult).then(() => {
if (this.move.getMove() instanceof AttackMove) { if (move instanceof AttackMove) {
this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target.getFieldIndex()); this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target.getFieldIndex());
} }
resolve(); resolve();
@ -2811,7 +2812,7 @@ export class MoveEffectPhase extends PokemonPhase {
).then(() => resolve()); ).then(() => resolve());
}); });
} else { } else {
applyMoveAttrs(NoEffectAttr, user, null, this.move.getMove()).then(() => resolve()); applyMoveAttrs(NoEffectAttr, user, null, move).then(() => resolve());
} }
}); });
} else { } else {
@ -2821,8 +2822,8 @@ export class MoveEffectPhase extends PokemonPhase {
})); }));
} }
// Trigger effect which should only apply one time after all targeted effects have already applied // Trigger effect which should only apply one time after all targeted effects have already applied
const postTarget = applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_TARGET, const postTarget = applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_TARGET,
user, null, this.move.getMove()); user, null, move);
if (applyAttrs.length) { // If there is a pending asynchronous move effect, do this after if (applyAttrs.length) { // If there is a pending asynchronous move effect, do this after
applyAttrs[applyAttrs.length - 1]?.then(() => postTarget); applyAttrs[applyAttrs.length - 1]?.then(() => postTarget);
@ -2836,6 +2837,8 @@ export class MoveEffectPhase extends PokemonPhase {
} }
end() { end() {
const move = this.move.getMove();
move.type = move.defaultType;
const user = this.getUserPokemon(); const user = this.getUserPokemon();
if (user) { if (user) {
if (--user.turnData.hitsLeft >= 1 && this.getTarget()?.isActive()) { if (--user.turnData.hitsLeft >= 1 && this.getTarget()?.isActive()) {
@ -3542,7 +3545,7 @@ export class FaintPhase extends PokemonPhase {
if (pokemon.turnData?.attacksReceived?.length) { if (pokemon.turnData?.attacksReceived?.length) {
const lastAttack = pokemon.turnData.attacksReceived[0]; const lastAttack = pokemon.turnData.attacksReceived[0];
applyPostFaintAbAttrs(PostFaintAbAttr, pokemon, this.scene.getPokemonById(lastAttack.sourceId), new PokemonMove(lastAttack.move), lastAttack.result); applyPostFaintAbAttrs(PostFaintAbAttr, pokemon, this.scene.getPokemonById(lastAttack.sourceId), new PokemonMove(lastAttack.move).getMove(), lastAttack.result);
} }
const alivePlayField = this.scene.getField(true); const alivePlayField = this.scene.getField(true);