[QoL] Add type inference to getAttrs methods and refactor accordingly (#1633)
* add type inference to getAttrs methods and refactor accordingly * Tests/infer types for get attrs methods (#1) * #1633: add spec/tests for coverage * move ability/move tests into /src/tests and rename to *.test.ts to match common naming patterns * use None in test cases to remove ambiguity * revert back to before test cases were merged --------- Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>
This commit is contained in:
parent
13c9888902
commit
d052d444b6
|
@ -55,8 +55,22 @@ export class Ability implements Localizable {
|
||||||
this.description = this.id ? i18next.t(`ability:${i18nKey}.description`) as string : "";
|
this.description = this.id ? i18next.t(`ability:${i18nKey}.description`) as string : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
getAttrs(attrType: { new(...args: any[]): AbAttr }): AbAttr[] {
|
/**
|
||||||
return this.attrs.filter(a => a instanceof attrType);
|
* Get all ability attributes that match `attrType`
|
||||||
|
* @param attrType any attribute that extends {@linkcode AbAttr}
|
||||||
|
* @returns Array of attributes that match `attrType`, Empty Array if none match.
|
||||||
|
*/
|
||||||
|
getAttrs<T extends AbAttr>(attrType: new(...args: any[]) => T ): T[] {
|
||||||
|
return this.attrs.filter((a): a is T => a instanceof attrType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an ability has an attribute that matches `attrType`
|
||||||
|
* @param attrType any attribute that extends {@linkcode AbAttr}
|
||||||
|
* @returns true if the ability has attribute `attrType`
|
||||||
|
*/
|
||||||
|
hasAttr<T extends AbAttr>(attrType: new(...args: any[]) => T): boolean {
|
||||||
|
return this.attrs.some((attr) => attr instanceof attrType);
|
||||||
}
|
}
|
||||||
|
|
||||||
attr<T extends new (...args: any[]) => AbAttr>(AttrType: T, ...args: ConstructorParameters<T>): Ability {
|
attr<T extends new (...args: any[]) => AbAttr>(AttrType: T, ...args: ConstructorParameters<T>): Ability {
|
||||||
|
@ -74,10 +88,6 @@ export class Ability implements Localizable {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasAttr(attrType: { new(...args: any[]): AbAttr }): boolean {
|
|
||||||
return !!this.getAttrs(attrType).length;
|
|
||||||
}
|
|
||||||
|
|
||||||
bypassFaint(): Ability {
|
bypassFaint(): Ability {
|
||||||
this.isBypassFaint = true;
|
this.isBypassFaint = true;
|
||||||
return this;
|
return this;
|
||||||
|
@ -341,7 +351,7 @@ export class TypeImmunityAbAttr 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: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||||
if ((move.getMove() instanceof AttackMove || move.getMove().getAttrs(StatusMoveTypeImmunityAttr).find(attr => (attr as StatusMoveTypeImmunityAttr).immuneType === this.immuneType)) && move.getMove().type === this.immuneType) {
|
if ((move.getMove() instanceof AttackMove || move.getMove().getAttrs(StatusMoveTypeImmunityAttr).find(attr => attr.immuneType === this.immuneType)) && move.getMove().type === this.immuneType) {
|
||||||
(args[0] as Utils.NumberHolder).value = 0;
|
(args[0] as Utils.NumberHolder).value = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -568,7 +578,7 @@ export class MoveImmunityStatChangeAbAttr extends MoveImmunityAbAttr {
|
||||||
|
|
||||||
export class ReverseDrainAbAttr extends PostDefendAbAttr {
|
export class ReverseDrainAbAttr extends PostDefendAbAttr {
|
||||||
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
|
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
|
||||||
if (!!move.getMove().getAttrs(HitHealAttr).length || !!move.getMove().getAttrs(StrengthSapHealAttr).length ) {
|
if (move.getMove().hasAttr(HitHealAttr) || move.getMove().hasAttr(StrengthSapHealAttr) ) {
|
||||||
pokemon.scene.queueMessage(getPokemonMessage(attacker, " sucked up the liquid ooze!"));
|
pokemon.scene.queueMessage(getPokemonMessage(attacker, " sucked up the liquid ooze!"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -2194,7 +2204,7 @@ function getAnticipationCondition(): AbAttrCondition {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// move is a OHKO
|
// move is a OHKO
|
||||||
if (move.getMove().findAttr(attr => attr instanceof OneHitKOAttr)) {
|
if (move.getMove().hasAttr(OneHitKOAttr)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// edge case for hidden power, type is computed
|
// edge case for hidden power, type is computed
|
||||||
|
@ -2248,7 +2258,7 @@ export class ForewarnAbAttr extends PostSummonAbAttr {
|
||||||
for (const move of opponent.moveset) {
|
for (const move of opponent.moveset) {
|
||||||
if (move.getMove() instanceof StatusMove) {
|
if (move.getMove() instanceof StatusMove) {
|
||||||
movePower = 1;
|
movePower = 1;
|
||||||
} else if (move.getMove().findAttr(attr => attr instanceof OneHitKOAttr)) {
|
} else if (move.getMove().hasAttr(OneHitKOAttr)) {
|
||||||
movePower = 150;
|
movePower = 150;
|
||||||
} else if (move.getMove().id === Moves.COUNTER || move.getMove().id === Moves.MIRROR_COAT || move.getMove().id === Moves.METAL_BURST) {
|
} else if (move.getMove().id === Moves.COUNTER || move.getMove().id === Moves.MIRROR_COAT || move.getMove().id === Moves.METAL_BURST) {
|
||||||
movePower = 120;
|
movePower = 120;
|
||||||
|
@ -3325,7 +3335,7 @@ function applyAbAttrsInternal<TAttr extends AbAttr>(attrType: { new(...args: any
|
||||||
}
|
}
|
||||||
|
|
||||||
const ability = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility());
|
const ability = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility());
|
||||||
const attrs = ability.getAttrs(attrType) as TAttr[];
|
const attrs = ability.getAttrs(attrType);
|
||||||
|
|
||||||
const clearSpliceQueueAndResolve = () => {
|
const clearSpliceQueueAndResolve = () => {
|
||||||
pokemon.scene.clearPhaseQueueSplice();
|
pokemon.scene.clearPhaseQueueSplice();
|
||||||
|
@ -3524,7 +3534,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().findAttr(attr => attr instanceof FlinchAttr)) ? 10 : 0, BattlerTagType.FLINCHED),
|
.attr(PostAttackApplyBattlerTagAbAttr, false, (user, target, move) => (move.getMove().category !== MoveCategory.STATUS && !move.getMove().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),
|
||||||
|
|
|
@ -468,7 +468,7 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
|
||||||
} else {
|
} else {
|
||||||
const loadedCheckTimer = setInterval(() => {
|
const loadedCheckTimer = setInterval(() => {
|
||||||
if (moveAnims.get(move) !== null) {
|
if (moveAnims.get(move) !== null) {
|
||||||
const chargeAttr = allMoves[move].getAttrs(ChargeAttr).find(() => true) as ChargeAttr || allMoves[move].getAttrs(DelayedAttackAttr).find(() => true) as DelayedAttackAttr;
|
const chargeAttr = allMoves[move].getAttrs(ChargeAttr)[0] || allMoves[move].getAttrs(DelayedAttackAttr)[0];
|
||||||
if (chargeAttr && chargeAnims.get(chargeAttr.chargeAnim) === null) {
|
if (chargeAttr && chargeAnims.get(chargeAttr.chargeAnim) === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -498,7 +498,7 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
|
||||||
} else {
|
} else {
|
||||||
populateMoveAnim(move, ba);
|
populateMoveAnim(move, ba);
|
||||||
}
|
}
|
||||||
const chargeAttr = allMoves[move].getAttrs(ChargeAttr).find(() => true) as ChargeAttr || allMoves[move].getAttrs(DelayedAttackAttr).find(() => true) as DelayedAttackAttr;
|
const chargeAttr = allMoves[move].getAttrs(ChargeAttr)[0] || allMoves[move].getAttrs(DelayedAttackAttr)[0];
|
||||||
if (chargeAttr) {
|
if (chargeAttr) {
|
||||||
initMoveChargeAnim(scene, chargeAttr.chargeAnim).then(() => resolve());
|
initMoveChargeAnim(scene, chargeAttr.chargeAnim).then(() => resolve());
|
||||||
} else {
|
} else {
|
||||||
|
@ -569,7 +569,7 @@ export function loadMoveAnimAssets(scene: BattleScene, moveIds: Moves[], startLo
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const moveAnimations = moveIds.map(m => moveAnims.get(m) as AnimConfig).flat();
|
const moveAnimations = moveIds.map(m => moveAnims.get(m) as AnimConfig).flat();
|
||||||
for (const moveId of moveIds) {
|
for (const moveId of moveIds) {
|
||||||
const chargeAttr = allMoves[moveId].getAttrs(ChargeAttr).find(() => true) as ChargeAttr || allMoves[moveId].getAttrs(DelayedAttackAttr).find(() => true) as DelayedAttackAttr;
|
const chargeAttr = allMoves[moveId].getAttrs(ChargeAttr)[0] || allMoves[moveId].getAttrs(DelayedAttackAttr)[0];
|
||||||
if (chargeAttr) {
|
if (chargeAttr) {
|
||||||
const moveChargeAnims = chargeAnims.get(chargeAttr.chargeAnim);
|
const moveChargeAnims = chargeAnims.get(chargeAttr.chargeAnim);
|
||||||
moveAnimations.push(moveChargeAnims instanceof AnimConfig ? moveChargeAnims : moveChargeAnims[0]);
|
moveAnimations.push(moveChargeAnims instanceof AnimConfig ? moveChargeAnims : moveChargeAnims[0]);
|
||||||
|
|
|
@ -509,7 +509,7 @@ export class EncoreTag extends BattlerTag {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allMoves[repeatableMove.move].getAttrs(ChargeAttr).length && repeatableMove.result === MoveResult.OTHER) {
|
if (allMoves[repeatableMove.move].hasAttr(ChargeAttr) && repeatableMove.result === MoveResult.OTHER) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -148,8 +148,22 @@ export default class Move implements Localizable {
|
||||||
this.effect = this.id ? `${i18next.t(`move:${i18nKey}.effect`)}${this.nameAppend}` : "";
|
this.effect = this.id ? `${i18next.t(`move:${i18nKey}.effect`)}${this.nameAppend}` : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
getAttrs(attrType: { new(...args: any[]): MoveAttr }): MoveAttr[] {
|
/**
|
||||||
return this.attrs.filter(a => a instanceof attrType);
|
* Get all move attributes that match `attrType`
|
||||||
|
* @param attrType any attribute that extends {@linkcode MoveAttr}
|
||||||
|
* @returns Array of attributes that match `attrType`, Empty Array if none match.
|
||||||
|
*/
|
||||||
|
getAttrs<T extends MoveAttr>(attrType: new(...args: any[]) => T): T[] {
|
||||||
|
return this.attrs.filter((a): a is T => a instanceof attrType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a move has an attribute that matches `attrType`
|
||||||
|
* @param attrType any attribute that extends {@linkcode MoveAttr}
|
||||||
|
* @returns true if the move has attribute `attrType`
|
||||||
|
*/
|
||||||
|
hasAttr<T extends MoveAttr>(attrType: new(...args: any[]) => T): boolean {
|
||||||
|
return this.attrs.some((attr) => attr instanceof attrType);
|
||||||
}
|
}
|
||||||
|
|
||||||
findAttr(attrPredicate: (attr: MoveAttr) => boolean): MoveAttr {
|
findAttr(attrPredicate: (attr: MoveAttr) => boolean): MoveAttr {
|
||||||
|
@ -3698,7 +3712,7 @@ export class ProtectAttr extends AddBattlerTagAttr {
|
||||||
|
|
||||||
while (moveHistory.length) {
|
while (moveHistory.length) {
|
||||||
turnMove = moveHistory.shift();
|
turnMove = moveHistory.shift();
|
||||||
if (!allMoves[turnMove.move].getAttrs(ProtectAttr).length || turnMove.result !== MoveResult.SUCCESS) {
|
if (!allMoves[turnMove.move].hasAttr(ProtectAttr) || turnMove.result !== MoveResult.SUCCESS) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
timesUsed++;
|
timesUsed++;
|
||||||
|
@ -4480,7 +4494,7 @@ const lastMoveCopiableCondition: MoveConditionFunc = (user, target, move) => {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allMoves[copiableMove].getAttrs(ChargeAttr).length) {
|
if (allMoves[copiableMove].hasAttr(ChargeAttr)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4570,7 +4584,7 @@ const targetMoveCopiableCondition: MoveConditionFunc = (user, target, move) => {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allMoves[copiableMove.move].getAttrs(ChargeAttr).length && copiableMove.result === MoveResult.OTHER) {
|
if (allMoves[copiableMove.move].hasAttr(ChargeAttr) && copiableMove.result === MoveResult.OTHER) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4987,7 +5001,7 @@ export function getMoveTargets(user: Pokemon, move: Moves): MoveTargetSet {
|
||||||
const variableTarget = new Utils.NumberHolder(0);
|
const variableTarget = new Utils.NumberHolder(0);
|
||||||
user.getOpponents().forEach(p => applyMoveAttrs(VariableTargetAttr, user, p, allMoves[move], variableTarget));
|
user.getOpponents().forEach(p => applyMoveAttrs(VariableTargetAttr, user, p, allMoves[move], variableTarget));
|
||||||
|
|
||||||
const moveTarget = allMoves[move].getAttrs(VariableTargetAttr).length ? variableTarget.value : move ? allMoves[move].moveTarget : move === undefined ? MoveTarget.NEAR_ENEMY : [];
|
const moveTarget = allMoves[move].hasAttr(VariableTargetAttr) ? variableTarget.value : move ? allMoves[move].moveTarget : move === undefined ? MoveTarget.NEAR_ENEMY : [];
|
||||||
const opponents = user.getOpponents();
|
const opponents = user.getOpponents();
|
||||||
|
|
||||||
let set: Pokemon[] = [];
|
let set: Pokemon[] = [];
|
||||||
|
|
|
@ -56,7 +56,7 @@ export class Terrain {
|
||||||
isMoveTerrainCancelled(user: Pokemon, targets: BattlerIndex[], move: Move): boolean {
|
isMoveTerrainCancelled(user: Pokemon, targets: BattlerIndex[], move: Move): boolean {
|
||||||
switch (this.terrainType) {
|
switch (this.terrainType) {
|
||||||
case TerrainType.PSYCHIC:
|
case TerrainType.PSYCHIC:
|
||||||
if (!move.getAttrs(ProtectAttr).length) {
|
if (!move.hasAttr(ProtectAttr)) {
|
||||||
const priority = new Utils.IntegerHolder(move.priority);
|
const priority = new Utils.IntegerHolder(move.priority);
|
||||||
applyAbAttrs(IncrementMovePriorityAbAttr, user, null, move, priority);
|
applyAbAttrs(IncrementMovePriorityAbAttr, user, null, move, priority);
|
||||||
return priority.value > 0 && user.getOpponents().filter(o => targets.includes(o.getBattlerIndex())).length > 0;
|
return priority.value > 0 && user.getOpponents().filter(o => targets.includes(o.getBattlerIndex())).length > 0;
|
||||||
|
|
|
@ -114,9 +114,9 @@ export class Weather {
|
||||||
const field = scene.getField(true);
|
const field = scene.getField(true);
|
||||||
|
|
||||||
for (const pokemon of field) {
|
for (const pokemon of field) {
|
||||||
let suppressWeatherEffectAbAttr = pokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr).find(() => true) as SuppressWeatherEffectAbAttr;
|
let suppressWeatherEffectAbAttr = pokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr)[0];
|
||||||
if (!suppressWeatherEffectAbAttr) {
|
if (!suppressWeatherEffectAbAttr) {
|
||||||
suppressWeatherEffectAbAttr = pokemon.hasPassive() ? pokemon.getPassiveAbility().getAttrs(SuppressWeatherEffectAbAttr).find(() => true) as SuppressWeatherEffectAbAttr : null;
|
suppressWeatherEffectAbAttr = pokemon.hasPassive() ? pokemon.getPassiveAbility().getAttrs(SuppressWeatherEffectAbAttr)[0] : null;
|
||||||
}
|
}
|
||||||
if (suppressWeatherEffectAbAttr && (!this.isImmutable() || suppressWeatherEffectAbAttr.affectsImmutable)) {
|
if (suppressWeatherEffectAbAttr && (!this.isImmutable() || suppressWeatherEffectAbAttr.affectsImmutable)) {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -1058,7 +1058,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
}
|
}
|
||||||
|
|
||||||
getAttackMoveEffectiveness(source: Pokemon, move: PokemonMove): TypeDamageMultiplier {
|
getAttackMoveEffectiveness(source: Pokemon, move: PokemonMove): TypeDamageMultiplier {
|
||||||
const typeless = !!move.getMove().getAttrs(TypelessAttr).length;
|
const typeless = move.getMove().hasAttr(TypelessAttr);
|
||||||
const typeMultiplier = new Utils.NumberHolder(this.getAttackTypeEffectiveness(move.getMove().type, source));
|
const typeMultiplier = new Utils.NumberHolder(this.getAttackTypeEffectiveness(move.getMove().type, source));
|
||||||
const cancelled = new Utils.BooleanHolder(false);
|
const cancelled = new Utils.BooleanHolder(false);
|
||||||
if (!typeless) {
|
if (!typeless) {
|
||||||
|
@ -1424,19 +1424,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isBoss()) { // Bosses never get self ko moves
|
if (this.isBoss()) { // Bosses never get self ko moves
|
||||||
movePool = movePool.filter(m => !allMoves[m[0]].getAttrs(SacrificialAttr).length);
|
movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(SacrificialAttr));
|
||||||
}
|
}
|
||||||
movePool = movePool.filter(m => !allMoves[m[0]].getAttrs(SacrificialAttrOnHit).length);
|
movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(SacrificialAttrOnHit));
|
||||||
if (this.hasTrainer()) {
|
if (this.hasTrainer()) {
|
||||||
// Trainers never get OHKO moves
|
// Trainers never get OHKO moves
|
||||||
movePool = movePool.filter(m => !allMoves[m[0]].getAttrs(OneHitKOAttr).length);
|
movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(OneHitKOAttr));
|
||||||
// Half the weight of self KO moves
|
// Half the weight of self KO moves
|
||||||
movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].getAttrs(SacrificialAttr).length ? 0.5 : 1)]);
|
movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].hasAttr(SacrificialAttr) ? 0.5 : 1)]);
|
||||||
movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].getAttrs(SacrificialAttrOnHit).length ? 0.5 : 1)]);
|
movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].hasAttr(SacrificialAttrOnHit) ? 0.5 : 1)]);
|
||||||
// Trainers get a weight bump to stat buffing moves
|
// Trainers get a weight bump to stat buffing moves
|
||||||
movePool = movePool.map(m => [m[0], m[1] * (allMoves[m[0]].getAttrs(StatChangeAttr).some(a => (a as StatChangeAttr).levels > 1 && (a as StatChangeAttr).selfTarget) ? 1.25 : 1)]);
|
movePool = movePool.map(m => [m[0], m[1] * (allMoves[m[0]].getAttrs(StatChangeAttr).some(a => a.levels > 1 && a.selfTarget) ? 1.25 : 1)]);
|
||||||
// Trainers get a weight decrease to multiturn moves
|
// Trainers get a weight decrease to multiturn moves
|
||||||
movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].getAttrs(ChargeAttr).length || !!allMoves[m[0]].getAttrs(RechargeAttr).length ? 0.7 : 1)]);
|
movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].hasAttr(ChargeAttr) || !!allMoves[m[0]].hasAttr(RechargeAttr) ? 0.7 : 1)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Weight towards higher power moves, by reducing the power of moves below the highest power.
|
// Weight towards higher power moves, by reducing the power of moves below the highest power.
|
||||||
|
@ -1627,8 +1627,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
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.getAttrs(TypelessAttr).length;
|
const typeless = move.hasAttr(TypelessAttr);
|
||||||
const typeMultiplier = new Utils.NumberHolder(!typeless && (moveCategory !== MoveCategory.STATUS || move.getAttrs(StatusMoveTypeImmunityAttr).find(attr => types.includes((attr as StatusMoveTypeImmunityAttr).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(type, source)
|
||||||
: 1);
|
: 1);
|
||||||
applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier);
|
applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier);
|
||||||
|
@ -1654,7 +1654,7 @@ 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.getAttrs(MultiHitAttr).length && !this.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
|
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)) {
|
||||||
power.value = 60;
|
power.value = 60;
|
||||||
}
|
}
|
||||||
applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, this, battlerMove, power);
|
applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, this, battlerMove, power);
|
||||||
|
@ -1756,7 +1756,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
if (!isTypeImmune) {
|
if (!isTypeImmune) {
|
||||||
damage.value = Math.ceil(((((2 * source.level / 5 + 2) * power.value * sourceAtk.value / targetDef.value) / 50) + 2) * stabMultiplier.value * typeMultiplier.value * arenaAttackTypeMultiplier.value * screenMultiplier.value * ((this.scene.randBattleSeedInt(15) + 85) / 100) * criticalMultiplier.value);
|
damage.value = Math.ceil(((((2 * source.level / 5 + 2) * power.value * sourceAtk.value / targetDef.value) / 50) + 2) * stabMultiplier.value * typeMultiplier.value * arenaAttackTypeMultiplier.value * screenMultiplier.value * ((this.scene.randBattleSeedInt(15) + 85) / 100) * criticalMultiplier.value);
|
||||||
if (isPhysical && source.status && source.status.effect === StatusEffect.BURN) {
|
if (isPhysical && source.status && source.status.effect === StatusEffect.BURN) {
|
||||||
if (!move.getAttrs(BypassBurnDamageReductionAttr).length) {
|
if (!move.hasAttr(BypassBurnDamageReductionAttr)) {
|
||||||
const burnDamageReductionCancelled = new Utils.BooleanHolder(false);
|
const burnDamageReductionCancelled = new Utils.BooleanHolder(false);
|
||||||
applyAbAttrs(BypassBurnDamageReductionAbAttr, source, burnDamageReductionCancelled);
|
applyAbAttrs(BypassBurnDamageReductionAbAttr, source, burnDamageReductionCancelled);
|
||||||
if (!burnDamageReductionCancelled.value) {
|
if (!burnDamageReductionCancelled.value) {
|
||||||
|
@ -1768,12 +1768,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
applyPreAttackAbAttrs(DamageBoostAbAttr, source, this, battlerMove, damage);
|
applyPreAttackAbAttrs(DamageBoostAbAttr, source, this, battlerMove, 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:
|
||||||
* The target has a {@link BattlerTagType} that this move interacts with
|
* The target has a {@link BattlerTagType} that this move interacts with
|
||||||
* AND
|
* AND
|
||||||
* The move doubles damage when used against that tag
|
* The move doubles damage when used against that tag
|
||||||
* */
|
*/
|
||||||
move.getAttrs(HitsTagAttr).map(hta => hta as HitsTagAttr).filter(hta => hta.doubleDamage).forEach(hta => {
|
move.getAttrs(HitsTagAttr).filter(hta => hta.doubleDamage).forEach(hta => {
|
||||||
if (this.getTag(hta.tagType)) {
|
if (this.getTag(hta.tagType)) {
|
||||||
damage.value *= 2;
|
damage.value *= 2;
|
||||||
}
|
}
|
||||||
|
@ -3454,7 +3454,7 @@ export class EnemyPokemon extends Pokemon {
|
||||||
if (!sortedBenefitScores.length) {
|
if (!sortedBenefitScores.length) {
|
||||||
// Set target to BattlerIndex.ATTACKER when using a counter move
|
// Set target to BattlerIndex.ATTACKER when using a counter move
|
||||||
// This is the same as when the player does so
|
// This is the same as when the player does so
|
||||||
if (!!move.findAttr(attr => attr instanceof CounterDamageAttr)) {
|
if (move.hasAttr(CounterDamageAttr)) {
|
||||||
return [BattlerIndex.ATTACKER];
|
return [BattlerIndex.ATTACKER];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1553,7 +1553,7 @@ export class SwitchSummonPhase extends SummonPhase {
|
||||||
const lastUsedMove = moveId ? allMoves[moveId] : undefined;
|
const lastUsedMove = moveId ? allMoves[moveId] : undefined;
|
||||||
|
|
||||||
const currentCommand = pokemon.scene.currentBattle.turnCommands[this.fieldIndex]?.command;
|
const currentCommand = pokemon.scene.currentBattle.turnCommands[this.fieldIndex]?.command;
|
||||||
const lastPokemonIsForceSwitchedAndNotFainted = !!lastUsedMove?.findAttr(attr => attr instanceof ForceSwitchOutAttr) && !this.lastPokemon.isFainted();
|
const lastPokemonIsForceSwitchedAndNotFainted = lastUsedMove?.hasAttr(ForceSwitchOutAttr) && !this.lastPokemon.isFainted();
|
||||||
|
|
||||||
// Compensate for turn spent summoning
|
// Compensate for turn spent summoning
|
||||||
// Or compensate for force switch move if switched out pokemon is not fainted
|
// Or compensate for force switch move if switched out pokemon is not fainted
|
||||||
|
@ -2459,9 +2459,9 @@ export class MovePhase extends BattlePhase {
|
||||||
const oldTarget = moveTarget.value;
|
const oldTarget = moveTarget.value;
|
||||||
this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, this.move.moveId, moveTarget));
|
this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, this.move.moveId, moveTarget));
|
||||||
//Check if this move is immune to being redirected, and restore its target to the intended target if it is.
|
//Check if this move is immune to being redirected, and restore its target to the intended target if it is.
|
||||||
if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) || this.move.getMove().getAttrs(BypassRedirectAttr).length)) {
|
if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) || this.move.getMove().hasAttr(BypassRedirectAttr))) {
|
||||||
//If an ability prevented this move from being redirected, display its ability pop up.
|
//If an ability prevented this move from being redirected, display its ability pop up.
|
||||||
if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) && !this.move.getMove().getAttrs(BypassRedirectAttr).length) && oldTarget !== moveTarget.value) {
|
if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) && !this.move.getMove().hasAttr(BypassRedirectAttr)) && oldTarget !== moveTarget.value) {
|
||||||
this.scene.unshiftPhase(new ShowAbilityPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr)));
|
this.scene.unshiftPhase(new ShowAbilityPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr)));
|
||||||
}
|
}
|
||||||
moveTarget.value = oldTarget;
|
moveTarget.value = oldTarget;
|
||||||
|
@ -2551,7 +2551,7 @@ export class MovePhase extends BattlePhase {
|
||||||
this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), ppUsed));
|
this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), ppUsed));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!allMoves[this.move.moveId].getAttrs(CopyMoveAttr).length) {
|
if (!allMoves[this.move.moveId].hasAttr(CopyMoveAttr)) {
|
||||||
this.scene.currentBattle.lastMove = this.move.moveId;
|
this.scene.currentBattle.lastMove = this.move.moveId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2635,7 +2635,7 @@ export class MovePhase extends BattlePhase {
|
||||||
}
|
}
|
||||||
|
|
||||||
showMoveText(): void {
|
showMoveText(): void {
|
||||||
if (this.move.getMove().getAttrs(ChargeAttr).length) {
|
if (this.move.getMove().hasAttr(ChargeAttr)) {
|
||||||
const lastMove = this.pokemon.getLastXMoves() as TurnMove[];
|
const lastMove = this.pokemon.getLastXMoves() as TurnMove[];
|
||||||
if (!lastMove.length || lastMove[0].move !== this.move.getMove().id || lastMove[0].result !== MoveResult.OTHER) {
|
if (!lastMove.length || lastMove[0].move !== this.move.getMove().id || lastMove[0].result !== MoveResult.OTHER) {
|
||||||
this.scene.queueMessage(getPokemonMessage(this.pokemon, ` used\n${this.move.getName()}!`), 500);
|
this.scene.queueMessage(getPokemonMessage(this.pokemon, ` used\n${this.move.getName()}!`), 500);
|
||||||
|
@ -2706,7 +2706,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||||
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(), this.move.getMove(), hitCount);
|
||||||
if (this.move.getMove() instanceof AttackMove && !this.move.getMove().getAttrs(FixedDamageAttr).length) {
|
if (this.move.getMove() instanceof AttackMove && !this.move.getMove().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;
|
||||||
|
@ -2717,7 +2717,7 @@ 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().getAttrs(VariableTargetAttr).length && !this.move.getMove().isMultiTarget() && !targetHitChecks[this.targets[0]])) {
|
if (!activeTargets.length || (!this.move.getMove().hasAttr(VariableTargetAttr) && !this.move.getMove().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) {
|
||||||
|
@ -2761,7 +2761,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||||
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit),
|
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit),
|
||||||
user, target, this.move.getMove()).then(() => {
|
user, target, this.move.getMove()).then(() => {
|
||||||
if (hitResult !== HitResult.FAIL) {
|
if (hitResult !== HitResult.FAIL) {
|
||||||
const chargeEffect = !!this.move.getMove().getAttrs(ChargeAttr).find(ca => (ca as ChargeAttr).usedChargeEffect(user, this.getTarget(), this.move.getMove()));
|
const chargeEffect = !!this.move.getMove().getAttrs(ChargeAttr).find(ca => ca.usedChargeEffect(user, this.getTarget(), this.move.getMove()));
|
||||||
// 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 as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY
|
||||||
&& (attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit), user, target, this.move.getMove())).then(() => {
|
&& (attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit), user, target, this.move.getMove())).then(() => {
|
||||||
|
@ -2861,7 +2861,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||||
}
|
}
|
||||||
|
|
||||||
const hiddenTag = target.getTag(HiddenTag);
|
const hiddenTag = target.getTag(HiddenTag);
|
||||||
if (hiddenTag && !this.move.getMove().getAttrs(HitsTagAttr).filter(hta => (hta as HitsTagAttr).tagType === hiddenTag.tagType).length) {
|
if (hiddenTag && !this.move.getMove().getAttrs(HitsTagAttr).some(hta => hta.tagType === hiddenTag.tagType)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2873,7 +2873,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isOhko = !!this.move.getMove().getAttrs(OneHitKOAccuracyAttr).length;
|
const isOhko = this.move.getMove().hasAttr(OneHitKOAccuracyAttr);
|
||||||
|
|
||||||
if (!isOhko) {
|
if (!isOhko) {
|
||||||
user.scene.applyModifiers(PokemonMoveAccuracyBoosterModifier, user.isPlayer(), user, moveAccuracy);
|
user.scene.applyModifiers(PokemonMoveAccuracyBoosterModifier, user.isPlayer(), user, moveAccuracy);
|
||||||
|
@ -3534,7 +3534,7 @@ export class FaintPhase extends PokemonPhase {
|
||||||
if (defeatSource?.isOnField()) {
|
if (defeatSource?.isOnField()) {
|
||||||
applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource);
|
applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource);
|
||||||
const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move];
|
const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move];
|
||||||
const pvattrs = pvmove.getAttrs(PostVictoryStatChangeAttr) as PostVictoryStatChangeAttr[];
|
const pvattrs = pvmove.getAttrs(PostVictoryStatChangeAttr);
|
||||||
if (pvattrs.length) {
|
if (pvattrs.length) {
|
||||||
for (const pvattr of pvattrs) {
|
for (const pvattr of pvattrs) {
|
||||||
pvattr.applyPostVictory(defeatSource, defeatSource, pvmove);
|
pvattr.applyPostVictory(defeatSource, defeatSource, pvmove);
|
||||||
|
|
Loading…
Reference in New Issue