Compare commits
23 Commits
a712bdc76b
...
53b9661f9b
Author | SHA1 | Date |
---|---|---|
Mason S | 53b9661f9b | |
flx-sta | 6030b780f2 | |
Jannik Tappert | e386504977 | |
podar | 106ed6b27b | |
flx-sta | 4389bff5d0 | |
Madmadness65 | 00ba2eebc8 | |
Leo Kim | fe69bd2b55 | |
Dakurei | 1fb5389765 | |
NightKev | d9fcce25d1 | |
NightKev | b4ef315e5e | |
NightKev | ad51530f78 | |
NightKev | cd8266babd | |
Mason S | 4fb7e860b2 | |
NightKev | 631e311372 | |
NightKev | 50fec80db5 | |
NightKev | 89b5893f82 | |
NightKev | e44c423aa8 | |
NightKev | 63d55abcc9 | |
NightKev | 839df96cde | |
NightKev | e14f9c7113 | |
NightKev | c76642798d | |
NightKev | c63745bd7d | |
Mason | 063aa24588 |
|
@ -55,7 +55,7 @@ Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to
|
||||||
- Pokémon Sword/Shield
|
- Pokémon Sword/Shield
|
||||||
- Pokémon Legends: Arceus
|
- Pokémon Legends: Arceus
|
||||||
- Pokémon Scarlet/Violet
|
- Pokémon Scarlet/Violet
|
||||||
- Firel (Custom Laboratory, Metropolis, Seabed, and Space biome music)
|
- Firel (Custom Ice Cave, Laboratory, Metropolis, Plains, Power Plant, Seabed, Space, and Volcano biome music)
|
||||||
- Lmz (Custom Jungle biome music)
|
- Lmz (Custom Jungle biome music)
|
||||||
- Andr06 (Custom Slum and Sea biome music)
|
- Andr06 (Custom Slum and Sea biome music)
|
||||||
|
|
||||||
|
|
28
index.css
28
index.css
|
@ -26,10 +26,36 @@ body {
|
||||||
#app {
|
#app {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#app > div:first-child {
|
#app > div:first-child {
|
||||||
transform-origin: top !important;
|
transform-origin: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Supports automatic vertical centering as suggested in PR#1114, but only via CSS
|
||||||
|
|
||||||
|
Condition factorized to deduce CSS rules:
|
||||||
|
true if (isLandscape && !isMobile() && !hasTouchscreen() || (hasTouchscreen() && !isTouchControlsEnabled))
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* isLandscape && !isMobile() && !hasTouchscreen() */
|
||||||
|
@media (orientation: landscape) and (pointer: fine) {
|
||||||
|
#app {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (pointer: coarse) {
|
||||||
|
/* hasTouchscreen() && !isTouchControlsEnabled */
|
||||||
|
body:has(> #touchControls[class=visible]) #app {
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
body:has(> #touchControls[class=visible]) #app > div:first-child {
|
||||||
|
transform-origin: top !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#layout:fullscreen #dpad, #layout:fullscreen {
|
#layout:fullscreen #dpad, #layout:fullscreen {
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -2625,7 +2625,11 @@ export class PreStatStageChangeAbAttr extends AbAttr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Protect one or all {@linkcode BattleStat} from reductions caused by other Pokémon's moves and Abilities
|
||||||
|
*/
|
||||||
export class ProtectStatAbAttr extends PreStatStageChangeAbAttr {
|
export class ProtectStatAbAttr extends PreStatStageChangeAbAttr {
|
||||||
|
/** {@linkcode BattleStat} to protect or `undefined` if **all** {@linkcode BattleStat} are protected */
|
||||||
private protectedStat?: BattleStat;
|
private protectedStat?: BattleStat;
|
||||||
|
|
||||||
constructor(protectedStat?: BattleStat) {
|
constructor(protectedStat?: BattleStat) {
|
||||||
|
@ -2634,7 +2638,17 @@ export class ProtectStatAbAttr extends PreStatStageChangeAbAttr {
|
||||||
this.protectedStat = protectedStat;
|
this.protectedStat = protectedStat;
|
||||||
}
|
}
|
||||||
|
|
||||||
applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, _args: any[]): boolean {
|
/**
|
||||||
|
* Apply the {@linkcode ProtectedStatAbAttr} to an interaction
|
||||||
|
* @param _pokemon
|
||||||
|
* @param _passive
|
||||||
|
* @param simulated
|
||||||
|
* @param stat the {@linkcode BattleStat} being affected
|
||||||
|
* @param cancelled The {@linkcode Utils.BooleanHolder} that will be set to true if the stat is protected
|
||||||
|
* @param _args
|
||||||
|
* @returns true if the stat is protected, false otherwise
|
||||||
|
*/
|
||||||
|
applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, _args: any[]): boolean {
|
||||||
if (Utils.isNullOrUndefined(this.protectedStat) || stat === this.protectedStat) {
|
if (Utils.isNullOrUndefined(this.protectedStat) || stat === this.protectedStat) {
|
||||||
cancelled.value = true;
|
cancelled.value = true;
|
||||||
return true;
|
return true;
|
||||||
|
@ -3757,7 +3771,7 @@ export class StatStageChangeMultiplierAbAttr extends AbAttr {
|
||||||
this.multiplier = multiplier;
|
this.multiplier = multiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||||
(args[0] as Utils.IntegerHolder).value *= this.multiplier;
|
(args[0] as Utils.IntegerHolder).value *= this.multiplier;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
import { Arena } from "../field/arena";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { Type } from "./type";
|
import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, ProtectStatAbAttr, applyAbAttrs } from "#app/data/ability";
|
||||||
import * as Utils from "../utils";
|
import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims";
|
||||||
import { MoveCategory, allMoves, MoveTarget, IncrementMovePriorityAttr, applyMoveAttrs } from "./move";
|
import { IncrementMovePriorityAttr, MoveCategory, MoveTarget, allMoves, applyMoveAttrs } from "#app/data/move";
|
||||||
import { getPokemonNameWithAffix } from "../messages";
|
import { StatusEffect } from "#app/data/status-effect";
|
||||||
import Pokemon, { HitResult, PokemonMove } from "../field/pokemon";
|
import { Type } from "#app/data/type";
|
||||||
import { StatusEffect } from "./status-effect";
|
import { Arena } from "#app/field/arena";
|
||||||
import { BattlerIndex } from "../battle";
|
import Pokemon, { HitResult, PokemonMove } from "#app/field/pokemon";
|
||||||
import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, ProtectStatAbAttr, applyAbAttrs } from "./ability";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import { Stat } from "#enums/stat";
|
|
||||||
import { CommonAnim, CommonBattleAnim } from "./battle-anims";
|
|
||||||
import i18next from "i18next";
|
|
||||||
import { Abilities } from "#enums/abilities";
|
|
||||||
import { ArenaTagType } from "#enums/arena-tag-type";
|
|
||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
|
||||||
import { Moves } from "#enums/moves";
|
|
||||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||||
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
|
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
|
||||||
import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
|
import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
|
||||||
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
|
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
|
||||||
|
import * as Utils from "#app/utils";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||||
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Stat } from "#enums/stat";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
export enum ArenaTagSide {
|
export enum ArenaTagSide {
|
||||||
BOTH,
|
BOTH,
|
||||||
|
@ -26,20 +26,13 @@ export enum ArenaTagSide {
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class ArenaTag {
|
export abstract class ArenaTag {
|
||||||
public tagType: ArenaTagType;
|
constructor(
|
||||||
public turnCount: integer;
|
public tagType: ArenaTagType,
|
||||||
public sourceMove?: Moves;
|
public turnCount: number,
|
||||||
public sourceId?: integer;
|
public sourceMove?: Moves,
|
||||||
public side: ArenaTagSide;
|
public sourceId?: number,
|
||||||
|
public side: ArenaTagSide = ArenaTagSide.BOTH
|
||||||
|
) {}
|
||||||
constructor(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId?: integer, side: ArenaTagSide = ArenaTagSide.BOTH) {
|
|
||||||
this.tagType = tagType;
|
|
||||||
this.turnCount = turnCount;
|
|
||||||
this.sourceMove = sourceMove;
|
|
||||||
this.sourceId = sourceId;
|
|
||||||
this.side = side;
|
|
||||||
}
|
|
||||||
|
|
||||||
apply(arena: Arena, args: any[]): boolean {
|
apply(arena: Arena, args: any[]): boolean {
|
||||||
return true;
|
return true;
|
||||||
|
@ -64,6 +57,18 @@ export abstract class ArenaTag {
|
||||||
? allMoves[this.sourceMove].name
|
? allMoves[this.sourceMove].name
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When given a arena tag or json representing one, load the data for it.
|
||||||
|
* This is meant to be inherited from by any arena tag with custom attributes
|
||||||
|
* @param {ArenaTag | any} source An arena tag
|
||||||
|
*/
|
||||||
|
loadTag(source : ArenaTag | any) : void {
|
||||||
|
this.turnCount = source.turnCount;
|
||||||
|
this.sourceMove = source.sourceMove;
|
||||||
|
this.sourceId = source.sourceId;
|
||||||
|
this.side = source.side;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -71,7 +76,7 @@ export abstract class ArenaTag {
|
||||||
* Prevents Pokémon on the opposing side from lowering the stats of the Pokémon in the Mist.
|
* Prevents Pokémon on the opposing side from lowering the stats of the Pokémon in the Mist.
|
||||||
*/
|
*/
|
||||||
export class MistTag extends ArenaTag {
|
export class MistTag extends ArenaTag {
|
||||||
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
|
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.MIST, turnCount, Moves.MIST, sourceId, side);
|
super(ArenaTagType.MIST, turnCount, Moves.MIST, sourceId, side);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,7 +120,7 @@ export class WeakenMoveScreenTag extends ArenaTag {
|
||||||
* @param side - The side (player or enemy) the tag affects.
|
* @param side - The side (player or enemy) the tag affects.
|
||||||
* @param weakenedCategories - The categories of moves that are weakened by this tag.
|
* @param weakenedCategories - The categories of moves that are weakened by this tag.
|
||||||
*/
|
*/
|
||||||
constructor(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, side: ArenaTagSide, weakenedCategories: MoveCategory[]) {
|
constructor(tagType: ArenaTagType, turnCount: number, sourceMove: Moves, sourceId: number, side: ArenaTagSide, weakenedCategories: MoveCategory[]) {
|
||||||
super(tagType, turnCount, sourceMove, sourceId, side);
|
super(tagType, turnCount, sourceMove, sourceId, side);
|
||||||
|
|
||||||
this.weakenedCategories = weakenedCategories;
|
this.weakenedCategories = weakenedCategories;
|
||||||
|
@ -146,7 +151,7 @@ export class WeakenMoveScreenTag extends ArenaTag {
|
||||||
* Used by {@linkcode Moves.REFLECT}
|
* Used by {@linkcode Moves.REFLECT}
|
||||||
*/
|
*/
|
||||||
class ReflectTag extends WeakenMoveScreenTag {
|
class ReflectTag extends WeakenMoveScreenTag {
|
||||||
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
|
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.REFLECT, turnCount, Moves.REFLECT, sourceId, side, [MoveCategory.PHYSICAL]);
|
super(ArenaTagType.REFLECT, turnCount, Moves.REFLECT, sourceId, side, [MoveCategory.PHYSICAL]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,7 +167,7 @@ class ReflectTag extends WeakenMoveScreenTag {
|
||||||
* Used by {@linkcode Moves.LIGHT_SCREEN}
|
* Used by {@linkcode Moves.LIGHT_SCREEN}
|
||||||
*/
|
*/
|
||||||
class LightScreenTag extends WeakenMoveScreenTag {
|
class LightScreenTag extends WeakenMoveScreenTag {
|
||||||
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
|
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.LIGHT_SCREEN, turnCount, Moves.LIGHT_SCREEN, sourceId, side, [MoveCategory.SPECIAL]);
|
super(ArenaTagType.LIGHT_SCREEN, turnCount, Moves.LIGHT_SCREEN, sourceId, side, [MoveCategory.SPECIAL]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,7 +183,7 @@ class LightScreenTag extends WeakenMoveScreenTag {
|
||||||
* Used by {@linkcode Moves.AURORA_VEIL}
|
* Used by {@linkcode Moves.AURORA_VEIL}
|
||||||
*/
|
*/
|
||||||
class AuroraVeilTag extends WeakenMoveScreenTag {
|
class AuroraVeilTag extends WeakenMoveScreenTag {
|
||||||
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
|
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.AURORA_VEIL, turnCount, Moves.AURORA_VEIL, sourceId, side, [MoveCategory.SPECIAL, MoveCategory.PHYSICAL]);
|
super(ArenaTagType.AURORA_VEIL, turnCount, Moves.AURORA_VEIL, sourceId, side, [MoveCategory.SPECIAL, MoveCategory.PHYSICAL]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,7 +206,7 @@ export class ConditionalProtectTag extends ArenaTag {
|
||||||
/** Does this apply to all moves, including those that ignore other forms of protection? */
|
/** Does this apply to all moves, including those that ignore other forms of protection? */
|
||||||
protected ignoresBypass: boolean;
|
protected ignoresBypass: boolean;
|
||||||
|
|
||||||
constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: integer, side: ArenaTagSide, condition: ProtectConditionFunc, ignoresBypass: boolean = false) {
|
constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: number, side: ArenaTagSide, condition: ProtectConditionFunc, ignoresBypass: boolean = false) {
|
||||||
super(tagType, 1, sourceMove, sourceId, side);
|
super(tagType, 1, sourceMove, sourceId, side);
|
||||||
|
|
||||||
this.protectConditionFunc = condition;
|
this.protectConditionFunc = condition;
|
||||||
|
@ -263,7 +268,7 @@ export class ConditionalProtectTag extends ArenaTag {
|
||||||
*/
|
*/
|
||||||
const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => {
|
const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => {
|
||||||
const move = allMoves[moveId];
|
const move = allMoves[moveId];
|
||||||
const priority = new Utils.IntegerHolder(move.priority);
|
const priority = new Utils.NumberHolder(move.priority);
|
||||||
const effectPhase = arena.scene.getCurrentPhase();
|
const effectPhase = arena.scene.getCurrentPhase();
|
||||||
|
|
||||||
if (effectPhase instanceof MoveEffectPhase) {
|
if (effectPhase instanceof MoveEffectPhase) {
|
||||||
|
@ -279,7 +284,7 @@ const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => {
|
||||||
* Condition: The incoming move has increased priority.
|
* Condition: The incoming move has increased priority.
|
||||||
*/
|
*/
|
||||||
class QuickGuardTag extends ConditionalProtectTag {
|
class QuickGuardTag extends ConditionalProtectTag {
|
||||||
constructor(sourceId: integer, side: ArenaTagSide) {
|
constructor(sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.QUICK_GUARD, Moves.QUICK_GUARD, sourceId, side, QuickGuardConditionFunc);
|
super(ArenaTagType.QUICK_GUARD, Moves.QUICK_GUARD, sourceId, side, QuickGuardConditionFunc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -310,7 +315,7 @@ const WideGuardConditionFunc: ProtectConditionFunc = (arena, moveId) : boolean =
|
||||||
* can be an ally or enemy.
|
* can be an ally or enemy.
|
||||||
*/
|
*/
|
||||||
class WideGuardTag extends ConditionalProtectTag {
|
class WideGuardTag extends ConditionalProtectTag {
|
||||||
constructor(sourceId: integer, side: ArenaTagSide) {
|
constructor(sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.WIDE_GUARD, Moves.WIDE_GUARD, sourceId, side, WideGuardConditionFunc);
|
super(ArenaTagType.WIDE_GUARD, Moves.WIDE_GUARD, sourceId, side, WideGuardConditionFunc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -332,7 +337,7 @@ const MatBlockConditionFunc: ProtectConditionFunc = (arena, moveId) : boolean =>
|
||||||
* Condition: The incoming move is a Physical or Special attack move.
|
* Condition: The incoming move is a Physical or Special attack move.
|
||||||
*/
|
*/
|
||||||
class MatBlockTag extends ConditionalProtectTag {
|
class MatBlockTag extends ConditionalProtectTag {
|
||||||
constructor(sourceId: integer, side: ArenaTagSide) {
|
constructor(sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.MAT_BLOCK, Moves.MAT_BLOCK, sourceId, side, MatBlockConditionFunc);
|
super(ArenaTagType.MAT_BLOCK, Moves.MAT_BLOCK, sourceId, side, MatBlockConditionFunc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,7 +375,7 @@ const CraftyShieldConditionFunc: ProtectConditionFunc = (arena, moveId) => {
|
||||||
* not target all Pokemon or sides of the field.
|
* not target all Pokemon or sides of the field.
|
||||||
*/
|
*/
|
||||||
class CraftyShieldTag extends ConditionalProtectTag {
|
class CraftyShieldTag extends ConditionalProtectTag {
|
||||||
constructor(sourceId: integer, side: ArenaTagSide) {
|
constructor(sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.CRAFTY_SHIELD, Moves.CRAFTY_SHIELD, sourceId, side, CraftyShieldConditionFunc, true);
|
super(ArenaTagType.CRAFTY_SHIELD, Moves.CRAFTY_SHIELD, sourceId, side, CraftyShieldConditionFunc, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -382,12 +387,12 @@ class CraftyShieldTag extends ConditionalProtectTag {
|
||||||
export class NoCritTag extends ArenaTag {
|
export class NoCritTag extends ArenaTag {
|
||||||
/**
|
/**
|
||||||
* Constructor method for the NoCritTag class
|
* Constructor method for the NoCritTag class
|
||||||
* @param turnCount `integer` the number of turns this effect lasts
|
* @param turnCount `number` the number of turns this effect lasts
|
||||||
* @param sourceMove {@linkcode Moves} the move that created this effect
|
* @param sourceMove {@linkcode Moves} the move that created this effect
|
||||||
* @param sourceId `integer` the ID of the {@linkcode Pokemon} that created this effect
|
* @param sourceId `number` the ID of the {@linkcode Pokemon} that created this effect
|
||||||
* @param side {@linkcode ArenaTagSide} the side to which this effect belongs
|
* @param side {@linkcode ArenaTagSide} the side to which this effect belongs
|
||||||
*/
|
*/
|
||||||
constructor(turnCount: integer, sourceMove: Moves, sourceId: integer, side: ArenaTagSide) {
|
constructor(turnCount: number, sourceMove: Moves, sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.NO_CRIT, turnCount, sourceMove, sourceId, side);
|
super(ArenaTagType.NO_CRIT, turnCount, sourceMove, sourceId, side);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -417,7 +422,7 @@ class WishTag extends ArenaTag {
|
||||||
private triggerMessage: string;
|
private triggerMessage: string;
|
||||||
private healHp: number;
|
private healHp: number;
|
||||||
|
|
||||||
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
|
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.WISH, turnCount, Moves.WISH, sourceId, side);
|
super(ArenaTagType.WISH, turnCount, Moves.WISH, sourceId, side);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -458,7 +463,7 @@ export class WeakenMoveTypeTag extends ArenaTag {
|
||||||
* @param sourceMove - The move that created the tag.
|
* @param sourceMove - The move that created the tag.
|
||||||
* @param sourceId - The ID of the source of the tag.
|
* @param sourceId - The ID of the source of the tag.
|
||||||
*/
|
*/
|
||||||
constructor(tagType: ArenaTagType, turnCount: integer, type: Type, sourceMove: Moves, sourceId: integer) {
|
constructor(tagType: ArenaTagType, turnCount: number, type: Type, sourceMove: Moves, sourceId: number) {
|
||||||
super(tagType, turnCount, sourceMove, sourceId);
|
super(tagType, turnCount, sourceMove, sourceId);
|
||||||
|
|
||||||
this.weakenedType = type;
|
this.weakenedType = type;
|
||||||
|
@ -479,7 +484,7 @@ export class WeakenMoveTypeTag extends ArenaTag {
|
||||||
* Weakens Electric type moves for a set amount of turns, usually 5.
|
* Weakens Electric type moves for a set amount of turns, usually 5.
|
||||||
*/
|
*/
|
||||||
class MudSportTag extends WeakenMoveTypeTag {
|
class MudSportTag extends WeakenMoveTypeTag {
|
||||||
constructor(turnCount: integer, sourceId: integer) {
|
constructor(turnCount: number, sourceId: number) {
|
||||||
super(ArenaTagType.MUD_SPORT, turnCount, Type.ELECTRIC, Moves.MUD_SPORT, sourceId);
|
super(ArenaTagType.MUD_SPORT, turnCount, Type.ELECTRIC, Moves.MUD_SPORT, sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -497,7 +502,7 @@ class MudSportTag extends WeakenMoveTypeTag {
|
||||||
* Weakens Fire type moves for a set amount of turns, usually 5.
|
* Weakens Fire type moves for a set amount of turns, usually 5.
|
||||||
*/
|
*/
|
||||||
class WaterSportTag extends WeakenMoveTypeTag {
|
class WaterSportTag extends WeakenMoveTypeTag {
|
||||||
constructor(turnCount: integer, sourceId: integer) {
|
constructor(turnCount: number, sourceId: number) {
|
||||||
super(ArenaTagType.WATER_SPORT, turnCount, Type.FIRE, Moves.WATER_SPORT, sourceId);
|
super(ArenaTagType.WATER_SPORT, turnCount, Type.FIRE, Moves.WATER_SPORT, sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -514,8 +519,8 @@ class WaterSportTag extends WeakenMoveTypeTag {
|
||||||
* Abstract class to implement arena traps.
|
* Abstract class to implement arena traps.
|
||||||
*/
|
*/
|
||||||
export class ArenaTrapTag extends ArenaTag {
|
export class ArenaTrapTag extends ArenaTag {
|
||||||
public layers: integer;
|
public layers: number;
|
||||||
public maxLayers: integer;
|
public maxLayers: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of the ArenaTrapTag class.
|
* Creates a new instance of the ArenaTrapTag class.
|
||||||
|
@ -526,7 +531,7 @@ export class ArenaTrapTag extends ArenaTag {
|
||||||
* @param side - The side (player or enemy) the tag affects.
|
* @param side - The side (player or enemy) the tag affects.
|
||||||
* @param maxLayers - The maximum amount of layers this tag can have.
|
* @param maxLayers - The maximum amount of layers this tag can have.
|
||||||
*/
|
*/
|
||||||
constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: integer, side: ArenaTagSide, maxLayers: integer) {
|
constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: number, side: ArenaTagSide, maxLayers: number) {
|
||||||
super(tagType, 0, sourceMove, sourceId, side);
|
super(tagType, 0, sourceMove, sourceId, side);
|
||||||
|
|
||||||
this.layers = 1;
|
this.layers = 1;
|
||||||
|
@ -557,6 +562,12 @@ export class ArenaTrapTag extends ArenaTag {
|
||||||
getMatchupScoreMultiplier(pokemon: Pokemon): number {
|
getMatchupScoreMultiplier(pokemon: Pokemon): number {
|
||||||
return pokemon.isGrounded() ? 1 : Phaser.Math.Linear(0, 1 / Math.pow(2, this.layers), Math.min(pokemon.getHpRatio(), 0.5) * 2);
|
return pokemon.isGrounded() ? 1 : Phaser.Math.Linear(0, 1 / Math.pow(2, this.layers), Math.min(pokemon.getHpRatio(), 0.5) * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadTag(source: any): void {
|
||||||
|
super.loadTag(source);
|
||||||
|
this.layers = source.layers;
|
||||||
|
this.maxLayers = source.maxLayers;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -565,7 +576,7 @@ export class ArenaTrapTag extends ArenaTag {
|
||||||
* in damage for 1, 2, or 3 layers of Spikes respectively if they are summoned into this trap.
|
* in damage for 1, 2, or 3 layers of Spikes respectively if they are summoned into this trap.
|
||||||
*/
|
*/
|
||||||
class SpikesTag extends ArenaTrapTag {
|
class SpikesTag extends ArenaTrapTag {
|
||||||
constructor(sourceId: integer, side: ArenaTagSide) {
|
constructor(sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.SPIKES, Moves.SPIKES, sourceId, side, 3);
|
super(ArenaTagType.SPIKES, Moves.SPIKES, sourceId, side, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -609,7 +620,7 @@ class SpikesTag extends ArenaTrapTag {
|
||||||
class ToxicSpikesTag extends ArenaTrapTag {
|
class ToxicSpikesTag extends ArenaTrapTag {
|
||||||
private neutralized: boolean;
|
private neutralized: boolean;
|
||||||
|
|
||||||
constructor(sourceId: integer, side: ArenaTagSide) {
|
constructor(sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.TOXIC_SPIKES, Moves.TOXIC_SPIKES, sourceId, side, 2);
|
super(ArenaTagType.TOXIC_SPIKES, Moves.TOXIC_SPIKES, sourceId, side, 2);
|
||||||
this.neutralized = false;
|
this.neutralized = false;
|
||||||
}
|
}
|
||||||
|
@ -667,7 +678,7 @@ class ToxicSpikesTag extends ArenaTrapTag {
|
||||||
class DelayedAttackTag extends ArenaTag {
|
class DelayedAttackTag extends ArenaTag {
|
||||||
public targetIndex: BattlerIndex;
|
public targetIndex: BattlerIndex;
|
||||||
|
|
||||||
constructor(tagType: ArenaTagType, sourceMove: Moves | undefined, sourceId: integer, targetIndex: BattlerIndex) {
|
constructor(tagType: ArenaTagType, sourceMove: Moves | undefined, sourceId: number, targetIndex: BattlerIndex) {
|
||||||
super(tagType, 3, sourceMove, sourceId);
|
super(tagType, 3, sourceMove, sourceId);
|
||||||
|
|
||||||
this.targetIndex = targetIndex;
|
this.targetIndex = targetIndex;
|
||||||
|
@ -692,7 +703,7 @@ class DelayedAttackTag extends ArenaTag {
|
||||||
* who is summoned into the trap, based on the Rock type's type effectiveness.
|
* who is summoned into the trap, based on the Rock type's type effectiveness.
|
||||||
*/
|
*/
|
||||||
class StealthRockTag extends ArenaTrapTag {
|
class StealthRockTag extends ArenaTrapTag {
|
||||||
constructor(sourceId: integer, side: ArenaTagSide) {
|
constructor(sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.STEALTH_ROCK, Moves.STEALTH_ROCK, sourceId, side, 1);
|
super(ArenaTagType.STEALTH_ROCK, Moves.STEALTH_ROCK, sourceId, side, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -768,7 +779,7 @@ class StealthRockTag extends ArenaTrapTag {
|
||||||
* to any Pokémon who is summoned into this trap.
|
* to any Pokémon who is summoned into this trap.
|
||||||
*/
|
*/
|
||||||
class StickyWebTag extends ArenaTrapTag {
|
class StickyWebTag extends ArenaTrapTag {
|
||||||
constructor(sourceId: integer, side: ArenaTagSide) {
|
constructor(sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.STICKY_WEB, Moves.STICKY_WEB, sourceId, side, 1);
|
super(ArenaTagType.STICKY_WEB, Moves.STICKY_WEB, sourceId, side, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -802,7 +813,7 @@ class StickyWebTag extends ArenaTrapTag {
|
||||||
* also reversing the turn order for all Pokémon on the field as well.
|
* also reversing the turn order for all Pokémon on the field as well.
|
||||||
*/
|
*/
|
||||||
export class TrickRoomTag extends ArenaTag {
|
export class TrickRoomTag extends ArenaTag {
|
||||||
constructor(turnCount: integer, sourceId: integer) {
|
constructor(turnCount: number, sourceId: number) {
|
||||||
super(ArenaTagType.TRICK_ROOM, turnCount, Moves.TRICK_ROOM, sourceId);
|
super(ArenaTagType.TRICK_ROOM, turnCount, Moves.TRICK_ROOM, sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -830,7 +841,7 @@ export class TrickRoomTag extends ArenaTag {
|
||||||
* {@linkcode Abilities.LEVITATE} for the duration of the arena tag, usually 5 turns.
|
* {@linkcode Abilities.LEVITATE} for the duration of the arena tag, usually 5 turns.
|
||||||
*/
|
*/
|
||||||
export class GravityTag extends ArenaTag {
|
export class GravityTag extends ArenaTag {
|
||||||
constructor(turnCount: integer) {
|
constructor(turnCount: number) {
|
||||||
super(ArenaTagType.GRAVITY, turnCount, Moves.GRAVITY);
|
super(ArenaTagType.GRAVITY, turnCount, Moves.GRAVITY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -854,7 +865,7 @@ export class GravityTag extends ArenaTag {
|
||||||
* Applies this arena tag for 4 turns (including the turn the move was used).
|
* Applies this arena tag for 4 turns (including the turn the move was used).
|
||||||
*/
|
*/
|
||||||
class TailwindTag extends ArenaTag {
|
class TailwindTag extends ArenaTag {
|
||||||
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
|
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.TAILWIND, turnCount, Moves.TAILWIND, sourceId, side);
|
super(ArenaTagType.TAILWIND, turnCount, Moves.TAILWIND, sourceId, side);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -892,7 +903,7 @@ class TailwindTag extends ArenaTag {
|
||||||
* Doubles the prize money from trainers and money moves like {@linkcode Moves.PAY_DAY} and {@linkcode Moves.MAKE_IT_RAIN}.
|
* Doubles the prize money from trainers and money moves like {@linkcode Moves.PAY_DAY} and {@linkcode Moves.MAKE_IT_RAIN}.
|
||||||
*/
|
*/
|
||||||
class HappyHourTag extends ArenaTag {
|
class HappyHourTag extends ArenaTag {
|
||||||
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
|
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.HAPPY_HOUR, turnCount, Moves.HAPPY_HOUR, sourceId, side);
|
super(ArenaTagType.HAPPY_HOUR, turnCount, Moves.HAPPY_HOUR, sourceId, side);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -906,7 +917,7 @@ class HappyHourTag extends ArenaTag {
|
||||||
}
|
}
|
||||||
|
|
||||||
class SafeguardTag extends ArenaTag {
|
class SafeguardTag extends ArenaTag {
|
||||||
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
|
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.SAFEGUARD, turnCount, Moves.SAFEGUARD, sourceId, side);
|
super(ArenaTagType.SAFEGUARD, turnCount, Moves.SAFEGUARD, sourceId, side);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -919,8 +930,14 @@ class SafeguardTag extends ArenaTag {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NoneTag extends ArenaTag {
|
||||||
|
constructor() {
|
||||||
|
super(ArenaTagType.NONE, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId: integer, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null {
|
// TODO: swap `sourceMove` and `sourceId` and make `sourceMove` an optional parameter
|
||||||
|
export function getArenaTag(tagType: ArenaTagType, turnCount: number, sourceMove: Moves | undefined, sourceId: number, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null {
|
||||||
switch (tagType) {
|
switch (tagType) {
|
||||||
case ArenaTagType.MIST:
|
case ArenaTagType.MIST:
|
||||||
return new MistTag(turnCount, sourceId, side);
|
return new MistTag(turnCount, sourceId, side);
|
||||||
|
@ -971,3 +988,16 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When given a battler tag or json representing one, creates an actual ArenaTag object with the same data.
|
||||||
|
* @param {ArenaTag | any} source An arena tag
|
||||||
|
* @return {ArenaTag} The valid arena tag
|
||||||
|
*/
|
||||||
|
export function loadArenaTag(source: ArenaTag | any): ArenaTag {
|
||||||
|
const tag = getArenaTag(source.tagType, source.turnCount, source.sourceMove, source.sourceId, source.targetIndex, source.side)
|
||||||
|
?? new NoneTag();
|
||||||
|
tag.loadTag(source);
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -488,14 +488,14 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
|
||||||
} else {
|
} else {
|
||||||
moveAnims.set(move, null);
|
moveAnims.set(move, null);
|
||||||
const defaultMoveAnim = allMoves[move] instanceof AttackMove ? Moves.TACKLE : allMoves[move] instanceof SelfStatusMove ? Moves.FOCUS_ENERGY : Moves.TAIL_WHIP;
|
const defaultMoveAnim = allMoves[move] instanceof AttackMove ? Moves.TACKLE : allMoves[move] instanceof SelfStatusMove ? Moves.FOCUS_ENERGY : Moves.TAIL_WHIP;
|
||||||
const moveName = Moves[move].toLowerCase().replace(/\_/g, "-");
|
|
||||||
const fetchAnimAndResolve = (move: Moves) => {
|
const fetchAnimAndResolve = (move: Moves) => {
|
||||||
scene.cachedFetch(`./battle-anims/${moveName}.json`)
|
scene.cachedFetch(`./battle-anims/${Utils.animationFileName(move)}.json`)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
const contentType = response.headers.get("content-type");
|
const contentType = response.headers.get("content-type");
|
||||||
if (!response.ok || contentType?.indexOf("application/json") === -1) {
|
if (!response.ok || contentType?.indexOf("application/json") === -1) {
|
||||||
console.error(`Could not load animation file for move '${moveName}'`, response.status, response.statusText);
|
useDefaultAnim(move, defaultMoveAnim);
|
||||||
populateMoveAnim(move, moveAnims.get(defaultMoveAnim));
|
logMissingMoveAnim(move, response.status, response.statusText);
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
return response.json();
|
return response.json();
|
||||||
|
@ -515,6 +515,11 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
|
||||||
} else {
|
} else {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
useDefaultAnim(move, defaultMoveAnim);
|
||||||
|
logMissingMoveAnim(move, error);
|
||||||
|
return resolve();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
fetchAnimAndResolve(move);
|
fetchAnimAndResolve(move);
|
||||||
|
@ -522,6 +527,29 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates the default animation for the given move.
|
||||||
|
*
|
||||||
|
* @param move the move to populate an animation for
|
||||||
|
* @param defaultMoveAnim the move to use as the default animation
|
||||||
|
*/
|
||||||
|
function useDefaultAnim(move: Moves, defaultMoveAnim: Moves) {
|
||||||
|
populateMoveAnim(move, moveAnims.get(defaultMoveAnim));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method for printing a warning to the console when a move animation is missing.
|
||||||
|
*
|
||||||
|
* @param move the move to populate an animation for
|
||||||
|
* @param optionalParams parameters to add to the error logging
|
||||||
|
*
|
||||||
|
* @remarks use {@linkcode useDefaultAnim} to use a default animation
|
||||||
|
*/
|
||||||
|
function logMissingMoveAnim(move: Moves, ...optionalParams: any[]) {
|
||||||
|
const moveName = Utils.animationFileName(move);
|
||||||
|
console.warn(`Could not load animation file for move '${moveName}'`, ...optionalParams);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches animation configs to be used in a Mystery Encounter
|
* Fetches animation configs to be used in a Mystery Encounter
|
||||||
* @param scene
|
* @param scene
|
||||||
|
|
|
@ -3839,7 +3839,7 @@ export class StormAccuracyAttr extends VariableAccuracyAttr {
|
||||||
* @extends VariableAccuracyAttr
|
* @extends VariableAccuracyAttr
|
||||||
* @see {@linkcode apply}
|
* @see {@linkcode apply}
|
||||||
*/
|
*/
|
||||||
export class MinimizeAccuracyAttr extends VariableAccuracyAttr {
|
export class AlwaysHitMinimizeAttr extends VariableAccuracyAttr {
|
||||||
/**
|
/**
|
||||||
* @see {@linkcode apply}
|
* @see {@linkcode apply}
|
||||||
* @param user N/A
|
* @param user N/A
|
||||||
|
@ -4855,10 +4855,10 @@ export class RemoveAllSubstitutesAttr extends MoveEffectAttr {
|
||||||
* Attribute used when a move hits a {@linkcode BattlerTagType} for double damage
|
* Attribute used when a move hits a {@linkcode BattlerTagType} for double damage
|
||||||
* @extends MoveAttr
|
* @extends MoveAttr
|
||||||
*/
|
*/
|
||||||
export class HitsTagAttr extends MoveAttr {
|
export class DealsDoubleDamageToTagAttr extends MoveAttr {
|
||||||
/** The {@linkcode BattlerTagType} this move hits */
|
/** The {@linkcode BattlerTagType} this move hits */
|
||||||
public tagType: BattlerTagType;
|
public tagType: BattlerTagType;
|
||||||
/** Should this move deal double damage against {@linkcode HitsTagAttr.tagType}? */
|
/** Should this move deal double damage against {@linkcode DealsDoubleDamageToTagAttr.tagType}? */
|
||||||
public doubleDamage: boolean;
|
public doubleDamage: boolean;
|
||||||
|
|
||||||
constructor(tagType: BattlerTagType, doubleDamage?: boolean) {
|
constructor(tagType: BattlerTagType, doubleDamage?: boolean) {
|
||||||
|
@ -6752,12 +6752,11 @@ export function initMoves() {
|
||||||
new AttackMove(Moves.CUT, Type.NORMAL, MoveCategory.PHYSICAL, 50, 95, 30, -1, 0, 1)
|
new AttackMove(Moves.CUT, Type.NORMAL, MoveCategory.PHYSICAL, 50, 95, 30, -1, 0, 1)
|
||||||
.slicingMove(),
|
.slicingMove(),
|
||||||
new AttackMove(Moves.GUST, Type.FLYING, MoveCategory.SPECIAL, 40, 100, 35, -1, 0, 1)
|
new AttackMove(Moves.GUST, Type.FLYING, MoveCategory.SPECIAL, 40, 100, 35, -1, 0, 1)
|
||||||
.attr(HitsTagAttr, BattlerTagType.FLYING, true)
|
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING, true)
|
||||||
.windMove(),
|
.windMove(),
|
||||||
new AttackMove(Moves.WING_ATTACK, Type.FLYING, MoveCategory.PHYSICAL, 60, 100, 35, -1, 0, 1),
|
new AttackMove(Moves.WING_ATTACK, Type.FLYING, MoveCategory.PHYSICAL, 60, 100, 35, -1, 0, 1),
|
||||||
new StatusMove(Moves.WHIRLWIND, Type.NORMAL, -1, 20, -1, -6, 1)
|
new StatusMove(Moves.WHIRLWIND, Type.NORMAL, -1, 20, -1, -6, 1)
|
||||||
.attr(ForceSwitchOutAttr)
|
.attr(ForceSwitchOutAttr)
|
||||||
.attr(HitsTagAttr, BattlerTagType.FLYING, false)
|
|
||||||
.ignoresSubstitute()
|
.ignoresSubstitute()
|
||||||
.hidesTarget()
|
.hidesTarget()
|
||||||
.windMove(),
|
.windMove(),
|
||||||
|
@ -6770,8 +6769,8 @@ export function initMoves() {
|
||||||
new AttackMove(Moves.SLAM, Type.NORMAL, MoveCategory.PHYSICAL, 80, 75, 20, -1, 0, 1),
|
new AttackMove(Moves.SLAM, Type.NORMAL, MoveCategory.PHYSICAL, 80, 75, 20, -1, 0, 1),
|
||||||
new AttackMove(Moves.VINE_WHIP, Type.GRASS, MoveCategory.PHYSICAL, 45, 100, 25, -1, 0, 1),
|
new AttackMove(Moves.VINE_WHIP, Type.GRASS, MoveCategory.PHYSICAL, 45, 100, 25, -1, 0, 1),
|
||||||
new AttackMove(Moves.STOMP, Type.NORMAL, MoveCategory.PHYSICAL, 65, 100, 20, 30, 0, 1)
|
new AttackMove(Moves.STOMP, Type.NORMAL, MoveCategory.PHYSICAL, 65, 100, 20, 30, 0, 1)
|
||||||
.attr(MinimizeAccuracyAttr)
|
.attr(AlwaysHitMinimizeAttr)
|
||||||
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
|
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true)
|
||||||
.attr(FlinchAttr),
|
.attr(FlinchAttr),
|
||||||
new AttackMove(Moves.DOUBLE_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 30, 100, 30, -1, 0, 1)
|
new AttackMove(Moves.DOUBLE_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 30, 100, 30, -1, 0, 1)
|
||||||
.attr(MultiHitAttr, MultiHitType._2),
|
.attr(MultiHitAttr, MultiHitType._2),
|
||||||
|
@ -6795,8 +6794,8 @@ export function initMoves() {
|
||||||
.attr(OneHitKOAccuracyAttr),
|
.attr(OneHitKOAccuracyAttr),
|
||||||
new AttackMove(Moves.TACKLE, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 35, -1, 0, 1),
|
new AttackMove(Moves.TACKLE, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 35, -1, 0, 1),
|
||||||
new AttackMove(Moves.BODY_SLAM, Type.NORMAL, MoveCategory.PHYSICAL, 85, 100, 15, 30, 0, 1)
|
new AttackMove(Moves.BODY_SLAM, Type.NORMAL, MoveCategory.PHYSICAL, 85, 100, 15, 30, 0, 1)
|
||||||
.attr(MinimizeAccuracyAttr)
|
.attr(AlwaysHitMinimizeAttr)
|
||||||
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
|
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true)
|
||||||
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
|
||||||
new AttackMove(Moves.WRAP, Type.NORMAL, MoveCategory.PHYSICAL, 15, 90, 20, -1, 0, 1)
|
new AttackMove(Moves.WRAP, Type.NORMAL, MoveCategory.PHYSICAL, 15, 90, 20, -1, 0, 1)
|
||||||
.attr(TrapAttr, BattlerTagType.WRAP),
|
.attr(TrapAttr, BattlerTagType.WRAP),
|
||||||
|
@ -6864,7 +6863,7 @@ export function initMoves() {
|
||||||
new AttackMove(Moves.HYDRO_PUMP, Type.WATER, MoveCategory.SPECIAL, 110, 80, 5, -1, 0, 1),
|
new AttackMove(Moves.HYDRO_PUMP, Type.WATER, MoveCategory.SPECIAL, 110, 80, 5, -1, 0, 1),
|
||||||
new AttackMove(Moves.SURF, Type.WATER, MoveCategory.SPECIAL, 90, 100, 15, -1, 0, 1)
|
new AttackMove(Moves.SURF, Type.WATER, MoveCategory.SPECIAL, 90, 100, 15, -1, 0, 1)
|
||||||
.target(MoveTarget.ALL_NEAR_OTHERS)
|
.target(MoveTarget.ALL_NEAR_OTHERS)
|
||||||
.attr(HitsTagAttr, BattlerTagType.UNDERWATER, true)
|
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.UNDERWATER, true)
|
||||||
.attr(GulpMissileTagAttr),
|
.attr(GulpMissileTagAttr),
|
||||||
new AttackMove(Moves.ICE_BEAM, Type.ICE, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 1)
|
new AttackMove(Moves.ICE_BEAM, Type.ICE, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 1)
|
||||||
.attr(StatusEffectAttr, StatusEffect.FREEZE),
|
.attr(StatusEffectAttr, StatusEffect.FREEZE),
|
||||||
|
@ -6947,18 +6946,18 @@ export function initMoves() {
|
||||||
new AttackMove(Moves.THUNDER, Type.ELECTRIC, MoveCategory.SPECIAL, 110, 70, 10, 30, 0, 1)
|
new AttackMove(Moves.THUNDER, Type.ELECTRIC, MoveCategory.SPECIAL, 110, 70, 10, 30, 0, 1)
|
||||||
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
|
||||||
.attr(ThunderAccuracyAttr)
|
.attr(ThunderAccuracyAttr)
|
||||||
.attr(HitsTagAttr, BattlerTagType.FLYING, false),
|
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING, false),
|
||||||
new AttackMove(Moves.ROCK_THROW, Type.ROCK, MoveCategory.PHYSICAL, 50, 90, 15, -1, 0, 1)
|
new AttackMove(Moves.ROCK_THROW, Type.ROCK, MoveCategory.PHYSICAL, 50, 90, 15, -1, 0, 1)
|
||||||
.makesContact(false),
|
.makesContact(false),
|
||||||
new AttackMove(Moves.EARTHQUAKE, Type.GROUND, MoveCategory.PHYSICAL, 100, 100, 10, -1, 0, 1)
|
new AttackMove(Moves.EARTHQUAKE, Type.GROUND, MoveCategory.PHYSICAL, 100, 100, 10, -1, 0, 1)
|
||||||
.attr(HitsTagAttr, BattlerTagType.UNDERGROUND, true)
|
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.UNDERGROUND, true)
|
||||||
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1)
|
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1)
|
||||||
.makesContact(false)
|
.makesContact(false)
|
||||||
.target(MoveTarget.ALL_NEAR_OTHERS),
|
.target(MoveTarget.ALL_NEAR_OTHERS),
|
||||||
new AttackMove(Moves.FISSURE, Type.GROUND, MoveCategory.PHYSICAL, 200, 30, 5, -1, 0, 1)
|
new AttackMove(Moves.FISSURE, Type.GROUND, MoveCategory.PHYSICAL, 200, 30, 5, -1, 0, 1)
|
||||||
.attr(OneHitKOAttr)
|
.attr(OneHitKOAttr)
|
||||||
.attr(OneHitKOAccuracyAttr)
|
.attr(OneHitKOAccuracyAttr)
|
||||||
.attr(HitsTagAttr, BattlerTagType.UNDERGROUND, false)
|
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.UNDERGROUND, false)
|
||||||
.makesContact(false),
|
.makesContact(false),
|
||||||
new AttackMove(Moves.DIG, Type.GROUND, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 1)
|
new AttackMove(Moves.DIG, Type.GROUND, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 1)
|
||||||
.attr(ChargeAttr, ChargeAnim.DIG_CHARGING, i18next.t("moveTriggers:dugAHole", {pokemonName: "{USER}"}), BattlerTagType.UNDERGROUND)
|
.attr(ChargeAttr, ChargeAnim.DIG_CHARGING, i18next.t("moveTriggers:dugAHole", {pokemonName: "{USER}"}), BattlerTagType.UNDERGROUND)
|
||||||
|
@ -7347,7 +7346,7 @@ export function initMoves() {
|
||||||
.attr(PreMoveMessageAttr, magnitudeMessageFunc)
|
.attr(PreMoveMessageAttr, magnitudeMessageFunc)
|
||||||
.attr(MagnitudePowerAttr)
|
.attr(MagnitudePowerAttr)
|
||||||
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1)
|
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1)
|
||||||
.attr(HitsTagAttr, BattlerTagType.UNDERGROUND, true)
|
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.UNDERGROUND, true)
|
||||||
.makesContact(false)
|
.makesContact(false)
|
||||||
.target(MoveTarget.ALL_NEAR_OTHERS),
|
.target(MoveTarget.ALL_NEAR_OTHERS),
|
||||||
new AttackMove(Moves.DYNAMIC_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 50, 5, 100, 0, 2)
|
new AttackMove(Moves.DYNAMIC_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 50, 5, 100, 0, 2)
|
||||||
|
@ -7403,7 +7402,7 @@ export function initMoves() {
|
||||||
new AttackMove(Moves.CROSS_CHOP, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 80, 5, -1, 0, 2)
|
new AttackMove(Moves.CROSS_CHOP, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 80, 5, -1, 0, 2)
|
||||||
.attr(HighCritAttr),
|
.attr(HighCritAttr),
|
||||||
new AttackMove(Moves.TWISTER, Type.DRAGON, MoveCategory.SPECIAL, 40, 100, 20, 20, 0, 2)
|
new AttackMove(Moves.TWISTER, Type.DRAGON, MoveCategory.SPECIAL, 40, 100, 20, 20, 0, 2)
|
||||||
.attr(HitsTagAttr, BattlerTagType.FLYING, true)
|
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING, true)
|
||||||
.attr(FlinchAttr)
|
.attr(FlinchAttr)
|
||||||
.windMove()
|
.windMove()
|
||||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||||
|
@ -7435,7 +7434,7 @@ export function initMoves() {
|
||||||
.attr(StatStageChangeAttr, [ Stat.DEF ], -1),
|
.attr(StatStageChangeAttr, [ Stat.DEF ], -1),
|
||||||
new AttackMove(Moves.WHIRLPOOL, Type.WATER, MoveCategory.SPECIAL, 35, 85, 15, -1, 0, 2)
|
new AttackMove(Moves.WHIRLPOOL, Type.WATER, MoveCategory.SPECIAL, 35, 85, 15, -1, 0, 2)
|
||||||
.attr(TrapAttr, BattlerTagType.WHIRLPOOL)
|
.attr(TrapAttr, BattlerTagType.WHIRLPOOL)
|
||||||
.attr(HitsTagAttr, BattlerTagType.UNDERWATER, true),
|
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.UNDERWATER, true),
|
||||||
new AttackMove(Moves.BEAT_UP, Type.DARK, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 2)
|
new AttackMove(Moves.BEAT_UP, Type.DARK, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 2)
|
||||||
.attr(MultiHitAttr, MultiHitType.BEAT_UP)
|
.attr(MultiHitAttr, MultiHitType.BEAT_UP)
|
||||||
.attr(BeatUpAttr)
|
.attr(BeatUpAttr)
|
||||||
|
@ -7658,7 +7657,7 @@ export function initMoves() {
|
||||||
new AttackMove(Moves.EXTRASENSORY, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 20, 10, 0, 3)
|
new AttackMove(Moves.EXTRASENSORY, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 20, 10, 0, 3)
|
||||||
.attr(FlinchAttr),
|
.attr(FlinchAttr),
|
||||||
new AttackMove(Moves.SKY_UPPERCUT, Type.FIGHTING, MoveCategory.PHYSICAL, 85, 90, 15, -1, 0, 3)
|
new AttackMove(Moves.SKY_UPPERCUT, Type.FIGHTING, MoveCategory.PHYSICAL, 85, 90, 15, -1, 0, 3)
|
||||||
.attr(HitsTagAttr, BattlerTagType.FLYING)
|
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING)
|
||||||
.punchingMove(),
|
.punchingMove(),
|
||||||
new AttackMove(Moves.SAND_TOMB, Type.GROUND, MoveCategory.PHYSICAL, 35, 85, 15, -1, 0, 3)
|
new AttackMove(Moves.SAND_TOMB, Type.GROUND, MoveCategory.PHYSICAL, 35, 85, 15, -1, 0, 3)
|
||||||
.attr(TrapAttr, BattlerTagType.SAND_TOMB)
|
.attr(TrapAttr, BattlerTagType.SAND_TOMB)
|
||||||
|
@ -7889,8 +7888,8 @@ export function initMoves() {
|
||||||
new AttackMove(Moves.DRAGON_PULSE, Type.DRAGON, MoveCategory.SPECIAL, 85, 100, 10, -1, 0, 4)
|
new AttackMove(Moves.DRAGON_PULSE, Type.DRAGON, MoveCategory.SPECIAL, 85, 100, 10, -1, 0, 4)
|
||||||
.pulseMove(),
|
.pulseMove(),
|
||||||
new AttackMove(Moves.DRAGON_RUSH, Type.DRAGON, MoveCategory.PHYSICAL, 100, 75, 10, 20, 0, 4)
|
new AttackMove(Moves.DRAGON_RUSH, Type.DRAGON, MoveCategory.PHYSICAL, 100, 75, 10, 20, 0, 4)
|
||||||
.attr(MinimizeAccuracyAttr)
|
.attr(AlwaysHitMinimizeAttr)
|
||||||
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
|
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true)
|
||||||
.attr(FlinchAttr),
|
.attr(FlinchAttr),
|
||||||
new AttackMove(Moves.POWER_GEM, Type.ROCK, MoveCategory.SPECIAL, 80, 100, 20, -1, 0, 4),
|
new AttackMove(Moves.POWER_GEM, Type.ROCK, MoveCategory.SPECIAL, 80, 100, 20, -1, 0, 4),
|
||||||
new AttackMove(Moves.DRAIN_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 75, 100, 10, -1, 0, 4)
|
new AttackMove(Moves.DRAIN_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 75, 100, 10, -1, 0, 4)
|
||||||
|
@ -8087,7 +8086,7 @@ export function initMoves() {
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true)
|
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true)
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED)
|
.attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED)
|
||||||
.attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN])
|
.attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN])
|
||||||
.attr(HitsTagAttr, BattlerTagType.FLYING, false)
|
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING, false)
|
||||||
.makesContact(false),
|
.makesContact(false),
|
||||||
new AttackMove(Moves.STORM_THROW, Type.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, -1, 0, 5)
|
new AttackMove(Moves.STORM_THROW, Type.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, -1, 0, 5)
|
||||||
.attr(CritOnlyAttr),
|
.attr(CritOnlyAttr),
|
||||||
|
@ -8100,9 +8099,9 @@ export function initMoves() {
|
||||||
.attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true)
|
.attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true)
|
||||||
.danceMove(),
|
.danceMove(),
|
||||||
new AttackMove(Moves.HEAVY_SLAM, Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 5)
|
new AttackMove(Moves.HEAVY_SLAM, Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 5)
|
||||||
.attr(MinimizeAccuracyAttr)
|
.attr(AlwaysHitMinimizeAttr)
|
||||||
.attr(CompareWeightPowerAttr)
|
.attr(CompareWeightPowerAttr)
|
||||||
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true),
|
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true),
|
||||||
new AttackMove(Moves.SYNCHRONOISE, Type.PSYCHIC, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 5)
|
new AttackMove(Moves.SYNCHRONOISE, Type.PSYCHIC, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 5)
|
||||||
.target(MoveTarget.ALL_NEAR_OTHERS)
|
.target(MoveTarget.ALL_NEAR_OTHERS)
|
||||||
.condition(unknownTypeCondition)
|
.condition(unknownTypeCondition)
|
||||||
|
@ -8253,12 +8252,14 @@ export function initMoves() {
|
||||||
.attr(StatStageChangeAttr, [ Stat.DEF ], -1)
|
.attr(StatStageChangeAttr, [ Stat.DEF ], -1)
|
||||||
.slicingMove(),
|
.slicingMove(),
|
||||||
new AttackMove(Moves.HEAT_CRASH, Type.FIRE, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 5)
|
new AttackMove(Moves.HEAT_CRASH, Type.FIRE, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 5)
|
||||||
.attr(MinimizeAccuracyAttr)
|
.attr(AlwaysHitMinimizeAttr)
|
||||||
.attr(CompareWeightPowerAttr)
|
.attr(CompareWeightPowerAttr)
|
||||||
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true),
|
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true),
|
||||||
new AttackMove(Moves.LEAF_TORNADO, Type.GRASS, MoveCategory.SPECIAL, 65, 90, 10, 50, 0, 5)
|
new AttackMove(Moves.LEAF_TORNADO, Type.GRASS, MoveCategory.SPECIAL, 65, 90, 10, 50, 0, 5)
|
||||||
.attr(StatStageChangeAttr, [ Stat.ACC ], -1),
|
.attr(StatStageChangeAttr, [ Stat.ACC ], -1),
|
||||||
new AttackMove(Moves.STEAMROLLER, Type.BUG, MoveCategory.PHYSICAL, 65, 100, 20, 30, 0, 5)
|
new AttackMove(Moves.STEAMROLLER, Type.BUG, MoveCategory.PHYSICAL, 65, 100, 20, 30, 0, 5)
|
||||||
|
.attr(AlwaysHitMinimizeAttr)
|
||||||
|
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true)
|
||||||
.attr(FlinchAttr),
|
.attr(FlinchAttr),
|
||||||
new SelfStatusMove(Moves.COTTON_GUARD, Type.GRASS, -1, 10, -1, 0, 5)
|
new SelfStatusMove(Moves.COTTON_GUARD, Type.GRASS, -1, 10, -1, 0, 5)
|
||||||
.attr(StatStageChangeAttr, [ Stat.DEF ], 3, true),
|
.attr(StatStageChangeAttr, [ Stat.DEF ], 3, true),
|
||||||
|
@ -8271,7 +8272,7 @@ export function initMoves() {
|
||||||
new AttackMove(Moves.HURRICANE, Type.FLYING, MoveCategory.SPECIAL, 110, 70, 10, 30, 0, 5)
|
new AttackMove(Moves.HURRICANE, Type.FLYING, MoveCategory.SPECIAL, 110, 70, 10, 30, 0, 5)
|
||||||
.attr(ThunderAccuracyAttr)
|
.attr(ThunderAccuracyAttr)
|
||||||
.attr(ConfuseAttr)
|
.attr(ConfuseAttr)
|
||||||
.attr(HitsTagAttr, BattlerTagType.FLYING, false)
|
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING, false)
|
||||||
.windMove(),
|
.windMove(),
|
||||||
new AttackMove(Moves.HEAD_CHARGE, Type.NORMAL, MoveCategory.PHYSICAL, 120, 100, 15, -1, 0, 5)
|
new AttackMove(Moves.HEAD_CHARGE, Type.NORMAL, MoveCategory.PHYSICAL, 120, 100, 15, -1, 0, 5)
|
||||||
.attr(RecoilAttr)
|
.attr(RecoilAttr)
|
||||||
|
@ -8325,9 +8326,9 @@ export function initMoves() {
|
||||||
.attr(LastMoveDoublePowerAttr, Moves.FUSION_FLARE)
|
.attr(LastMoveDoublePowerAttr, Moves.FUSION_FLARE)
|
||||||
.makesContact(false),
|
.makesContact(false),
|
||||||
new AttackMove(Moves.FLYING_PRESS, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 95, 10, -1, 0, 6)
|
new AttackMove(Moves.FLYING_PRESS, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 95, 10, -1, 0, 6)
|
||||||
.attr(MinimizeAccuracyAttr)
|
.attr(AlwaysHitMinimizeAttr)
|
||||||
.attr(FlyingTypeMultiplierAttr)
|
.attr(FlyingTypeMultiplierAttr)
|
||||||
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
|
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true)
|
||||||
.condition(failOnGravityCondition),
|
.condition(failOnGravityCondition),
|
||||||
new StatusMove(Moves.MAT_BLOCK, Type.FIGHTING, -1, 10, -1, 0, 6)
|
new StatusMove(Moves.MAT_BLOCK, Type.FIGHTING, -1, 10, -1, 0, 6)
|
||||||
.target(MoveTarget.USER_SIDE)
|
.target(MoveTarget.USER_SIDE)
|
||||||
|
@ -8498,8 +8499,8 @@ export function initMoves() {
|
||||||
new AttackMove(Moves.THOUSAND_ARROWS, Type.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6)
|
new AttackMove(Moves.THOUSAND_ARROWS, Type.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6)
|
||||||
.attr(NeutralDamageAgainstFlyingTypeMultiplierAttr)
|
.attr(NeutralDamageAgainstFlyingTypeMultiplierAttr)
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true)
|
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true)
|
||||||
.attr(HitsTagAttr, BattlerTagType.FLYING, false)
|
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING, false)
|
||||||
.attr(HitsTagAttr, BattlerTagType.MAGNET_RISEN, false)
|
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.MAGNET_RISEN, false)
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED)
|
.attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED)
|
||||||
.attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN])
|
.attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN])
|
||||||
.makesContact(false)
|
.makesContact(false)
|
||||||
|
@ -8756,6 +8757,8 @@ export function initMoves() {
|
||||||
.partial()
|
.partial()
|
||||||
.ignoresVirtual(),
|
.ignoresVirtual(),
|
||||||
new AttackMove(Moves.MALICIOUS_MOONSAULT, Type.DARK, MoveCategory.PHYSICAL, 180, -1, 1, -1, 0, 7)
|
new AttackMove(Moves.MALICIOUS_MOONSAULT, Type.DARK, MoveCategory.PHYSICAL, 180, -1, 1, -1, 0, 7)
|
||||||
|
.attr(AlwaysHitMinimizeAttr)
|
||||||
|
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true)
|
||||||
.partial()
|
.partial()
|
||||||
.ignoresVirtual(),
|
.ignoresVirtual(),
|
||||||
new AttackMove(Moves.OCEANIC_OPERETTA, Type.WATER, MoveCategory.SPECIAL, 195, -1, 1, -1, 0, 7)
|
new AttackMove(Moves.OCEANIC_OPERETTA, Type.WATER, MoveCategory.SPECIAL, 195, -1, 1, -1, 0, 7)
|
||||||
|
|
|
@ -746,7 +746,7 @@ export class Arena {
|
||||||
case Biome.TOWN:
|
case Biome.TOWN:
|
||||||
return 7.288;
|
return 7.288;
|
||||||
case Biome.PLAINS:
|
case Biome.PLAINS:
|
||||||
return 7.693;
|
return 17.485;
|
||||||
case Biome.GRASS:
|
case Biome.GRASS:
|
||||||
return 1.995;
|
return 1.995;
|
||||||
case Biome.TALL_GRASS:
|
case Biome.TALL_GRASS:
|
||||||
|
@ -774,13 +774,13 @@ export class Arena {
|
||||||
case Biome.DESERT:
|
case Biome.DESERT:
|
||||||
return 1.143;
|
return 1.143;
|
||||||
case Biome.ICE_CAVE:
|
case Biome.ICE_CAVE:
|
||||||
return 15.010;
|
return 0.000;
|
||||||
case Biome.MEADOW:
|
case Biome.MEADOW:
|
||||||
return 3.891;
|
return 3.891;
|
||||||
case Biome.POWER_PLANT:
|
case Biome.POWER_PLANT:
|
||||||
return 2.810;
|
return 9.447;
|
||||||
case Biome.VOLCANO:
|
case Biome.VOLCANO:
|
||||||
return 5.116;
|
return 17.637;
|
||||||
case Biome.GRAVEYARD:
|
case Biome.GRAVEYARD:
|
||||||
return 3.232;
|
return 3.232;
|
||||||
case Biome.DOJO:
|
case Biome.DOJO:
|
||||||
|
|
|
@ -3,7 +3,7 @@ import BattleScene, { AnySound } from "../battle-scene";
|
||||||
import { Variant, VariantSet, variantColorCache } from "#app/data/variant";
|
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 Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget } from "../data/move";
|
import Move, { HighCritAttr, DealsDoubleDamageToTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget } 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 { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils";
|
import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils";
|
||||||
import * as Utils from "../utils";
|
import * as Utils from "../utils";
|
||||||
|
@ -1273,13 +1273,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
* @param attrType {@linkcode AbAttr} The ability attribute to check for.
|
* @param attrType {@linkcode AbAttr} The ability attribute to check for.
|
||||||
* @param canApply {@linkcode Boolean} If false, it doesn't check whether the ability is currently active
|
* @param canApply {@linkcode Boolean} If false, it doesn't check whether the ability is currently active
|
||||||
* @param ignoreOverride {@linkcode Boolean} If true, it ignores ability changing effects
|
* @param ignoreOverride {@linkcode Boolean} If true, it ignores ability changing effects
|
||||||
* @returns {AbAttr[]} A list of all the ability attributes on this ability.
|
* @returns A list of all the ability attributes on this ability.
|
||||||
*/
|
*/
|
||||||
getAbilityAttrs(attrType: { new(...args: any[]): AbAttr }, canApply: boolean = true, ignoreOverride?: boolean): AbAttr[] {
|
getAbilityAttrs<T extends AbAttr = AbAttr>(attrType: { new(...args: any[]): T }, canApply: boolean = true, ignoreOverride?: boolean): T[] {
|
||||||
const abilityAttrs: AbAttr[] = [];
|
const abilityAttrs: T[] = [];
|
||||||
|
|
||||||
if (!canApply || this.canApplyAbility()) {
|
if (!canApply || this.canApplyAbility()) {
|
||||||
abilityAttrs.push(...this.getAbility(ignoreOverride).getAttrs(attrType));
|
abilityAttrs.push(...this.getAbility(ignoreOverride).getAttrs<T>(attrType));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!canApply || this.canApplyAbility(true)) {
|
if (!canApply || this.canApplyAbility(true)) {
|
||||||
|
@ -1513,7 +1513,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
|
|
||||||
const immuneTags = this.findTags(tag => tag instanceof TypeImmuneTag && tag.immuneType === moveType);
|
const immuneTags = this.findTags(tag => tag instanceof TypeImmuneTag && tag.immuneType === moveType);
|
||||||
for (const tag of immuneTags) {
|
for (const tag of immuneTags) {
|
||||||
if (move && !move.getAttrs(HitsTagAttr).some(attr => attr.tagType === tag.tagType)) {
|
if (move && !move.getAttrs(DealsDoubleDamageToTagAttr).some(attr => attr.tagType === tag.tagType)) {
|
||||||
typeMultiplier.value = 0;
|
typeMultiplier.value = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -2489,13 +2489,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, move.category, this.scene.currentBattle.double, screenMultiplier);
|
this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, move.category, this.scene.currentBattle.double, screenMultiplier);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For each {@linkcode HitsTagAttr} the move has, doubles the damage of the move if:
|
* For each {@linkcode DealsDoubleDamageToTagAttr} the move has, doubles the damage of the move if:
|
||||||
* The target has a {@linkcode BattlerTagType} that this move interacts with
|
* The target has a {@linkcode 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
|
||||||
*/
|
*/
|
||||||
const hitsTagMultiplier = new Utils.NumberHolder(1);
|
const hitsTagMultiplier = new Utils.NumberHolder(1);
|
||||||
move.getAttrs(HitsTagAttr).filter(hta => hta.doubleDamage).forEach(hta => {
|
move.getAttrs(DealsDoubleDamageToTagAttr).filter(hta => hta.doubleDamage).forEach(hta => {
|
||||||
if (this.getTag(hta.tagType)) {
|
if (this.getTag(hta.tagType)) {
|
||||||
hitsTagMultiplier.value *= 2;
|
hitsTagMultiplier.value *= 2;
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,7 +108,7 @@
|
||||||
"forest": "PMD EoS Dusk Forest",
|
"forest": "PMD EoS Dusk Forest",
|
||||||
"grass": "PMD EoS Apple Woods",
|
"grass": "PMD EoS Apple Woods",
|
||||||
"graveyard": "PMD EoS Mystifying Forest",
|
"graveyard": "PMD EoS Mystifying Forest",
|
||||||
"ice_cave": "PMD EoS Vast Ice Mountain",
|
"ice_cave": "Firel - -60F",
|
||||||
"island": "PMD EoS Craggy Coast",
|
"island": "PMD EoS Craggy Coast",
|
||||||
"jungle": "Lmz - Jungle",
|
"jungle": "Lmz - Jungle",
|
||||||
"laboratory": "Firel - Laboratory",
|
"laboratory": "Firel - Laboratory",
|
||||||
|
@ -116,8 +116,8 @@
|
||||||
"meadow": "PMD EoS Sky Peak Forest",
|
"meadow": "PMD EoS Sky Peak Forest",
|
||||||
"metropolis": "Firel - Metropolis",
|
"metropolis": "Firel - Metropolis",
|
||||||
"mountain": "PMD EoS Mt. Horn",
|
"mountain": "PMD EoS Mt. Horn",
|
||||||
"plains": "PMD EoS Sky Peak Prairie",
|
"plains": "Firel - Route 888",
|
||||||
"power_plant": "PMD EoS Far Amp Plains",
|
"power_plant": "Firel - The Klink",
|
||||||
"ruins": "PMD EoS Deep Sealed Ruin",
|
"ruins": "PMD EoS Deep Sealed Ruin",
|
||||||
"sea": "Andr06 - Marine Mystique",
|
"sea": "Andr06 - Marine Mystique",
|
||||||
"seabed": "Firel - Seabed",
|
"seabed": "Firel - Seabed",
|
||||||
|
@ -128,7 +128,7 @@
|
||||||
"tall_grass": "PMD EoS Foggy Forest",
|
"tall_grass": "PMD EoS Foggy Forest",
|
||||||
"temple": "PMD EoS Aegis Cave",
|
"temple": "PMD EoS Aegis Cave",
|
||||||
"town": "PMD EoS Random Dungeon Theme 3",
|
"town": "PMD EoS Random Dungeon Theme 3",
|
||||||
"volcano": "PMD EoS Steam Cave",
|
"volcano": "Firel - Twisturn Volcano",
|
||||||
"wasteland": "PMD EoS Hidden Highland",
|
"wasteland": "PMD EoS Hidden Highland",
|
||||||
"encounter_ace_trainer": "BW Trainers' Eyes Meet (Ace Trainer)",
|
"encounter_ace_trainer": "BW Trainers' Eyes Meet (Ace Trainer)",
|
||||||
"encounter_backpacker": "BW Trainers' Eyes Meet (Backpacker)",
|
"encounter_backpacker": "BW Trainers' Eyes Meet (Backpacker)",
|
||||||
|
|
|
@ -13,8 +13,10 @@
|
||||||
"ALL": "전부",
|
"ALL": "전부",
|
||||||
"PASS_BATON": "배턴터치한다",
|
"PASS_BATON": "배턴터치한다",
|
||||||
"UNPAUSE_EVOLUTION": "진화 재개",
|
"UNPAUSE_EVOLUTION": "진화 재개",
|
||||||
|
"PAUSE_EVOLUTION": "진화 중지",
|
||||||
"REVIVE": "되살린다",
|
"REVIVE": "되살린다",
|
||||||
"RENAME": "닉네임 바꾸기",
|
"RENAME": "닉네임 바꾸기",
|
||||||
|
"SELECT": "선택한다",
|
||||||
"choosePokemon": "포켓몬을 선택하세요.",
|
"choosePokemon": "포켓몬을 선택하세요.",
|
||||||
"doWhatWithThisPokemon": "포켓몬을 어떻게 하겠습니까?",
|
"doWhatWithThisPokemon": "포켓몬을 어떻게 하겠습니까?",
|
||||||
"noEnergy": "{{pokemonName}}[[는]] 싸울 수 있는\n기력이 남아 있지 않습니다!",
|
"noEnergy": "{{pokemonName}}[[는]] 싸울 수 있는\n기력이 남아 있지 않습니다!",
|
||||||
|
@ -23,6 +25,7 @@
|
||||||
"tooManyItems": "{{pokemonName}}[[는]] 지닌 도구의 수가\n너무 많습니다",
|
"tooManyItems": "{{pokemonName}}[[는]] 지닌 도구의 수가\n너무 많습니다",
|
||||||
"anyEffect": "써도 효과가 없다.",
|
"anyEffect": "써도 효과가 없다.",
|
||||||
"unpausedEvolutions": "{{pokemonName}}의 진화가 재개되었다.",
|
"unpausedEvolutions": "{{pokemonName}}의 진화가 재개되었다.",
|
||||||
|
"pausedEvolutions": "{{pokemonName}}[[가]] 진화하지 않도록 했다.",
|
||||||
"unspliceConfirmation": "{{pokemonName}}로부터 {{fusionName}}의 융합을 해제하시겠습니까?\n{{fusionName}}는 사라지게 됩니다.",
|
"unspliceConfirmation": "{{pokemonName}}로부터 {{fusionName}}의 융합을 해제하시겠습니까?\n{{fusionName}}는 사라지게 됩니다.",
|
||||||
"wasReverted": "{{fusionName}}은 {{pokemonName}}의 모습으로 돌아갔습니다!",
|
"wasReverted": "{{fusionName}}은 {{pokemonName}}의 모습으로 돌아갔습니다!",
|
||||||
"releaseConfirmation": "{{pokemonName}}[[를]]\n정말 놓아주겠습니까?",
|
"releaseConfirmation": "{{pokemonName}}[[를]]\n정말 놓아주겠습니까?",
|
||||||
|
|
|
@ -11,6 +11,10 @@
|
||||||
"expGainsSpeed": "경험치 획득 속도",
|
"expGainsSpeed": "경험치 획득 속도",
|
||||||
"expPartyDisplay": "파티 경험치 표시",
|
"expPartyDisplay": "파티 경험치 표시",
|
||||||
"skipSeenDialogues": "본 대화 생략",
|
"skipSeenDialogues": "본 대화 생략",
|
||||||
|
"eggSkip": "알 스킵",
|
||||||
|
"never": "안 함",
|
||||||
|
"always": "항상",
|
||||||
|
"ask": "확인하기",
|
||||||
"battleStyle": "시합 룰",
|
"battleStyle": "시합 룰",
|
||||||
"enableRetries": "재도전 허용",
|
"enableRetries": "재도전 허용",
|
||||||
"hideIvs": "개체값탐지기 효과 끄기",
|
"hideIvs": "개체값탐지기 효과 끄기",
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { PostTurnStatusEffectPhase } from "#app/phases/post-turn-status-effect-phase";
|
||||||
|
import { Phase } from "#app/phase";
|
||||||
|
import { BattlerIndex } from "#app/battle";
|
||||||
|
import BattleScene from "#app/battle-scene";
|
||||||
|
|
||||||
|
export class CheckStatusEffectPhase extends Phase {
|
||||||
|
private order : BattlerIndex[];
|
||||||
|
constructor(scene : BattleScene, order : BattlerIndex[]) {
|
||||||
|
super(scene);
|
||||||
|
this.scene = scene;
|
||||||
|
this.order = order;
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
const field = this.scene.getField();
|
||||||
|
for (const o of this.order) {
|
||||||
|
if (field[o].status && field[o].status.isPostTurn()) {
|
||||||
|
this.scene.unshiftPhase(new PostTurnStatusEffectPhase(this.scene, o));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.end();
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ import { applyPreAttackAbAttrs, AddSecondStrikeAbAttr, IgnoreMoveEffectsAbAttr,
|
||||||
import { ArenaTagSide, ConditionalProtectTag } from "#app/data/arena-tag";
|
import { ArenaTagSide, ConditionalProtectTag } from "#app/data/arena-tag";
|
||||||
import { MoveAnim } from "#app/data/battle-anims";
|
import { MoveAnim } from "#app/data/battle-anims";
|
||||||
import { BattlerTagLapseType, DamageProtectedTag, ProtectedTag, SemiInvulnerableTag, SubstituteTag } from "#app/data/battler-tags";
|
import { BattlerTagLapseType, DamageProtectedTag, ProtectedTag, SemiInvulnerableTag, SubstituteTag } from "#app/data/battler-tags";
|
||||||
import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, MoveEffectTrigger, ChargeAttr, MoveCategory, NoEffectAttr, HitsTagAttr } from "#app/data/move";
|
import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, MoveEffectTrigger, ChargeAttr, MoveCategory, NoEffectAttr, DealsDoubleDamageToTagAttr } from "#app/data/move";
|
||||||
import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms";
|
import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms";
|
||||||
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
||||||
import { Moves } from "#app/enums/moves";
|
import { Moves } from "#app/enums/moves";
|
||||||
|
@ -394,7 +394,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||||
}
|
}
|
||||||
|
|
||||||
const semiInvulnerableTag = target.getTag(SemiInvulnerableTag);
|
const semiInvulnerableTag = target.getTag(SemiInvulnerableTag);
|
||||||
if (semiInvulnerableTag && !this.move.getMove().getAttrs(HitsTagAttr).some(hta => hta.tagType === semiInvulnerableTag.tagType)) {
|
if (semiInvulnerableTag && !this.move.getMove().getAttrs(DealsDoubleDamageToTagAttr).some(hta => hta.tagType === semiInvulnerableTag.tagType)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import { StatusEffect } from "#app/enums/status-effect";
|
||||||
import Pokemon from "#app/field/pokemon";
|
import Pokemon from "#app/field/pokemon";
|
||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import { PokemonPhase } from "./pokemon-phase";
|
import { PokemonPhase } from "./pokemon-phase";
|
||||||
import { PostTurnStatusEffectPhase } from "./post-turn-status-effect-phase";
|
|
||||||
|
|
||||||
export class ObtainStatusEffectPhase extends PokemonPhase {
|
export class ObtainStatusEffectPhase extends PokemonPhase {
|
||||||
private statusEffect: StatusEffect | undefined;
|
private statusEffect: StatusEffect | undefined;
|
||||||
|
@ -33,9 +32,6 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
|
||||||
pokemon.updateInfo(true);
|
pokemon.updateInfo(true);
|
||||||
new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(this.scene, false, () => {
|
new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(this.scene, false, () => {
|
||||||
this.scene.queueMessage(getStatusEffectObtainText(this.statusEffect, getPokemonNameWithAffix(pokemon), this.sourceText ?? undefined));
|
this.scene.queueMessage(getStatusEffectObtainText(this.statusEffect, getPokemonNameWithAffix(pokemon), this.sourceText ?? undefined));
|
||||||
if (pokemon.status?.isPostTurn()) {
|
|
||||||
this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, this.battlerIndex));
|
|
||||||
}
|
|
||||||
this.end();
|
this.end();
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -6,7 +6,7 @@ import Pokemon from "#app/field/pokemon";
|
||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import { ResetNegativeStatStageModifier } from "#app/modifier/modifier";
|
import { ResetNegativeStatStageModifier } from "#app/modifier/modifier";
|
||||||
import { handleTutorial, Tutorial } from "#app/tutorial";
|
import { handleTutorial, Tutorial } from "#app/tutorial";
|
||||||
import * as Utils from "#app/utils";
|
import { NumberHolder, BooleanHolder } from "#app/utils";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { PokemonPhase } from "./pokemon-phase";
|
import { PokemonPhase } from "./pokemon-phase";
|
||||||
import { Stat, type BattleStat, getStatKey, getStatStageChangeDescriptionKey } from "#enums/stat";
|
import { Stat, type BattleStat, getStatKey, getStatStageChangeDescriptionKey } from "#enums/stat";
|
||||||
|
@ -42,17 +42,23 @@ export class StatStageChangePhase extends PokemonPhase {
|
||||||
return this.end();
|
return this.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stages = new NumberHolder(this.stages);
|
||||||
|
|
||||||
|
if (!this.ignoreAbilities) {
|
||||||
|
applyAbAttrs(StatStageChangeMultiplierAbAttr, pokemon, null, false, stages);
|
||||||
|
}
|
||||||
|
|
||||||
let simulate = false;
|
let simulate = false;
|
||||||
|
|
||||||
const filteredStats = this.stats.filter(stat => {
|
const filteredStats = this.stats.filter(stat => {
|
||||||
const cancelled = new Utils.BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
|
|
||||||
if (!this.selfTarget && this.stages < 0) {
|
if (!this.selfTarget && stages.value < 0) {
|
||||||
// TODO: Include simulate boolean when tag applications can be simulated
|
// TODO: Include simulate boolean when tag applications can be simulated
|
||||||
this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, cancelled);
|
this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, cancelled);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cancelled.value && !this.selfTarget && this.stages < 0) {
|
if (!cancelled.value && !this.selfTarget && stages.value < 0) {
|
||||||
applyPreStatStageChangeAbAttrs(ProtectStatAbAttr, pokemon, stat, cancelled, simulate);
|
applyPreStatStageChangeAbAttrs(ProtectStatAbAttr, pokemon, stat, cancelled, simulate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,12 +70,6 @@ export class StatStageChangePhase extends PokemonPhase {
|
||||||
return !cancelled.value;
|
return !cancelled.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
const stages = new Utils.IntegerHolder(this.stages);
|
|
||||||
|
|
||||||
if (!this.ignoreAbilities) {
|
|
||||||
applyAbAttrs(StatStageChangeMultiplierAbAttr, pokemon, null, false, stages);
|
|
||||||
}
|
|
||||||
|
|
||||||
const relLevels = filteredStats.map(s => (stages.value >= 1 ? Math.min(pokemon.getStatStage(s) + stages.value, 6) : Math.max(pokemon.getStatStage(s) + stages.value, -6)) - pokemon.getStatStage(s));
|
const relLevels = filteredStats.map(s => (stages.value >= 1 ? Math.min(pokemon.getStatStage(s) + stages.value, 6) : Math.max(pokemon.getStatStage(s) + stages.value, -6)) - pokemon.getStatStage(s));
|
||||||
|
|
||||||
this.onChange && this.onChange(this.getPokemon(), filteredStats, relLevels);
|
this.onChange && this.onChange(this.getPokemon(), filteredStats, relLevels);
|
||||||
|
|
|
@ -13,10 +13,10 @@ import { BerryPhase } from "./berry-phase";
|
||||||
import { FieldPhase } from "./field-phase";
|
import { FieldPhase } from "./field-phase";
|
||||||
import { MoveHeaderPhase } from "./move-header-phase";
|
import { MoveHeaderPhase } from "./move-header-phase";
|
||||||
import { MovePhase } from "./move-phase";
|
import { MovePhase } from "./move-phase";
|
||||||
import { PostTurnStatusEffectPhase } from "./post-turn-status-effect-phase";
|
|
||||||
import { SwitchSummonPhase } from "./switch-summon-phase";
|
import { SwitchSummonPhase } from "./switch-summon-phase";
|
||||||
import { TurnEndPhase } from "./turn-end-phase";
|
import { TurnEndPhase } from "./turn-end-phase";
|
||||||
import { WeatherEffectPhase } from "./weather-effect-phase";
|
import { WeatherEffectPhase } from "./weather-effect-phase";
|
||||||
|
import { CheckStatusEffectPhase } from "#app/phases/check-status-effect-phase";
|
||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { TrickRoomTag } from "#app/data/arena-tag";
|
import { TrickRoomTag } from "#app/data/arena-tag";
|
||||||
|
|
||||||
|
@ -207,11 +207,8 @@ export class TurnStartPhase extends FieldPhase {
|
||||||
|
|
||||||
this.scene.pushPhase(new WeatherEffectPhase(this.scene));
|
this.scene.pushPhase(new WeatherEffectPhase(this.scene));
|
||||||
|
|
||||||
for (const o of moveOrder) {
|
/** Add a new phase to check who should be taking status damage */
|
||||||
if (field[o].status && field[o].status.isPostTurn()) {
|
this.scene.pushPhase(new CheckStatusEffectPhase(this.scene, moveOrder));
|
||||||
this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, o));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scene.pushPhase(new BerryPhase(this.scene));
|
this.scene.pushPhase(new BerryPhase(this.scene));
|
||||||
this.scene.pushPhase(new TurnEndPhase(this.scene));
|
this.scene.pushPhase(new TurnEndPhase(this.scene));
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Arena } from "../field/arena";
|
import { Arena } from "../field/arena";
|
||||||
import { ArenaTag } from "../data/arena-tag";
|
import { ArenaTag, loadArenaTag } from "../data/arena-tag";
|
||||||
import { Biome } from "#enums/biome";
|
import { Biome } from "#enums/biome";
|
||||||
import { Weather } from "../data/weather";
|
import { Weather } from "../data/weather";
|
||||||
import { Terrain } from "#app/data/terrain";
|
import { Terrain } from "#app/data/terrain";
|
||||||
|
@ -15,6 +15,10 @@ export default class ArenaData {
|
||||||
this.biome = sourceArena ? sourceArena.biomeType : source.biome;
|
this.biome = sourceArena ? sourceArena.biomeType : source.biome;
|
||||||
this.weather = sourceArena ? sourceArena.weather : source.weather ? new Weather(source.weather.weatherType, source.weather.turnsLeft) : null;
|
this.weather = sourceArena ? sourceArena.weather : source.weather ? new Weather(source.weather.weatherType, source.weather.turnsLeft) : null;
|
||||||
this.terrain = sourceArena ? sourceArena.terrain : source.terrain ? new Terrain(source.terrain.terrainType, source.terrain.turnsLeft) : null;
|
this.terrain = sourceArena ? sourceArena.terrain : source.terrain ? new Terrain(source.terrain.terrainType, source.terrain.turnsLeft) : null;
|
||||||
this.tags = sourceArena ? sourceArena.tags : [];
|
this.tags = [];
|
||||||
|
|
||||||
|
if (source.tags) {
|
||||||
|
this.tags = source.tags.map(t => loadArenaTag(t));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,8 @@ import { speciesEggMoves } from "../data/egg-moves";
|
||||||
import { allMoves } from "../data/move";
|
import { allMoves } from "../data/move";
|
||||||
import { TrainerVariant } from "../field/trainer";
|
import { TrainerVariant } from "../field/trainer";
|
||||||
import { Variant } from "#app/data/variant";
|
import { Variant } from "#app/data/variant";
|
||||||
import {setSettingGamepad, SettingGamepad, settingGamepadDefaults} from "./settings/settings-gamepad";
|
import { setSettingGamepad, SettingGamepad, settingGamepadDefaults } from "./settings/settings-gamepad";
|
||||||
import {setSettingKeyboard, SettingKeyboard} from "#app/system/settings/settings-keyboard";
|
import { setSettingKeyboard, SettingKeyboard } from "#app/system/settings/settings-keyboard";
|
||||||
import { TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
|
import { TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
|
||||||
import * as Modifier from "../modifier/modifier";
|
import * as Modifier from "../modifier/modifier";
|
||||||
import { StatusEffect } from "#app/data/status-effect";
|
import { StatusEffect } from "#app/data/status-effect";
|
||||||
|
@ -45,6 +45,8 @@ import { TerrainType } from "#app/data/terrain";
|
||||||
import { OutdatedPhase } from "#app/phases/outdated-phase";
|
import { OutdatedPhase } from "#app/phases/outdated-phase";
|
||||||
import { ReloadSessionPhase } from "#app/phases/reload-session-phase";
|
import { ReloadSessionPhase } from "#app/phases/reload-session-phase";
|
||||||
import { RUN_HISTORY_LIMIT } from "#app/ui/run-history-ui-handler";
|
import { RUN_HISTORY_LIMIT } from "#app/ui/run-history-ui-handler";
|
||||||
|
import { TagAddedEvent } from "#app/events/arena";
|
||||||
|
import { ArenaTrapTag } from "#app/data/arena-tag";
|
||||||
import { applySessionDataPatches, applySettingsDataPatches, applySystemDataPatches } from "./version-converter";
|
import { applySessionDataPatches, applySettingsDataPatches, applySystemDataPatches } from "./version-converter";
|
||||||
import { MysteryEncounterSaveData } from "../data/mystery-encounters/mystery-encounter-save-data";
|
import { MysteryEncounterSaveData } from "../data/mystery-encounters/mystery-encounter-save-data";
|
||||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
|
@ -1071,8 +1073,18 @@ export class GameData {
|
||||||
|
|
||||||
scene.arena.terrain = sessionData.arena.terrain;
|
scene.arena.terrain = sessionData.arena.terrain;
|
||||||
scene.arena.eventTarget.dispatchEvent(new TerrainChangedEvent(TerrainType.NONE, scene.arena.terrain?.terrainType!, scene.arena.terrain?.turnsLeft!)); // TODO: is this bang correct?
|
scene.arena.eventTarget.dispatchEvent(new TerrainChangedEvent(TerrainType.NONE, scene.arena.terrain?.terrainType!, scene.arena.terrain?.turnsLeft!)); // TODO: is this bang correct?
|
||||||
// TODO
|
|
||||||
//scene.arena.tags = sessionData.arena.tags;
|
scene.arena.tags = sessionData.arena.tags;
|
||||||
|
if (scene.arena.tags) {
|
||||||
|
for (const tag of scene.arena.tags) {
|
||||||
|
if (tag instanceof ArenaTrapTag) {
|
||||||
|
const { tagType, side, turnCount, layers, maxLayers } = tag as ArenaTrapTag;
|
||||||
|
scene.arena.eventTarget.dispatchEvent(new TagAddedEvent(tagType, side, turnCount, layers, maxLayers));
|
||||||
|
} else {
|
||||||
|
scene.arena.eventTarget.dispatchEvent(new TagAddedEvent(tag.tagType, tag.side, tag.turnCount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const modifierData of sessionData.modifiers) {
|
for (const modifierData of sessionData.modifiers) {
|
||||||
const modifier = modifierData.toModifier(scene, Modifier[modifierData.className]);
|
const modifier = modifierData.toModifier(scene, Modifier[modifierData.className]);
|
||||||
|
|
|
@ -31,7 +31,7 @@ describe("Abilities - Contrary", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should invert stat changes when applied", async() => {
|
it("should invert stat changes when applied", async() => {
|
||||||
await game.startBattle([
|
await game.classicMode.startBattle([
|
||||||
Species.SLOWBRO
|
Species.SLOWBRO
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -39,4 +39,39 @@ describe("Abilities - Contrary", () => {
|
||||||
|
|
||||||
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
|
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
|
describe("With Clear Body", () => {
|
||||||
|
it("should apply positive effects", async () => {
|
||||||
|
game.override
|
||||||
|
.enemyPassiveAbility(Abilities.CLEAR_BODY)
|
||||||
|
.moveset([Moves.TAIL_WHIP]);
|
||||||
|
await game.classicMode.startBattle([Species.SLOWBRO]);
|
||||||
|
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
|
||||||
|
|
||||||
|
game.move.select(Moves.TAIL_WHIP);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should block negative effects", async () => {
|
||||||
|
game.override
|
||||||
|
.enemyPassiveAbility(Abilities.CLEAR_BODY)
|
||||||
|
.enemyMoveset([Moves.HOWL, Moves.HOWL, Moves.HOWL, Moves.HOWL])
|
||||||
|
.moveset([Moves.SPLASH]);
|
||||||
|
await game.classicMode.startBattle([Species.SLOWBRO]);
|
||||||
|
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
import { StatusEffect } from "#app/data/status-effect";
|
import { StatusEffect } from "#app/data/status-effect";
|
||||||
import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
|
|
||||||
import { MessagePhase } from "#app/phases/message-phase";
|
|
||||||
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
|
||||||
import i18next, { initI18n } from "#app/plugins/i18n";
|
import i18next, { initI18n } from "#app/plugins/i18n";
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
|
@ -10,6 +7,7 @@ import GameManager from "#test/utils/gameManager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
const TIMEOUT = 20 * 1000;
|
||||||
|
|
||||||
describe("Items - Toxic orb", () => {
|
describe("Items - Toxic orb", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
|
@ -27,42 +25,34 @@ describe("Items - Toxic orb", () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
const moveToUse = Moves.GROWTH;
|
game.override
|
||||||
const oppMoveToUse = Moves.TACKLE;
|
.battleType("single")
|
||||||
game.override.battleType("single");
|
.enemySpecies(Species.RATTATA)
|
||||||
game.override.enemySpecies(Species.RATTATA);
|
.ability(Abilities.BALL_FETCH)
|
||||||
game.override.ability(Abilities.INSOMNIA);
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
game.override.enemyAbility(Abilities.INSOMNIA);
|
.moveset([Moves.SPLASH])
|
||||||
game.override.startingLevel(2000);
|
.enemyMoveset(Moves.SPLASH)
|
||||||
game.override.moveset([moveToUse]);
|
.startingHeldItems([{
|
||||||
game.override.enemyMoveset([oppMoveToUse, oppMoveToUse, oppMoveToUse, oppMoveToUse]);
|
name: "TOXIC_ORB",
|
||||||
game.override.startingHeldItems([{
|
}]);
|
||||||
name: "TOXIC_ORB",
|
|
||||||
}]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("TOXIC ORB", async () => {
|
it("badly poisons the holder", async () => {
|
||||||
initI18n();
|
initI18n();
|
||||||
i18next.changeLanguage("en");
|
i18next.changeLanguage("en");
|
||||||
const moveToUse = Moves.GROWTH;
|
await game.classicMode.startBattle([Species.MIGHTYENA]);
|
||||||
await game.startBattle([
|
|
||||||
Species.MIGHTYENA,
|
|
||||||
Species.MIGHTYENA,
|
|
||||||
]);
|
|
||||||
expect(game.scene.modifiers[0].type.id).toBe("TOXIC_ORB");
|
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
const player = game.scene.getPlayerField()[0];
|
||||||
|
|
||||||
// will run the 13 phase from enemyCommandPhase to TurnEndPhase
|
game.move.select(Moves.SPLASH);
|
||||||
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase);
|
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
// Toxic orb should trigger here
|
// Toxic orb should trigger here
|
||||||
await game.phaseInterceptor.run(MessagePhase);
|
await game.phaseInterceptor.run("MessagePhase");
|
||||||
const message = game.textInterceptor.getLatestMessage();
|
const message = game.textInterceptor.getLatestMessage();
|
||||||
expect(message).toContain("was badly poisoned by the Toxic Orb");
|
expect(message).toContain("was badly poisoned by the Toxic Orb");
|
||||||
await game.phaseInterceptor.run(MessagePhase);
|
expect(player.status?.effect).toBe(StatusEffect.TOXIC);
|
||||||
const message2 = game.textInterceptor.getLatestMessage();
|
// Damage should not have ticked yet.
|
||||||
expect(message2).toContain("is hurt");
|
expect(player.status?.turnCount).toBe(0);
|
||||||
expect(message2).toContain("by poison");
|
}, TIMEOUT);
|
||||||
expect(game.scene.getParty()[0].status!.effect).toBe(StatusEffect.TOXIC);
|
|
||||||
}, 20000);
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { BattlerIndex } from "#app/battle";
|
||||||
|
import { allMoves } from "#app/data/move";
|
||||||
|
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
||||||
|
import { DamageCalculationResult } from "#app/field/pokemon";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
describe("Moves - Steamroller", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
game.override.moveset([Moves.STEAMROLLER]).battleType("single").enemyAbility(Abilities.BALL_FETCH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should always hit a minimzed target with double damage", async () => {
|
||||||
|
game.override.enemySpecies(Species.DITTO).enemyMoveset(Moves.MINIMIZE);
|
||||||
|
await game.classicMode.startBattle([Species.IRON_BOULDER]);
|
||||||
|
|
||||||
|
const ditto = game.scene.getEnemyPokemon()!;
|
||||||
|
vi.spyOn(ditto, "getAttackDamage");
|
||||||
|
ditto.hp = 5000;
|
||||||
|
const steamroller = allMoves[Moves.STEAMROLLER];
|
||||||
|
vi.spyOn(steamroller, "calculateBattleAccuracy");
|
||||||
|
const ironBoulder = game.scene.getPlayerPokemon()!;
|
||||||
|
vi.spyOn(ironBoulder, "getAccuracyMultiplier");
|
||||||
|
// Turn 1
|
||||||
|
game.move.select(Moves.STEAMROLLER);
|
||||||
|
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
||||||
|
await game.toNextTurn();
|
||||||
|
// Turn 2
|
||||||
|
game.move.select(Moves.STEAMROLLER);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
const [dmgCalcTurn1, dmgCalcTurn2]: DamageCalculationResult[] = vi
|
||||||
|
.mocked(ditto.getAttackDamage)
|
||||||
|
.mock.results.map((r) => r.value);
|
||||||
|
|
||||||
|
expect(dmgCalcTurn2.damage).toBeGreaterThanOrEqual(dmgCalcTurn1.damage * 2);
|
||||||
|
expect(ditto.getTag(BattlerTagType.MINIMIZED)).toBeDefined();
|
||||||
|
expect(steamroller.calculateBattleAccuracy).toHaveReturnedWith(-1);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,136 @@
|
||||||
|
import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag";
|
||||||
|
import { StatusEffect } from "#app/data/status-effect";
|
||||||
|
import { decrypt, encrypt, GameData, SessionSaveData } from "#app/system/game-data";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
const TIMEOUT = 20 * 1000;
|
||||||
|
|
||||||
|
describe("Moves - Toxic Spikes", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
game.override
|
||||||
|
.battleType("single")
|
||||||
|
.startingWave(5)
|
||||||
|
.enemySpecies(Species.RATTATA)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH)
|
||||||
|
.moveset([Moves.TOXIC_SPIKES, Moves.SPLASH, Moves.ROAR]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not affect the opponent if they do not switch", async() => {
|
||||||
|
await game.classicMode.runToSummon([Species.MIGHTYENA, Species.POOCHYENA]);
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyField()[0];
|
||||||
|
|
||||||
|
game.move.select(Moves.TOXIC_SPIKES);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
game.doSwitchPokemon(1);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
expect(enemy.hp).toBe(enemy.getMaxHp());
|
||||||
|
expect(enemy.status?.effect).toBeUndefined();
|
||||||
|
}, TIMEOUT);
|
||||||
|
|
||||||
|
it("should poison the opponent if they switch into 1 layer", async() => {
|
||||||
|
await game.classicMode.runToSummon([Species.MIGHTYENA]);
|
||||||
|
|
||||||
|
game.move.select(Moves.TOXIC_SPIKES);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
game.move.select(Moves.ROAR);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyField()[0];
|
||||||
|
|
||||||
|
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
||||||
|
expect(enemy.status?.effect).toBe(StatusEffect.POISON);
|
||||||
|
}, TIMEOUT);
|
||||||
|
|
||||||
|
it("should badly poison the opponent if they switch into 2 layers", async() => {
|
||||||
|
await game.classicMode.runToSummon([Species.MIGHTYENA]);
|
||||||
|
|
||||||
|
game.move.select(Moves.TOXIC_SPIKES);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
game.move.select(Moves.TOXIC_SPIKES);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
game.move.select(Moves.ROAR);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyField()[0];
|
||||||
|
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
||||||
|
expect(enemy.status?.effect).toBe(StatusEffect.TOXIC);
|
||||||
|
}, TIMEOUT);
|
||||||
|
|
||||||
|
it("should be removed if a grounded poison pokemon switches in", async() => {
|
||||||
|
game.override.enemySpecies(Species.GRIMER);
|
||||||
|
await game.classicMode.runToSummon([Species.MIGHTYENA]);
|
||||||
|
|
||||||
|
game.move.select(Moves.TOXIC_SPIKES);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
game.move.select(Moves.TOXIC_SPIKES);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
game.move.select(Moves.ROAR);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyField()[0];
|
||||||
|
expect(enemy.hp).toBe(enemy.getMaxHp());
|
||||||
|
expect(enemy.status?.effect).toBeUndefined();
|
||||||
|
|
||||||
|
expect(game.scene.arena.tags.length).toBe(0);
|
||||||
|
}, TIMEOUT);
|
||||||
|
|
||||||
|
it("shouldn't create multiple layers per use in doubles", async() => {
|
||||||
|
await game.classicMode.runToSummon([Species.MIGHTYENA, Species.POOCHYENA]);
|
||||||
|
|
||||||
|
game.move.select(Moves.TOXIC_SPIKES);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
const arenaTags = (game.scene.arena.getTagOnSide(ArenaTagType.TOXIC_SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag);
|
||||||
|
expect(arenaTags.tagType).toBe(ArenaTagType.TOXIC_SPIKES);
|
||||||
|
expect(arenaTags.layers).toBe(1);
|
||||||
|
}, TIMEOUT);
|
||||||
|
|
||||||
|
it("should persist through reload", async() => {
|
||||||
|
game.override.startingWave(1);
|
||||||
|
const scene = game.scene;
|
||||||
|
const gameData = new GameData(scene);
|
||||||
|
|
||||||
|
await game.classicMode.runToSummon([Species.MIGHTYENA]);
|
||||||
|
|
||||||
|
game.move.select(Moves.TOXIC_SPIKES);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.doKillOpponents();
|
||||||
|
await game.phaseInterceptor.to("BattleEndPhase");
|
||||||
|
await game.toNextWave();
|
||||||
|
|
||||||
|
const sessionData : SessionSaveData = gameData["getSessionSaveData"](game.scene);
|
||||||
|
localStorage.setItem("sessionTestData", encrypt(JSON.stringify(sessionData), true));
|
||||||
|
const recoveredData : SessionSaveData = gameData.parseSessionData(decrypt(localStorage.getItem("sessionTestData")!, true));
|
||||||
|
gameData.loadSession(game.scene, 0, recoveredData);
|
||||||
|
|
||||||
|
expect(sessionData.arena.tags).toEqual(recoveredData.arena.tags);
|
||||||
|
localStorage.removeItem("sessionTestData");
|
||||||
|
}, TIMEOUT);
|
||||||
|
});
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { BattlerIndex } from "#app/battle";
|
||||||
|
import { allMoves } from "#app/data/move";
|
||||||
|
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest";
|
||||||
|
|
||||||
|
describe("Moves - Whirlwind", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
game.override
|
||||||
|
.battleType("single")
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.WHIRLWIND)
|
||||||
|
.enemySpecies(Species.PIDGEY);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
{ move: Moves.FLY, name: "Fly" },
|
||||||
|
{ move: Moves.BOUNCE, name: "Bounce" },
|
||||||
|
{ move: Moves.SKY_DROP, name: "Sky Drop" },
|
||||||
|
])("should not hit a flying target: $name (=$move)", async ({ move }) => {
|
||||||
|
game.override.moveset([move]);
|
||||||
|
await game.classicMode.startBattle([Species.STARAPTOR]);
|
||||||
|
|
||||||
|
const staraptor = game.scene.getPlayerPokemon()!;
|
||||||
|
const whirlwind = allMoves[Moves.WHIRLWIND];
|
||||||
|
vi.spyOn(whirlwind, "getFailedText");
|
||||||
|
|
||||||
|
game.move.select(move);
|
||||||
|
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(staraptor.findTag((t) => t.tagType === BattlerTagType.FLYING)).toBeDefined();
|
||||||
|
expect(whirlwind.getFailedText).toHaveBeenCalledOnce();
|
||||||
|
});
|
||||||
|
});
|
|
@ -162,7 +162,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
|
||||||
this.splicedIcon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 12, 15), Phaser.Geom.Rectangle.Contains);
|
this.splicedIcon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 12, 15), Phaser.Geom.Rectangle.Contains);
|
||||||
this.add(this.splicedIcon);
|
this.add(this.splicedIcon);
|
||||||
|
|
||||||
this.statusIndicator = this.scene.add.sprite(0, 0, `statuses_${i18next.resolvedLanguage}`);
|
this.statusIndicator = this.scene.add.sprite(0, 0, Utils.getLocalizedSpriteKey("statuses"));
|
||||||
this.statusIndicator.setName("icon_status");
|
this.statusIndicator.setName("icon_status");
|
||||||
this.statusIndicator.setVisible(false);
|
this.statusIndicator.setVisible(false);
|
||||||
this.statusIndicator.setOrigin(0, 0);
|
this.statusIndicator.setOrigin(0, 0);
|
||||||
|
|
|
@ -91,7 +91,7 @@ export default class MoveInfoOverlay extends Phaser.GameObjects.Container implem
|
||||||
valuesBg.setOrigin(0, 0);
|
valuesBg.setOrigin(0, 0);
|
||||||
this.val.add(valuesBg);
|
this.val.add(valuesBg);
|
||||||
|
|
||||||
this.typ = this.scene.add.sprite(25, EFF_HEIGHT - 35, `types${Utils.verifyLang(i18next.language) ? `_${i18next.language}` : ""}`, "unknown");
|
this.typ = this.scene.add.sprite(25, EFF_HEIGHT - 35, Utils.getLocalizedSpriteKey("types"), "unknown");
|
||||||
this.typ.setScale(0.8);
|
this.typ.setScale(0.8);
|
||||||
this.val.add(this.typ);
|
this.val.add(this.typ);
|
||||||
|
|
||||||
|
@ -138,7 +138,7 @@ export default class MoveInfoOverlay extends Phaser.GameObjects.Container implem
|
||||||
this.pow.setText(move.power >= 0 ? move.power.toString() : "---");
|
this.pow.setText(move.power >= 0 ? move.power.toString() : "---");
|
||||||
this.acc.setText(move.accuracy >= 0 ? move.accuracy.toString() : "---");
|
this.acc.setText(move.accuracy >= 0 ? move.accuracy.toString() : "---");
|
||||||
this.pp.setText(move.pp >= 0 ? move.pp.toString() : "---");
|
this.pp.setText(move.pp >= 0 ? move.pp.toString() : "---");
|
||||||
this.typ.setTexture(`types${Utils.verifyLang(i18next.language) ? `_${i18next.language}` : ""}`, Type[move.type].toLowerCase());
|
this.typ.setTexture(Utils.getLocalizedSpriteKey("types"), Type[move.type].toLowerCase());
|
||||||
this.cat.setFrame(MoveCategory[move.category].toLowerCase());
|
this.cat.setFrame(MoveCategory[move.category].toLowerCase());
|
||||||
|
|
||||||
this.desc.setText(move?.effect || "");
|
this.desc.setText(move?.effect || "");
|
||||||
|
|
|
@ -1272,7 +1272,7 @@ class PartySlot extends Phaser.GameObjects.Container {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.pokemon.status) {
|
if (this.pokemon.status) {
|
||||||
const statusIndicator = this.scene.add.sprite(0, 0, `statuses_${i18next.resolvedLanguage}`);
|
const statusIndicator = this.scene.add.sprite(0, 0, Utils.getLocalizedSpriteKey("statuses"));
|
||||||
statusIndicator.setFrame(StatusEffect[this.pokemon.status?.effect].toLowerCase());
|
statusIndicator.setFrame(StatusEffect[this.pokemon.status?.effect].toLowerCase());
|
||||||
statusIndicator.setOrigin(0, 0);
|
statusIndicator.setOrigin(0, 0);
|
||||||
statusIndicator.setPositionRelative(slotLevelLabel, this.slotIndex >= battlerCount ? 43 : 55, 0);
|
statusIndicator.setPositionRelative(slotLevelLabel, this.slotIndex >= battlerCount ? 43 : 55, 0);
|
||||||
|
|
|
@ -214,7 +214,7 @@ export default class SummaryUiHandler extends UiHandler {
|
||||||
|
|
||||||
this.statusContainer.add(statusLabel);
|
this.statusContainer.add(statusLabel);
|
||||||
|
|
||||||
this.status = this.scene.add.sprite(91, 4, `statuses_${i18next.resolvedLanguage}`);
|
this.status = this.scene.add.sprite(91, 4, Utils.getLocalizedSpriteKey("statuses"));
|
||||||
this.status.setOrigin(0.5, 0);
|
this.status.setOrigin(0.5, 0);
|
||||||
|
|
||||||
this.statusContainer.add(this.status);
|
this.statusContainer.add(this.status);
|
||||||
|
|
10
src/utils.ts
10
src/utils.ts
|
@ -1,4 +1,5 @@
|
||||||
import { MoneyFormat } from "#enums/money-format";
|
import { MoneyFormat } from "#enums/money-format";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
|
||||||
export const MissingTextureKey = "__MISSING";
|
export const MissingTextureKey = "__MISSING";
|
||||||
|
@ -628,3 +629,12 @@ export function getLocalizedSpriteKey(baseKey: string) {
|
||||||
export function isBetween(num: number, min: number, max: number): boolean {
|
export function isBetween(num: number, min: number, max: number): boolean {
|
||||||
return num >= min && num <= max;
|
return num >= min && num <= max;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to return the animation filename for a given move
|
||||||
|
*
|
||||||
|
* @param move the move for which the animation filename is needed
|
||||||
|
*/
|
||||||
|
export function animationFileName(move: Moves): string {
|
||||||
|
return Moves[move].toLowerCase().replace(/\_/g, "-");
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue