mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-04 23:42:18 +02:00
Merge branch 'beta' into activation-order
This commit is contained in:
commit
d6df829f85
7
package-lock.json
generated
7
package-lock.json
generated
@ -10,6 +10,7 @@
|
|||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@material/material-color-utilities": "^0.2.7",
|
"@material/material-color-utilities": "^0.2.7",
|
||||||
|
"compare-versions": "^6.1.1",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"i18next": "^24.2.2",
|
"i18next": "^24.2.2",
|
||||||
"i18next-browser-languagedetector": "^8.0.4",
|
"i18next-browser-languagedetector": "^8.0.4",
|
||||||
@ -3605,6 +3606,12 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/compare-versions": {
|
||||||
|
"version": "6.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz",
|
||||||
|
"integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
|
@ -55,6 +55,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@material/material-color-utilities": "^0.2.7",
|
"@material/material-color-utilities": "^0.2.7",
|
||||||
|
"compare-versions": "^6.1.1",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"i18next": "^24.2.2",
|
"i18next": "^24.2.2",
|
||||||
"i18next-browser-languagedetector": "^8.0.4",
|
"i18next-browser-languagedetector": "^8.0.4",
|
||||||
|
6
src/@types/SessionSaveMigrator.ts
Normal file
6
src/@types/SessionSaveMigrator.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import type { SessionSaveData } from "#app/system/game-data";
|
||||||
|
|
||||||
|
export interface SessionSaveMigrator {
|
||||||
|
version: string;
|
||||||
|
migrate: (data: SessionSaveData) => void;
|
||||||
|
}
|
5
src/@types/SettingsSaveMigrator.ts
Normal file
5
src/@types/SettingsSaveMigrator.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export interface SettingsSaveMigrator {
|
||||||
|
version: string;
|
||||||
|
// biome-ignore lint/complexity/noBannedTypes: TODO - refactor settings
|
||||||
|
migrate: (data: Object) => void;
|
||||||
|
}
|
6
src/@types/SystemSaveMigrator.ts
Normal file
6
src/@types/SystemSaveMigrator.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import type { SystemSaveData } from "#app/system/game-data";
|
||||||
|
|
||||||
|
export interface SystemSaveMigrator {
|
||||||
|
version: string;
|
||||||
|
migrate: (data: SystemSaveData) => void;
|
||||||
|
}
|
@ -1539,8 +1539,6 @@ export default class BattleScene extends SceneBase {
|
|||||||
this.currentBattle.mysteryEncounterType = mysteryEncounterType;
|
this.currentBattle.mysteryEncounterType = mysteryEncounterType;
|
||||||
}
|
}
|
||||||
|
|
||||||
//this.pushPhase(new TrainerMessageTestPhase(this, TrainerType.RIVAL, TrainerType.RIVAL_2, TrainerType.RIVAL_3, TrainerType.RIVAL_4, TrainerType.RIVAL_5, TrainerType.RIVAL_6));
|
|
||||||
|
|
||||||
if (!waveIndex && lastBattle) {
|
if (!waveIndex && lastBattle) {
|
||||||
const isWaveIndexMultipleOfTen = !(lastBattle.waveIndex % 10);
|
const isWaveIndexMultipleOfTen = !(lastBattle.waveIndex % 10);
|
||||||
const isEndlessOrDaily = this.gameMode.hasShortBiomes || this.gameMode.isDaily;
|
const isEndlessOrDaily = this.gameMode.hasShortBiomes || this.gameMode.isDaily;
|
||||||
|
@ -1000,7 +1000,7 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr {
|
|||||||
|
|
||||||
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
|
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
|
||||||
const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)];
|
const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)];
|
||||||
return move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status
|
return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && !attacker.status
|
||||||
&& (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !move.hitsSubstitute(attacker, pokemon)
|
&& (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !move.hitsSubstitute(attacker, pokemon)
|
||||||
&& attacker.canSetStatus(effect, true, false, pokemon);
|
&& attacker.canSetStatus(effect, true, false, pokemon);
|
||||||
}
|
}
|
||||||
@ -1040,7 +1040,7 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
|
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
|
||||||
return move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance
|
return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && pokemon.randSeedInt(100) < this.chance
|
||||||
&& !move.hitsSubstitute(attacker, pokemon) && attacker.canAddTag(this.tagType);
|
&& !move.hitsSubstitute(attacker, pokemon) && attacker.canAddTag(this.tagType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1087,7 +1087,7 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
|
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
|
||||||
return !simulated && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)
|
return !simulated && move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon})
|
||||||
&& !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !move.hitsSubstitute(attacker, pokemon);
|
&& !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !move.hitsSubstitute(attacker, pokemon);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1120,8 +1120,7 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
|
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
|
||||||
return (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !move.hitsSubstitute(attacker, pokemon))
|
return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && !attacker.getTag(BattlerTagType.PERISH_SONG);
|
||||||
&& !attacker.getTag(BattlerTagType.PERISH_SONG);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void {
|
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void {
|
||||||
@ -1165,7 +1164,7 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
|
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
|
||||||
return move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)
|
return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon})
|
||||||
&& attacker.getAbility().isSwappable && !move.hitsSubstitute(attacker, pokemon);
|
&& attacker.getAbility().isSwappable && !move.hitsSubstitute(attacker, pokemon);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1191,7 +1190,7 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
|
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
|
||||||
return move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && attacker.getAbility().isSuppressable
|
return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && attacker.getAbility().isSuppressable
|
||||||
&& !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr) && !move.hitsSubstitute(attacker, pokemon);
|
&& !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr) && !move.hitsSubstitute(attacker, pokemon);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1222,7 +1221,7 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr {
|
|||||||
|
|
||||||
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
|
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
|
||||||
return attacker.getTag(BattlerTagType.DISABLED) === null && !move.hitsSubstitute(attacker, pokemon)
|
return attacker.getTag(BattlerTagType.DISABLED) === null && !move.hitsSubstitute(attacker, pokemon)
|
||||||
&& move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance);
|
&& move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance);
|
||||||
}
|
}
|
||||||
|
|
||||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void {
|
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void {
|
||||||
@ -1927,7 +1926,7 @@ export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr {
|
|||||||
super.canApplyPostAttack(pokemon, passive, simulated, attacker, move, hitResult, args)
|
super.canApplyPostAttack(pokemon, passive, simulated, attacker, move, hitResult, args)
|
||||||
&& !(pokemon !== attacker && move.hitsSubstitute(attacker, pokemon))
|
&& !(pokemon !== attacker && move.hitsSubstitute(attacker, pokemon))
|
||||||
&& (simulated || !attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && pokemon !== attacker
|
&& (simulated || !attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && pokemon !== attacker
|
||||||
&& (!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance && !pokemon.status)
|
&& (!this.contactRequired || move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon})) && pokemon.randSeedInt(100) < this.chance && !pokemon.status)
|
||||||
) {
|
) {
|
||||||
const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)];
|
const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)];
|
||||||
return simulated || attacker.canSetStatus(effect, true, false, pokemon);
|
return simulated || attacker.canSetStatus(effect, true, false, pokemon);
|
||||||
@ -1966,7 +1965,7 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr {
|
|||||||
/**Battler tags inflicted by abilities post attacking are also considered additional effects.*/
|
/**Battler tags inflicted by abilities post attacking are also considered additional effects.*/
|
||||||
return super.canApplyPostAttack(pokemon, passive, simulated, attacker, move, hitResult, args) &&
|
return super.canApplyPostAttack(pokemon, passive, simulated, attacker, move, hitResult, args) &&
|
||||||
!attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && pokemon !== attacker &&
|
!attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && pokemon !== attacker &&
|
||||||
(!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) &&
|
(!this.contactRequired || move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon})) &&
|
||||||
pokemon.randSeedInt(100) < this.chance(attacker, pokemon, move) && !pokemon.status;
|
pokemon.randSeedInt(100) < this.chance(attacker, pokemon, move) && !pokemon.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2298,6 +2297,11 @@ export class PostSummonAbAttr extends AbAttr {
|
|||||||
applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void {}
|
applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for ability attributes which remove an effect on summon
|
||||||
|
*/
|
||||||
|
export class PostSummonRemoveEffectAbAttr extends PostSummonAbAttr {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes specified arena tags when a Pokemon is summoned.
|
* Removes specified arena tags when a Pokemon is summoned.
|
||||||
*/
|
*/
|
||||||
@ -2408,6 +2412,31 @@ export class PostSummonAddBattlerTagAbAttr extends PostSummonAbAttr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes Specific battler tags when a Pokemon is summoned
|
||||||
|
*
|
||||||
|
* This should realistically only ever activate on gain rather than on summon
|
||||||
|
*/
|
||||||
|
export class PostSummonRemoveBattlerTagAbAttr extends PostSummonRemoveEffectAbAttr {
|
||||||
|
private immuneTags: BattlerTagType[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param immuneTags - The {@linkcode BattlerTagType | battler tags} the Pokémon is immune to.
|
||||||
|
*/
|
||||||
|
constructor(...immuneTags: BattlerTagType[]) {
|
||||||
|
super();
|
||||||
|
this.immuneTags = immuneTags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
|
||||||
|
return this.immuneTags.some(tagType => !!pokemon.getTag(tagType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void {
|
||||||
|
this.immuneTags.forEach(tagType => pokemon.removeTag(tagType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class PostSummonStatStageChangeAbAttr extends PostSummonAbAttr {
|
export class PostSummonStatStageChangeAbAttr extends PostSummonAbAttr {
|
||||||
private stats: BattleStat[];
|
private stats: BattleStat[];
|
||||||
private stages: number;
|
private stages: number;
|
||||||
@ -2595,6 +2624,43 @@ export class PostSummonTerrainChangeAbAttr extends PostSummonAbAttr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Heals a status effect if the Pokemon is afflicted with it upon switch in (or gain)
|
||||||
|
*/
|
||||||
|
export class PostSummonHealStatusAbAttr extends PostSummonRemoveEffectAbAttr {
|
||||||
|
private immuneEffects: StatusEffect[];
|
||||||
|
private statusHealed: StatusEffect;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param immuneEffects - The {@linkcode StatusEffect}s the Pokémon is immune to.
|
||||||
|
*/
|
||||||
|
constructor(...immuneEffects: StatusEffect[]) {
|
||||||
|
super();
|
||||||
|
this.immuneEffects = immuneEffects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
|
||||||
|
const status = pokemon.status?.effect;
|
||||||
|
return !Utils.isNullOrUndefined(status) && (this.immuneEffects.length < 1 || this.immuneEffects.includes(status))
|
||||||
|
}
|
||||||
|
|
||||||
|
public override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void {
|
||||||
|
const status = pokemon.status?.effect;
|
||||||
|
if (!Utils.isNullOrUndefined(status)) {
|
||||||
|
this.statusHealed = status;
|
||||||
|
pokemon.resetStatus(false);
|
||||||
|
pokemon.updateInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override getTriggerMessage(_pokemon: Pokemon, _abilityName: string, ..._args: any[]): string | null {
|
||||||
|
if (this.statusHealed) {
|
||||||
|
return getStatusEffectHealText(this.statusHealed, getPokemonNameWithAffix(_pokemon));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class PostSummonFormChangeAbAttr extends PostSummonAbAttr {
|
export class PostSummonFormChangeAbAttr extends PostSummonAbAttr {
|
||||||
private formFunc: (p: Pokemon) => number;
|
private formFunc: (p: Pokemon) => number;
|
||||||
|
|
||||||
@ -3501,8 +3567,18 @@ export class BonusCritAbAttr extends AbAttr {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super(false);
|
super(false);
|
||||||
}
|
}
|
||||||
override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void {
|
|
||||||
(args[0] as Utils.BooleanHolder).value = true;
|
/**
|
||||||
|
* Apply the bonus crit ability by increasing the value in the provided number holder by 1
|
||||||
|
*
|
||||||
|
* @param pokemon The pokemon with the BonusCrit ability (unused)
|
||||||
|
* @param passive Unused
|
||||||
|
* @param simulated Unused
|
||||||
|
* @param cancelled Unused
|
||||||
|
* @param args Args[0] is a number holder containing the crit stage.
|
||||||
|
*/
|
||||||
|
override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: [Utils.NumberHolder, ...any]): void {
|
||||||
|
(args[0] as Utils.NumberHolder).value += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4753,7 +4829,7 @@ export class PostFaintContactDamageAbAttr extends PostFaintAbAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override canApplyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker?: Pokemon, move?: Move, hitResult?: HitResult, ...args: any[]): boolean {
|
override canApplyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker?: Pokemon, move?: Move, hitResult?: HitResult, ...args: any[]): boolean {
|
||||||
const diedToDirectDamage = move !== undefined && attacker !== undefined && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon);
|
const diedToDirectDamage = move !== undefined && attacker !== undefined && move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon});
|
||||||
const cancelled = new Utils.BooleanHolder(false);
|
const cancelled = new Utils.BooleanHolder(false);
|
||||||
globalScene.getField(true).map(p => applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled, simulated));
|
globalScene.getField(true).map(p => applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled, simulated));
|
||||||
if (!diedToDirectDamage || cancelled.value || attacker!.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) {
|
if (!diedToDirectDamage || cancelled.value || attacker!.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) {
|
||||||
@ -6299,6 +6375,7 @@ export function initAbilities() {
|
|||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.LIMBER, 3)
|
new Ability(Abilities.LIMBER, 3)
|
||||||
.attr(StatusEffectImmunityAbAttr, StatusEffect.PARALYSIS)
|
.attr(StatusEffectImmunityAbAttr, StatusEffect.PARALYSIS)
|
||||||
|
.attr(PostSummonHealStatusAbAttr, StatusEffect.PARALYSIS)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.SAND_VEIL, 3)
|
new Ability(Abilities.SAND_VEIL, 3)
|
||||||
.attr(StatMultiplierAbAttr, Stat.EVA, 1.2)
|
.attr(StatMultiplierAbAttr, Stat.EVA, 1.2)
|
||||||
@ -6316,6 +6393,7 @@ export function initAbilities() {
|
|||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.OBLIVIOUS, 3)
|
new Ability(Abilities.OBLIVIOUS, 3)
|
||||||
.attr(BattlerTagImmunityAbAttr, [ BattlerTagType.INFATUATED, BattlerTagType.TAUNT ])
|
.attr(BattlerTagImmunityAbAttr, [ BattlerTagType.INFATUATED, BattlerTagType.TAUNT ])
|
||||||
|
.attr(PostSummonRemoveBattlerTagAbAttr, BattlerTagType.INFATUATED, BattlerTagType.TAUNT)
|
||||||
.attr(IntimidateImmunityAbAttr)
|
.attr(IntimidateImmunityAbAttr)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.CLOUD_NINE, 3)
|
new Ability(Abilities.CLOUD_NINE, 3)
|
||||||
@ -6328,6 +6406,7 @@ export function initAbilities() {
|
|||||||
.attr(StatMultiplierAbAttr, Stat.ACC, 1.3),
|
.attr(StatMultiplierAbAttr, Stat.ACC, 1.3),
|
||||||
new Ability(Abilities.INSOMNIA, 3)
|
new Ability(Abilities.INSOMNIA, 3)
|
||||||
.attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP)
|
.attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP)
|
||||||
|
.attr(PostSummonHealStatusAbAttr, StatusEffect.SLEEP)
|
||||||
.attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY)
|
.attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.COLOR_CHANGE, 3)
|
new Ability(Abilities.COLOR_CHANGE, 3)
|
||||||
@ -6335,6 +6414,7 @@ export function initAbilities() {
|
|||||||
.condition(getSheerForceHitDisableAbCondition()),
|
.condition(getSheerForceHitDisableAbCondition()),
|
||||||
new Ability(Abilities.IMMUNITY, 3)
|
new Ability(Abilities.IMMUNITY, 3)
|
||||||
.attr(StatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
|
.attr(StatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
|
||||||
|
.attr(PostSummonHealStatusAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.FLASH_FIRE, 3)
|
new Ability(Abilities.FLASH_FIRE, 3)
|
||||||
.attr(TypeImmunityAddBattlerTagAbAttr, PokemonType.FIRE, BattlerTagType.FIRE_BOOST, 1)
|
.attr(TypeImmunityAddBattlerTagAbAttr, PokemonType.FIRE, BattlerTagType.FIRE_BOOST, 1)
|
||||||
@ -6344,6 +6424,7 @@ export function initAbilities() {
|
|||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.OWN_TEMPO, 3)
|
new Ability(Abilities.OWN_TEMPO, 3)
|
||||||
.attr(BattlerTagImmunityAbAttr, BattlerTagType.CONFUSED)
|
.attr(BattlerTagImmunityAbAttr, BattlerTagType.CONFUSED)
|
||||||
|
.attr(PostSummonRemoveBattlerTagAbAttr, BattlerTagType.CONFUSED)
|
||||||
.attr(IntimidateImmunityAbAttr)
|
.attr(IntimidateImmunityAbAttr)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.SUCTION_CUPS, 3)
|
new Ability(Abilities.SUCTION_CUPS, 3)
|
||||||
@ -6409,9 +6490,11 @@ export function initAbilities() {
|
|||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.MAGMA_ARMOR, 3)
|
new Ability(Abilities.MAGMA_ARMOR, 3)
|
||||||
.attr(StatusEffectImmunityAbAttr, StatusEffect.FREEZE)
|
.attr(StatusEffectImmunityAbAttr, StatusEffect.FREEZE)
|
||||||
|
.attr(PostSummonHealStatusAbAttr, StatusEffect.FREEZE)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.WATER_VEIL, 3)
|
new Ability(Abilities.WATER_VEIL, 3)
|
||||||
.attr(StatusEffectImmunityAbAttr, StatusEffect.BURN)
|
.attr(StatusEffectImmunityAbAttr, StatusEffect.BURN)
|
||||||
|
.attr(PostSummonHealStatusAbAttr, StatusEffect.BURN)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.MAGNET_PULL, 3)
|
new Ability(Abilities.MAGNET_PULL, 3)
|
||||||
.attr(ArenaTrapAbAttr, (user, target) => {
|
.attr(ArenaTrapAbAttr, (user, target) => {
|
||||||
@ -6505,6 +6588,7 @@ export function initAbilities() {
|
|||||||
.attr(DoubleBattleChanceAbAttr),
|
.attr(DoubleBattleChanceAbAttr),
|
||||||
new Ability(Abilities.VITAL_SPIRIT, 3)
|
new Ability(Abilities.VITAL_SPIRIT, 3)
|
||||||
.attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP)
|
.attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP)
|
||||||
|
.attr(PostSummonHealStatusAbAttr, StatusEffect.SLEEP)
|
||||||
.attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY)
|
.attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.WHITE_SMOKE, 3)
|
new Ability(Abilities.WHITE_SMOKE, 3)
|
||||||
@ -6843,6 +6927,7 @@ export function initAbilities() {
|
|||||||
.attr(MoveTypeChangeAbAttr, PokemonType.ICE, 1.2, (user, target, move) => move.type === PokemonType.NORMAL && !move.hasAttr(VariableMoveTypeAttr)),
|
.attr(MoveTypeChangeAbAttr, PokemonType.ICE, 1.2, (user, target, move) => move.type === PokemonType.NORMAL && !move.hasAttr(VariableMoveTypeAttr)),
|
||||||
new Ability(Abilities.SWEET_VEIL, 6)
|
new Ability(Abilities.SWEET_VEIL, 6)
|
||||||
.attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.SLEEP)
|
.attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.SLEEP)
|
||||||
|
.attr(PostSummonUserFieldRemoveStatusEffectAbAttr, StatusEffect.SLEEP)
|
||||||
.attr(UserFieldBattlerTagImmunityAbAttr, BattlerTagType.DROWSY)
|
.attr(UserFieldBattlerTagImmunityAbAttr, BattlerTagType.DROWSY)
|
||||||
.ignorable()
|
.ignorable()
|
||||||
.partial(), // Mold Breaker ally should not be affected by Sweet Veil
|
.partial(), // Mold Breaker ally should not be affected by Sweet Veil
|
||||||
@ -6927,6 +7012,7 @@ export function initAbilities() {
|
|||||||
.attr(ReceivedTypeDamageMultiplierAbAttr, PokemonType.FIRE, 0.5)
|
.attr(ReceivedTypeDamageMultiplierAbAttr, PokemonType.FIRE, 0.5)
|
||||||
.attr(MoveTypePowerBoostAbAttr, PokemonType.WATER, 2)
|
.attr(MoveTypePowerBoostAbAttr, PokemonType.WATER, 2)
|
||||||
.attr(StatusEffectImmunityAbAttr, StatusEffect.BURN)
|
.attr(StatusEffectImmunityAbAttr, StatusEffect.BURN)
|
||||||
|
.attr(PostSummonHealStatusAbAttr, StatusEffect.BURN)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.STEELWORKER, 7)
|
new Ability(Abilities.STEELWORKER, 7)
|
||||||
.attr(MoveTypePowerBoostAbAttr, PokemonType.STEEL),
|
.attr(MoveTypePowerBoostAbAttr, PokemonType.STEEL),
|
||||||
@ -7205,6 +7291,7 @@ export function initAbilities() {
|
|||||||
new Ability(Abilities.THERMAL_EXCHANGE, 9)
|
new Ability(Abilities.THERMAL_EXCHANGE, 9)
|
||||||
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === PokemonType.FIRE && move.category !== MoveCategory.STATUS, Stat.ATK, 1)
|
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === PokemonType.FIRE && move.category !== MoveCategory.STATUS, Stat.ATK, 1)
|
||||||
.attr(StatusEffectImmunityAbAttr, StatusEffect.BURN)
|
.attr(StatusEffectImmunityAbAttr, StatusEffect.BURN)
|
||||||
|
.attr(PostSummonHealStatusAbAttr, StatusEffect.BURN)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.ANGER_SHELL, 9)
|
new Ability(Abilities.ANGER_SHELL, 9)
|
||||||
.attr(PostDefendHpGatedStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, 0.5, [ Stat.ATK, Stat.SPATK, Stat.SPD ], 1)
|
.attr(PostDefendHpGatedStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, 0.5, [ Stat.ATK, Stat.SPATK, Stat.SPD ], 1)
|
||||||
|
@ -1428,7 +1428,8 @@ export class MoveAnim extends BattleAnim {
|
|||||||
public move: Moves;
|
public move: Moves;
|
||||||
|
|
||||||
constructor(move: Moves, user: Pokemon, target: BattlerIndex, playOnEmptyField = false) {
|
constructor(move: Moves, user: Pokemon, target: BattlerIndex, playOnEmptyField = false) {
|
||||||
super(user, globalScene.getField()[target], playOnEmptyField);
|
// Set target to the user pokemon if no target is found to avoid crashes
|
||||||
|
super(user, globalScene.getField()[target] ?? user, playOnEmptyField);
|
||||||
|
|
||||||
this.move = move;
|
this.move = move;
|
||||||
}
|
}
|
||||||
|
@ -346,7 +346,7 @@ export default class Move implements Localizable {
|
|||||||
* @param target The {@linkcode Pokemon} targeted by this move
|
* @param target The {@linkcode Pokemon} targeted by this move
|
||||||
* @returns `true` if the move can bypass the target's Substitute; `false` otherwise.
|
* @returns `true` if the move can bypass the target's Substitute; `false` otherwise.
|
||||||
*/
|
*/
|
||||||
hitsSubstitute(user: Pokemon, target: Pokemon | null): boolean {
|
hitsSubstitute(user: Pokemon, target?: Pokemon): boolean {
|
||||||
if ([ MoveTarget.USER, MoveTarget.USER_SIDE, MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES ].includes(this.moveTarget)
|
if ([ MoveTarget.USER, MoveTarget.USER_SIDE, MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES ].includes(this.moveTarget)
|
||||||
|| !target?.getTag(BattlerTagType.SUBSTITUTE)) {
|
|| !target?.getTag(BattlerTagType.SUBSTITUTE)) {
|
||||||
return false;
|
return false;
|
||||||
@ -618,12 +618,30 @@ export default class Move implements Localizable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the move flag applies to the pokemon(s) using/receiving the move
|
* Checks if the move flag applies to the pokemon(s) using/receiving the move
|
||||||
|
*
|
||||||
|
* This method will take the `user`'s ability into account when reporting flags, e.g.
|
||||||
|
* calling this method for {@linkcode MoveFlags.MAKES_CONTACT | MAKES_CONTACT}
|
||||||
|
* will return `false` if the user has a {@linkcode Abilities.LONG_REACH} that is not being suppressed.
|
||||||
|
*
|
||||||
|
* **Note:** This method only checks if the move should have effectively have the flag applied to its use.
|
||||||
|
* It does *not* check whether the flag will trigger related effects.
|
||||||
|
* For example using this method to check {@linkcode MoveFlags.WIND_MOVE}
|
||||||
|
* will not consider {@linkcode Abilities.WIND_RIDER | Wind Rider }.
|
||||||
|
*
|
||||||
|
* To simply check whether the move has a flag, use {@linkcode hasFlag}.
|
||||||
* @param flag {@linkcode MoveFlags} MoveFlag to check on user and/or target
|
* @param flag {@linkcode MoveFlags} MoveFlag to check on user and/or target
|
||||||
* @param user {@linkcode Pokemon} the Pokemon using the move
|
* @param user {@linkcode Pokemon} the Pokemon using the move
|
||||||
* @param target {@linkcode Pokemon} the Pokemon receiving the move
|
* @param target {@linkcode Pokemon} the Pokemon receiving the move
|
||||||
|
* @param isFollowUp (defaults to `false`) `true` if the move was used as a follow up
|
||||||
* @returns boolean
|
* @returns boolean
|
||||||
|
* @see {@linkcode hasFlag}
|
||||||
*/
|
*/
|
||||||
checkFlag(flag: MoveFlags, user: Pokemon, target: Pokemon | null): boolean {
|
doesFlagEffectApply({ flag, user, target, isFollowUp = false }: {
|
||||||
|
flag: MoveFlags;
|
||||||
|
user: Pokemon;
|
||||||
|
target?: Pokemon;
|
||||||
|
isFollowUp?: boolean;
|
||||||
|
}): boolean {
|
||||||
// special cases below, eg: if the move flag is MAKES_CONTACT, and the user pokemon has an ability that ignores contact (like "Long Reach"), then overrides and move does not make contact
|
// special cases below, eg: if the move flag is MAKES_CONTACT, and the user pokemon has an ability that ignores contact (like "Long Reach"), then overrides and move does not make contact
|
||||||
switch (flag) {
|
switch (flag) {
|
||||||
case MoveFlags.MAKES_CONTACT:
|
case MoveFlags.MAKES_CONTACT:
|
||||||
@ -638,11 +656,13 @@ export default class Move implements Localizable {
|
|||||||
if (abilityEffectsIgnored.value) {
|
if (abilityEffectsIgnored.value) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// Sunsteel strike, Moongeist beam, and photon geyser will not ignore abilities if invoked
|
||||||
|
// by another move, such as via metronome.
|
||||||
}
|
}
|
||||||
break;
|
return this.hasFlag(MoveFlags.IGNORE_ABILITIES) && !isFollowUp;
|
||||||
case MoveFlags.IGNORE_PROTECT:
|
case MoveFlags.IGNORE_PROTECT:
|
||||||
if (user.hasAbilityWithAttr(IgnoreProtectOnContactAbAttr)
|
if (user.hasAbilityWithAttr(IgnoreProtectOnContactAbAttr)
|
||||||
&& this.checkFlag(MoveFlags.MAKES_CONTACT, user, null)) {
|
&& this.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user })) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -1214,7 +1234,7 @@ export class MoveEffectAttr extends MoveAttr {
|
|||||||
canApply(user: Pokemon, target: Pokemon, move: Move, args?: any[]) {
|
canApply(user: Pokemon, target: Pokemon, move: Move, args?: any[]) {
|
||||||
return !! (this.selfTarget ? user.hp && !user.getTag(BattlerTagType.FRENZY) : target.hp)
|
return !! (this.selfTarget ? user.hp && !user.getTag(BattlerTagType.FRENZY) : target.hp)
|
||||||
&& (this.selfTarget || !target.getTag(BattlerTagType.PROTECTED) ||
|
&& (this.selfTarget || !target.getTag(BattlerTagType.PROTECTED) ||
|
||||||
move.checkFlag(MoveFlags.IGNORE_PROTECT, user, target));
|
move.doesFlagEffectApply({ flag: MoveFlags.IGNORE_PROTECT, user, target }));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Applies move effects so long as they are able based on {@linkcode canApply} */
|
/** Applies move effects so long as they are able based on {@linkcode canApply} */
|
||||||
@ -2423,13 +2443,9 @@ export class StatusEffectAttr extends MoveEffectAttr {
|
|||||||
const statusCheck = moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance;
|
const statusCheck = moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance;
|
||||||
if (statusCheck) {
|
if (statusCheck) {
|
||||||
const pokemon = this.selfTarget ? user : target;
|
const pokemon = this.selfTarget ? user : target;
|
||||||
if (pokemon.status) {
|
if (pokemon.status && !this.overrideStatus) {
|
||||||
if (this.overrideStatus) {
|
|
||||||
pokemon.resetStatus();
|
|
||||||
} else {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (user !== target && target.isSafeguarded(user)) {
|
if (user !== target && target.isSafeguarded(user)) {
|
||||||
if (move.category === MoveCategory.STATUS) {
|
if (move.category === MoveCategory.STATUS) {
|
||||||
@ -2437,8 +2453,8 @@ export class StatusEffectAttr extends MoveEffectAttr {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ((!pokemon.status || (pokemon.status.effect === this.effect && moveChance < 0))
|
if (((!pokemon.status || this.overrideStatus) || (pokemon.status.effect === this.effect && moveChance < 0))
|
||||||
&& pokemon.trySetStatus(this.effect, true, user, this.turnsRemaining)) {
|
&& pokemon.trySetStatus(this.effect, true, user, this.turnsRemaining, null, this.overrideStatus)) {
|
||||||
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, false, this.effect);
|
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, false, this.effect);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -5908,7 +5924,7 @@ export class AddArenaTagAttr extends MoveEffectAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance) && user.getLastXMoves(1)[0]?.result === MoveResult.SUCCESS) {
|
if ((move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance) && user.getLastXMoves(1)[0]?.result === MoveResult.SUCCESS) {
|
||||||
const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
const side = ((this.selfSideTarget ? user : target).isPlayer() !== (move.hasAttr(AddArenaTrapTagAttr) && target === user)) ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||||
globalScene.arena.addTag(this.tagType, this.turnCount, move.id, user.id, side);
|
globalScene.arena.addTag(this.tagType, this.turnCount, move.id, user.id, side);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -5957,7 +5973,7 @@ export class RemoveArenaTagsAttr extends MoveEffectAttr {
|
|||||||
export class AddArenaTrapTagAttr extends AddArenaTagAttr {
|
export class AddArenaTrapTagAttr extends AddArenaTagAttr {
|
||||||
getCondition(): MoveConditionFunc {
|
getCondition(): MoveConditionFunc {
|
||||||
return (user, target, move) => {
|
return (user, target, move) => {
|
||||||
const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
const side = (this.selfSideTarget !== user.isPlayer()) ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER;
|
||||||
const tag = globalScene.arena.getTagOnSide(this.tagType, side) as ArenaTrapTag;
|
const tag = globalScene.arena.getTagOnSide(this.tagType, side) as ArenaTrapTag;
|
||||||
if (!tag) {
|
if (!tag) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -264,6 +264,7 @@ import { StatusEffect } from "#enums/status-effect";
|
|||||||
import { doShinySparkleAnim } from "#app/field/anims";
|
import { doShinySparkleAnim } from "#app/field/anims";
|
||||||
import { MoveFlags } from "#enums/MoveFlags";
|
import { MoveFlags } from "#enums/MoveFlags";
|
||||||
import { timedEventManager } from "#app/global-event-manager";
|
import { timedEventManager } from "#app/global-event-manager";
|
||||||
|
import { ResetStatusPhase } from "#app/phases/reset-status-phase";
|
||||||
|
|
||||||
export enum LearnMoveSituation {
|
export enum LearnMoveSituation {
|
||||||
MISC,
|
MISC,
|
||||||
@ -1340,8 +1341,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the critical-hit stage considering the move used and the Pokemon
|
* Calculate the critical-hit stage of a move used against this pokemon by
|
||||||
* who used it.
|
* the given source
|
||||||
|
*
|
||||||
* @param source the {@linkcode Pokemon} who using the move
|
* @param source the {@linkcode Pokemon} who using the move
|
||||||
* @param move the {@linkcode Move} being used
|
* @param move the {@linkcode Move} being used
|
||||||
* @returns the final critical-hit stage value
|
* @returns the final critical-hit stage value
|
||||||
@ -1360,14 +1362,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
source.isPlayer(),
|
source.isPlayer(),
|
||||||
critStage,
|
critStage,
|
||||||
);
|
);
|
||||||
const bonusCrit = new Utils.BooleanHolder(false);
|
applyAbAttrs(BonusCritAbAttr, source, null, false, critStage)
|
||||||
//@ts-ignore
|
|
||||||
if (applyAbAttrs(BonusCritAbAttr, source, null, false, bonusCrit)) {
|
|
||||||
// TODO: resolve ts-ignore. This is a promise. Checking a promise is bogus.
|
|
||||||
if (bonusCrit.value) {
|
|
||||||
critStage.value += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const critBoostTag = source.getTag(CritBoostTag);
|
const critBoostTag = source.getTag(CritBoostTag);
|
||||||
if (critBoostTag) {
|
if (critBoostTag) {
|
||||||
if (critBoostTag instanceof DragonCheerTag) {
|
if (critBoostTag instanceof DragonCheerTag) {
|
||||||
@ -4819,7 +4814,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
if (newTag.canAdd(this)) {
|
if (newTag.canAdd(this)) {
|
||||||
this.summonData.tags.push(newTag);
|
this.summonData.tags.push(newTag);
|
||||||
newTag.onAdd(this);
|
newTag.onAdd(this);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5490,8 +5484,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
sourcePokemon: Pokemon | null = null,
|
sourcePokemon: Pokemon | null = null,
|
||||||
turnsRemaining = 0,
|
turnsRemaining = 0,
|
||||||
sourceText: string | null = null,
|
sourceText: string | null = null,
|
||||||
|
overrideStatus?: boolean
|
||||||
): boolean {
|
): boolean {
|
||||||
if (!this.canSetStatus(effect, asPhase, false, sourcePokemon)) {
|
if (!this.canSetStatus(effect, asPhase, overrideStatus, sourcePokemon)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.isFainted() && effect !== StatusEffect.FAINT) {
|
if (this.isFainted() && effect !== StatusEffect.FAINT) {
|
||||||
@ -5507,6 +5502,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (asPhase) {
|
if (asPhase) {
|
||||||
|
if (overrideStatus) {
|
||||||
|
this.resetStatus(false);
|
||||||
|
}
|
||||||
globalScene.unshiftPhase(
|
globalScene.unshiftPhase(
|
||||||
new ObtainStatusEffectPhase(
|
new ObtainStatusEffectPhase(
|
||||||
this.getBattlerIndex(),
|
this.getBattlerIndex(),
|
||||||
@ -5546,20 +5544,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
effect = effect!; // If `effect` is undefined then `trySetStatus()` will have already returned early via the `canSetStatus()` call
|
effect = effect!; // If `effect` is undefined then `trySetStatus()` will have already returned early via the `canSetStatus()` call
|
||||||
this.status = new Status(effect, 0, sleepTurnsRemaining?.value);
|
this.status = new Status(effect, 0, sleepTurnsRemaining?.value);
|
||||||
|
|
||||||
if (effect !== StatusEffect.FAINT) {
|
|
||||||
globalScene.triggerPokemonFormChange(
|
|
||||||
this,
|
|
||||||
SpeciesFormChangeStatusEffectTrigger,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
applyPostSetStatusAbAttrs(
|
|
||||||
PostSetStatusAbAttr,
|
|
||||||
this,
|
|
||||||
effect,
|
|
||||||
sourcePokemon,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5574,21 +5558,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
if (!revive && lastStatus === StatusEffect.FAINT) {
|
if (!revive && lastStatus === StatusEffect.FAINT) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.status = null;
|
globalScene.unshiftPhase(new ResetStatusPhase(this, confusion, reloadAssets));
|
||||||
if (lastStatus === StatusEffect.SLEEP) {
|
|
||||||
this.setFrameRate(10);
|
|
||||||
if (this.getTag(BattlerTagType.NIGHTMARE)) {
|
|
||||||
this.lapseTag(BattlerTagType.NIGHTMARE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (confusion) {
|
|
||||||
if (this.getTag(BattlerTagType.CONFUSED)) {
|
|
||||||
this.lapseTag(BattlerTagType.CONFUSED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (reloadAssets) {
|
|
||||||
this.loadAssets(false).then(() => this.playAnim());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2734,7 +2734,7 @@ export class PokemonMoveAccuracyBoosterModifier extends PokemonHeldItemModifier
|
|||||||
* @returns always `true`
|
* @returns always `true`
|
||||||
*/
|
*/
|
||||||
override apply(_pokemon: Pokemon, moveAccuracy: NumberHolder): boolean {
|
override apply(_pokemon: Pokemon, moveAccuracy: NumberHolder): boolean {
|
||||||
moveAccuracy.value = Math.min(moveAccuracy.value + this.accuracyAmount * this.getStackCount(), 100);
|
moveAccuracy.value = moveAccuracy.value + this.accuracyAmount * this.getStackCount();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,6 @@ import { Phase } from "#app/phase";
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
|
|
||||||
export class AddEnemyBuffModifierPhase extends Phase {
|
export class AddEnemyBuffModifierPhase extends Phase {
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { applyAbAttrs, applyPreLeaveFieldAbAttrs, PreLeaveFieldAbAttr, RunSuccessAbAttr } from "#app/data/ability";
|
import { applyAbAttrs, applyPreLeaveFieldAbAttrs, PreLeaveFieldAbAttr, RunSuccessAbAttr } from "#app/data/ability";
|
||||||
import { Stat } from "#app/enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { StatusEffect } from "#app/enums/status-effect";
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
import type { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon";
|
import type { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import * as Utils from "#app/utils";
|
import { NumberHolder } from "#app/utils";
|
||||||
import { BattleEndPhase } from "./battle-end-phase";
|
import { BattleEndPhase } from "./battle-end-phase";
|
||||||
import { NewBattlePhase } from "./new-battle-phase";
|
import { NewBattlePhase } from "./new-battle-phase";
|
||||||
import { PokemonPhase } from "./pokemon-phase";
|
import { PokemonPhase } from "./pokemon-phase";
|
||||||
@ -22,7 +22,7 @@ export class AttemptRunPhase extends PokemonPhase {
|
|||||||
|
|
||||||
const playerPokemon = this.getPokemon();
|
const playerPokemon = this.getPokemon();
|
||||||
|
|
||||||
const escapeChance = new Utils.NumberHolder(0);
|
const escapeChance = new NumberHolder(0);
|
||||||
|
|
||||||
this.attemptRunAway(playerField, enemyField, escapeChance);
|
this.attemptRunAway(playerField, enemyField, escapeChance);
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ export class AttemptRunPhase extends PokemonPhase {
|
|||||||
this.end();
|
this.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
attemptRunAway(playerField: PlayerPokemon[], enemyField: EnemyPokemon[], escapeChance: Utils.NumberHolder) {
|
attemptRunAway(playerField: PlayerPokemon[], enemyField: EnemyPokemon[], escapeChance: NumberHolder) {
|
||||||
/** Sum of the speed of all enemy pokemon on the field */
|
/** Sum of the speed of all enemy pokemon on the field */
|
||||||
const enemySpeed = enemyField.reduce(
|
const enemySpeed = enemyField.reduce(
|
||||||
(total: number, enemyPokemon: Pokemon) => total + enemyPokemon.getStat(Stat.SPD),
|
(total: number, enemyPokemon: Pokemon) => total + enemyPokemon.getStat(Stat.SPD),
|
||||||
|
@ -26,13 +26,15 @@ export class BattleEndPhase extends BattlePhase {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
// `phaseQueuePrepend` is private, so we have to use this inefficient loop.
|
// `phaseQueuePrepend` is private, so we have to use this inefficient loop.
|
||||||
while (globalScene.tryRemoveUnshiftedPhase(phase => {
|
while (
|
||||||
|
globalScene.tryRemoveUnshiftedPhase(phase => {
|
||||||
if (phase instanceof BattleEndPhase) {
|
if (phase instanceof BattleEndPhase) {
|
||||||
this.isVictory ||= phase.isVictory;
|
this.isVictory ||= phase.isVictory;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
})) {}
|
})
|
||||||
|
) {}
|
||||||
|
|
||||||
globalScene.gameData.gameStats.battles++;
|
globalScene.gameData.gameStats.battles++;
|
||||||
if (
|
if (
|
||||||
|
@ -3,13 +3,13 @@ import { TrainerSlot } from "#enums/trainer-slot";
|
|||||||
import { Phase } from "#app/phase";
|
import { Phase } from "#app/phase";
|
||||||
|
|
||||||
export class BattlePhase extends Phase {
|
export class BattlePhase extends Phase {
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
showEnemyTrainer(trainerSlot: TrainerSlot = TrainerSlot.NONE): void {
|
showEnemyTrainer(trainerSlot: TrainerSlot = TrainerSlot.NONE): void {
|
||||||
const sprites = globalScene.currentBattle.trainer?.getSprites()!; // TODO: is this bang correct?
|
if (!globalScene.currentBattle.trainer) {
|
||||||
const tintSprites = globalScene.currentBattle.trainer?.getTintSprites()!; // TODO: is this bang correct?
|
console.warn("Enemy trainer is missing!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const sprites = globalScene.currentBattle.trainer.getSprites();
|
||||||
|
const tintSprites = globalScene.currentBattle.trainer.getTintSprites();
|
||||||
for (let i = 0; i < sprites.length; i++) {
|
for (let i = 0; i < sprites.length; i++) {
|
||||||
const visible = !trainerSlot || !i === (trainerSlot === TrainerSlot.TRAINER) || sprites.length < 2;
|
const visible = !trainerSlot || !i === (trainerSlot === TrainerSlot.TRAINER) || sprites.length < 2;
|
||||||
[sprites[i], tintSprites[i]].map(sprite => {
|
[sprites[i], tintSprites[i]].map(sprite => {
|
||||||
|
@ -4,7 +4,7 @@ import { BerryUsedEvent } from "#app/events/battle-scene";
|
|||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import { BerryModifier } from "#app/modifier/modifier";
|
import { BerryModifier } from "#app/modifier/modifier";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import * as Utils from "#app/utils";
|
import { BooleanHolder } from "#app/utils";
|
||||||
import { FieldPhase } from "./field-phase";
|
import { FieldPhase } from "./field-phase";
|
||||||
import { CommonAnimPhase } from "./common-anim-phase";
|
import { CommonAnimPhase } from "./common-anim-phase";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
@ -20,7 +20,7 @@ export class BerryPhase extends FieldPhase {
|
|||||||
}, pokemon.isPlayer());
|
}, pokemon.isPlayer());
|
||||||
|
|
||||||
if (hasUsableBerry) {
|
if (hasUsableBerry) {
|
||||||
const cancelled = new Utils.BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
pokemon.getOpponents().map(opp => applyAbAttrs(PreventBerryUseAbAttr, opp, cancelled));
|
pokemon.getOpponents().map(opp => applyAbAttrs(PreventBerryUseAbAttr, opp, cancelled));
|
||||||
|
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
@ -44,7 +44,7 @@ export class BerryPhase extends FieldPhase {
|
|||||||
|
|
||||||
globalScene.updateModifiers(pokemon.isPlayer());
|
globalScene.updateModifiers(pokemon.isPlayer());
|
||||||
|
|
||||||
applyAbAttrs(HealFromBerryUseAbAttr, pokemon, new Utils.BooleanHolder(false));
|
applyAbAttrs(HealFromBerryUseAbAttr, pokemon, new BooleanHolder(false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -6,13 +6,18 @@ import { PokemonPhase } from "./pokemon-phase";
|
|||||||
|
|
||||||
export class CommonAnimPhase extends PokemonPhase {
|
export class CommonAnimPhase extends PokemonPhase {
|
||||||
private anim: CommonAnim | null;
|
private anim: CommonAnim | null;
|
||||||
private targetIndex: number | undefined;
|
private targetIndex?: BattlerIndex;
|
||||||
private playOnEmptyField: boolean;
|
private playOnEmptyField: boolean;
|
||||||
|
|
||||||
constructor(battlerIndex?: BattlerIndex, targetIndex?: BattlerIndex, anim?: CommonAnim, playOnEmptyField = false) {
|
constructor(
|
||||||
|
battlerIndex?: BattlerIndex,
|
||||||
|
targetIndex?: BattlerIndex,
|
||||||
|
anim: CommonAnim | null = null,
|
||||||
|
playOnEmptyField = false,
|
||||||
|
) {
|
||||||
super(battlerIndex);
|
super(battlerIndex);
|
||||||
|
|
||||||
this.anim = anim!; // TODO: is this bang correct?
|
this.anim = anim;
|
||||||
this.targetIndex = targetIndex;
|
this.targetIndex = targetIndex;
|
||||||
this.playOnEmptyField = playOnEmptyField;
|
this.playOnEmptyField = playOnEmptyField;
|
||||||
}
|
}
|
||||||
|
@ -10,11 +10,16 @@ export class DamageAnimPhase extends PokemonPhase {
|
|||||||
private damageResult: DamageResult;
|
private damageResult: DamageResult;
|
||||||
private critical: boolean;
|
private critical: boolean;
|
||||||
|
|
||||||
constructor(battlerIndex: BattlerIndex, amount: number, damageResult?: DamageResult, critical = false) {
|
constructor(
|
||||||
|
battlerIndex: BattlerIndex,
|
||||||
|
amount: number,
|
||||||
|
damageResult: DamageResult = HitResult.EFFECTIVE,
|
||||||
|
critical = false,
|
||||||
|
) {
|
||||||
super(battlerIndex);
|
super(battlerIndex);
|
||||||
|
|
||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
this.damageResult = damageResult || HitResult.EFFECTIVE;
|
this.damageResult = damageResult;
|
||||||
this.critical = critical;
|
this.critical = critical;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import PokemonInfoContainer from "#app/ui/pokemon-info-container";
|
|||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
|
import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
|
||||||
import * as Utils from "#app/utils";
|
import { fixedInt, getFrameMs, randInt } from "#app/utils";
|
||||||
import type { EggLapsePhase } from "./egg-lapse-phase";
|
import type { EggLapsePhase } from "./egg-lapse-phase";
|
||||||
import type { EggHatchData } from "#app/data/egg-hatch-data";
|
import type { EggHatchData } from "#app/data/egg-hatch-data";
|
||||||
import { doShinySparkleAnim } from "#app/field/anims";
|
import { doShinySparkleAnim } from "#app/field/anims";
|
||||||
@ -306,17 +306,17 @@ export class EggHatchPhase extends Phase {
|
|||||||
this.canSkip = false;
|
this.canSkip = false;
|
||||||
this.hatched = true;
|
this.hatched = true;
|
||||||
if (this.evolutionBgm) {
|
if (this.evolutionBgm) {
|
||||||
SoundFade.fadeOut(globalScene, this.evolutionBgm, Utils.fixedInt(100));
|
SoundFade.fadeOut(globalScene, this.evolutionBgm, fixedInt(100));
|
||||||
}
|
}
|
||||||
for (let e = 0; e < 5; e++) {
|
for (let e = 0; e < 5; e++) {
|
||||||
globalScene.time.delayedCall(Utils.fixedInt(375 * e), () =>
|
globalScene.time.delayedCall(fixedInt(375 * e), () =>
|
||||||
globalScene.playSound("se/egg_hatch", { volume: 1 - e * 0.2 }),
|
globalScene.playSound("se/egg_hatch", { volume: 1 - e * 0.2 }),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.eggLightraysOverlay.setVisible(true);
|
this.eggLightraysOverlay.setVisible(true);
|
||||||
this.eggLightraysOverlay.play("egg_lightrays");
|
this.eggLightraysOverlay.play("egg_lightrays");
|
||||||
globalScene.tweens.add({
|
globalScene.tweens.add({
|
||||||
duration: Utils.fixedInt(125),
|
duration: fixedInt(125),
|
||||||
targets: this.eggHatchOverlay,
|
targets: this.eggHatchOverlay,
|
||||||
alpha: 1,
|
alpha: 1,
|
||||||
ease: "Cubic.easeIn",
|
ease: "Cubic.easeIn",
|
||||||
@ -325,7 +325,7 @@ export class EggHatchPhase extends Phase {
|
|||||||
this.canSkip = true;
|
this.canSkip = true;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
globalScene.time.delayedCall(Utils.fixedInt(1500), () => {
|
globalScene.time.delayedCall(fixedInt(1500), () => {
|
||||||
this.canSkip = false;
|
this.canSkip = false;
|
||||||
if (!this.skipped) {
|
if (!this.skipped) {
|
||||||
this.doReveal();
|
this.doReveal();
|
||||||
@ -363,18 +363,16 @@ export class EggHatchPhase extends Phase {
|
|||||||
this.pokemonSprite.setPipelineData("shiny", this.pokemon.shiny);
|
this.pokemonSprite.setPipelineData("shiny", this.pokemon.shiny);
|
||||||
this.pokemonSprite.setPipelineData("variant", this.pokemon.variant);
|
this.pokemonSprite.setPipelineData("variant", this.pokemon.variant);
|
||||||
this.pokemonSprite.setVisible(true);
|
this.pokemonSprite.setVisible(true);
|
||||||
globalScene.time.delayedCall(Utils.fixedInt(250), () => {
|
globalScene.time.delayedCall(fixedInt(250), () => {
|
||||||
this.eggsToHatchCount--;
|
this.eggsToHatchCount--;
|
||||||
this.eggHatchHandler.eventTarget.dispatchEvent(new EggCountChangedEvent(this.eggsToHatchCount));
|
this.eggHatchHandler.eventTarget.dispatchEvent(new EggCountChangedEvent(this.eggsToHatchCount));
|
||||||
this.pokemon.cry();
|
this.pokemon.cry();
|
||||||
if (isShiny) {
|
if (isShiny) {
|
||||||
globalScene.time.delayedCall(Utils.fixedInt(500), () => {
|
globalScene.time.delayedCall(fixedInt(500), () => {
|
||||||
doShinySparkleAnim(this.pokemonShinySparkle, this.pokemon.variant);
|
doShinySparkleAnim(this.pokemonShinySparkle, this.pokemon.variant);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
globalScene.time.delayedCall(
|
globalScene.time.delayedCall(fixedInt(!this.skipped ? (!isShiny ? 1250 : 1750) : !isShiny ? 250 : 750), () => {
|
||||||
Utils.fixedInt(!this.skipped ? (!isShiny ? 1250 : 1750) : !isShiny ? 250 : 750),
|
|
||||||
() => {
|
|
||||||
this.infoContainer.show(this.pokemon, false, this.skipped ? 2 : 1);
|
this.infoContainer.show(this.pokemon, false, this.skipped ? 2 : 1);
|
||||||
|
|
||||||
globalScene.playSoundWithoutBgm("evolution_fanfare");
|
globalScene.playSoundWithoutBgm("evolution_fanfare");
|
||||||
@ -398,11 +396,10 @@ export class EggHatchPhase extends Phase {
|
|||||||
true,
|
true,
|
||||||
3000,
|
3000,
|
||||||
);
|
);
|
||||||
},
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
globalScene.tweens.add({
|
globalScene.tweens.add({
|
||||||
duration: Utils.fixedInt(this.skipped ? 500 : 3000),
|
duration: fixedInt(this.skipped ? 500 : 3000),
|
||||||
targets: this.eggHatchOverlay,
|
targets: this.eggHatchOverlay,
|
||||||
alpha: 0,
|
alpha: 0,
|
||||||
ease: "Cubic.easeOut",
|
ease: "Cubic.easeOut",
|
||||||
@ -427,9 +424,9 @@ export class EggHatchPhase extends Phase {
|
|||||||
doSpray(intensity: number, offsetY?: number) {
|
doSpray(intensity: number, offsetY?: number) {
|
||||||
globalScene.tweens.addCounter({
|
globalScene.tweens.addCounter({
|
||||||
repeat: intensity,
|
repeat: intensity,
|
||||||
duration: Utils.getFrameMs(1),
|
duration: getFrameMs(1),
|
||||||
onRepeat: () => {
|
onRepeat: () => {
|
||||||
this.doSprayParticle(Utils.randInt(8), offsetY || 0);
|
this.doSprayParticle(randInt(8), offsetY || 0);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -448,12 +445,12 @@ export class EggHatchPhase extends Phase {
|
|||||||
|
|
||||||
let f = 0;
|
let f = 0;
|
||||||
let yOffset = 0;
|
let yOffset = 0;
|
||||||
const speed = 3 - Utils.randInt(8);
|
const speed = 3 - randInt(8);
|
||||||
const amp = 24 + Utils.randInt(32);
|
const amp = 24 + randInt(32);
|
||||||
|
|
||||||
const particleTimer = globalScene.tweens.addCounter({
|
const particleTimer = globalScene.tweens.addCounter({
|
||||||
repeat: -1,
|
repeat: -1,
|
||||||
duration: Utils.getFrameMs(1),
|
duration: getFrameMs(1),
|
||||||
onRepeat: () => {
|
onRepeat: () => {
|
||||||
updateParticle();
|
updateParticle();
|
||||||
},
|
},
|
||||||
|
@ -43,10 +43,10 @@ import { getNatureName } from "#app/data/nature";
|
|||||||
export class EncounterPhase extends BattlePhase {
|
export class EncounterPhase extends BattlePhase {
|
||||||
private loaded: boolean;
|
private loaded: boolean;
|
||||||
|
|
||||||
constructor(loaded?: boolean) {
|
constructor(loaded = false) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.loaded = !!loaded;
|
this.loaded = loaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
|
@ -5,7 +5,7 @@ import { globalScene } from "#app/global-scene";
|
|||||||
import type { SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions";
|
import type { SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions";
|
||||||
import { FusionSpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions";
|
import { FusionSpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions";
|
||||||
import type EvolutionSceneHandler from "#app/ui/evolution-scene-handler";
|
import type EvolutionSceneHandler from "#app/ui/evolution-scene-handler";
|
||||||
import * as Utils from "#app/utils";
|
import { fixedInt, getFrameMs, randInt } from "#app/utils";
|
||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
import { cos, sin } from "#app/field/anims";
|
import { cos, sin } from "#app/field/anims";
|
||||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||||
@ -332,9 +332,9 @@ export class EvolutionPhase extends Phase {
|
|||||||
() => this.end(),
|
() => this.end(),
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
Utils.fixedInt(4000),
|
fixedInt(4000),
|
||||||
);
|
);
|
||||||
globalScene.time.delayedCall(Utils.fixedInt(4250), () => globalScene.playBgm());
|
globalScene.time.delayedCall(fixedInt(4250), () => globalScene.playBgm());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -392,7 +392,7 @@ export class EvolutionPhase extends Phase {
|
|||||||
|
|
||||||
globalScene.tweens.addCounter({
|
globalScene.tweens.addCounter({
|
||||||
repeat: 64,
|
repeat: 64,
|
||||||
duration: Utils.getFrameMs(1),
|
duration: getFrameMs(1),
|
||||||
onRepeat: () => {
|
onRepeat: () => {
|
||||||
if (f < 64) {
|
if (f < 64) {
|
||||||
if (!(f & 7)) {
|
if (!(f & 7)) {
|
||||||
@ -411,7 +411,7 @@ export class EvolutionPhase extends Phase {
|
|||||||
|
|
||||||
globalScene.tweens.addCounter({
|
globalScene.tweens.addCounter({
|
||||||
repeat: 96,
|
repeat: 96,
|
||||||
duration: Utils.getFrameMs(1),
|
duration: getFrameMs(1),
|
||||||
onRepeat: () => {
|
onRepeat: () => {
|
||||||
if (f < 96) {
|
if (f < 96) {
|
||||||
if (f < 6) {
|
if (f < 6) {
|
||||||
@ -461,7 +461,7 @@ export class EvolutionPhase extends Phase {
|
|||||||
|
|
||||||
globalScene.tweens.addCounter({
|
globalScene.tweens.addCounter({
|
||||||
repeat: 48,
|
repeat: 48,
|
||||||
duration: Utils.getFrameMs(1),
|
duration: getFrameMs(1),
|
||||||
onRepeat: () => {
|
onRepeat: () => {
|
||||||
if (!f) {
|
if (!f) {
|
||||||
for (let i = 0; i < 16; i++) {
|
for (let i = 0; i < 16; i++) {
|
||||||
@ -482,14 +482,14 @@ export class EvolutionPhase extends Phase {
|
|||||||
|
|
||||||
globalScene.tweens.addCounter({
|
globalScene.tweens.addCounter({
|
||||||
repeat: 48,
|
repeat: 48,
|
||||||
duration: Utils.getFrameMs(1),
|
duration: getFrameMs(1),
|
||||||
onRepeat: () => {
|
onRepeat: () => {
|
||||||
if (!f) {
|
if (!f) {
|
||||||
for (let i = 0; i < 8; i++) {
|
for (let i = 0; i < 8; i++) {
|
||||||
this.doSprayParticle(i);
|
this.doSprayParticle(i);
|
||||||
}
|
}
|
||||||
} else if (f < 50) {
|
} else if (f < 50) {
|
||||||
this.doSprayParticle(Utils.randInt(8));
|
this.doSprayParticle(randInt(8));
|
||||||
}
|
}
|
||||||
f++;
|
f++;
|
||||||
},
|
},
|
||||||
@ -506,7 +506,7 @@ export class EvolutionPhase extends Phase {
|
|||||||
|
|
||||||
const particleTimer = globalScene.tweens.addCounter({
|
const particleTimer = globalScene.tweens.addCounter({
|
||||||
repeat: -1,
|
repeat: -1,
|
||||||
duration: Utils.getFrameMs(1),
|
duration: getFrameMs(1),
|
||||||
onRepeat: () => {
|
onRepeat: () => {
|
||||||
updateParticle();
|
updateParticle();
|
||||||
},
|
},
|
||||||
@ -543,7 +543,7 @@ export class EvolutionPhase extends Phase {
|
|||||||
|
|
||||||
const particleTimer = globalScene.tweens.addCounter({
|
const particleTimer = globalScene.tweens.addCounter({
|
||||||
repeat: -1,
|
repeat: -1,
|
||||||
duration: Utils.getFrameMs(1),
|
duration: getFrameMs(1),
|
||||||
onRepeat: () => {
|
onRepeat: () => {
|
||||||
updateParticle();
|
updateParticle();
|
||||||
},
|
},
|
||||||
@ -575,7 +575,7 @@ export class EvolutionPhase extends Phase {
|
|||||||
|
|
||||||
const particleTimer = globalScene.tweens.addCounter({
|
const particleTimer = globalScene.tweens.addCounter({
|
||||||
repeat: -1,
|
repeat: -1,
|
||||||
duration: Utils.getFrameMs(1),
|
duration: getFrameMs(1),
|
||||||
onRepeat: () => {
|
onRepeat: () => {
|
||||||
updateParticle();
|
updateParticle();
|
||||||
},
|
},
|
||||||
@ -605,12 +605,12 @@ export class EvolutionPhase extends Phase {
|
|||||||
|
|
||||||
let f = 0;
|
let f = 0;
|
||||||
let yOffset = 0;
|
let yOffset = 0;
|
||||||
const speed = 3 - Utils.randInt(8);
|
const speed = 3 - randInt(8);
|
||||||
const amp = 48 + Utils.randInt(64);
|
const amp = 48 + randInt(64);
|
||||||
|
|
||||||
const particleTimer = globalScene.tweens.addCounter({
|
const particleTimer = globalScene.tweens.addCounter({
|
||||||
repeat: -1,
|
repeat: -1,
|
||||||
duration: Utils.getFrameMs(1),
|
duration: getFrameMs(1),
|
||||||
onRepeat: () => {
|
onRepeat: () => {
|
||||||
updateParticle();
|
updateParticle();
|
||||||
},
|
},
|
||||||
|
@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene";
|
|||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import { ExpBoosterModifier } from "#app/modifier/modifier";
|
import { ExpBoosterModifier } from "#app/modifier/modifier";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import * as Utils from "#app/utils";
|
import { NumberHolder } from "#app/utils";
|
||||||
import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase";
|
import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase";
|
||||||
import { LevelUpPhase } from "./level-up-phase";
|
import { LevelUpPhase } from "./level-up-phase";
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ export class ExpPhase extends PlayerPartyMemberPokemonPhase {
|
|||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
const pokemon = this.getPokemon();
|
const pokemon = this.getPokemon();
|
||||||
const exp = new Utils.NumberHolder(this.expValue);
|
const exp = new NumberHolder(this.expValue);
|
||||||
globalScene.applyModifiers(ExpBoosterModifier, true, exp);
|
globalScene.applyModifiers(ExpBoosterModifier, true, exp);
|
||||||
exp.value = Math.floor(exp.value);
|
exp.value = Math.floor(exp.value);
|
||||||
globalScene.ui.showText(
|
globalScene.ui.showText(
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import * as Utils from "../utils";
|
import { fixedInt } from "#app/utils";
|
||||||
import { achvs } from "../system/achv";
|
import { achvs } from "../system/achv";
|
||||||
import type { SpeciesFormChange } from "../data/pokemon-forms";
|
import type { SpeciesFormChange } from "../data/pokemon-forms";
|
||||||
import { getSpeciesFormChangeMessage } from "../data/pokemon-forms";
|
import { getSpeciesFormChangeMessage } from "../data/pokemon-forms";
|
||||||
@ -9,7 +9,7 @@ import type PartyUiHandler from "../ui/party-ui-handler";
|
|||||||
import { getPokemonNameWithAffix } from "../messages";
|
import { getPokemonNameWithAffix } from "../messages";
|
||||||
import { EndEvolutionPhase } from "./end-evolution-phase";
|
import { EndEvolutionPhase } from "./end-evolution-phase";
|
||||||
import { EvolutionPhase } from "./evolution-phase";
|
import { EvolutionPhase } from "./evolution-phase";
|
||||||
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
import { SpeciesFormKey } from "#enums/species-form-key";
|
import { SpeciesFormKey } from "#enums/species-form-key";
|
||||||
|
|
||||||
export class FormChangePhase extends EvolutionPhase {
|
export class FormChangePhase extends EvolutionPhase {
|
||||||
@ -151,9 +151,9 @@ export class FormChangePhase extends EvolutionPhase {
|
|||||||
() => this.end(),
|
() => this.end(),
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
Utils.fixedInt(delay),
|
fixedInt(delay),
|
||||||
);
|
);
|
||||||
globalScene.time.delayedCall(Utils.fixedInt(delay + 250), () =>
|
globalScene.time.delayedCall(fixedInt(delay + 250), () =>
|
||||||
globalScene.playBgm(),
|
globalScene.playBgm(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -20,7 +20,7 @@ import { UnlockPhase } from "#app/phases/unlock-phase";
|
|||||||
import { achvs, ChallengeAchv } from "#app/system/achv";
|
import { achvs, ChallengeAchv } from "#app/system/achv";
|
||||||
import { Unlockables } from "#app/system/unlockables";
|
import { Unlockables } from "#app/system/unlockables";
|
||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
import * as Utils from "#app/utils";
|
import { isLocal, isLocalServerConnected } from "#app/utils";
|
||||||
import { PlayerGender } from "#enums/player-gender";
|
import { PlayerGender } from "#enums/player-gender";
|
||||||
import { TrainerType } from "#enums/trainer-type";
|
import { TrainerType } from "#enums/trainer-type";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
@ -219,7 +219,7 @@ export class GameOverPhase extends BattlePhase {
|
|||||||
/* Added a local check to see if the game is running offline
|
/* Added a local check to see if the game is running offline
|
||||||
If Online, execute apiFetch as intended
|
If Online, execute apiFetch as intended
|
||||||
If Offline, execute offlineNewClear() only for victory, a localStorage implementation of newClear daily run checks */
|
If Offline, execute offlineNewClear() only for victory, a localStorage implementation of newClear daily run checks */
|
||||||
if (!Utils.isLocal || Utils.isLocalServerConnected) {
|
if (!isLocal || isLocalServerConnected) {
|
||||||
pokerogueApi.savedata.session
|
pokerogueApi.savedata.session
|
||||||
.newclear({
|
.newclear({
|
||||||
slot: globalScene.sessionSlotId,
|
slot: globalScene.sessionSlotId,
|
||||||
|
@ -5,26 +5,26 @@ import { Phase } from "#app/phase";
|
|||||||
import { handleTutorial, Tutorial } from "#app/tutorial";
|
import { handleTutorial, Tutorial } from "#app/tutorial";
|
||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
import i18next, { t } from "i18next";
|
import i18next, { t } from "i18next";
|
||||||
import * as Utils from "#app/utils";
|
import { getCookie, sessionIdKey, executeIf, removeCookie } from "#app/utils";
|
||||||
import { SelectGenderPhase } from "./select-gender-phase";
|
import { SelectGenderPhase } from "./select-gender-phase";
|
||||||
import { UnavailablePhase } from "./unavailable-phase";
|
import { UnavailablePhase } from "./unavailable-phase";
|
||||||
|
|
||||||
export class LoginPhase extends Phase {
|
export class LoginPhase extends Phase {
|
||||||
private showText: boolean;
|
private showText: boolean;
|
||||||
|
|
||||||
constructor(showText?: boolean) {
|
constructor(showText = true) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.showText = showText === undefined || !!showText;
|
this.showText = showText;
|
||||||
}
|
}
|
||||||
|
|
||||||
start(): void {
|
start(): void {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
const hasSession = !!Utils.getCookie(Utils.sessionIdKey);
|
const hasSession = !!getCookie(sessionIdKey);
|
||||||
|
|
||||||
globalScene.ui.setMode(Mode.LOADING, { buttonActions: [] });
|
globalScene.ui.setMode(Mode.LOADING, { buttonActions: [] });
|
||||||
Utils.executeIf(bypassLogin || hasSession, updateUserInfo).then(response => {
|
executeIf(bypassLogin || hasSession, updateUserInfo).then(response => {
|
||||||
const success = response ? response[0] : false;
|
const success = response ? response[0] : false;
|
||||||
const statusCode = response ? response[1] : null;
|
const statusCode = response ? response[1] : null;
|
||||||
if (!success) {
|
if (!success) {
|
||||||
@ -38,7 +38,7 @@ export class LoginPhase extends Phase {
|
|||||||
const loadData = () => {
|
const loadData = () => {
|
||||||
updateUserInfo().then(success => {
|
updateUserInfo().then(success => {
|
||||||
if (!success[0]) {
|
if (!success[0]) {
|
||||||
Utils.removeCookie(Utils.sessionIdKey);
|
removeCookie(sessionIdKey);
|
||||||
globalScene.reset(true, true);
|
globalScene.reset(true, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -60,7 +60,7 @@ export class LoginPhase extends Phase {
|
|||||||
globalScene.ui.playSelect();
|
globalScene.ui.playSelect();
|
||||||
updateUserInfo().then(success => {
|
updateUserInfo().then(success => {
|
||||||
if (!success[0]) {
|
if (!success[0]) {
|
||||||
Utils.removeCookie(Utils.sessionIdKey);
|
removeCookie(sessionIdKey);
|
||||||
globalScene.reset(true, true);
|
globalScene.reset(true, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -89,7 +89,7 @@ export class LoginPhase extends Phase {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
} else if (statusCode === 401) {
|
} else if (statusCode === 401) {
|
||||||
Utils.removeCookie(Utils.sessionIdKey);
|
removeCookie(sessionIdKey);
|
||||||
globalScene.reset(true, true);
|
globalScene.reset(true, true);
|
||||||
} else {
|
} else {
|
||||||
globalScene.unshiftPhase(new UnavailablePhase());
|
globalScene.unshiftPhase(new UnavailablePhase());
|
||||||
|
@ -3,9 +3,9 @@ import { Phase } from "#app/phase";
|
|||||||
|
|
||||||
export class MessagePhase extends Phase {
|
export class MessagePhase extends Phase {
|
||||||
private text: string;
|
private text: string;
|
||||||
private callbackDelay: number | null;
|
private callbackDelay?: number | null;
|
||||||
private prompt: boolean | null;
|
private prompt?: boolean | null;
|
||||||
private promptDelay: number | null;
|
private promptDelay?: number | null;
|
||||||
private speaker?: string;
|
private speaker?: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -18,9 +18,9 @@ export class MessagePhase extends Phase {
|
|||||||
super();
|
super();
|
||||||
|
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.callbackDelay = callbackDelay!; // TODO: is this bang correct?
|
this.callbackDelay = callbackDelay;
|
||||||
this.prompt = prompt!; // TODO: is this bang correct?
|
this.prompt = prompt;
|
||||||
this.promptDelay = promptDelay!; // TODO: is this bang correct?
|
this.promptDelay = promptDelay;
|
||||||
this.speaker = speaker;
|
this.speaker = speaker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene";
|
|||||||
import { ArenaTagType } from "#app/enums/arena-tag-type";
|
import { ArenaTagType } from "#app/enums/arena-tag-type";
|
||||||
import { MoneyMultiplierModifier } from "#app/modifier/modifier";
|
import { MoneyMultiplierModifier } from "#app/modifier/modifier";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import * as Utils from "#app/utils";
|
import { NumberHolder } from "#app/utils";
|
||||||
import { BattlePhase } from "./battle-phase";
|
import { BattlePhase } from "./battle-phase";
|
||||||
|
|
||||||
export class MoneyRewardPhase extends BattlePhase {
|
export class MoneyRewardPhase extends BattlePhase {
|
||||||
@ -15,7 +15,7 @@ export class MoneyRewardPhase extends BattlePhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
const moneyAmount = new Utils.NumberHolder(globalScene.getWaveMoneyAmount(this.moneyMultiplier));
|
const moneyAmount = new NumberHolder(globalScene.getWaveMoneyAmount(this.moneyMultiplier));
|
||||||
|
|
||||||
globalScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount);
|
globalScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount);
|
||||||
|
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
|
||||||
import { initMoveAnim, loadMoveAnimAssets, MoveAnim } from "#app/data/battle-anims";
|
|
||||||
import { allMoves, SelfStatusMove } from "#app/data/moves/move";
|
|
||||||
import { Moves } from "#app/enums/moves";
|
|
||||||
import * as Utils from "#app/utils";
|
|
||||||
import { BattlePhase } from "./battle-phase";
|
|
||||||
|
|
||||||
export class MoveAnimTestPhase extends BattlePhase {
|
|
||||||
private moveQueue: Moves[];
|
|
||||||
|
|
||||||
constructor(moveQueue?: Moves[]) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.moveQueue = moveQueue || Utils.getEnumValues(Moves).slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
|
||||||
const moveQueue = this.moveQueue.slice(0);
|
|
||||||
this.playMoveAnim(moveQueue, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
playMoveAnim(moveQueue: Moves[], player: boolean) {
|
|
||||||
const moveId = player ? moveQueue[0] : moveQueue.shift();
|
|
||||||
if (moveId === undefined) {
|
|
||||||
this.playMoveAnim(this.moveQueue.slice(0), true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (player) {
|
|
||||||
console.log(Moves[moveId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
initMoveAnim(moveId).then(() => {
|
|
||||||
loadMoveAnimAssets([moveId], true).then(() => {
|
|
||||||
const user = player ? globalScene.getPlayerPokemon()! : globalScene.getEnemyPokemon()!;
|
|
||||||
const target =
|
|
||||||
player !== allMoves[moveId] instanceof SelfStatusMove
|
|
||||||
? globalScene.getEnemyPokemon()!
|
|
||||||
: globalScene.getPlayerPokemon()!;
|
|
||||||
new MoveAnim(moveId, user, target.getBattlerIndex()).play(allMoves[moveId].hitsSubstitute(user, target), () => {
|
|
||||||
// TODO: are the bangs correct here?
|
|
||||||
if (player) {
|
|
||||||
this.playMoveAnim(moveQueue, false);
|
|
||||||
} else {
|
|
||||||
this.playMoveAnim(moveQueue, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -26,6 +26,7 @@ import {
|
|||||||
} from "#app/data/battler-tags";
|
} from "#app/data/battler-tags";
|
||||||
import type { MoveAttr } from "#app/data/moves/move";
|
import type { MoveAttr } from "#app/data/moves/move";
|
||||||
import {
|
import {
|
||||||
|
AddArenaTrapTagAttr,
|
||||||
applyFilteredMoveAttrs,
|
applyFilteredMoveAttrs,
|
||||||
applyMoveAttrs,
|
applyMoveAttrs,
|
||||||
AttackMove,
|
AttackMove,
|
||||||
@ -209,12 +210,12 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
targets.some(t => t.hasAbilityWithAttr(ReflectStatusMoveAbAttr) || !!t.getTag(BattlerTagType.MAGIC_COAT));
|
targets.some(t => t.hasAbilityWithAttr(ReflectStatusMoveAbAttr) || !!t.getTag(BattlerTagType.MAGIC_COAT));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If no targets are left for the move to hit (FAIL), or the invoked move is non-reflectable, single-target
|
* If no targets are left for the move to hit and it is not a hazard move (FAIL), or the invoked move is non-reflectable, single-target
|
||||||
* (and not random target) and failed the hit check against its target (MISS), log the move
|
* (and not random target) and failed the hit check against its target (MISS), log the move
|
||||||
* as FAILed or MISSed (depending on the conditions above) and end this phase.
|
* as FAILed or MISSed (depending on the conditions above) and end this phase.
|
||||||
*/
|
*/
|
||||||
if (
|
if (
|
||||||
!hasActiveTargets ||
|
(!hasActiveTargets && !move.hasAttr(AddArenaTrapTagAttr)) ||
|
||||||
(!mayBounce &&
|
(!mayBounce &&
|
||||||
!move.hasAttr(VariableTargetAttr) &&
|
!move.hasAttr(VariableTargetAttr) &&
|
||||||
!move.isMultiTarget() &&
|
!move.isMultiTarget() &&
|
||||||
@ -239,18 +240,28 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
return this.end();
|
return this.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
const playOnEmptyField = globalScene.currentBattle?.mysteryEncounter?.hasBattleAnimationsWithoutTargets ?? false;
|
const playOnEmptyField =
|
||||||
// Move animation only needs one target
|
(globalScene.currentBattle?.mysteryEncounter?.hasBattleAnimationsWithoutTargets ?? false) ||
|
||||||
new MoveAnim(move.id as Moves, user, this.getFirstTarget()!.getBattlerIndex(), playOnEmptyField).play(
|
(!hasActiveTargets && move.hasAttr(AddArenaTrapTagAttr));
|
||||||
move.hitsSubstitute(user, this.getFirstTarget()!),
|
// Move animation only needs one target. The attacker is used as a fallback.
|
||||||
() => {
|
new MoveAnim(
|
||||||
|
move.id as Moves,
|
||||||
|
user,
|
||||||
|
this.getFirstTarget()?.getBattlerIndex() ?? BattlerIndex.ATTACKER,
|
||||||
|
playOnEmptyField,
|
||||||
|
).play(move.hitsSubstitute(user, this.getFirstTarget()!), () => {
|
||||||
/** Has the move successfully hit a target (for damage) yet? */
|
/** Has the move successfully hit a target (for damage) yet? */
|
||||||
let hasHit = false;
|
let hasHit = false;
|
||||||
|
|
||||||
// Prevent ENEMY_SIDE targeted moves from occurring twice in double battles
|
// Prevent ENEMY_SIDE targeted moves from occurring twice in double battles
|
||||||
// and check which target will magic bounce.
|
// and check which target will magic bounce.
|
||||||
|
// In the event that the move is a hazard move, there may be no target and the move should still succeed.
|
||||||
|
// In this case, the user is used as the "target" to prevent a crash.
|
||||||
|
// This should not affect normal execution of the move otherwise.
|
||||||
const trueTargets: Pokemon[] =
|
const trueTargets: Pokemon[] =
|
||||||
move.moveTarget !== MoveTarget.ENEMY_SIDE
|
!hasActiveTargets && move.hasAttr(AddArenaTrapTagAttr)
|
||||||
|
? [user]
|
||||||
|
: move.moveTarget !== MoveTarget.ENEMY_SIDE
|
||||||
? targets
|
? targets
|
||||||
: (() => {
|
: (() => {
|
||||||
const magicCoatTargets = targets.filter(
|
const magicCoatTargets = targets.filter(
|
||||||
@ -289,7 +300,8 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
/** Is the target protected by Protect, etc. or a relevant conditional protection effect? */
|
/** Is the target protected by Protect, etc. or a relevant conditional protection effect? */
|
||||||
const isProtected =
|
const isProtected =
|
||||||
![MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES].includes(this.move.getMove().moveTarget) &&
|
![MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES].includes(this.move.getMove().moveTarget) &&
|
||||||
(bypassIgnoreProtect.value || !this.move.getMove().checkFlag(MoveFlags.IGNORE_PROTECT, user, target)) &&
|
(bypassIgnoreProtect.value ||
|
||||||
|
!this.move.getMove().doesFlagEffectApply({ flag: MoveFlags.IGNORE_PROTECT, user, target })) &&
|
||||||
(hasConditionalProtectApplied.value ||
|
(hasConditionalProtectApplied.value ||
|
||||||
(!target.findTags(t => t instanceof DamageProtectedTag).length &&
|
(!target.findTags(t => t instanceof DamageProtectedTag).length &&
|
||||||
target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType))) ||
|
target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType))) ||
|
||||||
@ -307,7 +319,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
/** Is the target's magic bounce ability not ignored and able to reflect this move? */
|
/** Is the target's magic bounce ability not ignored and able to reflect this move? */
|
||||||
const canMagicBounce =
|
const canMagicBounce =
|
||||||
!isReflecting &&
|
!isReflecting &&
|
||||||
!move.checkFlag(MoveFlags.IGNORE_ABILITIES, user, target) &&
|
!move.doesFlagEffectApply({ flag: MoveFlags.IGNORE_ABILITIES, user, target }) &&
|
||||||
target.hasAbilityWithAttr(ReflectStatusMoveAbAttr);
|
target.hasAbilityWithAttr(ReflectStatusMoveAbAttr);
|
||||||
|
|
||||||
const semiInvulnerableTag = target.getTag(SemiInvulnerableTag);
|
const semiInvulnerableTag = target.getTag(SemiInvulnerableTag);
|
||||||
@ -337,9 +349,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
queuedPhases.push(new HideAbilityPhase());
|
queuedPhases.push(new HideAbilityPhase());
|
||||||
}
|
}
|
||||||
|
|
||||||
queuedPhases.push(
|
queuedPhases.push(new MovePhase(target, newTargets, new PokemonMove(move.id, 0, 0, true), true, true, true));
|
||||||
new MovePhase(target, newTargets, new PokemonMove(move.id, 0, 0, true), true, true, true),
|
|
||||||
);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,7 +366,10 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
if (
|
if (
|
||||||
target.switchOutStatus ||
|
target.switchOutStatus ||
|
||||||
isCommanding ||
|
isCommanding ||
|
||||||
(!isImmune && !isProtected && !targetHitChecks[target.getBattlerIndex()])
|
(!isImmune &&
|
||||||
|
!isProtected &&
|
||||||
|
!targetHitChecks[target.getBattlerIndex()] &&
|
||||||
|
!move.hasAttr(AddArenaTrapTagAttr))
|
||||||
) {
|
) {
|
||||||
this.stopMultiHit(target);
|
this.stopMultiHit(target);
|
||||||
if (!target.switchOutStatus) {
|
if (!target.switchOutStatus) {
|
||||||
@ -500,8 +513,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.end();
|
this.end();
|
||||||
},
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override end(): void {
|
public override end(): void {
|
||||||
|
@ -2,11 +2,18 @@ import { globalScene } from "#app/global-scene";
|
|||||||
import { BattlerTagLapseType } from "#app/data/battler-tags";
|
import { BattlerTagLapseType } from "#app/data/battler-tags";
|
||||||
import { PokemonPhase } from "./pokemon-phase";
|
import { PokemonPhase } from "./pokemon-phase";
|
||||||
import type { BattlerIndex } from "#app/battle";
|
import type { BattlerIndex } from "#app/battle";
|
||||||
|
import { applyPostSummonAbAttrs, PostSummonRemoveEffectAbAttr } from "#app/data/ability";
|
||||||
|
import type Pokemon from "#app/field/pokemon";
|
||||||
|
|
||||||
export class MoveEndPhase extends PokemonPhase {
|
export class MoveEndPhase extends PokemonPhase {
|
||||||
private wasFollowUp: boolean;
|
private wasFollowUp: boolean;
|
||||||
constructor(battlerIndex: BattlerIndex, wasFollowUp: boolean = false) {
|
|
||||||
|
/** Targets from the preceding MovePhase */
|
||||||
|
private targets: Pokemon[];
|
||||||
|
constructor(battlerIndex: BattlerIndex, targets: Pokemon[], wasFollowUp = false) {
|
||||||
super(battlerIndex);
|
super(battlerIndex);
|
||||||
|
|
||||||
|
this.targets = targets;
|
||||||
this.wasFollowUp = wasFollowUp;
|
this.wasFollowUp = wasFollowUp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,9 +24,15 @@ export class MoveEndPhase extends PokemonPhase {
|
|||||||
if (!this.wasFollowUp && pokemon?.isActive(true)) {
|
if (!this.wasFollowUp && pokemon?.isActive(true)) {
|
||||||
pokemon.lapseTags(BattlerTagLapseType.AFTER_MOVE);
|
pokemon.lapseTags(BattlerTagLapseType.AFTER_MOVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
globalScene.arena.setIgnoreAbilities(false);
|
globalScene.arena.setIgnoreAbilities(false);
|
||||||
|
|
||||||
|
// Remove effects which were set on a Pokemon which removes them on summon (i.e. via Mold Breaker)
|
||||||
|
for (const target of this.targets) {
|
||||||
|
if (target) {
|
||||||
|
applyPostSummonAbAttrs(PostSummonRemoveEffectAbAttr, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.end();
|
this.end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import type { DelayedAttackTag } from "#app/data/arena-tag";
|
|||||||
import { CommonAnim } from "#app/data/battle-anims";
|
import { CommonAnim } from "#app/data/battle-anims";
|
||||||
import { BattlerTagLapseType, CenterOfAttentionTag } from "#app/data/battler-tags";
|
import { BattlerTagLapseType, CenterOfAttentionTag } from "#app/data/battler-tags";
|
||||||
import {
|
import {
|
||||||
|
AddArenaTrapTagAttr,
|
||||||
allMoves,
|
allMoves,
|
||||||
applyMoveAttrs,
|
applyMoveAttrs,
|
||||||
BypassRedirectAttr,
|
BypassRedirectAttr,
|
||||||
@ -168,7 +169,11 @@ export class MovePhase extends BattlePhase {
|
|||||||
|
|
||||||
// Check move to see if arena.ignoreAbilities should be true.
|
// Check move to see if arena.ignoreAbilities should be true.
|
||||||
if (!this.followUp || this.reflected) {
|
if (!this.followUp || this.reflected) {
|
||||||
if (this.move.getMove().checkFlag(MoveFlags.IGNORE_ABILITIES, this.pokemon, null)) {
|
if (
|
||||||
|
this.move
|
||||||
|
.getMove()
|
||||||
|
.doesFlagEffectApply({ flag: MoveFlags.IGNORE_ABILITIES, user: this.pokemon, isFollowUp: this.followUp })
|
||||||
|
) {
|
||||||
globalScene.arena.setIgnoreAbilities(true, this.pokemon.getBattlerIndex());
|
globalScene.arena.setIgnoreAbilities(true, this.pokemon.getBattlerIndex());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,7 +206,10 @@ export class MovePhase extends BattlePhase {
|
|||||||
const targets = this.getActiveTargetPokemon();
|
const targets = this.getActiveTargetPokemon();
|
||||||
const moveQueue = this.pokemon.getMoveQueue();
|
const moveQueue = this.pokemon.getMoveQueue();
|
||||||
|
|
||||||
if (targets.length === 0 || (moveQueue.length && moveQueue[0].move === Moves.NONE)) {
|
if (
|
||||||
|
(targets.length === 0 && !this.move.getMove().hasAttr(AddArenaTrapTagAttr)) ||
|
||||||
|
(moveQueue.length && moveQueue[0].move === Moves.NONE)
|
||||||
|
) {
|
||||||
this.showMoveText();
|
this.showMoveText();
|
||||||
this.showFailedText();
|
this.showFailedText();
|
||||||
this.cancel();
|
this.cancel();
|
||||||
@ -227,7 +235,7 @@ export class MovePhase extends BattlePhase {
|
|||||||
(!this.pokemon.randSeedInt(4) || Overrides.STATUS_ACTIVATION_OVERRIDE === true) &&
|
(!this.pokemon.randSeedInt(4) || Overrides.STATUS_ACTIVATION_OVERRIDE === true) &&
|
||||||
Overrides.STATUS_ACTIVATION_OVERRIDE !== false;
|
Overrides.STATUS_ACTIVATION_OVERRIDE !== false;
|
||||||
break;
|
break;
|
||||||
case StatusEffect.SLEEP:
|
case StatusEffect.SLEEP: {
|
||||||
applyMoveAttrs(BypassSleepAttr, this.pokemon, null, this.move.getMove());
|
applyMoveAttrs(BypassSleepAttr, this.pokemon, null, this.move.getMove());
|
||||||
const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0);
|
const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0);
|
||||||
applyAbAttrs(
|
applyAbAttrs(
|
||||||
@ -242,6 +250,7 @@ export class MovePhase extends BattlePhase {
|
|||||||
healed = this.pokemon.status.sleepTurnsRemaining <= 0;
|
healed = this.pokemon.status.sleepTurnsRemaining <= 0;
|
||||||
activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP);
|
activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case StatusEffect.FREEZE:
|
case StatusEffect.FREEZE:
|
||||||
healed =
|
healed =
|
||||||
!!this.move
|
!!this.move
|
||||||
@ -468,7 +477,9 @@ export class MovePhase extends BattlePhase {
|
|||||||
* Queues a {@linkcode MoveEndPhase} and then ends the phase
|
* Queues a {@linkcode MoveEndPhase} and then ends the phase
|
||||||
*/
|
*/
|
||||||
public end(): void {
|
public end(): void {
|
||||||
globalScene.unshiftPhase(new MoveEndPhase(this.pokemon.getBattlerIndex(), this.followUp));
|
globalScene.unshiftPhase(
|
||||||
|
new MoveEndPhase(this.pokemon.getBattlerIndex(), this.getActiveTargetPokemon(), this.followUp),
|
||||||
|
);
|
||||||
|
|
||||||
super.end();
|
super.end();
|
||||||
}
|
}
|
||||||
|
@ -26,8 +26,7 @@ import { TrainerSlot } from "#enums/trainer-slot";
|
|||||||
import { IvScannerModifier } from "../modifier/modifier";
|
import { IvScannerModifier } from "../modifier/modifier";
|
||||||
import { Phase } from "../phase";
|
import { Phase } from "../phase";
|
||||||
import { Mode } from "../ui/ui";
|
import { Mode } from "../ui/ui";
|
||||||
import * as Utils from "../utils";
|
import { isNullOrUndefined, randSeedItem } from "#app/utils";
|
||||||
import { isNullOrUndefined } from "../utils";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will handle (in order):
|
* Will handle (in order):
|
||||||
@ -387,7 +386,7 @@ export class MysteryEncounterBattlePhase extends Phase {
|
|||||||
const trainer = globalScene.currentBattle.trainer;
|
const trainer = globalScene.currentBattle.trainer;
|
||||||
let message: string;
|
let message: string;
|
||||||
globalScene.executeWithSeedOffset(
|
globalScene.executeWithSeedOffset(
|
||||||
() => (message = Utils.randSeedItem(encounterMessages)),
|
() => (message = randSeedItem(encounterMessages)),
|
||||||
globalScene.currentBattle.mysteryEncounter?.getSeedOffset(),
|
globalScene.currentBattle.mysteryEncounter?.getSeedOffset(),
|
||||||
);
|
);
|
||||||
message = message!; // tell TS compiler it's defined now
|
message = message!; // tell TS compiler it's defined now
|
||||||
|
@ -4,10 +4,6 @@ import { getRandomWeatherType } from "#app/data/weather";
|
|||||||
import { NextEncounterPhase } from "./next-encounter-phase";
|
import { NextEncounterPhase } from "./next-encounter-phase";
|
||||||
|
|
||||||
export class NewBiomeEncounterPhase extends NextEncounterPhase {
|
export class NewBiomeEncounterPhase extends NextEncounterPhase {
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
doEncounter(): void {
|
doEncounter(): void {
|
||||||
globalScene.playBgm(undefined, true);
|
globalScene.playBgm(undefined, true);
|
||||||
|
|
||||||
|
@ -2,10 +2,6 @@ import { globalScene } from "#app/global-scene";
|
|||||||
import { EncounterPhase } from "./encounter-phase";
|
import { EncounterPhase } from "./encounter-phase";
|
||||||
|
|
||||||
export class NextEncounterPhase extends EncounterPhase {
|
export class NextEncounterPhase extends EncounterPhase {
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
super.start();
|
super.start();
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,9 @@ import { StatusEffect } from "#app/enums/status-effect";
|
|||||||
import type Pokemon from "#app/field/pokemon";
|
import type 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 { SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms";
|
||||||
|
import { applyPostSetStatusAbAttrs, PostSetStatusAbAttr } from "#app/data/ability";
|
||||||
|
import { isNullOrUndefined } from "#app/utils";
|
||||||
|
|
||||||
export class ObtainStatusEffectPhase extends PokemonPhase {
|
export class ObtainStatusEffectPhase extends PokemonPhase {
|
||||||
private statusEffect?: StatusEffect;
|
private statusEffect?: StatusEffect;
|
||||||
@ -44,6 +47,12 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
|
|||||||
this.sourceText ?? undefined,
|
this.sourceText ?? undefined,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
if (!isNullOrUndefined(this.statusEffect) && this.statusEffect !== StatusEffect.FAINT) {
|
||||||
|
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeStatusEffectTrigger, true);
|
||||||
|
// If mold breaker etc was used to set this status, it shouldn't apply to abilities activated afterwards
|
||||||
|
globalScene.arena.setIgnoreAbilities(false);
|
||||||
|
applyPostSetStatusAbAttrs(PostSetStatusAbAttr, pokemon, this.statusEffect, this.sourcePokemon);
|
||||||
|
}
|
||||||
this.end();
|
this.end();
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import * as Utils from "#app/utils";
|
import { fixedInt } from "#app/utils";
|
||||||
import { BattlePhase } from "./battle-phase";
|
import { BattlePhase } from "./battle-phase";
|
||||||
|
|
||||||
export class PartyHealPhase extends BattlePhase {
|
export class PartyHealPhase extends BattlePhase {
|
||||||
@ -28,7 +28,7 @@ export class PartyHealPhase extends BattlePhase {
|
|||||||
pokemon.updateInfo(true);
|
pokemon.updateInfo(true);
|
||||||
}
|
}
|
||||||
const healSong = globalScene.playSoundWithoutBgm("heal");
|
const healSong = globalScene.playSoundWithoutBgm("heal");
|
||||||
globalScene.time.delayedCall(Utils.fixedInt(healSong.totalDuration * 1000), () => {
|
globalScene.time.delayedCall(fixedInt(healSong.totalDuration * 1000), () => {
|
||||||
healSong.destroy();
|
healSong.destroy();
|
||||||
if (this.resumeBgm && bgmPlaying) {
|
if (this.resumeBgm && bgmPlaying) {
|
||||||
globalScene.playBgm();
|
globalScene.playBgm();
|
||||||
|
@ -8,18 +8,18 @@ import { Species } from "#enums/species";
|
|||||||
|
|
||||||
export class PokemonAnimPhase extends BattlePhase {
|
export class PokemonAnimPhase extends BattlePhase {
|
||||||
/** The type of animation to play in this phase */
|
/** The type of animation to play in this phase */
|
||||||
private key: PokemonAnimType;
|
protected key: PokemonAnimType;
|
||||||
/** The Pokemon to which this animation applies */
|
/** The Pokemon to which this animation applies */
|
||||||
private pokemon: Pokemon;
|
protected pokemon: Pokemon;
|
||||||
/** Any other field sprites affected by this animation */
|
/** Any other field sprites affected by this animation */
|
||||||
private fieldAssets: Phaser.GameObjects.Sprite[];
|
protected fieldAssets: Phaser.GameObjects.Sprite[];
|
||||||
|
|
||||||
constructor(key: PokemonAnimType, pokemon: Pokemon, fieldAssets?: Phaser.GameObjects.Sprite[]) {
|
constructor(key: PokemonAnimType, pokemon: Pokemon, fieldAssets: Phaser.GameObjects.Sprite[] = []) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.pokemon = pokemon;
|
this.pokemon = pokemon;
|
||||||
this.fieldAssets = fieldAssets ?? [];
|
this.fieldAssets = fieldAssets;
|
||||||
}
|
}
|
||||||
|
|
||||||
start(): void {
|
start(): void {
|
||||||
|
@ -8,7 +8,7 @@ import { getPokemonNameWithAffix } from "#app/messages";
|
|||||||
import { HealingBoosterModifier } from "#app/modifier/modifier";
|
import { HealingBoosterModifier } from "#app/modifier/modifier";
|
||||||
import { HealAchv } from "#app/system/achv";
|
import { HealAchv } from "#app/system/achv";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import * as Utils from "#app/utils";
|
import { NumberHolder } from "#app/utils";
|
||||||
import { CommonAnimPhase } from "./common-anim-phase";
|
import { CommonAnimPhase } from "./common-anim-phase";
|
||||||
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
||||||
import type { HealBlockTag } from "#app/data/battler-tags";
|
import type { HealBlockTag } from "#app/data/battler-tags";
|
||||||
@ -72,11 +72,11 @@ export class PokemonHealPhase extends CommonAnimPhase {
|
|||||||
return super.end();
|
return super.end();
|
||||||
}
|
}
|
||||||
if (healOrDamage) {
|
if (healOrDamage) {
|
||||||
const hpRestoreMultiplier = new Utils.NumberHolder(1);
|
const hpRestoreMultiplier = new NumberHolder(1);
|
||||||
if (!this.revive) {
|
if (!this.revive) {
|
||||||
globalScene.applyModifiers(HealingBoosterModifier, this.player, hpRestoreMultiplier);
|
globalScene.applyModifiers(HealingBoosterModifier, this.player, hpRestoreMultiplier);
|
||||||
}
|
}
|
||||||
const healAmount = new Utils.NumberHolder(Math.floor(this.hpHealed * hpRestoreMultiplier.value));
|
const healAmount = new NumberHolder(Math.floor(this.hpHealed * hpRestoreMultiplier.value));
|
||||||
if (healAmount.value < 0) {
|
if (healAmount.value < 0) {
|
||||||
pokemon.damageAndUpdate(healAmount.value * -1, { result: HitResult.INDIRECT });
|
pokemon.damageAndUpdate(healAmount.value * -1, { result: HitResult.INDIRECT });
|
||||||
healAmount.value = 0;
|
healAmount.value = 0;
|
||||||
|
@ -11,11 +11,14 @@ export abstract class PokemonPhase extends FieldPhase {
|
|||||||
constructor(battlerIndex?: BattlerIndex | number) {
|
constructor(battlerIndex?: BattlerIndex | number) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
if (battlerIndex === undefined) {
|
battlerIndex =
|
||||||
battlerIndex = globalScene
|
battlerIndex ??
|
||||||
|
globalScene
|
||||||
.getField()
|
.getField()
|
||||||
.find(p => p?.isActive())!
|
.find(p => p?.isActive())! // TODO: is the bang correct here?
|
||||||
.getBattlerIndex(); // TODO: is the bang correct here?
|
.getBattlerIndex();
|
||||||
|
if (battlerIndex === undefined) {
|
||||||
|
console.warn("There are no Pokemon on the field!"); // TODO: figure out a suitable fallback behavior
|
||||||
}
|
}
|
||||||
|
|
||||||
this.battlerIndex = battlerIndex;
|
this.battlerIndex = battlerIndex;
|
||||||
|
@ -4,12 +4,12 @@ import type { EndCardPhase } from "./end-card-phase";
|
|||||||
import { TitlePhase } from "./title-phase";
|
import { TitlePhase } from "./title-phase";
|
||||||
|
|
||||||
export class PostGameOverPhase extends Phase {
|
export class PostGameOverPhase extends Phase {
|
||||||
private endCardPhase: EndCardPhase | null;
|
private endCardPhase?: EndCardPhase;
|
||||||
|
|
||||||
constructor(endCardPhase?: EndCardPhase) {
|
constructor(endCardPhase?: EndCardPhase) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.endCardPhase = endCardPhase!; // TODO: is this bang correct?
|
this.endCardPhase = endCardPhase;
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
|
@ -13,7 +13,7 @@ import { getStatusEffectActivationText } from "#app/data/status-effect";
|
|||||||
import { BattleSpec } from "#app/enums/battle-spec";
|
import { BattleSpec } from "#app/enums/battle-spec";
|
||||||
import { StatusEffect } from "#app/enums/status-effect";
|
import { StatusEffect } from "#app/enums/status-effect";
|
||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import * as Utils from "#app/utils";
|
import { BooleanHolder, NumberHolder } from "#app/utils";
|
||||||
import { PokemonPhase } from "./pokemon-phase";
|
import { PokemonPhase } from "./pokemon-phase";
|
||||||
|
|
||||||
export class PostTurnStatusEffectPhase extends PokemonPhase {
|
export class PostTurnStatusEffectPhase extends PokemonPhase {
|
||||||
@ -26,7 +26,7 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
|
|||||||
const pokemon = this.getPokemon();
|
const pokemon = this.getPokemon();
|
||||||
if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn() && !pokemon.switchOutStatus) {
|
if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn() && !pokemon.switchOutStatus) {
|
||||||
pokemon.status.incrementTurn();
|
pokemon.status.incrementTurn();
|
||||||
const cancelled = new Utils.BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
||||||
applyAbAttrs(BlockStatusDamageAbAttr, pokemon, cancelled);
|
applyAbAttrs(BlockStatusDamageAbAttr, pokemon, cancelled);
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
|
|||||||
globalScene.queueMessage(
|
globalScene.queueMessage(
|
||||||
getStatusEffectActivationText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)),
|
getStatusEffectActivationText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)),
|
||||||
);
|
);
|
||||||
const damage = new Utils.NumberHolder(0);
|
const damage = new NumberHolder(0);
|
||||||
switch (pokemon.status.effect) {
|
switch (pokemon.status.effect) {
|
||||||
case StatusEffect.POISON:
|
case StatusEffect.POISON:
|
||||||
damage.value = Math.max(pokemon.getMaxHp() >> 3, 1);
|
damage.value = Math.max(pokemon.getMaxHp() >> 3, 1);
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { Phase } from "#app/phase";
|
import { Phase } from "#app/phase";
|
||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
import * as Utils from "#app/utils";
|
import { fixedInt } from "#app/utils";
|
||||||
|
|
||||||
export class ReloadSessionPhase extends Phase {
|
export class ReloadSessionPhase extends Phase {
|
||||||
private systemDataStr: string | null;
|
private systemDataStr?: string;
|
||||||
|
|
||||||
constructor(systemDataStr?: string) {
|
constructor(systemDataStr?: string) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.systemDataStr = systemDataStr ?? null;
|
this.systemDataStr = systemDataStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
start(): void {
|
start(): void {
|
||||||
@ -18,7 +18,7 @@ export class ReloadSessionPhase extends Phase {
|
|||||||
let delayElapsed = false;
|
let delayElapsed = false;
|
||||||
let loaded = false;
|
let loaded = false;
|
||||||
|
|
||||||
globalScene.time.delayedCall(Utils.fixedInt(1500), () => {
|
globalScene.time.delayedCall(fixedInt(1500), () => {
|
||||||
if (loaded) {
|
if (loaded) {
|
||||||
this.end();
|
this.end();
|
||||||
} else {
|
} else {
|
||||||
|
44
src/phases/reset-status-phase.ts
Normal file
44
src/phases/reset-status-phase.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import type Pokemon from "#app/field/pokemon";
|
||||||
|
import { BattlePhase } from "#app/phases/battle-phase";
|
||||||
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase which handles resetting a Pokemon's status to none
|
||||||
|
*
|
||||||
|
* This is necessary to perform in a phase primarly to ensure that the status icon disappears at the correct time in the battle
|
||||||
|
*/
|
||||||
|
export class ResetStatusPhase extends BattlePhase {
|
||||||
|
private readonly pokemon: Pokemon;
|
||||||
|
private readonly affectConfusion: boolean;
|
||||||
|
private readonly reloadAssets: boolean;
|
||||||
|
|
||||||
|
constructor(pokemon: Pokemon, affectConfusion: boolean, reloadAssets: boolean) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.pokemon = pokemon;
|
||||||
|
this.affectConfusion = affectConfusion;
|
||||||
|
this.reloadAssets = reloadAssets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override start() {
|
||||||
|
const lastStatus = this.pokemon.status?.effect;
|
||||||
|
this.pokemon.status = null;
|
||||||
|
if (lastStatus === StatusEffect.SLEEP) {
|
||||||
|
this.pokemon.setFrameRate(10);
|
||||||
|
if (this.pokemon.getTag(BattlerTagType.NIGHTMARE)) {
|
||||||
|
this.pokemon.lapseTag(BattlerTagType.NIGHTMARE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.affectConfusion) {
|
||||||
|
if (this.pokemon.getTag(BattlerTagType.CONFUSED)) {
|
||||||
|
this.pokemon.lapseTag(BattlerTagType.CONFUSED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.reloadAssets) {
|
||||||
|
this.pokemon.loadAssets(false).then(() => this.pokemon.playAnim());
|
||||||
|
}
|
||||||
|
this.pokemon.updateInfo(true);
|
||||||
|
this.end();
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ import i18next from "i18next";
|
|||||||
import { PokemonPhase } from "./pokemon-phase";
|
import { PokemonPhase } from "./pokemon-phase";
|
||||||
|
|
||||||
export class ScanIvsPhase extends PokemonPhase {
|
export class ScanIvsPhase extends PokemonPhase {
|
||||||
|
// biome-ignore lint/complexity/noUselessConstructor: This changes `battlerIndex` to be required
|
||||||
constructor(battlerIndex: BattlerIndex) {
|
constructor(battlerIndex: BattlerIndex) {
|
||||||
super(battlerIndex);
|
super(battlerIndex);
|
||||||
}
|
}
|
||||||
@ -24,7 +25,8 @@ export class ScanIvsPhase extends PokemonPhase {
|
|||||||
const uiTheme = globalScene.uiTheme; // Assuming uiTheme is accessible
|
const uiTheme = globalScene.uiTheme; // Assuming uiTheme is accessible
|
||||||
for (let e = 0; e < enemyField.length; e++) {
|
for (let e = 0; e < enemyField.length; e++) {
|
||||||
enemyIvs = enemyField[e].ivs;
|
enemyIvs = enemyField[e].ivs;
|
||||||
const currentIvs = globalScene.gameData.dexData[enemyField[e].species.getRootSpeciesId()].ivs; // we are using getRootSpeciesId() here because we want to check against the baby form, not the mid form if it exists
|
// we are using getRootSpeciesId() here because we want to check against the baby form, not the mid form if it exists
|
||||||
|
const currentIvs = globalScene.gameData.dexData[enemyField[e].species.getRootSpeciesId()].ivs;
|
||||||
statsContainer = enemyField[e].getBattleInfo().getStatsValueContainer().list as Phaser.GameObjects.Sprite[];
|
statsContainer = enemyField[e].getBattleInfo().getStatsValueContainer().list as Phaser.GameObjects.Sprite[];
|
||||||
statsContainerLabels = statsContainer.filter(m => m.name.indexOf("icon_stat_label") >= 0);
|
statsContainerLabels = statsContainer.filter(m => m.name.indexOf("icon_stat_label") >= 0);
|
||||||
for (let s = 0; s < statsContainerLabels.length; s++) {
|
for (let s = 0; s < statsContainerLabels.length; s++) {
|
||||||
|
@ -5,15 +5,11 @@ import { MoneyInterestModifier, MapModifier } from "#app/modifier/modifier";
|
|||||||
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
import { BattlePhase } from "./battle-phase";
|
import { BattlePhase } from "./battle-phase";
|
||||||
import * as Utils from "#app/utils";
|
import { randSeedInt } from "#app/utils";
|
||||||
import { PartyHealPhase } from "./party-heal-phase";
|
import { PartyHealPhase } from "./party-heal-phase";
|
||||||
import { SwitchBiomePhase } from "./switch-biome-phase";
|
import { SwitchBiomePhase } from "./switch-biome-phase";
|
||||||
|
|
||||||
export class SelectBiomePhase extends BattlePhase {
|
export class SelectBiomePhase extends BattlePhase {
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
@ -40,7 +36,7 @@ export class SelectBiomePhase extends BattlePhase {
|
|||||||
let biomes: Biome[] = [];
|
let biomes: Biome[] = [];
|
||||||
globalScene.executeWithSeedOffset(() => {
|
globalScene.executeWithSeedOffset(() => {
|
||||||
biomes = (biomeLinks[currentBiome] as (Biome | [Biome, number])[])
|
biomes = (biomeLinks[currentBiome] as (Biome | [Biome, number])[])
|
||||||
.filter(b => !Array.isArray(b) || !Utils.randSeedInt(b[1]))
|
.filter(b => !Array.isArray(b) || !randSeedInt(b[1]))
|
||||||
.map(b => (!Array.isArray(b) ? b : b[0]));
|
.map(b => (!Array.isArray(b) ? b : b[0]));
|
||||||
}, globalScene.currentBattle.waveIndex);
|
}, globalScene.currentBattle.waveIndex);
|
||||||
if (biomes.length > 1 && globalScene.findModifier(m => m instanceof MapModifier)) {
|
if (biomes.length > 1 && globalScene.findModifier(m => m instanceof MapModifier)) {
|
||||||
@ -51,7 +47,7 @@ export class SelectBiomePhase extends BattlePhase {
|
|||||||
? [biomeLinks[currentBiome] as Biome]
|
? [biomeLinks[currentBiome] as Biome]
|
||||||
: (biomeLinks[currentBiome] as (Biome | [Biome, number])[])
|
: (biomeLinks[currentBiome] as (Biome | [Biome, number])[])
|
||||||
)
|
)
|
||||||
.filter((b, _i) => !Array.isArray(b) || !Utils.randSeedInt(b[1]))
|
.filter(b => !Array.isArray(b) || !randSeedInt(b[1]))
|
||||||
.map(b => (Array.isArray(b) ? b[0] : b));
|
.map(b => (Array.isArray(b) ? b[0] : b));
|
||||||
}, globalScene.currentBattle.waveIndex);
|
}, globalScene.currentBattle.waveIndex);
|
||||||
const biomeSelectItems = biomeChoices.map(b => {
|
const biomeSelectItems = biomeChoices.map(b => {
|
||||||
@ -70,7 +66,7 @@ export class SelectBiomePhase extends BattlePhase {
|
|||||||
delay: 1000,
|
delay: 1000,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setNextBiome(biomes[Utils.randSeedInt(biomes.length)]);
|
setNextBiome(biomes[randSeedInt(biomes.length)]);
|
||||||
}
|
}
|
||||||
} else if (biomeLinks.hasOwnProperty(currentBiome)) {
|
} else if (biomeLinks.hasOwnProperty(currentBiome)) {
|
||||||
setNextBiome(biomeLinks[currentBiome] as Biome);
|
setNextBiome(biomeLinks[currentBiome] as Biome);
|
||||||
|
@ -3,10 +3,6 @@ import { Phase } from "#app/phase";
|
|||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
|
|
||||||
export class SelectChallengePhase extends Phase {
|
export class SelectChallengePhase extends Phase {
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
|
@ -6,10 +6,6 @@ import { Mode } from "#app/ui/ui";
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
|
||||||
export class SelectGenderPhase extends Phase {
|
export class SelectGenderPhase extends Phase {
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
start(): void {
|
start(): void {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
|
@ -26,7 +26,6 @@ import { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler";
|
|||||||
import PartyUiHandler, { PartyUiMode, PartyOption } from "#app/ui/party-ui-handler";
|
import PartyUiHandler, { PartyUiMode, PartyOption } from "#app/ui/party-ui-handler";
|
||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import * as Utils from "#app/utils";
|
|
||||||
import { BattlePhase } from "./battle-phase";
|
import { BattlePhase } from "./battle-phase";
|
||||||
import Overrides from "#app/overrides";
|
import Overrides from "#app/overrides";
|
||||||
import type { CustomModifierSettings } from "#app/modifier/modifier-type";
|
import type { CustomModifierSettings } from "#app/modifier/modifier-type";
|
||||||
@ -67,7 +66,7 @@ export class SelectModifierPhase extends BattlePhase {
|
|||||||
if (!this.isCopy) {
|
if (!this.isCopy) {
|
||||||
regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount);
|
regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount);
|
||||||
}
|
}
|
||||||
const modifierCount = new Utils.NumberHolder(3);
|
const modifierCount = new NumberHolder(3);
|
||||||
if (this.isPlayer()) {
|
if (this.isPlayer()) {
|
||||||
globalScene.applyModifiers(ExtraModifierModifier, true, modifierCount);
|
globalScene.applyModifiers(ExtraModifierModifier, true, modifierCount);
|
||||||
globalScene.applyModifiers(TempExtraModifierModifier, true, modifierCount);
|
globalScene.applyModifiers(TempExtraModifierModifier, true, modifierCount);
|
||||||
|
@ -15,10 +15,6 @@ import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
|
|||||||
import * as Utils from "../utils";
|
import * as Utils from "../utils";
|
||||||
|
|
||||||
export class SelectStarterPhase extends Phase {
|
export class SelectStarterPhase extends Phase {
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import i18next from "#app/plugins/i18n";
|
|||||||
import { allMoves } from "#app/data/moves/move";
|
import { allMoves } from "#app/data/moves/move";
|
||||||
|
|
||||||
export class SelectTargetPhase extends PokemonPhase {
|
export class SelectTargetPhase extends PokemonPhase {
|
||||||
|
// biome-ignore lint/complexity/noUselessConstructor: This makes `fieldIndex` required
|
||||||
constructor(fieldIndex: number) {
|
constructor(fieldIndex: number) {
|
||||||
super(fieldIndex);
|
super(fieldIndex);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import type { BattlerIndex } from "#app/battle";
|
|||||||
import { PokemonPhase } from "./pokemon-phase";
|
import { PokemonPhase } from "./pokemon-phase";
|
||||||
|
|
||||||
export class ShinySparklePhase extends PokemonPhase {
|
export class ShinySparklePhase extends PokemonPhase {
|
||||||
|
// biome-ignore lint/complexity/noUselessConstructor: This makes `battlerIndex` required
|
||||||
constructor(battlerIndex: BattlerIndex) {
|
constructor(battlerIndex: BattlerIndex) {
|
||||||
super(battlerIndex);
|
super(battlerIndex);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene";
|
|||||||
import { ExpGainsSpeed } from "#app/enums/exp-gains-speed";
|
import { ExpGainsSpeed } from "#app/enums/exp-gains-speed";
|
||||||
import { ExpNotification } from "#app/enums/exp-notification";
|
import { ExpNotification } from "#app/enums/exp-notification";
|
||||||
import { ExpBoosterModifier } from "#app/modifier/modifier";
|
import { ExpBoosterModifier } from "#app/modifier/modifier";
|
||||||
import * as Utils from "#app/utils";
|
import { NumberHolder } from "#app/utils";
|
||||||
import { HidePartyExpBarPhase } from "./hide-party-exp-bar-phase";
|
import { HidePartyExpBarPhase } from "./hide-party-exp-bar-phase";
|
||||||
import { LevelUpPhase } from "./level-up-phase";
|
import { LevelUpPhase } from "./level-up-phase";
|
||||||
import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase";
|
import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase";
|
||||||
@ -20,7 +20,7 @@ export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase {
|
|||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
const pokemon = this.getPokemon();
|
const pokemon = this.getPokemon();
|
||||||
const exp = new Utils.NumberHolder(this.expValue);
|
const exp = new NumberHolder(this.expValue);
|
||||||
globalScene.applyModifiers(ExpBoosterModifier, true, exp);
|
globalScene.applyModifiers(ExpBoosterModifier, true, exp);
|
||||||
exp.value = Math.floor(exp.value);
|
exp.value = Math.floor(exp.value);
|
||||||
|
|
||||||
|
@ -3,10 +3,6 @@ import { PlayerGender } from "#app/enums/player-gender";
|
|||||||
import { BattlePhase } from "./battle-phase";
|
import { BattlePhase } from "./battle-phase";
|
||||||
|
|
||||||
export class ShowTrainerPhase extends BattlePhase {
|
export class ShowTrainerPhase extends BattlePhase {
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
|
@ -4,10 +4,6 @@ import { SummonPhase } from "./summon-phase";
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
|
|
||||||
export class SummonMissingPhase extends SummonPhase {
|
export class SummonMissingPhase extends SummonPhase {
|
||||||
constructor(fieldIndex: number) {
|
|
||||||
super(fieldIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
preSummon(): void {
|
preSummon(): void {
|
||||||
globalScene.ui.showText(
|
globalScene.ui.showText(
|
||||||
i18next.t("battle:sendOutPokemon", {
|
i18next.t("battle:sendOutPokemon", {
|
||||||
|
@ -23,11 +23,11 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for creating a new SwitchSummonPhase
|
* Constructor for creating a new SwitchSummonPhase
|
||||||
* @param switchType the type of switch behavior
|
* @param switchType - The type of switch behavior
|
||||||
* @param fieldIndex integer representing position on the battle field
|
* @param fieldIndex - Position on the battle field
|
||||||
* @param slotIndex integer for the index of pokemon (in party of 6) to switch into
|
* @param slotIndex - The index of pokemon (in party of 6) to switch into
|
||||||
* @param doReturn boolean whether to render "comeback" dialogue
|
* @param doReturn - Whether to render "comeback" dialogue
|
||||||
* @param player boolean if the switch is from the player
|
* @param player - (Optional) `true` if the switch is from the player
|
||||||
*/
|
*/
|
||||||
constructor(switchType: SwitchType, fieldIndex: number, slotIndex: number, doReturn: boolean, player?: boolean) {
|
constructor(switchType: SwitchType, fieldIndex: number, slotIndex: number, doReturn: boolean, player?: boolean) {
|
||||||
super(fieldIndex, player !== undefined ? player : true);
|
super(fieldIndex, player !== undefined ? player : true);
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
import { MessagePhase } from "./message-phase";
|
|
||||||
|
|
||||||
export class TestMessagePhase extends MessagePhase {
|
|
||||||
constructor(message: string) {
|
|
||||||
super(message, null, true);
|
|
||||||
}
|
|
||||||
}
|
|
@ -18,7 +18,7 @@ import { vouchers } from "#app/system/voucher";
|
|||||||
import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||||
import { SaveSlotUiMode } from "#app/ui/save-slot-select-ui-handler";
|
import { SaveSlotUiMode } from "#app/ui/save-slot-select-ui-handler";
|
||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
import * as Utils from "#app/utils";
|
import { isLocal, isLocalServerConnected, isNullOrUndefined } from "#app/utils";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { CheckSwitchPhase } from "./check-switch-phase";
|
import { CheckSwitchPhase } from "./check-switch-phase";
|
||||||
import { EncounterPhase } from "./encounter-phase";
|
import { EncounterPhase } from "./encounter-phase";
|
||||||
@ -29,16 +29,10 @@ import { globalScene } from "#app/global-scene";
|
|||||||
import Overrides from "#app/overrides";
|
import Overrides from "#app/overrides";
|
||||||
|
|
||||||
export class TitlePhase extends Phase {
|
export class TitlePhase extends Phase {
|
||||||
private loaded: boolean;
|
private loaded = false;
|
||||||
private lastSessionData: SessionSaveData;
|
private lastSessionData: SessionSaveData;
|
||||||
public gameMode: GameModes;
|
public gameMode: GameModes;
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.loaded = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
start(): void {
|
start(): void {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
@ -282,7 +276,7 @@ export class TitlePhase extends Phase {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// If Online, calls seed fetch from db to generate daily run. If Offline, generates a daily run based on current date.
|
// If Online, calls seed fetch from db to generate daily run. If Offline, generates a daily run based on current date.
|
||||||
if (!Utils.isLocal || Utils.isLocalServerConnected) {
|
if (!isLocal || isLocalServerConnected) {
|
||||||
fetchDailyRunSeed()
|
fetchDailyRunSeed()
|
||||||
.then(seed => {
|
.then(seed => {
|
||||||
if (seed) {
|
if (seed) {
|
||||||
@ -296,7 +290,7 @@ export class TitlePhase extends Phase {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
let seed: string = btoa(new Date().toISOString().substring(0, 10));
|
let seed: string = btoa(new Date().toISOString().substring(0, 10));
|
||||||
if (!Utils.isNullOrUndefined(Overrides.DAILY_RUN_SEED_OVERRIDE)) {
|
if (!isNullOrUndefined(Overrides.DAILY_RUN_SEED_OVERRIDE)) {
|
||||||
seed = Overrides.DAILY_RUN_SEED_OVERRIDE;
|
seed = Overrides.DAILY_RUN_SEED_OVERRIDE;
|
||||||
}
|
}
|
||||||
generateDaily(seed);
|
generateDaily(seed);
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
|
||||||
import { trainerConfigs } from "#app/data/trainers/trainer-config";
|
|
||||||
import type { TrainerType } from "#app/enums/trainer-type";
|
|
||||||
import { BattlePhase } from "./battle-phase";
|
|
||||||
import { TestMessagePhase } from "./test-message-phase";
|
|
||||||
|
|
||||||
export class TrainerMessageTestPhase extends BattlePhase {
|
|
||||||
private trainerTypes: TrainerType[];
|
|
||||||
|
|
||||||
constructor(...trainerTypes: TrainerType[]) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.trainerTypes = trainerTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
|
||||||
super.start();
|
|
||||||
|
|
||||||
const testMessages: string[] = [];
|
|
||||||
|
|
||||||
for (const t of Object.keys(trainerConfigs)) {
|
|
||||||
const type = Number.parseInt(t);
|
|
||||||
if (this.trainerTypes.length && !this.trainerTypes.find(tt => tt === (type as TrainerType))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const config = trainerConfigs[type];
|
|
||||||
[
|
|
||||||
config.encounterMessages,
|
|
||||||
config.femaleEncounterMessages,
|
|
||||||
config.victoryMessages,
|
|
||||||
config.femaleVictoryMessages,
|
|
||||||
config.defeatMessages,
|
|
||||||
config.femaleDefeatMessages,
|
|
||||||
].map(messages => {
|
|
||||||
if (messages?.length) {
|
|
||||||
testMessages.push(...messages);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const message of testMessages) {
|
|
||||||
globalScene.pushPhase(new TestMessagePhase(message));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.end();
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,7 +3,7 @@ import { TrainerType } from "#app/enums/trainer-type";
|
|||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||||
import { vouchers } from "#app/system/voucher";
|
import { vouchers } from "#app/system/voucher";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import * as Utils from "#app/utils";
|
import { randSeedItem } from "#app/utils";
|
||||||
import { BattlePhase } from "./battle-phase";
|
import { BattlePhase } from "./battle-phase";
|
||||||
import { ModifierRewardPhase } from "./modifier-reward-phase";
|
import { ModifierRewardPhase } from "./modifier-reward-phase";
|
||||||
import { MoneyRewardPhase } from "./money-reward-phase";
|
import { MoneyRewardPhase } from "./money-reward-phase";
|
||||||
@ -14,10 +14,6 @@ import { achvs } from "#app/system/achv";
|
|||||||
import { timedEventManager } from "#app/global-event-manager";
|
import { timedEventManager } from "#app/global-event-manager";
|
||||||
|
|
||||||
export class TrainerVictoryPhase extends BattlePhase {
|
export class TrainerVictoryPhase extends BattlePhase {
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
globalScene.disableMenu = true;
|
globalScene.disableMenu = true;
|
||||||
|
|
||||||
@ -82,7 +78,7 @@ export class TrainerVictoryPhase extends BattlePhase {
|
|||||||
const victoryMessages = globalScene.currentBattle.trainer?.getVictoryMessages()!; // TODO: is this bang correct?
|
const victoryMessages = globalScene.currentBattle.trainer?.getVictoryMessages()!; // TODO: is this bang correct?
|
||||||
let message: string;
|
let message: string;
|
||||||
globalScene.executeWithSeedOffset(
|
globalScene.executeWithSeedOffset(
|
||||||
() => (message = Utils.randSeedItem(victoryMessages)),
|
() => (message = randSeedItem(victoryMessages)),
|
||||||
globalScene.currentBattle.waveIndex,
|
globalScene.currentBattle.waveIndex,
|
||||||
);
|
);
|
||||||
message = message!; // tell TS compiler it's defined now
|
message = message!; // tell TS compiler it's defined now
|
||||||
|
@ -18,10 +18,6 @@ import { PokemonHealPhase } from "./pokemon-heal-phase";
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
|
|
||||||
export class TurnEndPhase extends FieldPhase {
|
export class TurnEndPhase extends FieldPhase {
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
|
@ -15,10 +15,6 @@ import { TurnStartPhase } from "./turn-start-phase";
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
|
|
||||||
export class TurnInitPhase extends FieldPhase {
|
export class TurnInitPhase extends FieldPhase {
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import type Pokemon from "#app/field/pokemon";
|
|||||||
import { PokemonMove } from "#app/field/pokemon";
|
import { PokemonMove } from "#app/field/pokemon";
|
||||||
import { BypassSpeedChanceModifier } from "#app/modifier/modifier";
|
import { BypassSpeedChanceModifier } from "#app/modifier/modifier";
|
||||||
import { Command } from "#app/ui/command-ui-handler";
|
import { Command } from "#app/ui/command-ui-handler";
|
||||||
import * as Utils from "#app/utils";
|
import { randSeedShuffle, BooleanHolder } from "#app/utils";
|
||||||
import { AttemptCapturePhase } from "./attempt-capture-phase";
|
import { AttemptCapturePhase } from "./attempt-capture-phase";
|
||||||
import { AttemptRunPhase } from "./attempt-run-phase";
|
import { AttemptRunPhase } from "./attempt-run-phase";
|
||||||
import { BerryPhase } from "./berry-phase";
|
import { BerryPhase } from "./berry-phase";
|
||||||
@ -24,10 +24,6 @@ import { globalScene } from "#app/global-scene";
|
|||||||
import { TeraPhase } from "./tera-phase";
|
import { TeraPhase } from "./tera-phase";
|
||||||
|
|
||||||
export class TurnStartPhase extends FieldPhase {
|
export class TurnStartPhase extends FieldPhase {
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This orders the active Pokemon on the field by speed into an BattlerIndex array and returns that array.
|
* This orders the active Pokemon on the field by speed into an BattlerIndex array and returns that array.
|
||||||
* It also checks for Trick Room and reverses the array if it is present.
|
* It also checks for Trick Room and reverses the array if it is present.
|
||||||
@ -43,14 +39,14 @@ export class TurnStartPhase extends FieldPhase {
|
|||||||
// was varying based on how long since you last reloaded
|
// was varying based on how long since you last reloaded
|
||||||
globalScene.executeWithSeedOffset(
|
globalScene.executeWithSeedOffset(
|
||||||
() => {
|
() => {
|
||||||
orderedTargets = Utils.randSeedShuffle(orderedTargets);
|
orderedTargets = randSeedShuffle(orderedTargets);
|
||||||
},
|
},
|
||||||
globalScene.currentBattle.turn,
|
globalScene.currentBattle.turn,
|
||||||
globalScene.waveSeed,
|
globalScene.waveSeed,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Next, a check for Trick Room is applied to determine sort order.
|
// Next, a check for Trick Room is applied to determine sort order.
|
||||||
const speedReversed = new Utils.BooleanHolder(false);
|
const speedReversed = new BooleanHolder(false);
|
||||||
globalScene.arena.applyTags(TrickRoomTag, false, speedReversed);
|
globalScene.arena.applyTags(TrickRoomTag, false, speedReversed);
|
||||||
|
|
||||||
// Adjust the sort function based on whether Trick Room is active.
|
// Adjust the sort function based on whether Trick Room is active.
|
||||||
@ -80,8 +76,8 @@ export class TurnStartPhase extends FieldPhase {
|
|||||||
.getField(true)
|
.getField(true)
|
||||||
.filter(p => p.summonData)
|
.filter(p => p.summonData)
|
||||||
.map(p => {
|
.map(p => {
|
||||||
const bypassSpeed = new Utils.BooleanHolder(false);
|
const bypassSpeed = new BooleanHolder(false);
|
||||||
const canCheckHeldItems = new Utils.BooleanHolder(true);
|
const canCheckHeldItems = new BooleanHolder(true);
|
||||||
applyAbAttrs(BypassSpeedChanceAbAttr, p, null, false, bypassSpeed);
|
applyAbAttrs(BypassSpeedChanceAbAttr, p, null, false, bypassSpeed);
|
||||||
applyAbAttrs(PreventBypassSpeedChanceAbAttr, p, null, false, bypassSpeed, canCheckHeldItems);
|
applyAbAttrs(PreventBypassSpeedChanceAbAttr, p, null, false, bypassSpeed, canCheckHeldItems);
|
||||||
if (canCheckHeldItems.value) {
|
if (canCheckHeldItems.value) {
|
||||||
|
@ -4,10 +4,6 @@ import { Mode } from "#app/ui/ui";
|
|||||||
import { LoginPhase } from "./login-phase";
|
import { LoginPhase } from "./login-phase";
|
||||||
|
|
||||||
export class UnavailablePhase extends Phase {
|
export class UnavailablePhase extends Phase {
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
start(): void {
|
start(): void {
|
||||||
globalScene.ui.setMode(Mode.UNAVAILABLE, () => {
|
globalScene.ui.setMode(Mode.UNAVAILABLE, () => {
|
||||||
globalScene.unshiftPhase(new LoginPhase(true));
|
globalScene.unshiftPhase(new LoginPhase(true));
|
||||||
|
@ -15,7 +15,7 @@ import { BattlerTagType } from "#app/enums/battler-tag-type";
|
|||||||
import { WeatherType } from "#app/enums/weather-type";
|
import { WeatherType } from "#app/enums/weather-type";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import { HitResult } from "#app/field/pokemon";
|
import { HitResult } from "#app/field/pokemon";
|
||||||
import * as Utils from "#app/utils";
|
import { BooleanHolder, toDmgValue } from "#app/utils";
|
||||||
import { CommonAnimPhase } from "./common-anim-phase";
|
import { CommonAnimPhase } from "./common-anim-phase";
|
||||||
|
|
||||||
export class WeatherEffectPhase extends CommonAnimPhase {
|
export class WeatherEffectPhase extends CommonAnimPhase {
|
||||||
@ -35,14 +35,13 @@ export class WeatherEffectPhase extends CommonAnimPhase {
|
|||||||
this.weather = globalScene?.arena?.weather;
|
this.weather = globalScene?.arena?.weather;
|
||||||
|
|
||||||
if (!this.weather) {
|
if (!this.weather) {
|
||||||
this.end();
|
return this.end();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setAnimation(CommonAnim.SUNNY + (this.weather.weatherType - 1));
|
this.setAnimation(CommonAnim.SUNNY + (this.weather.weatherType - 1));
|
||||||
|
|
||||||
if (this.weather.isDamaging()) {
|
if (this.weather.isDamaging()) {
|
||||||
const cancelled = new Utils.BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
|
|
||||||
this.executeForAll((pokemon: Pokemon) =>
|
this.executeForAll((pokemon: Pokemon) =>
|
||||||
applyPreWeatherEffectAbAttrs(SuppressWeatherEffectAbAttr, pokemon, this.weather, cancelled),
|
applyPreWeatherEffectAbAttrs(SuppressWeatherEffectAbAttr, pokemon, this.weather, cancelled),
|
||||||
@ -50,7 +49,7 @@ export class WeatherEffectPhase extends CommonAnimPhase {
|
|||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
const inflictDamage = (pokemon: Pokemon) => {
|
const inflictDamage = (pokemon: Pokemon) => {
|
||||||
const cancelled = new Utils.BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
|
|
||||||
applyPreWeatherEffectAbAttrs(PreWeatherDamageAbAttr, pokemon, this.weather, cancelled);
|
applyPreWeatherEffectAbAttrs(PreWeatherDamageAbAttr, pokemon, this.weather, cancelled);
|
||||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
||||||
@ -63,9 +62,9 @@ export class WeatherEffectPhase extends CommonAnimPhase {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const damage = Utils.toDmgValue(pokemon.getMaxHp() / 16);
|
const damage = toDmgValue(pokemon.getMaxHp() / 16);
|
||||||
|
|
||||||
globalScene.queueMessage(getWeatherDamageMessage(this.weather?.weatherType!, pokemon)!); // TODO: are those bangs correct?
|
globalScene.queueMessage(getWeatherDamageMessage(this.weather!.weatherType, pokemon) ?? "");
|
||||||
pokemon.damageAndUpdate(damage, { result: HitResult.INDIRECT, ignoreSegments: true });
|
pokemon.damageAndUpdate(damage, { result: HitResult.INDIRECT, ignoreSegments: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,19 +1,104 @@
|
|||||||
import type { SessionSaveData, SystemSaveData } from "../game-data";
|
import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator";
|
||||||
|
import type { SettingsSaveMigrator } from "#app/@types/SettingsSaveMigrator";
|
||||||
|
import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator";
|
||||||
|
import type { SessionSaveData, SystemSaveData } from "#app/system/game-data";
|
||||||
|
import { compareVersions } from "compare-versions";
|
||||||
import { version } from "../../../package.json";
|
import { version } from "../../../package.json";
|
||||||
|
|
||||||
|
/*
|
||||||
|
// template for save migrator creation
|
||||||
|
// versions/vA_B_C.ts
|
||||||
|
|
||||||
|
// The version for each migrator should match the filename, ie: `vA_B_C.ts` -> `version: "A.B.C"
|
||||||
|
// This is the target version (aka the version we're ending up on after the migrators are run)
|
||||||
|
|
||||||
|
// The name for each migrator should match its purpose. For example, if you're fixing
|
||||||
|
// the ability index of a pokemon, it might be called `migratePokemonAbilityIndex`
|
||||||
|
|
||||||
|
const systemMigratorA: SystemSaveMigrator = {
|
||||||
|
version: "A.B.C",
|
||||||
|
migrate: (data: SystemSaveData): void => {
|
||||||
|
// migration code goes here
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const systemMigrators: Readonly<SystemSaveMigrator[]> = [systemMigratorA] as const;
|
||||||
|
|
||||||
|
const sessionMigratorA: SessionSaveMigrator = {
|
||||||
|
version: "A.B.C",
|
||||||
|
migrate: (data: SessionSaveData): void => {
|
||||||
|
// migration code goes here
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sessionMigrators: Readonly<SessionSaveMigrator[]> = [sessionMigratorA] as const;
|
||||||
|
|
||||||
|
const settingsMigratorA: SettingsSaveMigrator = {
|
||||||
|
version: "A.B.C",
|
||||||
|
// biome-ignore lint/complexity/noBannedTypes: TODO - refactor settings
|
||||||
|
migrate: (data: Object): void => {
|
||||||
|
// migration code goes here
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const settingsMigrators: Readonly<SettingsSaveMigrator[]> = [settingsMigratorA] as const;
|
||||||
|
*/
|
||||||
|
|
||||||
|
// --- vA.B.C PATCHES --- //
|
||||||
|
// import * as vA_B_C from "./versions/vA_B_C";
|
||||||
|
|
||||||
// --- v1.0.4 (and below) PATCHES --- //
|
// --- v1.0.4 (and below) PATCHES --- //
|
||||||
import * as v1_0_4 from "./versions/v1_0_4";
|
import * as v1_0_4 from "./versions/v1_0_4";
|
||||||
|
|
||||||
// --- v1.1.0 PATCHES --- //
|
|
||||||
import * as v1_1_0 from "./versions/v1_1_0";
|
|
||||||
|
|
||||||
// --- v1.7.0 PATCHES --- //
|
// --- v1.7.0 PATCHES --- //
|
||||||
import * as v1_7_0 from "./versions/v1_7_0";
|
import * as v1_7_0 from "./versions/v1_7_0";
|
||||||
|
|
||||||
// --- v1.8.3 PATCHES --- //
|
// --- v1.8.3 PATCHES --- //
|
||||||
import * as v1_8_3 from "./versions/v1_8_3";
|
import * as v1_8_3 from "./versions/v1_8_3";
|
||||||
|
|
||||||
const LATEST_VERSION = version.split(".").map(value => Number.parseInt(value));
|
/** Current game version */
|
||||||
|
const LATEST_VERSION = version;
|
||||||
|
|
||||||
|
type SaveMigrator = SystemSaveMigrator | SessionSaveMigrator | SettingsSaveMigrator;
|
||||||
|
|
||||||
|
// biome-ignore lint/complexity/noBannedTypes: TODO - refactor settings
|
||||||
|
type SaveData = SystemSaveData | SessionSaveData | Object;
|
||||||
|
|
||||||
|
// To add a new set of migrators, create a new `.push()` line like so:
|
||||||
|
// `systemMigrators.push(...vA_B_C.systemMigrators);`
|
||||||
|
|
||||||
|
/** All system save migrators */
|
||||||
|
const systemMigrators: SystemSaveMigrator[] = [];
|
||||||
|
systemMigrators.push(...v1_0_4.systemMigrators);
|
||||||
|
systemMigrators.push(...v1_7_0.systemMigrators);
|
||||||
|
systemMigrators.push(...v1_8_3.systemMigrators);
|
||||||
|
|
||||||
|
/** All session save migrators */
|
||||||
|
const sessionMigrators: SessionSaveMigrator[] = [];
|
||||||
|
sessionMigrators.push(...v1_0_4.sessionMigrators);
|
||||||
|
sessionMigrators.push(...v1_7_0.sessionMigrators);
|
||||||
|
|
||||||
|
/** All settings migrators */
|
||||||
|
const settingsMigrators: SettingsSaveMigrator[] = [];
|
||||||
|
settingsMigrators.push(...v1_0_4.settingsMigrators);
|
||||||
|
|
||||||
|
/** Sorts migrators by their stated version, ensuring they are applied in order from oldest to newest */
|
||||||
|
const sortMigrators = (migrators: SaveMigrator[]): void => {
|
||||||
|
migrators.sort((a, b) => compareVersions(a.version, b.version));
|
||||||
|
};
|
||||||
|
|
||||||
|
sortMigrators(systemMigrators);
|
||||||
|
sortMigrators(sessionMigrators);
|
||||||
|
sortMigrators(settingsMigrators);
|
||||||
|
|
||||||
|
const applyMigrators = (migrators: readonly SaveMigrator[], data: SaveData, saveVersion: string) => {
|
||||||
|
for (const migrator of migrators) {
|
||||||
|
const isMigratorVersionHigher = compareVersions(saveVersion, migrator.version) === -1;
|
||||||
|
if (isMigratorVersionHigher) {
|
||||||
|
migrator.migrate(data as any);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts incoming {@linkcode SystemSaveData} that has a version below the
|
* Converts incoming {@linkcode SystemSaveData} that has a version below the
|
||||||
@ -26,12 +111,12 @@ const LATEST_VERSION = version.split(".").map(value => Number.parseInt(value));
|
|||||||
* @see {@link SystemVersionConverter}
|
* @see {@link SystemVersionConverter}
|
||||||
*/
|
*/
|
||||||
export function applySystemVersionMigration(data: SystemSaveData) {
|
export function applySystemVersionMigration(data: SystemSaveData) {
|
||||||
const curVersion = data.gameVersion.split(".").map(value => Number.parseInt(value));
|
const prevVersion = data.gameVersion;
|
||||||
|
const isCurrentVersionHigher = compareVersions(prevVersion, LATEST_VERSION) === -1;
|
||||||
|
|
||||||
if (!curVersion.every((value, index) => value === LATEST_VERSION[index])) {
|
if (isCurrentVersionHigher) {
|
||||||
const converter = new SystemVersionConverter();
|
applyMigrators(systemMigrators, data, prevVersion);
|
||||||
converter.applyStaticPreprocessors(data);
|
console.log(`System data successfully migrated to v${LATEST_VERSION}!`);
|
||||||
converter.applyMigration(data, curVersion);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,12 +131,15 @@ export function applySystemVersionMigration(data: SystemSaveData) {
|
|||||||
* @see {@link SessionVersionConverter}
|
* @see {@link SessionVersionConverter}
|
||||||
*/
|
*/
|
||||||
export function applySessionVersionMigration(data: SessionSaveData) {
|
export function applySessionVersionMigration(data: SessionSaveData) {
|
||||||
const curVersion = data.gameVersion.split(".").map(value => Number.parseInt(value));
|
const prevVersion = data.gameVersion;
|
||||||
|
const isCurrentVersionHigher = compareVersions(prevVersion, LATEST_VERSION) === -1;
|
||||||
|
|
||||||
if (!curVersion.every((value, index) => value === LATEST_VERSION[index])) {
|
if (isCurrentVersionHigher) {
|
||||||
const converter = new SessionVersionConverter();
|
// Always sanitize money as a safeguard
|
||||||
converter.applyStaticPreprocessors(data);
|
data.money = Math.floor(data.money);
|
||||||
converter.applyMigration(data, curVersion);
|
|
||||||
|
applyMigrators(sessionMigrators, data, prevVersion);
|
||||||
|
console.log(`Session data successfully migrated to v${LATEST_VERSION}!`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,156 +153,13 @@ export function applySessionVersionMigration(data: SessionSaveData) {
|
|||||||
* @param data Settings data object
|
* @param data Settings data object
|
||||||
* @see {@link SettingsVersionConverter}
|
* @see {@link SettingsVersionConverter}
|
||||||
*/
|
*/
|
||||||
|
// biome-ignore lint/complexity/noBannedTypes: TODO - refactor settings
|
||||||
export function applySettingsVersionMigration(data: Object) {
|
export function applySettingsVersionMigration(data: Object) {
|
||||||
const gameVersion: string = data.hasOwnProperty("gameVersion") ? data["gameVersion"] : "1.0.0";
|
const prevVersion: string = data.hasOwnProperty("gameVersion") ? data["gameVersion"] : "1.0.0";
|
||||||
const curVersion = gameVersion.split(".").map(value => Number.parseInt(value));
|
const isCurrentVersionHigher = compareVersions(prevVersion, LATEST_VERSION) === -1;
|
||||||
|
|
||||||
if (!curVersion.every((value, index) => value === LATEST_VERSION[index])) {
|
if (isCurrentVersionHigher) {
|
||||||
const converter = new SettingsVersionConverter();
|
applyMigrators(settingsMigrators, data, prevVersion);
|
||||||
converter.applyStaticPreprocessors(data);
|
console.log(`Settings successfully migrated to v${LATEST_VERSION}!`);
|
||||||
converter.applyMigration(data, curVersion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstract class encapsulating the logic for migrating data from a given version up to
|
|
||||||
* the current version listed in `package.json`.
|
|
||||||
*
|
|
||||||
* Note that, for any version converter, the corresponding `applyMigration`
|
|
||||||
* function would only need to be changed once when the first migration for a
|
|
||||||
* given version is introduced. Similarly, a version file (within the `versions`
|
|
||||||
* folder) would only need to be created for a version once with the appropriate
|
|
||||||
* array nomenclature.
|
|
||||||
*/
|
|
||||||
abstract class VersionConverter {
|
|
||||||
/**
|
|
||||||
* Iterates through an array of designated migration functions that are each
|
|
||||||
* called one by one to transform the data.
|
|
||||||
* @param data The data to be operated on
|
|
||||||
* @param migrationArr An array of functions that will transform the incoming data
|
|
||||||
*/
|
|
||||||
callMigrators(data: any, migrationArr: readonly any[]) {
|
|
||||||
for (const migrate of migrationArr) {
|
|
||||||
migrate(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies any version-agnostic data sanitation as defined within the function
|
|
||||||
* body.
|
|
||||||
* @param data The data to be operated on
|
|
||||||
*/
|
|
||||||
applyStaticPreprocessors(_data: any): void {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses the current version the incoming data to determine the starting point
|
|
||||||
* of the migration which will cascade up to the latest version, calling the
|
|
||||||
* necessary migration functions in the process.
|
|
||||||
* @param data The data to be operated on
|
|
||||||
* @param curVersion [0] Current major version
|
|
||||||
* [1] Current minor version
|
|
||||||
* [2] Current patch version
|
|
||||||
*/
|
|
||||||
abstract applyMigration(data: any, curVersion: number[]): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class encapsulating the logic for migrating {@linkcode SessionSaveData} from
|
|
||||||
* a given version up to the current version listed in `package.json`.
|
|
||||||
* @extends VersionConverter
|
|
||||||
*/
|
|
||||||
class SessionVersionConverter extends VersionConverter {
|
|
||||||
override applyStaticPreprocessors(data: SessionSaveData): void {
|
|
||||||
// Always sanitize money as a safeguard
|
|
||||||
data.money = Math.floor(data.money);
|
|
||||||
}
|
|
||||||
|
|
||||||
override applyMigration(data: SessionSaveData, curVersion: number[]): void {
|
|
||||||
const [curMajor, curMinor, curPatch] = curVersion;
|
|
||||||
|
|
||||||
if (curMajor === 1) {
|
|
||||||
if (curMinor === 0) {
|
|
||||||
if (curPatch <= 5) {
|
|
||||||
console.log("Applying v1.0.4 session data migration!");
|
|
||||||
this.callMigrators(data, v1_0_4.sessionMigrators);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (curMinor <= 1) {
|
|
||||||
console.log("Applying v1.1.0 session data migration!");
|
|
||||||
this.callMigrators(data, v1_1_0.sessionMigrators);
|
|
||||||
}
|
|
||||||
if (curMinor < 7) {
|
|
||||||
console.log("Applying v1.7.0 session data migration!");
|
|
||||||
this.callMigrators(data, v1_7_0.sessionMigrators);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Session data successfully migrated to v${version}!`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class encapsulating the logic for migrating {@linkcode SystemSaveData} from
|
|
||||||
* a given version up to the current version listed in `package.json`.
|
|
||||||
* @extends VersionConverter
|
|
||||||
*/
|
|
||||||
class SystemVersionConverter extends VersionConverter {
|
|
||||||
override applyMigration(data: SystemSaveData, curVersion: number[]): void {
|
|
||||||
const [curMajor, curMinor, curPatch] = curVersion;
|
|
||||||
|
|
||||||
if (curMajor === 1) {
|
|
||||||
if (curMinor === 0) {
|
|
||||||
if (curPatch <= 4) {
|
|
||||||
console.log("Applying v1.0.4 system data migraton!");
|
|
||||||
this.callMigrators(data, v1_0_4.systemMigrators);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (curMinor <= 1) {
|
|
||||||
console.log("Applying v1.1.0 system data migraton!");
|
|
||||||
this.callMigrators(data, v1_1_0.systemMigrators);
|
|
||||||
}
|
|
||||||
if (curMinor < 7) {
|
|
||||||
console.log("Applying v1.7.0 system data migration!");
|
|
||||||
this.callMigrators(data, v1_7_0.systemMigrators);
|
|
||||||
}
|
|
||||||
if (curMinor === 8) {
|
|
||||||
if (curPatch <= 2) {
|
|
||||||
console.log("Applying v1.8.3 system data migration!");
|
|
||||||
this.callMigrators(data, v1_8_3.systemMigrators);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`System data successfully migrated to v${version}!`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class encapsulating the logic for migrating settings data from
|
|
||||||
* a given version up to the current version listed in `package.json`.
|
|
||||||
* @extends VersionConverter
|
|
||||||
*/
|
|
||||||
class SettingsVersionConverter extends VersionConverter {
|
|
||||||
override applyMigration(data: Object, curVersion: number[]): void {
|
|
||||||
const [curMajor, curMinor, curPatch] = curVersion;
|
|
||||||
|
|
||||||
if (curMajor === 1) {
|
|
||||||
if (curMinor === 0) {
|
|
||||||
if (curPatch <= 4) {
|
|
||||||
console.log("Applying v1.0.4 settings data migraton!");
|
|
||||||
this.callMigrators(data, v1_0_4.settingsMigrators);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (curMinor <= 1) {
|
|
||||||
console.log("Applying v1.1.0 settings data migraton!");
|
|
||||||
this.callMigrators(data, v1_1_0.settingsMigrators);
|
|
||||||
}
|
|
||||||
if (curMinor < 7) {
|
|
||||||
console.log("Applying v1.7.0 settings data migration!");
|
|
||||||
this.callMigrators(data, v1_7_0.settingsMigrators);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Settings data successfully migrated to v${version}!`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,15 +4,18 @@ import { AbilityAttr, defaultStarterSpecies, DexAttr } from "#app/system/game-da
|
|||||||
import { allSpecies } from "#app/data/pokemon-species";
|
import { allSpecies } from "#app/data/pokemon-species";
|
||||||
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||||
import { isNullOrUndefined } from "#app/utils";
|
import { isNullOrUndefined } from "#app/utils";
|
||||||
|
import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator";
|
||||||
|
import type { SettingsSaveMigrator } from "#app/@types/SettingsSaveMigrator";
|
||||||
|
import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator";
|
||||||
|
|
||||||
export const systemMigrators = [
|
/**
|
||||||
/**
|
|
||||||
* Migrate ability starter data if empty for caught species.
|
* Migrate ability starter data if empty for caught species.
|
||||||
* @param data {@linkcode SystemSaveData}
|
* @param data - {@linkcode SystemSaveData}
|
||||||
*/
|
*/
|
||||||
function migrateAbilityData(data: SystemSaveData) {
|
const migrateAbilityData: SystemSaveMigrator = {
|
||||||
|
version: "1.0.4",
|
||||||
|
migrate: (data: SystemSaveData): void => {
|
||||||
if (data.starterData && data.dexData) {
|
if (data.starterData && data.dexData) {
|
||||||
// biome-ignore lint/complexity/noForEach: <explanation>
|
|
||||||
Object.keys(data.starterData).forEach(sd => {
|
Object.keys(data.starterData).forEach(sd => {
|
||||||
if (data.dexData[sd]?.caughtAttr && data.starterData[sd] && !data.starterData[sd].abilityAttr) {
|
if (data.dexData[sd]?.caughtAttr && data.starterData[sd] && !data.starterData[sd].abilityAttr) {
|
||||||
data.starterData[sd].abilityAttr = 1;
|
data.starterData[sd].abilityAttr = 1;
|
||||||
@ -20,12 +23,15 @@ export const systemMigrators = [
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Populate legendary Pokémon statistics if they are missing.
|
* Populate legendary Pokémon statistics if they are missing.
|
||||||
* @param data {@linkcode SystemSaveData}
|
* @param data - {@linkcode SystemSaveData}
|
||||||
*/
|
*/
|
||||||
function fixLegendaryStats(data: SystemSaveData) {
|
const fixLegendaryStats: SystemSaveMigrator = {
|
||||||
|
version: "1.0.4",
|
||||||
|
migrate: (data: SystemSaveData): void => {
|
||||||
if (
|
if (
|
||||||
data.gameStats &&
|
data.gameStats &&
|
||||||
data.gameStats.legendaryPokemonCaught !== undefined &&
|
data.gameStats.legendaryPokemonCaught !== undefined &&
|
||||||
@ -34,7 +40,6 @@ export const systemMigrators = [
|
|||||||
data.gameStats.subLegendaryPokemonSeen = 0;
|
data.gameStats.subLegendaryPokemonSeen = 0;
|
||||||
data.gameStats.subLegendaryPokemonCaught = 0;
|
data.gameStats.subLegendaryPokemonCaught = 0;
|
||||||
data.gameStats.subLegendaryPokemonHatched = 0;
|
data.gameStats.subLegendaryPokemonHatched = 0;
|
||||||
// biome-ignore lint/complexity/noForEach: <explanation>
|
|
||||||
allSpecies
|
allSpecies
|
||||||
.filter(s => s.subLegendary)
|
.filter(s => s.subLegendary)
|
||||||
.forEach(s => {
|
.forEach(s => {
|
||||||
@ -66,12 +71,15 @@ export const systemMigrators = [
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unlock all starters' first ability and female gender option.
|
* Unlock all starters' first ability and female gender option.
|
||||||
* @param data {@linkcode SystemSaveData}
|
* @param data - {@linkcode SystemSaveData}
|
||||||
*/
|
*/
|
||||||
function fixStarterData(data: SystemSaveData) {
|
const fixStarterData: SystemSaveMigrator = {
|
||||||
|
version: "1.0.4",
|
||||||
|
migrate: (data: SystemSaveData): void => {
|
||||||
if (!isNullOrUndefined(data.starterData)) {
|
if (!isNullOrUndefined(data.starterData)) {
|
||||||
for (const starterId of defaultStarterSpecies) {
|
for (const starterId of defaultStarterSpecies) {
|
||||||
if (data.starterData[starterId]?.abilityAttr) {
|
if (data.starterData[starterId]?.abilityAttr) {
|
||||||
@ -83,17 +91,22 @@ export const systemMigrators = [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const systemMigrators: Readonly<SystemSaveMigrator[]> = [
|
||||||
|
migrateAbilityData,
|
||||||
|
fixLegendaryStats,
|
||||||
|
fixStarterData,
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const settingsMigrators = [
|
/**
|
||||||
/**
|
* Migrate from `REROLL_TARGET` property to {@linkcode SettingKeys.Shop_Cursor_Target}
|
||||||
* Migrate from "REROLL_TARGET" property to {@linkcode
|
* @param data - The `settings` object
|
||||||
* SettingKeys.Shop_Cursor_Target}.
|
|
||||||
* @param data the `settings` object
|
|
||||||
*/
|
*/
|
||||||
|
const fixRerollTarget: SettingsSaveMigrator = {
|
||||||
// biome-ignore lint/complexity/noBannedTypes: TODO: fix the type to not be object...
|
version: "1.0.4",
|
||||||
function fixRerollTarget(data: Object) {
|
// biome-ignore lint/complexity/noBannedTypes: TODO - refactor settings
|
||||||
|
migrate: (data: Object): void => {
|
||||||
if (data.hasOwnProperty("REROLL_TARGET") && !data.hasOwnProperty(SettingKeys.Shop_Cursor_Target)) {
|
if (data.hasOwnProperty("REROLL_TARGET") && !data.hasOwnProperty(SettingKeys.Shop_Cursor_Target)) {
|
||||||
data[SettingKeys.Shop_Cursor_Target] = data["REROLL_TARGET"];
|
data[SettingKeys.Shop_Cursor_Target] = data["REROLL_TARGET"];
|
||||||
// biome-ignore lint/performance/noDelete: intentional
|
// biome-ignore lint/performance/noDelete: intentional
|
||||||
@ -101,16 +114,20 @@ export const settingsMigrators = [
|
|||||||
localStorage.setItem("settings", JSON.stringify(data));
|
localStorage.setItem("settings", JSON.stringify(data));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
] as const;
|
};
|
||||||
|
|
||||||
export const sessionMigrators = [
|
export const settingsMigrators: Readonly<SettingsSaveMigrator[]> = [fixRerollTarget] as const;
|
||||||
/**
|
|
||||||
|
/**
|
||||||
* Converts old lapsing modifiers (battle items, lures, and Dire Hit) and
|
* Converts old lapsing modifiers (battle items, lures, and Dire Hit) and
|
||||||
* other miscellaneous modifiers (vitamins, White Herb) to any new class
|
* other miscellaneous modifiers (vitamins, White Herb) to any new class
|
||||||
* names and/or change in reload arguments.
|
* names and/or change in reload arguments.
|
||||||
* @param data {@linkcode SessionSaveData}
|
* @param data - {@linkcode SessionSaveData}
|
||||||
*/
|
*/
|
||||||
function migrateModifiers(data: SessionSaveData) {
|
const migrateModifiers: SessionSaveMigrator = {
|
||||||
|
version: "1.0.4",
|
||||||
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: necessary?
|
||||||
|
migrate: (data: SessionSaveData): void => {
|
||||||
for (const m of data.modifiers) {
|
for (const m of data.modifiers) {
|
||||||
if (m.className === "PokemonBaseStatModifier") {
|
if (m.className === "PokemonBaseStatModifier") {
|
||||||
m.className = "BaseStatModifier";
|
m.className = "BaseStatModifier";
|
||||||
@ -163,12 +180,11 @@ export const sessionMigrators = [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/**
|
};
|
||||||
* Converts old Pokemon natureOverride and mysteryEncounterData
|
|
||||||
* to use the new conjoined {@linkcode Pokemon.customPokemonData} structure instead.
|
const migrateCustomPokemonData: SessionSaveMigrator = {
|
||||||
* @param data {@linkcode SessionSaveData}
|
version: "1.0.4",
|
||||||
*/
|
migrate: (data: SessionSaveData): void => {
|
||||||
function migrateCustomPokemonDataAndNatureOverrides(data: SessionSaveData) {
|
|
||||||
// Fix Pokemon nature overrides and custom data migration
|
// Fix Pokemon nature overrides and custom data migration
|
||||||
for (const pokemon of data.party) {
|
for (const pokemon of data.party) {
|
||||||
if (pokemon["mysteryEncounterPokemonData"]) {
|
if (pokemon["mysteryEncounterPokemonData"]) {
|
||||||
@ -186,4 +202,6 @@ export const sessionMigrators = [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
] as const;
|
};
|
||||||
|
|
||||||
|
export const sessionMigrators: Readonly<SessionSaveMigrator[]> = [migrateModifiers, migrateCustomPokemonData] as const;
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
export const systemMigrators = [] as const;
|
|
||||||
|
|
||||||
export const settingsMigrators = [] as const;
|
|
||||||
|
|
||||||
export const sessionMigrators = [] as const;
|
|
@ -1,15 +1,18 @@
|
|||||||
|
import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator";
|
||||||
|
import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator";
|
||||||
import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species";
|
import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { DexAttr, type SessionSaveData, type SystemSaveData } from "#app/system/game-data";
|
import { DexAttr, type SessionSaveData, type SystemSaveData } from "#app/system/game-data";
|
||||||
import * as Utils from "#app/utils";
|
import { isNullOrUndefined } from "#app/utils";
|
||||||
|
|
||||||
export const systemMigrators = [
|
/**
|
||||||
/**
|
|
||||||
* If a starter is caught, but the only forms registered as caught are not starterSelectable,
|
* If a starter is caught, but the only forms registered as caught are not starterSelectable,
|
||||||
* unlock the default form.
|
* unlock the default form.
|
||||||
* @param data {@linkcode SystemSaveData}
|
* @param data - {@linkcode SystemSaveData}
|
||||||
*/
|
*/
|
||||||
function migrateUnselectableForms(data: SystemSaveData) {
|
const migrateUnselectableForms: SystemSaveMigrator = {
|
||||||
|
version: "1.7.0",
|
||||||
|
migrate: (data: SystemSaveData): void => {
|
||||||
if (data.starterData && data.dexData) {
|
if (data.starterData && data.dexData) {
|
||||||
Object.keys(data.starterData).forEach(sd => {
|
Object.keys(data.starterData).forEach(sd => {
|
||||||
const caughtAttr = data.dexData[sd]?.caughtAttr;
|
const caughtAttr = data.dexData[sd]?.caughtAttr;
|
||||||
@ -30,12 +33,13 @@ export const systemMigrators = [
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
] as const;
|
};
|
||||||
|
|
||||||
export const settingsMigrators = [] as const;
|
export const systemMigrators: Readonly<SystemSaveMigrator[]> = [migrateUnselectableForms] as const;
|
||||||
|
|
||||||
export const sessionMigrators = [
|
const migrateTera: SessionSaveMigrator = {
|
||||||
function migrateTera(data: SessionSaveData) {
|
version: "1.7.0",
|
||||||
|
migrate: (data: SessionSaveData): void => {
|
||||||
for (let i = 0; i < data.modifiers.length; ) {
|
for (let i = 0; i < data.modifiers.length; ) {
|
||||||
if (data.modifiers[i].className === "TerastallizeModifier") {
|
if (data.modifiers[i].className === "TerastallizeModifier") {
|
||||||
data.party.forEach(p => {
|
data.party.forEach(p => {
|
||||||
@ -63,15 +67,17 @@ export const sessionMigrators = [
|
|||||||
}
|
}
|
||||||
|
|
||||||
data.party.forEach(p => {
|
data.party.forEach(p => {
|
||||||
if (Utils.isNullOrUndefined(p.teraType)) {
|
if (isNullOrUndefined(p.teraType)) {
|
||||||
p.teraType = getPokemonSpeciesForm(p.species, p.formIndex).type1;
|
p.teraType = getPokemonSpeciesForm(p.species, p.formIndex).type1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
data.enemyParty.forEach(p => {
|
data.enemyParty.forEach(p => {
|
||||||
if (Utils.isNullOrUndefined(p.teraType)) {
|
if (isNullOrUndefined(p.teraType)) {
|
||||||
p.teraType = getPokemonSpeciesForm(p.species, p.formIndex).type1;
|
p.teraType = getPokemonSpeciesForm(p.species, p.formIndex).type1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
] as const;
|
};
|
||||||
|
|
||||||
|
export const sessionMigrators: Readonly<SessionSaveMigrator[]> = [migrateTera] as const;
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
|
import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator";
|
||||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||||
import { DexAttr, type SystemSaveData } from "#app/system/game-data";
|
import { DexAttr, type SystemSaveData } from "#app/system/game-data";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
|
|
||||||
export const systemMigrators = [
|
/**
|
||||||
/**
|
|
||||||
* If a starter is caught, but the only forms registered as caught are not starterSelectable,
|
* If a starter is caught, but the only forms registered as caught are not starterSelectable,
|
||||||
* unlock the default form.
|
* unlock the default form.
|
||||||
* @param data {@linkcode SystemSaveData}
|
* @param data - {@linkcode SystemSaveData}
|
||||||
*/
|
*/
|
||||||
function migratePichuForms(data: SystemSaveData) {
|
const migratePichuForms: SystemSaveMigrator = {
|
||||||
|
version: "1.8.3",
|
||||||
|
migrate: (data: SystemSaveData): void => {
|
||||||
if (data.starterData && data.dexData) {
|
if (data.starterData && data.dexData) {
|
||||||
// This is Pichu's Pokédex number
|
// This is Pichu's Pokédex number
|
||||||
const sd = 172;
|
const sd = 172;
|
||||||
@ -23,8 +25,6 @@ export const systemMigrators = [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
] as const;
|
};
|
||||||
|
|
||||||
export const settingsMigrators = [] as const;
|
export const systemMigrators: Readonly<SystemSaveMigrator[]> = [migratePichuForms] as const;
|
||||||
|
|
||||||
export const sessionMigrators = [] as const;
|
|
||||||
|
51
test/abilities/immunity.test.ts
Normal file
51
test/abilities/immunity.test.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Abilities - Immunity", () => {
|
||||||
|
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.SPLASH ])
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove poison when gained", async () => {
|
||||||
|
game.override.ability(Abilities.IMMUNITY)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.moveset(Moves.SKILL_SWAP)
|
||||||
|
.enemyMoveset(Moves.SPLASH),
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
const enemy = game.scene.getEnemyPokemon();
|
||||||
|
enemy?.trySetStatus(StatusEffect.POISON);
|
||||||
|
expect(enemy?.status?.effect).toBe(StatusEffect.POISON);
|
||||||
|
|
||||||
|
game.move.select(Moves.SKILL_SWAP);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(enemy?.status).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
51
test/abilities/insomnia.test.ts
Normal file
51
test/abilities/insomnia.test.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Abilities - Insomnia", () => {
|
||||||
|
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.SPLASH ])
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove sleep when gained", async () => {
|
||||||
|
game.override.ability(Abilities.INSOMNIA)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.moveset(Moves.SKILL_SWAP)
|
||||||
|
.enemyMoveset(Moves.SPLASH),
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
const enemy = game.scene.getEnemyPokemon();
|
||||||
|
enemy?.trySetStatus(StatusEffect.SLEEP);
|
||||||
|
expect(enemy?.status?.effect).toBe(StatusEffect.SLEEP);
|
||||||
|
|
||||||
|
game.move.select(Moves.SKILL_SWAP);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(enemy?.status).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
51
test/abilities/limber.test.ts
Normal file
51
test/abilities/limber.test.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Abilities - Limber", () => {
|
||||||
|
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.SPLASH ])
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove paralysis when gained", async () => {
|
||||||
|
game.override.ability(Abilities.LIMBER)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.moveset(Moves.SKILL_SWAP)
|
||||||
|
.enemyMoveset(Moves.SPLASH),
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
const enemy = game.scene.getEnemyPokemon();
|
||||||
|
enemy?.trySetStatus(StatusEffect.PARALYSIS);
|
||||||
|
expect(enemy?.status?.effect).toBe(StatusEffect.PARALYSIS);
|
||||||
|
|
||||||
|
game.move.select(Moves.SKILL_SWAP);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(enemy?.status).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
51
test/abilities/magma_armor.test.ts
Normal file
51
test/abilities/magma_armor.test.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Abilities - Magma Armor", () => {
|
||||||
|
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.SPLASH ])
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove freeze when gained", async () => {
|
||||||
|
game.override.ability(Abilities.MAGMA_ARMOR)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.moveset(Moves.SKILL_SWAP)
|
||||||
|
.enemyMoveset(Moves.SPLASH),
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
const enemy = game.scene.getEnemyPokemon();
|
||||||
|
enemy?.trySetStatus(StatusEffect.FREEZE);
|
||||||
|
expect(enemy?.status?.effect).toBe(StatusEffect.FREEZE);
|
||||||
|
|
||||||
|
game.move.select(Moves.SKILL_SWAP);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(enemy?.status).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
69
test/abilities/oblivious.test.ts
Normal file
69
test/abilities/oblivious.test.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
describe("Abilities - Oblivious", () => {
|
||||||
|
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.SPLASH ])
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove taunt when gained", async () => {
|
||||||
|
game.override.ability(Abilities.OBLIVIOUS)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.moveset(Moves.SKILL_SWAP)
|
||||||
|
.enemyMoveset(Moves.SPLASH),
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
const enemy = game.scene.getEnemyPokemon();
|
||||||
|
enemy?.addTag(BattlerTagType.TAUNT);
|
||||||
|
expect(enemy?.getTag(BattlerTagType.TAUNT)).toBeTruthy();
|
||||||
|
|
||||||
|
game.move.select(Moves.SKILL_SWAP);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(enemy?.getTag(BattlerTagType.TAUNT)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove infatuation when gained", async () => {
|
||||||
|
game.override.ability(Abilities.OBLIVIOUS)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.moveset(Moves.SKILL_SWAP)
|
||||||
|
.enemyMoveset(Moves.SPLASH),
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
const enemy = game.scene.getEnemyPokemon();
|
||||||
|
vi.spyOn(enemy!, "isOppositeGender").mockReturnValue(true);
|
||||||
|
enemy?.addTag(BattlerTagType.INFATUATED, 5, Moves.JUDGMENT, game.scene.getPlayerPokemon()?.id); // sourceID needs to be defined
|
||||||
|
expect(enemy?.getTag(BattlerTagType.INFATUATED)).toBeTruthy();
|
||||||
|
|
||||||
|
game.move.select(Moves.SKILL_SWAP);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(enemy?.getTag(BattlerTagType.INFATUATED)).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
51
test/abilities/own_tempo.test.ts
Normal file
51
test/abilities/own_tempo.test.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Abilities - Own Tempo", () => {
|
||||||
|
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.SPLASH ])
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove confusion when gained", async () => {
|
||||||
|
game.override.ability(Abilities.OWN_TEMPO)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.moveset(Moves.SKILL_SWAP)
|
||||||
|
.enemyMoveset(Moves.SPLASH),
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
const enemy = game.scene.getEnemyPokemon();
|
||||||
|
enemy?.addTag(BattlerTagType.CONFUSED);
|
||||||
|
expect(enemy?.getTag(BattlerTagType.CONFUSED)).toBeTruthy();
|
||||||
|
|
||||||
|
game.move.select(Moves.SKILL_SWAP);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(enemy?.getTag(BattlerTagType.CONFUSED)).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
43
test/abilities/super_luck.test.ts
Normal file
43
test/abilities/super_luck.test.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
describe("Abilities - Super Luck", () => {
|
||||||
|
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.TACKLE])
|
||||||
|
.ability(Abilities.SUPER_LUCK)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should increase the crit stage of a user by 1", async () => {
|
||||||
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
const fn = vi.spyOn(enemy, "getCritStage");
|
||||||
|
game.move.select(Moves.TACKLE);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
expect(fn).toHaveReturnedWith(1);
|
||||||
|
fn.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
51
test/abilities/thermal_exchange.test.ts
Normal file
51
test/abilities/thermal_exchange.test.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Abilities - Thermal Exchange", () => {
|
||||||
|
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.SPLASH ])
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove burn when gained", async () => {
|
||||||
|
game.override.ability(Abilities.THERMAL_EXCHANGE)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.moveset(Moves.SKILL_SWAP)
|
||||||
|
.enemyMoveset(Moves.SPLASH),
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
const enemy = game.scene.getEnemyPokemon();
|
||||||
|
enemy?.trySetStatus(StatusEffect.BURN);
|
||||||
|
expect(enemy?.status?.effect).toBe(StatusEffect.BURN);
|
||||||
|
|
||||||
|
game.move.select(Moves.SKILL_SWAP);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(enemy?.status).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
51
test/abilities/vital_spirit.test.ts
Normal file
51
test/abilities/vital_spirit.test.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Abilities - Vital Spirit", () => {
|
||||||
|
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.SPLASH ])
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove sleep when gained", async () => {
|
||||||
|
game.override.ability(Abilities.INSOMNIA)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.moveset(Moves.SKILL_SWAP)
|
||||||
|
.enemyMoveset(Moves.SPLASH),
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
const enemy = game.scene.getEnemyPokemon();
|
||||||
|
enemy?.trySetStatus(StatusEffect.SLEEP);
|
||||||
|
expect(enemy?.status?.effect).toBe(StatusEffect.SLEEP);
|
||||||
|
|
||||||
|
game.move.select(Moves.SKILL_SWAP);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(enemy?.status).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
51
test/abilities/water_bubble.test.ts
Normal file
51
test/abilities/water_bubble.test.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Abilities - Water Bubble", () => {
|
||||||
|
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.SPLASH ])
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove burn when gained", async () => {
|
||||||
|
game.override.ability(Abilities.THERMAL_EXCHANGE)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.moveset(Moves.SKILL_SWAP)
|
||||||
|
.enemyMoveset(Moves.SPLASH),
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
const enemy = game.scene.getEnemyPokemon();
|
||||||
|
enemy?.trySetStatus(StatusEffect.BURN);
|
||||||
|
expect(enemy?.status?.effect).toBe(StatusEffect.BURN);
|
||||||
|
|
||||||
|
game.move.select(Moves.SKILL_SWAP);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(enemy?.status).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
51
test/abilities/water_veil.test.ts
Normal file
51
test/abilities/water_veil.test.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Abilities - Water Veil", () => {
|
||||||
|
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.SPLASH ])
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove burn when gained", async () => {
|
||||||
|
game.override.ability(Abilities.THERMAL_EXCHANGE)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.moveset(Moves.SKILL_SWAP)
|
||||||
|
.enemyMoveset(Moves.SPLASH),
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
const enemy = game.scene.getEnemyPokemon();
|
||||||
|
enemy?.trySetStatus(StatusEffect.BURN);
|
||||||
|
expect(enemy?.status?.effect).toBe(StatusEffect.BURN);
|
||||||
|
|
||||||
|
game.move.select(Moves.SKILL_SWAP);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(enemy?.status).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
@ -4,6 +4,7 @@ import { Species } from "#enums/species";
|
|||||||
import GameManager from "#test/testUtils/gameManager";
|
import GameManager from "#test/testUtils/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";
|
||||||
|
import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag";
|
||||||
|
|
||||||
describe("Moves - Spikes", () => {
|
describe("Moves - Spikes", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
@ -77,4 +78,17 @@ describe("Moves - Spikes", () => {
|
|||||||
const enemy = game.scene.getEnemyParty()[0];
|
const enemy = game.scene.getEnemyParty()[0];
|
||||||
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
|
it("should work when all targets fainted", async () => {
|
||||||
|
game.override.enemySpecies(Species.DIGLETT);
|
||||||
|
game.override.battleType("double");
|
||||||
|
game.override.startingLevel(50);
|
||||||
|
await game.classicMode.startBattle([Species.RAYQUAZA, Species.ROWLET]);
|
||||||
|
|
||||||
|
game.move.select(Moves.EARTHQUAKE);
|
||||||
|
game.move.select(Moves.SPIKES, 1);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
expect(game.scene.arena.getTagOnSide(ArenaTrapTag, ArenaTagSide.ENEMY)).toBeDefined();
|
||||||
|
}, 20000);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user