Merge branch 'beta' of https://github.com/pagefaultgames/pokerogue into assistbug2
This commit is contained in:
commit
25e4037e45
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "pokemon-rogue-battle",
|
||||
"version": "1.0.4",
|
||||
"version": "1.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pokemon-rogue-battle",
|
||||
"version": "1.0.4",
|
||||
"version": "1.1.0",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@material/material-color-utilities": "^0.2.7",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "pokemon-rogue-battle",
|
||||
"private": true,
|
||||
"version": "1.0.4",
|
||||
"version": "1.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
|
@ -20,7 +20,7 @@
|
|||
"depcruise": "depcruise src",
|
||||
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg",
|
||||
"create-test": "node ./create-test-boilerplate.js",
|
||||
"postinstall": "npx lefthook install && npx lefthook run post-merge"
|
||||
"postinstall": "npx lefthook install && npx lefthook run post-merge"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.3.0",
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 441 B After Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 6.2 KiB |
|
@ -4,7 +4,7 @@ import Pokemon, { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
|
|||
import PokemonSpecies, { allSpecies, getPokemonSpecies, PokemonSpeciesFilter } from "#app/data/pokemon-species";
|
||||
import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils";
|
||||
import * as Utils from "#app/utils";
|
||||
import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, Modifier, ModifierBar, ModifierPredicate, MultipleParticipantExpBonusModifier, overrideHeldItems, overrideModifiers, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, TerastallizeModifier, TurnHeldItemTransferModifier } from "./modifier/modifier";
|
||||
import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, Modifier, ModifierBar, ModifierPredicate, MultipleParticipantExpBonusModifier, overrideHeldItems, overrideModifiers, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, RememberMoveModifier, TerastallizeModifier, TurnHeldItemTransferModifier } from "./modifier/modifier";
|
||||
import { PokeballType } from "#app/data/pokeball";
|
||||
import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "#app/data/battle-anims";
|
||||
import { Phase } from "#app/phase";
|
||||
|
@ -95,6 +95,7 @@ import { ExpPhase } from "#app/phases/exp-phase";
|
|||
import { ShowPartyExpBarPhase } from "#app/phases/show-party-exp-bar-phase";
|
||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||
import { ExpGainsSpeed } from "#enums/exp-gains-speed";
|
||||
import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#app/data/balance/starters";
|
||||
|
||||
export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1";
|
||||
|
||||
|
@ -789,7 +790,7 @@ export default class BattleScene extends SceneBase {
|
|||
}
|
||||
|
||||
getEnemyParty(): EnemyPokemon[] {
|
||||
return this.currentBattle?.enemyParty || [];
|
||||
return this.currentBattle?.enemyParty ?? [];
|
||||
}
|
||||
|
||||
getEnemyPokemon(): EnemyPokemon | undefined {
|
||||
|
@ -2425,7 +2426,7 @@ export default class BattleScene extends SceneBase {
|
|||
return Math.floor(moneyValue / 10) * 10;
|
||||
}
|
||||
|
||||
addModifier(modifier: Modifier | null, ignoreUpdate?: boolean, playSound?: boolean, virtual?: boolean, instant?: boolean): Promise<boolean> {
|
||||
addModifier(modifier: Modifier | null, ignoreUpdate?: boolean, playSound?: boolean, virtual?: boolean, instant?: boolean, cost?: number): Promise<boolean> {
|
||||
if (!modifier) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
@ -2482,6 +2483,8 @@ export default class BattleScene extends SceneBase {
|
|||
}
|
||||
} else if (modifier instanceof FusePokemonModifier) {
|
||||
args.push(this.getPokemonById(modifier.fusePokemonId) as PlayerPokemon);
|
||||
} else if (modifier instanceof RememberMoveModifier && !Utils.isNullOrUndefined(cost)) {
|
||||
args.push(cost);
|
||||
}
|
||||
|
||||
if (modifier.shouldApply(pokemon, ...args)) {
|
||||
|
@ -3052,7 +3055,7 @@ export default class BattleScene extends SceneBase {
|
|||
const pId = partyMember.id;
|
||||
const participated = participantIds.has(pId);
|
||||
if (participated && pokemonDefeated) {
|
||||
partyMember.addFriendship(2);
|
||||
partyMember.addFriendship(FRIENDSHIP_GAIN_FROM_BATTLE);
|
||||
const machoBraceModifier = partyMember.getHeldItems().find(m => m instanceof PokemonIncrementingStatModifier);
|
||||
if (machoBraceModifier && machoBraceModifier.stackCount < machoBraceModifier.getMaxStackCount(this)) {
|
||||
machoBraceModifier.stackCount++;
|
||||
|
|
|
@ -4913,8 +4913,7 @@ export function initAbilities() {
|
|||
.attr(TypeImmunityAddBattlerTagAbAttr, Type.FIRE, BattlerTagType.FIRE_BOOST, 1)
|
||||
.ignorable(),
|
||||
new Ability(Abilities.SHIELD_DUST, 3)
|
||||
.attr(IgnoreMoveEffectsAbAttr)
|
||||
.edgeCase(), // Does not work with secret power (unimplemented)
|
||||
.attr(IgnoreMoveEffectsAbAttr),
|
||||
new Ability(Abilities.OWN_TEMPO, 3)
|
||||
.attr(BattlerTagImmunityAbAttr, BattlerTagType.CONFUSED)
|
||||
.attr(IntimidateImmunityAbAttr)
|
||||
|
@ -4958,8 +4957,7 @@ export function initAbilities() {
|
|||
.attr(TypeImmunityStatStageChangeAbAttr, Type.ELECTRIC, Stat.SPATK, 1)
|
||||
.ignorable(),
|
||||
new Ability(Abilities.SERENE_GRACE, 3)
|
||||
.attr(MoveEffectChanceMultiplierAbAttr, 2)
|
||||
.edgeCase(), // does not work with secret power (unimplemented)
|
||||
.attr(MoveEffectChanceMultiplierAbAttr, 2),
|
||||
new Ability(Abilities.SWIFT_SWIM, 3)
|
||||
.attr(StatMultiplierAbAttr, Stat.SPD, 2)
|
||||
.condition(getWeatherCondition(WeatherType.RAIN, WeatherType.HEAVY_RAIN)),
|
||||
|
|
|
@ -2,6 +2,12 @@ import { Species } from "#enums/species";
|
|||
|
||||
export const POKERUS_STARTER_COUNT = 5;
|
||||
|
||||
// #region Friendship constants
|
||||
export const CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER = 2;
|
||||
export const FRIENDSHIP_GAIN_FROM_BATTLE = 2;
|
||||
export const FRIENDSHIP_GAIN_FROM_RARE_CANDY = 5;
|
||||
export const FRIENDSHIP_LOSS_FROM_FAINT = 10;
|
||||
|
||||
/**
|
||||
* Function to get the cumulative friendship threshold at which a candy is earned
|
||||
* @param starterCost The cost of the starter, found in {@linkcode speciesStarterCosts}
|
||||
|
|
172
src/data/move.ts
172
src/data/move.ts
|
@ -1013,7 +1013,7 @@ export class MoveEffectAttr extends MoveAttr {
|
|||
|
||||
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, false, moveChance, move, target, selfEffect, showAbility);
|
||||
|
||||
if (!move.hasAttr(FlinchAttr) || moveChance.value <= move.chance) {
|
||||
if ((!move.hasAttr(FlinchAttr) || moveChance.value <= move.chance) && !move.hasAttr(SecretPowerAttr)) {
|
||||
const userSide = user.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||
user.scene.arena.applyTagsForSide(ArenaTagType.WATER_FIRE_PLEDGE, userSide, false, moveChance);
|
||||
}
|
||||
|
@ -2865,6 +2865,162 @@ export class StatStageChangeAttr extends MoveEffectAttr {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute used to determine the Biome/Terrain-based secondary effect of Secret Power
|
||||
*/
|
||||
export class SecretPowerAttr extends MoveEffectAttr {
|
||||
constructor() {
|
||||
super(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to determine if the move should apply a secondary effect based on Secret Power's 30% chance
|
||||
* @returns `true` if the move's secondary effect should apply
|
||||
*/
|
||||
override canApply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean {
|
||||
this.effectChanceOverride = move.chance;
|
||||
const moveChance = this.getMoveChance(user, target, move, this.selfTarget);
|
||||
if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to apply the secondary effect to the target Pokemon
|
||||
* @returns `true` if a secondary effect is successfully applied
|
||||
*/
|
||||
override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean | Promise<boolean> {
|
||||
if (!super.apply(user, target, move, args)) {
|
||||
return false;
|
||||
}
|
||||
let secondaryEffect: MoveEffectAttr;
|
||||
const terrain = user.scene.arena.getTerrainType();
|
||||
if (terrain !== TerrainType.NONE) {
|
||||
secondaryEffect = this.determineTerrainEffect(terrain);
|
||||
} else {
|
||||
const biome = user.scene.arena.biomeType;
|
||||
secondaryEffect = this.determineBiomeEffect(biome);
|
||||
}
|
||||
// effectChanceOverride used in the application of the actual secondary effect
|
||||
secondaryEffect.effectChanceOverride = 100;
|
||||
return secondaryEffect.apply(user, target, move, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the secondary effect based on terrain.
|
||||
* Takes precedence over biome-based effects.
|
||||
* ```
|
||||
* Electric Terrain | Paralysis
|
||||
* Misty Terrain | SpAtk -1
|
||||
* Grassy Terrain | Sleep
|
||||
* Psychic Terrain | Speed -1
|
||||
* ```
|
||||
* @param terrain - {@linkcode TerrainType} The current terrain
|
||||
* @returns the chosen secondary effect {@linkcode MoveEffectAttr}
|
||||
*/
|
||||
private determineTerrainEffect(terrain: TerrainType): MoveEffectAttr {
|
||||
let secondaryEffect: MoveEffectAttr;
|
||||
switch (terrain) {
|
||||
case TerrainType.ELECTRIC:
|
||||
default:
|
||||
secondaryEffect = new StatusEffectAttr(StatusEffect.PARALYSIS, false);
|
||||
break;
|
||||
case TerrainType.MISTY:
|
||||
secondaryEffect = new StatStageChangeAttr([ Stat.SPATK ], -1, false);
|
||||
break;
|
||||
case TerrainType.GRASSY:
|
||||
secondaryEffect = new StatusEffectAttr(StatusEffect.SLEEP, false);
|
||||
break;
|
||||
case TerrainType.PSYCHIC:
|
||||
secondaryEffect = new StatStageChangeAttr([ Stat.SPD ], -1, false);
|
||||
break;
|
||||
}
|
||||
return secondaryEffect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the secondary effect based on biome
|
||||
* ```
|
||||
* Town, Metropolis, Slum, Dojo, Laboratory, Power Plant + Default | Paralysis
|
||||
* Plains, Grass, Tall Grass, Forest, Jungle, Meadow | Sleep
|
||||
* Swamp, Mountain, Temple, Ruins | Speed -1
|
||||
* Ice Cave, Snowy Forest | Freeze
|
||||
* Volcano | Burn
|
||||
* Fairy Cave | SpAtk -1
|
||||
* Desert, Construction Site, Beach, Island, Badlands | Accuracy -1
|
||||
* Sea, Lake, Seabed | Atk -1
|
||||
* Cave, Wasteland, Graveyard, Abyss, Space | Flinch
|
||||
* End | Def -1
|
||||
* ```
|
||||
* @param biome - The current {@linkcode Biome} the battle is set in
|
||||
* @returns the chosen secondary effect {@linkcode MoveEffectAttr}
|
||||
*/
|
||||
private determineBiomeEffect(biome: Biome): MoveEffectAttr {
|
||||
let secondaryEffect: MoveEffectAttr;
|
||||
switch (biome) {
|
||||
case Biome.PLAINS:
|
||||
case Biome.GRASS:
|
||||
case Biome.TALL_GRASS:
|
||||
case Biome.FOREST:
|
||||
case Biome.JUNGLE:
|
||||
case Biome.MEADOW:
|
||||
secondaryEffect = new StatusEffectAttr(StatusEffect.SLEEP, false);
|
||||
break;
|
||||
case Biome.SWAMP:
|
||||
case Biome.MOUNTAIN:
|
||||
case Biome.TEMPLE:
|
||||
case Biome.RUINS:
|
||||
secondaryEffect = new StatStageChangeAttr([ Stat.SPD ], -1, false);
|
||||
break;
|
||||
case Biome.ICE_CAVE:
|
||||
case Biome.SNOWY_FOREST:
|
||||
secondaryEffect = new StatusEffectAttr(StatusEffect.FREEZE, false);
|
||||
break;
|
||||
case Biome.VOLCANO:
|
||||
secondaryEffect = new StatusEffectAttr(StatusEffect.BURN, false);
|
||||
break;
|
||||
case Biome.FAIRY_CAVE:
|
||||
secondaryEffect = new StatStageChangeAttr([ Stat.SPATK ], -1, false);
|
||||
break;
|
||||
case Biome.DESERT:
|
||||
case Biome.CONSTRUCTION_SITE:
|
||||
case Biome.BEACH:
|
||||
case Biome.ISLAND:
|
||||
case Biome.BADLANDS:
|
||||
secondaryEffect = new StatStageChangeAttr([ Stat.ACC ], -1, false);
|
||||
break;
|
||||
case Biome.SEA:
|
||||
case Biome.LAKE:
|
||||
case Biome.SEABED:
|
||||
secondaryEffect = new StatStageChangeAttr([ Stat.ATK ], -1, false);
|
||||
break;
|
||||
case Biome.CAVE:
|
||||
case Biome.WASTELAND:
|
||||
case Biome.GRAVEYARD:
|
||||
case Biome.ABYSS:
|
||||
case Biome.SPACE:
|
||||
secondaryEffect = new AddBattlerTagAttr(BattlerTagType.FLINCHED, false, true);
|
||||
break;
|
||||
case Biome.END:
|
||||
secondaryEffect = new StatStageChangeAttr([ Stat.DEF ], -1, false);
|
||||
break;
|
||||
case Biome.TOWN:
|
||||
case Biome.METROPOLIS:
|
||||
case Biome.SLUM:
|
||||
case Biome.DOJO:
|
||||
case Biome.FACTORY:
|
||||
case Biome.LABORATORY:
|
||||
case Biome.POWER_PLANT:
|
||||
default:
|
||||
secondaryEffect = new StatusEffectAttr(StatusEffect.PARALYSIS, false);
|
||||
break;
|
||||
}
|
||||
return secondaryEffect;
|
||||
}
|
||||
}
|
||||
|
||||
export class PostVictoryStatStageChangeAttr extends MoveAttr {
|
||||
private stats: BattleStat[];
|
||||
private stages: number;
|
||||
|
@ -3824,8 +3980,8 @@ export class LastMoveDoublePowerAttr extends VariablePowerAttr {
|
|||
|
||||
for (const p of pokemonActed) {
|
||||
const [ lastMove ] = p.getLastXMoves(1);
|
||||
if (lastMove.result !== MoveResult.FAIL) {
|
||||
if ((lastMove.result === MoveResult.SUCCESS) && (lastMove.move === this.move)) {
|
||||
if (lastMove?.result !== MoveResult.FAIL) {
|
||||
if ((lastMove?.result === MoveResult.SUCCESS) && (lastMove?.move === this.move)) {
|
||||
power.value *= 2;
|
||||
return true;
|
||||
} else {
|
||||
|
@ -4726,7 +4882,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
|
|||
}
|
||||
|
||||
canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
if (!super.canApply(user, target, move, args) || (this.cancelOnFail === true && user.getLastXMoves(1)[0].result === MoveResult.FAIL)) {
|
||||
if (!super.canApply(user, target, move, args) || (this.cancelOnFail === true && user.getLastXMoves(1)[0]?.result === MoveResult.FAIL)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
|
@ -5164,7 +5320,7 @@ export class AddArenaTagAttr extends MoveEffectAttr {
|
|||
return false;
|
||||
}
|
||||
|
||||
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) {
|
||||
user.scene.arena.addTag(this.tagType, this.turnCount, move.id, user.id, (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY);
|
||||
return true;
|
||||
}
|
||||
|
@ -5239,7 +5395,7 @@ export class AddArenaTrapTagHitAttr extends AddArenaTagAttr {
|
|||
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true);
|
||||
const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||
const tag = user.scene.arena.getTagOnSide(this.tagType, side) as ArenaTrapTag;
|
||||
if ((moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) && user.getLastXMoves(1)[0].result === MoveResult.SUCCESS) {
|
||||
if ((moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) && user.getLastXMoves(1)[0]?.result === MoveResult.SUCCESS) {
|
||||
user.scene.arena.addTag(this.tagType, 0, move.id, user.id, side);
|
||||
if (!tag) {
|
||||
return true;
|
||||
|
@ -5376,7 +5532,7 @@ export class AddPledgeEffectAttr extends AddArenaTagAttr {
|
|||
|
||||
override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
// TODO: add support for `HIT` effect triggering in AddArenaTagAttr to remove the need for this check
|
||||
if (user.getLastXMoves(1)[0].result !== MoveResult.SUCCESS) {
|
||||
if (user.getLastXMoves(1)[0]?.result !== MoveResult.SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -8168,7 +8324,7 @@ export function initMoves() {
|
|||
.unimplemented(),
|
||||
new AttackMove(Moves.SECRET_POWER, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, 30, 0, 3)
|
||||
.makesContact(false)
|
||||
.partial(), // No effect implemented
|
||||
.attr(SecretPowerAttr),
|
||||
new AttackMove(Moves.DIVE, Type.WATER, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 3)
|
||||
.attr(ChargeAttr, ChargeAnim.DIVE_CHARGING, i18next.t("moveTriggers:hidUnderwater", { pokemonName: "{USER}" }), BattlerTagType.UNDERWATER, true)
|
||||
.attr(GulpMissileTagAttr),
|
||||
|
|
|
@ -154,7 +154,7 @@ export const ATrainersTestEncounter: MysteryEncounter =
|
|||
};
|
||||
encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.epic`));
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.SACRED_ASH ], guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ULTRA ], fillRemaining: true }, [ eggOptions ]);
|
||||
return initBattleWithEnemyConfig(scene, config);
|
||||
await initBattleWithEnemyConfig(scene, config);
|
||||
}
|
||||
)
|
||||
.withSimpleOption(
|
||||
|
|
|
@ -286,7 +286,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
|
|||
ignorePp: true
|
||||
});
|
||||
|
||||
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
|
||||
await transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
|
||||
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
|
||||
})
|
||||
.build()
|
||||
|
@ -328,7 +328,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
|
|||
});
|
||||
await scene.updateModifiers(true);
|
||||
|
||||
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
|
||||
await transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
})
|
||||
.build()
|
||||
|
@ -359,7 +359,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
|
|||
greedent.moveset = [ new PokemonMove(Moves.THRASH), new PokemonMove(Moves.BODY_PRESS), new PokemonMove(Moves.STUFF_CHEEKS), new PokemonMove(Moves.SLACK_OFF) ];
|
||||
greedent.passive = true;
|
||||
|
||||
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
|
||||
await transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
|
||||
await catchPokemon(scene, greedent, null, PokeballType.POKEBALL, false);
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
})
|
||||
|
|
|
@ -228,7 +228,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
|
|||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Learn its Dance
|
||||
hideOricorioPokemon(scene);
|
||||
await hideOricorioPokemon(scene);
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
})
|
||||
.build()
|
||||
|
@ -303,7 +303,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
|
|||
}
|
||||
}
|
||||
|
||||
hideOricorioPokemon(scene);
|
||||
await hideOricorioPokemon(scene);
|
||||
await catchPokemon(scene, oricorio, null, PokeballType.POKEBALL, false);
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
})
|
||||
|
|
|
@ -182,7 +182,7 @@ export const DarkDealEncounter: MysteryEncounter =
|
|||
const config: EnemyPartyConfig = {
|
||||
pokemonConfigs: [ pokemonConfig ],
|
||||
};
|
||||
return initBattleWithEnemyConfig(scene, config);
|
||||
await initBattleWithEnemyConfig(scene, config);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
|
|
|
@ -222,12 +222,13 @@ export const FieryFalloutEncounter: MysteryEncounter =
|
|||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene) => {
|
||||
// Do NOT await this, to prevent player from repeatedly pressing options
|
||||
transitionMysteryEncounterIntroVisuals(scene, false, false, 2000);
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Fire types help calm the Volcarona
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
transitionMysteryEncounterIntroVisuals(scene);
|
||||
await transitionMysteryEncounterIntroVisuals(scene);
|
||||
setEncounterRewards(scene,
|
||||
{ fillRemaining: true },
|
||||
undefined,
|
||||
|
|
|
@ -152,7 +152,7 @@ export const FunAndGamesEncounter: MysteryEncounter =
|
|||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Leave encounter with no rewards or exp
|
||||
transitionMysteryEncounterIntroVisuals(scene, true, true);
|
||||
await transitionMysteryEncounterIntroVisuals(scene, true, true);
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -399,7 +399,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
|
|||
if (modifier.stackCount === 0) {
|
||||
scene.removeModifier(modifier);
|
||||
}
|
||||
scene.updateModifiers(true, true);
|
||||
await scene.updateModifiers(true, true);
|
||||
|
||||
// Generate a trainer name
|
||||
const traderName = generateRandomTraderName();
|
||||
|
|
|
@ -129,7 +129,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
|||
*
|
||||
* @param scene Battle scene
|
||||
*/
|
||||
async function handlePokemonGuidingYouPhase(scene: BattleScene) {
|
||||
function handlePokemonGuidingYouPhase(scene: BattleScene) {
|
||||
const laprasSpecies = getPokemonSpecies(Species.LAPRAS);
|
||||
const { mysteryEncounter } = scene.currentBattle;
|
||||
|
||||
|
|
|
@ -147,11 +147,11 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
|
|||
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.TM_COMMON, modifierTypes.TM_GREAT, modifierTypes.MEMORY_MUSHROOM ], fillRemaining: true });
|
||||
|
||||
// Seed offsets to remove possibility of different trainers having exact same teams
|
||||
let ret;
|
||||
let initBattlePromise: Promise<void>;
|
||||
scene.executeWithSeedOffset(() => {
|
||||
ret = initBattleWithEnemyConfig(scene, config);
|
||||
initBattlePromise = initBattleWithEnemyConfig(scene, config);
|
||||
}, scene.currentBattle.waveIndex * 10);
|
||||
return ret;
|
||||
await initBattlePromise!;
|
||||
}
|
||||
)
|
||||
.withSimpleOption(
|
||||
|
@ -172,11 +172,11 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
|
|||
setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], fillRemaining: true });
|
||||
|
||||
// Seed offsets to remove possibility of different trainers having exact same teams
|
||||
let ret;
|
||||
let initBattlePromise: Promise<void>;
|
||||
scene.executeWithSeedOffset(() => {
|
||||
ret = initBattleWithEnemyConfig(scene, config);
|
||||
initBattlePromise = initBattleWithEnemyConfig(scene, config);
|
||||
}, scene.currentBattle.waveIndex * 100);
|
||||
return ret;
|
||||
await initBattlePromise!;
|
||||
}
|
||||
)
|
||||
.withSimpleOption(
|
||||
|
@ -200,11 +200,11 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
|
|||
setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT ], fillRemaining: true });
|
||||
|
||||
// Seed offsets to remove possibility of different trainers having exact same teams
|
||||
let ret;
|
||||
let initBattlePromise: Promise<void>;
|
||||
scene.executeWithSeedOffset(() => {
|
||||
ret = initBattleWithEnemyConfig(scene, config);
|
||||
initBattlePromise = initBattleWithEnemyConfig(scene, config);
|
||||
}, scene.currentBattle.waveIndex * 1000);
|
||||
return ret;
|
||||
await initBattlePromise!;
|
||||
}
|
||||
)
|
||||
.withOutroDialogue([
|
||||
|
|
|
@ -184,7 +184,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
|||
scene.unshiftPhase(new GameOverPhase(scene));
|
||||
} else {
|
||||
// Show which Pokemon was KOed, then start battle against Gimmighoul
|
||||
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
|
||||
await transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
|
||||
setEncounterRewards(scene, { fillRemaining: true });
|
||||
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
|
||||
}
|
||||
|
|
|
@ -303,13 +303,16 @@ async function summonSafariPokemon(scene: BattleScene) {
|
|||
scene.unshiftPhase(new SummonPhase(scene, 0, false));
|
||||
|
||||
encounter.setDialogueToken("pokemonName", getPokemonNameWithAffix(pokemon));
|
||||
showEncounterText(scene, getEncounterText(scene, "battle:singleWildAppeared") ?? "", null, 1500, false)
|
||||
.then(() => {
|
||||
const ivScannerModifier = scene.findModifier(m => m instanceof IvScannerModifier);
|
||||
if (ivScannerModifier) {
|
||||
scene.pushPhase(new ScanIvsPhase(scene, pokemon.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6)));
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: If we await showEncounterText here, then the text will display without
|
||||
// the wild Pokemon on screen, but if we don't await it, then the text never
|
||||
// shows up and the IV scanner breaks. For now, we place the IV scanner code
|
||||
// separately so that at least the IV scanner works.
|
||||
|
||||
const ivScannerModifier = scene.findModifier(m => m instanceof IvScannerModifier);
|
||||
if (ivScannerModifier) {
|
||||
scene.pushPhase(new ScanIvsPhase(scene, pokemon.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6)));
|
||||
}
|
||||
}
|
||||
|
||||
function throwPokeball(scene: BattleScene, pokemon: EnemyPokemon): Promise<boolean> {
|
||||
|
|
|
@ -142,7 +142,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
|
|||
encounter.setDialogueToken("newNature", getNatureName(newNature));
|
||||
queueEncounterMessage(scene, `${namespace}:cheap_side_effects`);
|
||||
setEncounterExp(scene, [ chosenPokemon.id ], 100);
|
||||
chosenPokemon.updateInfo();
|
||||
await chosenPokemon.updateInfo();
|
||||
})
|
||||
.build()
|
||||
)
|
||||
|
@ -204,7 +204,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
|
|||
queueEncounterMessage(scene, `${namespace}:no_bad_effects`);
|
||||
setEncounterExp(scene, [ chosenPokemon.id ], 100);
|
||||
|
||||
chosenPokemon.updateInfo();
|
||||
await chosenPokemon.updateInfo();
|
||||
})
|
||||
.build()
|
||||
)
|
||||
|
|
|
@ -149,7 +149,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
|
|||
const magnet = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.STEEL ])!;
|
||||
const metalCoat = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.ELECTRIC ])!;
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeOptions: [ magnet, metalCoat ], fillRemaining: true });
|
||||
transitionMysteryEncounterIntroVisuals(scene, true, true);
|
||||
await transitionMysteryEncounterIntroVisuals(scene, true, true);
|
||||
await initBattleWithEnemyConfig(scene, config);
|
||||
}
|
||||
)
|
||||
|
|
|
@ -61,7 +61,7 @@ const POOL_1_POKEMON: (Species | BreederSpeciesEvolution)[][] = [
|
|||
const POOL_2_POKEMON: (Species | BreederSpeciesEvolution)[][] = [
|
||||
[ Species.PICHU, new BreederSpeciesEvolution(Species.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.RAICHU, FINAL_STAGE_EVOLUTION_WAVE) ],
|
||||
[ Species.PICHU, new BreederSpeciesEvolution(Species.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.ALOLA_RAICHU, FINAL_STAGE_EVOLUTION_WAVE) ],
|
||||
[ Species.JYNX ],
|
||||
[ Species.SMOOCHUM, new BreederSpeciesEvolution(Species.JYNX, SECOND_STAGE_EVOLUTION_WAVE) ],
|
||||
[ Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONLEE, SECOND_STAGE_EVOLUTION_WAVE) ],
|
||||
[ Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONCHAN, SECOND_STAGE_EVOLUTION_WAVE) ],
|
||||
[ Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONTOP, SECOND_STAGE_EVOLUTION_WAVE) ],
|
||||
|
@ -245,7 +245,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
|||
}
|
||||
|
||||
encounter.onGameOver = onGameOver;
|
||||
initBattleWithEnemyConfig(scene, config);
|
||||
await initBattleWithEnemyConfig(scene, config);
|
||||
})
|
||||
.withPostOptionPhase(async (scene: BattleScene) => {
|
||||
await doPostEncounterCleanup(scene);
|
||||
|
@ -297,7 +297,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
|||
}
|
||||
|
||||
encounter.onGameOver = onGameOver;
|
||||
initBattleWithEnemyConfig(scene, config);
|
||||
await initBattleWithEnemyConfig(scene, config);
|
||||
})
|
||||
.withPostOptionPhase(async (scene: BattleScene) => {
|
||||
await doPostEncounterCleanup(scene);
|
||||
|
@ -349,7 +349,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
|||
}
|
||||
|
||||
encounter.onGameOver = onGameOver;
|
||||
initBattleWithEnemyConfig(scene, config);
|
||||
await initBattleWithEnemyConfig(scene, config);
|
||||
})
|
||||
.withPostOptionPhase(async (scene: BattleScene) => {
|
||||
await doPostEncounterCleanup(scene);
|
||||
|
|
|
@ -201,7 +201,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
|
|||
});
|
||||
|
||||
encounter.dialogue.outro = [];
|
||||
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
|
||||
await transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
|
||||
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
|
||||
}
|
||||
)
|
||||
|
|
|
@ -111,8 +111,8 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter =
|
|||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Spawn 5 trainer battles back to back with Macho Brace in rewards
|
||||
scene.currentBattle.mysteryEncounter!.doContinueEncounter = (scene: BattleScene) => {
|
||||
return endTrainerBattleAndShowDialogue(scene);
|
||||
scene.currentBattle.mysteryEncounter!.doContinueEncounter = async (scene: BattleScene) => {
|
||||
await endTrainerBattleAndShowDialogue(scene);
|
||||
};
|
||||
await transitionMysteryEncounterIntroVisuals(scene, true, false);
|
||||
await spawnNextTrainerOrEndEncounter(scene);
|
||||
|
|
|
@ -162,7 +162,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
|
|||
|
||||
setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase);
|
||||
|
||||
return initBattleWithEnemyConfig(scene, config);
|
||||
await initBattleWithEnemyConfig(scene, config);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
|
@ -238,7 +238,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
|
|||
|
||||
setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase);
|
||||
|
||||
return initBattleWithEnemyConfig(scene, config);
|
||||
await initBattleWithEnemyConfig(scene, config);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
|
@ -351,7 +351,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
|
|||
|
||||
setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase);
|
||||
|
||||
return initBattleWithEnemyConfig(scene, config);
|
||||
await initBattleWithEnemyConfig(scene, config);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
|
|
|
@ -105,7 +105,7 @@ export const TrashToTreasureEncounter: MysteryEncounter =
|
|||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Gain 2 Leftovers and 2 Shell Bell
|
||||
transitionMysteryEncounterIntroVisuals(scene);
|
||||
await transitionMysteryEncounterIntroVisuals(scene);
|
||||
await tryApplyDigRewardItems(scene);
|
||||
|
||||
const blackSludge = generateModifierType(scene, modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE, [ SHOP_ITEM_COST_MULTIPLIER ]);
|
||||
|
@ -136,7 +136,7 @@ export const TrashToTreasureEncounter: MysteryEncounter =
|
|||
// Investigate garbage, battle Gmax Garbodor
|
||||
scene.setFieldScale(0.75);
|
||||
await showEncounterText(scene, `${namespace}:option.2.selected_2`);
|
||||
transitionMysteryEncounterIntroVisuals(scene);
|
||||
await transitionMysteryEncounterIntroVisuals(scene);
|
||||
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
|
||||
|
@ -222,7 +222,7 @@ async function tryApplyDigRewardItems(scene: BattleScene) {
|
|||
await showEncounterText(scene, i18next.t("battle:rewardGainCount", { modifierName: shellBell.name, count: 2 }), null, undefined, true);
|
||||
}
|
||||
|
||||
async function doGarbageDig(scene: BattleScene) {
|
||||
function doGarbageDig(scene: BattleScene) {
|
||||
scene.playSound("battle_anims/PRSFX- Dig2");
|
||||
scene.time.delayedCall(SOUND_EFFECT_WAIT_TIME, () => {
|
||||
scene.playSound("battle_anims/PRSFX- Dig2");
|
||||
|
|
|
@ -5,7 +5,7 @@ import { variantData } from "#app/data/variant";
|
|||
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "#app/ui/battle-info";
|
||||
import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget, CombinedPledgeStabBoostAttr } from "#app/data/move";
|
||||
import { default as PokemonSpecies, PokemonSpeciesForm, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species";
|
||||
import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters";
|
||||
import { CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER, getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters";
|
||||
import { starterPassiveAbilities } from "#app/data/balance/passives";
|
||||
import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils";
|
||||
import * as Utils from "#app/utils";
|
||||
|
@ -2810,15 +2810,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
if (this.isFainted()) {
|
||||
// set splice index here, so future scene queues happen before FaintedPhase
|
||||
this.scene.setPhaseQueueSplice();
|
||||
this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex(), isOneHitKo));
|
||||
if (!isNullOrUndefined(destinyTag) && dmg) {
|
||||
// Destiny Bond will activate during FaintPhase
|
||||
this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex(), isOneHitKo, destinyTag, source));
|
||||
} else {
|
||||
this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex(), isOneHitKo));
|
||||
}
|
||||
this.destroySubstitute();
|
||||
this.resetSummonData();
|
||||
}
|
||||
|
||||
if (dmg) {
|
||||
destinyTag?.lapse(source, BattlerTagLapseType.CUSTOM);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -4081,7 +4082,7 @@ export class PlayerPokemon extends Pokemon {
|
|||
fusionStarterSpeciesId ? this.scene.gameData.starterData[fusionStarterSpeciesId] : null
|
||||
].filter(d => !!d);
|
||||
const amount = new Utils.IntegerHolder(friendship);
|
||||
const starterAmount = new Utils.IntegerHolder(Math.floor(friendship * (this.scene.gameMode.isClassic && friendship > 0 ? 2 : 1) / (fusionStarterSpeciesId ? 2 : 1)));
|
||||
const starterAmount = new Utils.IntegerHolder(Math.floor(friendship * (this.scene.gameMode.isClassic && friendship > 0 ? CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER : 1) / (fusionStarterSpeciesId ? 2 : 1)));
|
||||
if (amount.value > 0) {
|
||||
this.scene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount);
|
||||
this.scene.applyModifier(PokemonFriendshipBoosterModifier, true, this, starterAmount);
|
||||
|
|
|
@ -2227,7 +2227,8 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, base
|
|||
],
|
||||
[
|
||||
new ModifierTypeOption(modifierTypes.HYPER_POTION(), 0, baseCost * 0.8),
|
||||
new ModifierTypeOption(modifierTypes.MAX_REVIVE(), 0, baseCost * 2.75)
|
||||
new ModifierTypeOption(modifierTypes.MAX_REVIVE(), 0, baseCost * 2.75),
|
||||
new ModifierTypeOption(modifierTypes.MEMORY_MUSHROOM(), 0, baseCost * 4)
|
||||
],
|
||||
[
|
||||
new ModifierTypeOption(modifierTypes.MAX_POTION(), 0, baseCost * 1.5),
|
||||
|
|
|
@ -11,7 +11,7 @@ import Pokemon, { type PlayerPokemon } from "#app/field/pokemon";
|
|||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import Overrides from "#app/overrides";
|
||||
import { EvolutionPhase } from "#app/phases/evolution-phase";
|
||||
import { LearnMovePhase } from "#app/phases/learn-move-phase";
|
||||
import { LearnMovePhase, LearnMoveType } from "#app/phases/learn-move-phase";
|
||||
import { LevelUpPhase } from "#app/phases/level-up-phase";
|
||||
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
|
||||
import { achvs } from "#app/system/achv";
|
||||
|
@ -30,6 +30,7 @@ import { StatusEffect } from "#enums/status-effect";
|
|||
import i18next from "i18next";
|
||||
import { type DoubleBattleChanceBoosterModifierType, type EvolutionItemModifierType, type FormChangeItemModifierType, type ModifierOverride, type ModifierType, type PokemonBaseStatTotalModifierType, type PokemonExpBoosterModifierType, type PokemonFriendshipBoosterModifierType, type PokemonMoveAccuracyBoosterModifierType, type PokemonMultiHitModifierType, type TerastallizeModifierType, type TmModifierType, getModifierType, ModifierPoolType, ModifierTypeGenerator, modifierTypes, PokemonHeldItemModifierType } from "./modifier-type";
|
||||
import { Color, ShadowColor } from "#enums/color";
|
||||
import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters";
|
||||
|
||||
export type ModifierPredicate = (modifier: Modifier) => boolean;
|
||||
|
||||
|
@ -2213,7 +2214,7 @@ export class PokemonLevelIncrementModifier extends ConsumablePokemonModifier {
|
|||
playerPokemon.levelExp = 0;
|
||||
}
|
||||
|
||||
playerPokemon.addFriendship(5);
|
||||
playerPokemon.addFriendship(FRIENDSHIP_GAIN_FROM_RARE_CANDY);
|
||||
|
||||
playerPokemon.scene.unshiftPhase(new LevelUpPhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), playerPokemon.level - levelCount.value, playerPokemon.level));
|
||||
|
||||
|
@ -2235,7 +2236,7 @@ export class TmModifier extends ConsumablePokemonModifier {
|
|||
*/
|
||||
override apply(playerPokemon: PlayerPokemon): boolean {
|
||||
|
||||
playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), this.type.moveId, true));
|
||||
playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), this.type.moveId, LearnMoveType.TM));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -2255,8 +2256,9 @@ export class RememberMoveModifier extends ConsumablePokemonModifier {
|
|||
* @param playerPokemon The {@linkcode PlayerPokemon} that should remember the move
|
||||
* @returns always `true`
|
||||
*/
|
||||
override apply(playerPokemon: PlayerPokemon): boolean {
|
||||
playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), playerPokemon.getLearnableLevelMoves()[this.levelMoveIndex]));
|
||||
override apply(playerPokemon: PlayerPokemon, cost?: number): boolean {
|
||||
|
||||
playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), playerPokemon.getLearnableLevelMoves()[this.levelMoveIndex], LearnMoveType.MEMORY, cost));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import BattleScene from "#app/battle-scene";
|
||||
import { BattlerIndex, BattleType } from "#app/battle";
|
||||
import { applyPostFaintAbAttrs, PostFaintAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr } from "#app/data/ability";
|
||||
import { BattlerTagLapseType } from "#app/data/battler-tags";
|
||||
import { BattlerTagLapseType, DestinyBondTag } from "#app/data/battler-tags";
|
||||
import { battleSpecDialogue } from "#app/data/dialogue";
|
||||
import { allMoves, PostVictoryStatStageChangeAttr } from "#app/data/move";
|
||||
import { BattleSpec } from "#app/enums/battle-spec";
|
||||
import { StatusEffect } from "#app/enums/status-effect";
|
||||
import { PokemonMove, EnemyPokemon, PlayerPokemon, HitResult } from "#app/field/pokemon";
|
||||
import Pokemon, { PokemonMove, EnemyPokemon, PlayerPokemon, HitResult } from "#app/field/pokemon";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { PokemonInstantReviveModifier } from "#app/modifier/modifier";
|
||||
import i18next from "i18next";
|
||||
|
@ -19,19 +19,40 @@ import { SwitchPhase } from "./switch-phase";
|
|||
import { VictoryPhase } from "./victory-phase";
|
||||
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
|
||||
import { SwitchType } from "#enums/switch-type";
|
||||
import { isNullOrUndefined } from "#app/utils";
|
||||
import { FRIENDSHIP_LOSS_FROM_FAINT } from "#app/data/balance/starters";
|
||||
|
||||
export class FaintPhase extends PokemonPhase {
|
||||
/**
|
||||
* Whether or not enduring (for this phase's purposes, Reviver Seed) should be prevented
|
||||
*/
|
||||
private preventEndure: boolean;
|
||||
|
||||
constructor(scene: BattleScene, battlerIndex: BattlerIndex, preventEndure?: boolean) {
|
||||
/**
|
||||
* Destiny Bond tag belonging to the currently fainting Pokemon, if applicable
|
||||
*/
|
||||
private destinyTag?: DestinyBondTag;
|
||||
|
||||
/**
|
||||
* The source Pokemon that dealt fatal damage and should get KO'd by Destiny Bond, if applicable
|
||||
*/
|
||||
private source?: Pokemon;
|
||||
|
||||
constructor(scene: BattleScene, battlerIndex: BattlerIndex, preventEndure: boolean = false, destinyTag?: DestinyBondTag, source?: Pokemon) {
|
||||
super(scene, battlerIndex);
|
||||
|
||||
this.preventEndure = preventEndure!; // TODO: is this bang correct?
|
||||
this.preventEndure = preventEndure;
|
||||
this.destinyTag = destinyTag;
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
start() {
|
||||
super.start();
|
||||
|
||||
if (!isNullOrUndefined(this.destinyTag) && !isNullOrUndefined(this.source)) {
|
||||
this.destinyTag.lapse(this.source, BattlerTagLapseType.CUSTOM);
|
||||
}
|
||||
|
||||
if (!this.preventEndure) {
|
||||
const instantReviveModifier = this.scene.applyModifier(PokemonInstantReviveModifier, this.player, this.getPokemon()) as PokemonInstantReviveModifier;
|
||||
|
||||
|
@ -127,7 +148,7 @@ export class FaintPhase extends PokemonPhase {
|
|||
|
||||
pokemon.faintCry(() => {
|
||||
if (pokemon instanceof PlayerPokemon) {
|
||||
pokemon.addFriendship(-10);
|
||||
pokemon.addFriendship(-FRIENDSHIP_LOSS_FROM_FAINT);
|
||||
}
|
||||
pokemon.hideInfo();
|
||||
this.scene.playSound("se/faint");
|
||||
|
|
|
@ -2,24 +2,37 @@ import BattleScene from "#app/battle-scene";
|
|||
import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims";
|
||||
import Move, { allMoves } from "#app/data/move";
|
||||
import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import Overrides from "#app/overrides";
|
||||
import EvolutionSceneHandler from "#app/ui/evolution-scene-handler";
|
||||
import { SummaryUiMode } from "#app/ui/summary-ui-handler";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import i18next from "i18next";
|
||||
import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase";
|
||||
import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase";
|
||||
import Pokemon from "#app/field/pokemon";
|
||||
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
|
||||
|
||||
export enum LearnMoveType {
|
||||
/** For learning a move via level-up, evolution, or other non-item-based event */
|
||||
LEARN_MOVE,
|
||||
/** For learning a move via Memory Mushroom */
|
||||
MEMORY,
|
||||
/** For learning a move via TM */
|
||||
TM
|
||||
}
|
||||
|
||||
export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
|
||||
private moveId: Moves;
|
||||
private messageMode: Mode;
|
||||
private fromTM: boolean;
|
||||
private learnMoveType;
|
||||
private cost: number;
|
||||
|
||||
constructor(scene: BattleScene, partyMemberIndex: integer, moveId: Moves, fromTM?: boolean) {
|
||||
constructor(scene: BattleScene, partyMemberIndex: integer, moveId: Moves, learnMoveType: LearnMoveType = LearnMoveType.LEARN_MOVE, cost: number = -1) {
|
||||
super(scene, partyMemberIndex);
|
||||
this.moveId = moveId;
|
||||
this.fromTM = fromTM ?? false;
|
||||
this.learnMoveType = learnMoveType;
|
||||
this.cost = cost;
|
||||
}
|
||||
|
||||
start() {
|
||||
|
@ -136,11 +149,23 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
|
|||
* @param Pokemon The Pokemon learning the move
|
||||
*/
|
||||
async learnMove(index: number, move: Move, pokemon: Pokemon, textMessage?: string) {
|
||||
if (this.fromTM) {
|
||||
if (this.learnMoveType === LearnMoveType.TM) {
|
||||
if (!pokemon.usedTMs) {
|
||||
pokemon.usedTMs = [];
|
||||
}
|
||||
pokemon.usedTMs.push(this.moveId);
|
||||
this.scene.tryRemovePhase((phase) => phase instanceof SelectModifierPhase);
|
||||
} else if (this.learnMoveType === LearnMoveType.MEMORY) {
|
||||
if (this.cost !== -1) {
|
||||
if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
|
||||
this.scene.money -= this.cost;
|
||||
this.scene.updateMoneyText();
|
||||
this.scene.animateMoneyChanged(false);
|
||||
}
|
||||
this.scene.playSound("se/buy");
|
||||
} else {
|
||||
this.scene.tryRemovePhase((phase) => phase instanceof SelectModifierPhase);
|
||||
}
|
||||
}
|
||||
pokemon.setMove(index, this.moveId);
|
||||
initMoveAnim(this.scene, this.moveId).then(() => {
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
import BattleScene from "#app/battle-scene";
|
||||
import { Phase } from "#app/phase";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
|
||||
export class OutdatedPhase extends Phase {
|
||||
constructor(scene: BattleScene) {
|
||||
super(scene);
|
||||
}
|
||||
|
||||
start(): void {
|
||||
this.scene.ui.setMode(Mode.OUTDATED);
|
||||
}
|
||||
}
|
|
@ -16,26 +16,32 @@ export class SelectModifierPhase extends BattlePhase {
|
|||
private rerollCount: integer;
|
||||
private modifierTiers?: ModifierTier[];
|
||||
private customModifierSettings?: CustomModifierSettings;
|
||||
private isCopy: boolean;
|
||||
|
||||
constructor(scene: BattleScene, rerollCount: integer = 0, modifierTiers?: ModifierTier[], customModifierSettings?: CustomModifierSettings) {
|
||||
private typeOptions: ModifierTypeOption[];
|
||||
|
||||
constructor(scene: BattleScene, rerollCount: integer = 0, modifierTiers?: ModifierTier[], customModifierSettings?: CustomModifierSettings, isCopy: boolean = false) {
|
||||
super(scene);
|
||||
|
||||
this.rerollCount = rerollCount;
|
||||
this.modifierTiers = modifierTiers;
|
||||
this.customModifierSettings = customModifierSettings;
|
||||
this.isCopy = isCopy;
|
||||
}
|
||||
|
||||
start() {
|
||||
super.start();
|
||||
|
||||
if (!this.rerollCount) {
|
||||
if (!this.rerollCount && !this.isCopy) {
|
||||
this.updateSeed();
|
||||
} else {
|
||||
} else if (this.rerollCount) {
|
||||
this.scene.reroll = false;
|
||||
}
|
||||
|
||||
const party = this.scene.getParty();
|
||||
regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount);
|
||||
if (!this.isCopy) {
|
||||
regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount);
|
||||
}
|
||||
const modifierCount = new Utils.IntegerHolder(3);
|
||||
if (this.isPlayer()) {
|
||||
this.scene.applyModifiers(ExtraModifierModifier, true, modifierCount);
|
||||
|
@ -54,7 +60,7 @@ export class SelectModifierPhase extends BattlePhase {
|
|||
}
|
||||
}
|
||||
|
||||
const typeOptions: ModifierTypeOption[] = this.getModifierTypeOptions(modifierCount.value);
|
||||
this.typeOptions = this.getModifierTypeOptions(modifierCount.value);
|
||||
|
||||
const modifierSelectCallback = (rowCursor: integer, cursor: integer) => {
|
||||
if (rowCursor < 0 || cursor < 0) {
|
||||
|
@ -63,13 +69,13 @@ export class SelectModifierPhase extends BattlePhase {
|
|||
this.scene.ui.revertMode();
|
||||
this.scene.ui.setMode(Mode.MESSAGE);
|
||||
super.end();
|
||||
}, () => this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)));
|
||||
}, () => this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)));
|
||||
});
|
||||
return false;
|
||||
}
|
||||
let modifierType: ModifierType;
|
||||
let cost: integer;
|
||||
const rerollCost = this.getRerollCost(typeOptions, this.scene.lockModifierTiers);
|
||||
const rerollCost = this.getRerollCost(this.scene.lockModifierTiers);
|
||||
switch (rowCursor) {
|
||||
case 0:
|
||||
switch (cursor) {
|
||||
|
@ -79,7 +85,7 @@ export class SelectModifierPhase extends BattlePhase {
|
|||
return false;
|
||||
} else {
|
||||
this.scene.reroll = true;
|
||||
this.scene.unshiftPhase(new SelectModifierPhase(this.scene, this.rerollCount + 1, typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[]));
|
||||
this.scene.unshiftPhase(new SelectModifierPhase(this.scene, this.rerollCount + 1, this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[]));
|
||||
this.scene.ui.clearText();
|
||||
this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end());
|
||||
if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
|
||||
|
@ -98,13 +104,13 @@ export class SelectModifierPhase extends BattlePhase {
|
|||
const itemModifier = itemModifiers[itemIndex];
|
||||
this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity);
|
||||
} else {
|
||||
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers));
|
||||
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers));
|
||||
}
|
||||
}, PartyUiHandler.FilterItemMaxStacks);
|
||||
break;
|
||||
case 2:
|
||||
this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.CHECK, -1, () => {
|
||||
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers));
|
||||
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers));
|
||||
});
|
||||
break;
|
||||
case 3:
|
||||
|
@ -115,21 +121,21 @@ export class SelectModifierPhase extends BattlePhase {
|
|||
}
|
||||
this.scene.lockModifierTiers = !this.scene.lockModifierTiers;
|
||||
const uiHandler = this.scene.ui.getHandler() as ModifierSelectUiHandler;
|
||||
uiHandler.setRerollCost(this.getRerollCost(typeOptions, this.scene.lockModifierTiers));
|
||||
uiHandler.setRerollCost(this.getRerollCost(this.scene.lockModifierTiers));
|
||||
uiHandler.updateLockRaritiesText();
|
||||
uiHandler.updateRerollCostText();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
case 1:
|
||||
if (typeOptions.length === 0) {
|
||||
if (this.typeOptions.length === 0) {
|
||||
this.scene.ui.clearText();
|
||||
this.scene.ui.setMode(Mode.MESSAGE);
|
||||
super.end();
|
||||
return true;
|
||||
}
|
||||
if (typeOptions[cursor].type) {
|
||||
modifierType = typeOptions[cursor].type;
|
||||
if (this.typeOptions[cursor].type) {
|
||||
modifierType = this.typeOptions[cursor].type;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
@ -151,8 +157,16 @@ export class SelectModifierPhase extends BattlePhase {
|
|||
}
|
||||
|
||||
const applyModifier = (modifier: Modifier, playSound: boolean = false) => {
|
||||
const result = this.scene.addModifier(modifier, false, playSound);
|
||||
if (cost) {
|
||||
const result = this.scene.addModifier(modifier, false, playSound, undefined, undefined, cost);
|
||||
// Queue a copy of this phase when applying a TM or Memory Mushroom.
|
||||
// If the player selects either of these, then escapes out of consuming them,
|
||||
// they are returned to a shop in the same state.
|
||||
if (modifier.type instanceof RememberMoveModifierType ||
|
||||
modifier.type instanceof TmModifierType) {
|
||||
this.scene.unshiftPhase(this.copy());
|
||||
}
|
||||
|
||||
if (cost && !(modifier.type instanceof RememberMoveModifierType)) {
|
||||
result.then(success => {
|
||||
if (success) {
|
||||
if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
|
||||
|
@ -189,7 +203,7 @@ export class SelectModifierPhase extends BattlePhase {
|
|||
applyModifier(modifier, true);
|
||||
});
|
||||
} else {
|
||||
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers));
|
||||
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers));
|
||||
}
|
||||
}, modifierType.selectFilter);
|
||||
} else {
|
||||
|
@ -216,7 +230,7 @@ export class SelectModifierPhase extends BattlePhase {
|
|||
applyModifier(modifier!, true); // TODO: is the bang correct?
|
||||
});
|
||||
} else {
|
||||
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers));
|
||||
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers));
|
||||
}
|
||||
}, pokemonModifierType.selectFilter, modifierType instanceof PokemonMoveModifierType ? (modifierType as PokemonMoveModifierType).moveSelectFilter : undefined, tmMoveId, isPpRestoreModifier);
|
||||
}
|
||||
|
@ -226,7 +240,7 @@ export class SelectModifierPhase extends BattlePhase {
|
|||
|
||||
return !cost!;// TODO: is the bang correct?
|
||||
};
|
||||
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers));
|
||||
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers));
|
||||
}
|
||||
|
||||
updateSeed(): void {
|
||||
|
@ -237,13 +251,13 @@ export class SelectModifierPhase extends BattlePhase {
|
|||
return true;
|
||||
}
|
||||
|
||||
getRerollCost(typeOptions: ModifierTypeOption[], lockRarities: boolean): number {
|
||||
getRerollCost(lockRarities: boolean): number {
|
||||
let baseValue = 0;
|
||||
if (Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
|
||||
return baseValue;
|
||||
} else if (lockRarities) {
|
||||
const tierValues = [ 50, 125, 300, 750, 2000 ];
|
||||
for (const opt of typeOptions) {
|
||||
for (const opt of this.typeOptions) {
|
||||
baseValue += tierValues[opt.type.tier ?? 0];
|
||||
}
|
||||
} else {
|
||||
|
@ -271,6 +285,16 @@ export class SelectModifierPhase extends BattlePhase {
|
|||
return getPlayerModifierTypeOptions(modifierCount, this.scene.getParty(), this.scene.lockModifierTiers ? this.modifierTiers : undefined, this.customModifierSettings);
|
||||
}
|
||||
|
||||
copy(): SelectModifierPhase {
|
||||
return new SelectModifierPhase(
|
||||
this.scene,
|
||||
this.rerollCount,
|
||||
this.modifierTiers,
|
||||
{ guaranteedModifierTypeOptions: this.typeOptions, rerollMultiplier: this.customModifierSettings?.rerollMultiplier, allowLuckUpgrades: false },
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
addModifier(modifier: Modifier): Promise<boolean> {
|
||||
return this.scene.addModifier(modifier, false, true);
|
||||
}
|
||||
|
|
|
@ -43,10 +43,9 @@ import { Species } from "#enums/species";
|
|||
import { applyChallenges, ChallengeType } from "#app/data/challenge";
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import { TerrainType } from "#app/data/terrain";
|
||||
import { OutdatedPhase } from "#app/phases/outdated-phase";
|
||||
import { ReloadSessionPhase } from "#app/phases/reload-session-phase";
|
||||
import { RUN_HISTORY_LIMIT } from "#app/ui/run-history-ui-handler";
|
||||
import { applySessionDataPatches, applySettingsDataPatches, applySystemDataPatches } from "#app/system/version-converter";
|
||||
import { applySessionVersionMigration, applySystemVersionMigration, applySettingsVersionMigration } from "./version_migration/version_converter";
|
||||
import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { PokerogueApiClearSessionData } from "#app/@types/pokerogue-api";
|
||||
|
@ -403,10 +402,7 @@ export class GameData {
|
|||
.then(error => {
|
||||
this.scene.ui.savingIcon.hide();
|
||||
if (error) {
|
||||
if (error.startsWith("client version out of date")) {
|
||||
this.scene.clearPhaseQueue();
|
||||
this.scene.unshiftPhase(new OutdatedPhase(this.scene));
|
||||
} else if (error.startsWith("session out of date")) {
|
||||
if (error.startsWith("session out of date")) {
|
||||
this.scene.clearPhaseQueue();
|
||||
this.scene.unshiftPhase(new ReloadSessionPhase(this.scene));
|
||||
}
|
||||
|
@ -482,7 +478,7 @@ export class GameData {
|
|||
localStorage.setItem(lsItemKey, "");
|
||||
}
|
||||
|
||||
applySystemDataPatches(systemData);
|
||||
applySystemVersionMigration(systemData);
|
||||
|
||||
this.trainerId = systemData.trainerId;
|
||||
this.secretId = systemData.secretId;
|
||||
|
@ -857,7 +853,7 @@ export class GameData {
|
|||
|
||||
const settings = JSON.parse(localStorage.getItem("settings")!); // TODO: is this bang correct?
|
||||
|
||||
applySettingsDataPatches(settings);
|
||||
applySettingsVersionMigration(settings);
|
||||
|
||||
for (const setting of Object.keys(settings)) {
|
||||
setSetting(this.scene, setting, settings[setting]);
|
||||
|
@ -1313,7 +1309,7 @@ export class GameData {
|
|||
return v;
|
||||
}) as SessionSaveData;
|
||||
|
||||
applySessionDataPatches(sessionData);
|
||||
applySessionVersionMigration(sessionData);
|
||||
|
||||
return sessionData;
|
||||
}
|
||||
|
@ -1354,10 +1350,7 @@ export class GameData {
|
|||
this.scene.ui.savingIcon.hide();
|
||||
}
|
||||
if (error) {
|
||||
if (error.startsWith("client version out of date")) {
|
||||
this.scene.clearPhaseQueue();
|
||||
this.scene.unshiftPhase(new OutdatedPhase(this.scene));
|
||||
} else if (error.startsWith("session out of date")) {
|
||||
if (error.startsWith("session out of date")) {
|
||||
this.scene.clearPhaseQueue();
|
||||
this.scene.unshiftPhase(new ReloadSessionPhase(this.scene));
|
||||
}
|
||||
|
|
|
@ -1,157 +0,0 @@
|
|||
import { allSpecies } from "#app/data/pokemon-species";
|
||||
import { AbilityAttr, defaultStarterSpecies, DexAttr, SessionSaveData, SystemSaveData } from "./game-data";
|
||||
import { SettingKeys } from "./settings/settings";
|
||||
|
||||
const LATEST_VERSION = "1.0.5";
|
||||
|
||||
export function applySessionDataPatches(data: SessionSaveData) {
|
||||
const curVersion = data.gameVersion;
|
||||
|
||||
// Always sanitize money as a safeguard
|
||||
data.money = Math.floor(data.money);
|
||||
|
||||
if (curVersion !== LATEST_VERSION) {
|
||||
switch (curVersion) {
|
||||
case "1.0.0":
|
||||
case "1.0.1":
|
||||
case "1.0.2":
|
||||
case "1.0.3":
|
||||
case "1.0.4":
|
||||
// --- PATCHES ---
|
||||
|
||||
// Fix Battle Items, Vitamins, and Lures
|
||||
data.modifiers.forEach((m) => {
|
||||
if (m.className === "PokemonBaseStatModifier") {
|
||||
m.className = "BaseStatModifier";
|
||||
} else if (m.className === "PokemonResetNegativeStatStageModifier") {
|
||||
m.className = "ResetNegativeStatStageModifier";
|
||||
} else if (m.className === "TempBattleStatBoosterModifier") {
|
||||
// Dire Hit no longer a part of the TempBattleStatBoosterModifierTypeGenerator
|
||||
if (m.typeId !== "DIRE_HIT") {
|
||||
m.className = "TempStatStageBoosterModifier";
|
||||
m.typeId = "TEMP_STAT_STAGE_BOOSTER";
|
||||
|
||||
// Migration from TempBattleStat to Stat
|
||||
const newStat = m.typePregenArgs[0] + 1;
|
||||
m.typePregenArgs[0] = newStat;
|
||||
|
||||
// From [ stat, battlesLeft ] to [ stat, maxBattles, battleCount ]
|
||||
m.args = [ newStat, 5, m.args[1] ];
|
||||
} else {
|
||||
m.className = "TempCritBoosterModifier";
|
||||
m.typePregenArgs = [];
|
||||
|
||||
// From [ stat, battlesLeft ] to [ maxBattles, battleCount ]
|
||||
m.args = [ 5, m.args[1] ];
|
||||
}
|
||||
|
||||
} else if (m.className === "DoubleBattleChanceBoosterModifier" && m.args.length === 1) {
|
||||
let maxBattles: number;
|
||||
switch (m.typeId) {
|
||||
case "MAX_LURE":
|
||||
maxBattles = 30;
|
||||
break;
|
||||
case "SUPER_LURE":
|
||||
maxBattles = 15;
|
||||
break;
|
||||
default:
|
||||
maxBattles = 10;
|
||||
break;
|
||||
}
|
||||
|
||||
// From [ battlesLeft ] to [ maxBattles, battleCount ]
|
||||
m.args = [ maxBattles, m.args[0] ];
|
||||
}
|
||||
});
|
||||
|
||||
data.enemyModifiers.forEach((m) => {
|
||||
if (m.className === "PokemonBaseStatModifier") {
|
||||
m.className = "BaseStatModifier";
|
||||
} else if (m.className === "PokemonResetNegativeStatStageModifier") {
|
||||
m.className = "ResetNegativeStatStageModifier";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
data.gameVersion = LATEST_VERSION;
|
||||
}
|
||||
}
|
||||
|
||||
export function applySystemDataPatches(data: SystemSaveData) {
|
||||
const curVersion = data.gameVersion;
|
||||
if (curVersion !== LATEST_VERSION) {
|
||||
switch (curVersion) {
|
||||
case "1.0.0":
|
||||
case "1.0.1":
|
||||
case "1.0.2":
|
||||
case "1.0.3":
|
||||
case "1.0.4":
|
||||
// --- LEGACY PATCHES ---
|
||||
if (data.starterData && data.dexData) {
|
||||
// Migrate ability starter data if empty for caught species
|
||||
Object.keys(data.starterData).forEach(sd => {
|
||||
if (data.dexData[sd]?.caughtAttr && (data.starterData[sd] && !data.starterData[sd].abilityAttr)) {
|
||||
data.starterData[sd].abilityAttr = 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Fix Legendary Stats
|
||||
if (data.gameStats && (data.gameStats.legendaryPokemonCaught !== undefined && data.gameStats.subLegendaryPokemonCaught === undefined)) {
|
||||
data.gameStats.subLegendaryPokemonSeen = 0;
|
||||
data.gameStats.subLegendaryPokemonCaught = 0;
|
||||
data.gameStats.subLegendaryPokemonHatched = 0;
|
||||
allSpecies.filter(s => s.subLegendary).forEach(s => {
|
||||
const dexEntry = data.dexData[s.speciesId];
|
||||
data.gameStats.subLegendaryPokemonSeen += dexEntry.seenCount;
|
||||
data.gameStats.legendaryPokemonSeen = Math.max(data.gameStats.legendaryPokemonSeen - dexEntry.seenCount, 0);
|
||||
data.gameStats.subLegendaryPokemonCaught += dexEntry.caughtCount;
|
||||
data.gameStats.legendaryPokemonCaught = Math.max(data.gameStats.legendaryPokemonCaught - dexEntry.caughtCount, 0);
|
||||
data.gameStats.subLegendaryPokemonHatched += dexEntry.hatchedCount;
|
||||
data.gameStats.legendaryPokemonHatched = Math.max(data.gameStats.legendaryPokemonHatched - dexEntry.hatchedCount, 0);
|
||||
});
|
||||
data.gameStats.subLegendaryPokemonSeen = Math.max(data.gameStats.subLegendaryPokemonSeen, data.gameStats.subLegendaryPokemonCaught);
|
||||
data.gameStats.legendaryPokemonSeen = Math.max(data.gameStats.legendaryPokemonSeen, data.gameStats.legendaryPokemonCaught);
|
||||
data.gameStats.mythicalPokemonSeen = Math.max(data.gameStats.mythicalPokemonSeen, data.gameStats.mythicalPokemonCaught);
|
||||
}
|
||||
|
||||
// --- PATCHES ---
|
||||
|
||||
// Fix Starter Data
|
||||
if (data.starterData && data.dexData) {
|
||||
for (const starterId of defaultStarterSpecies) {
|
||||
if (data.starterData[starterId]?.abilityAttr) {
|
||||
data.starterData[starterId].abilityAttr |= AbilityAttr.ABILITY_1;
|
||||
}
|
||||
if (data.dexData[starterId]?.caughtAttr) {
|
||||
data.dexData[starterId].caughtAttr |= DexAttr.FEMALE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.gameVersion = LATEST_VERSION;
|
||||
}
|
||||
}
|
||||
|
||||
export function applySettingsDataPatches(settings: Object) {
|
||||
const curVersion = settings.hasOwnProperty("gameVersion") ? settings["gameVersion"] : "1.0.0";
|
||||
if (curVersion !== LATEST_VERSION) {
|
||||
switch (curVersion) {
|
||||
case "1.0.0":
|
||||
case "1.0.1":
|
||||
case "1.0.2":
|
||||
case "1.0.3":
|
||||
case "1.0.4":
|
||||
// --- PATCHES ---
|
||||
|
||||
// Fix Reward Cursor Target
|
||||
if (settings.hasOwnProperty("REROLL_TARGET") && !settings.hasOwnProperty(SettingKeys.Shop_Cursor_Target)) {
|
||||
settings[SettingKeys.Shop_Cursor_Target] = settings["REROLL_TARGET"];
|
||||
delete settings["REROLL_TARGET"];
|
||||
localStorage.setItem("settings", JSON.stringify(settings));
|
||||
}
|
||||
}
|
||||
// Note that the current game version will be written at `saveSettings`
|
||||
}
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
import { SessionSaveData, SystemSaveData } from "../game-data";
|
||||
import { version } from "../../../package.json";
|
||||
|
||||
// --- v1.0.4 (and below) PATCHES --- //
|
||||
import * as v1_0_4 from "./versions/v1_0_4";
|
||||
|
||||
const LATEST_VERSION = version.split(".").map(value => parseInt(value));
|
||||
|
||||
/**
|
||||
* Converts incoming {@linkcode SystemSaveData} that has a version below the
|
||||
* current version number listed in `package.json`.
|
||||
*
|
||||
* Note that no transforms act on the {@linkcode data} if its version matches
|
||||
* the current version or if there are no migrations made between its version up
|
||||
* to the current version.
|
||||
* @param data {@linkcode SystemSaveData}
|
||||
* @see {@link SystemVersionConverter}
|
||||
*/
|
||||
export function applySystemVersionMigration(data: SystemSaveData) {
|
||||
const curVersion = data.gameVersion.split(".").map(value => parseInt(value));
|
||||
|
||||
if (!curVersion.every((value, index) => value === LATEST_VERSION[index])) {
|
||||
const converter = new SystemVersionConverter();
|
||||
converter.applyStaticPreprocessors(data);
|
||||
converter.applyMigration(data, curVersion);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts incoming {@linkcode SessionSavaData} that has a version below the
|
||||
* current version number listed in `package.json`.
|
||||
*
|
||||
* Note that no transforms act on the {@linkcode data} if its version matches
|
||||
* the current version or if there are no migrations made between its version up
|
||||
* to the current version.
|
||||
* @param data {@linkcode SessionSaveData}
|
||||
* @see {@link SessionVersionConverter}
|
||||
*/
|
||||
export function applySessionVersionMigration(data: SessionSaveData) {
|
||||
const curVersion = data.gameVersion.split(".").map(value => parseInt(value));
|
||||
|
||||
if (!curVersion.every((value, index) => value === LATEST_VERSION[index])) {
|
||||
const converter = new SessionVersionConverter();
|
||||
converter.applyStaticPreprocessors(data);
|
||||
converter.applyMigration(data, curVersion);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts incoming settings data that has a version below the
|
||||
* current version number listed in `package.json`.
|
||||
*
|
||||
* Note that no transforms act on the {@linkcode data} if its version matches
|
||||
* the current version or if there are no migrations made between its version up
|
||||
* to the current version.
|
||||
* @param data Settings data object
|
||||
* @see {@link SettingsVersionConverter}
|
||||
*/
|
||||
export function applySettingsVersionMigration(data: Object) {
|
||||
const gameVersion: string = data.hasOwnProperty("gameVersion") ? data["gameVersion"] : "1.0.0";
|
||||
const curVersion = gameVersion.split(".").map(value => parseInt(value));
|
||||
|
||||
if (!curVersion.every((value, index) => value === LATEST_VERSION[index])) {
|
||||
const converter = new SettingsVersionConverter();
|
||||
converter.applyStaticPreprocessors(data);
|
||||
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 <= 4) {
|
||||
console.log("Applying v1.0.4 session data migration!");
|
||||
this.callMigrators(data, v1_0_4.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`System data successfully migrated to v${version}!`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
import { SettingKeys } from "../../settings/settings";
|
||||
import { AbilityAttr, defaultStarterSpecies, DexAttr, SystemSaveData, SessionSaveData } from "../../game-data";
|
||||
import { allSpecies } from "../../../data/pokemon-species";
|
||||
|
||||
export const systemMigrators = [
|
||||
/**
|
||||
* Migrate ability starter data if empty for caught species.
|
||||
* @param data {@linkcode SystemSaveData}
|
||||
*/
|
||||
function migrateAbilityData(data: SystemSaveData) {
|
||||
if (data.starterData && data.dexData) {
|
||||
Object.keys(data.starterData).forEach(sd => {
|
||||
if (data.dexData[sd]?.caughtAttr && (data.starterData[sd] && !data.starterData[sd].abilityAttr)) {
|
||||
data.starterData[sd].abilityAttr = 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Populate legendary Pokémon statistics if they are missing.
|
||||
* @param data {@linkcode SystemSaveData}
|
||||
*/
|
||||
function fixLegendaryStats(data: SystemSaveData) {
|
||||
if (data.gameStats && (data.gameStats.legendaryPokemonCaught !== undefined && data.gameStats.subLegendaryPokemonCaught === undefined)) {
|
||||
data.gameStats.subLegendaryPokemonSeen = 0;
|
||||
data.gameStats.subLegendaryPokemonCaught = 0;
|
||||
data.gameStats.subLegendaryPokemonHatched = 0;
|
||||
allSpecies.filter(s => s.subLegendary).forEach(s => {
|
||||
const dexEntry = data.dexData[s.speciesId];
|
||||
data.gameStats.subLegendaryPokemonSeen += dexEntry.seenCount;
|
||||
data.gameStats.legendaryPokemonSeen = Math.max(data.gameStats.legendaryPokemonSeen - dexEntry.seenCount, 0);
|
||||
data.gameStats.subLegendaryPokemonCaught += dexEntry.caughtCount;
|
||||
data.gameStats.legendaryPokemonCaught = Math.max(data.gameStats.legendaryPokemonCaught - dexEntry.caughtCount, 0);
|
||||
data.gameStats.subLegendaryPokemonHatched += dexEntry.hatchedCount;
|
||||
data.gameStats.legendaryPokemonHatched = Math.max(data.gameStats.legendaryPokemonHatched - dexEntry.hatchedCount, 0);
|
||||
});
|
||||
data.gameStats.subLegendaryPokemonSeen = Math.max(data.gameStats.subLegendaryPokemonSeen, data.gameStats.subLegendaryPokemonCaught);
|
||||
data.gameStats.legendaryPokemonSeen = Math.max(data.gameStats.legendaryPokemonSeen, data.gameStats.legendaryPokemonCaught);
|
||||
data.gameStats.mythicalPokemonSeen = Math.max(data.gameStats.mythicalPokemonSeen, data.gameStats.mythicalPokemonCaught);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Unlock all starters' first ability and female gender option.
|
||||
* @param data {@linkcode SystemSaveData}
|
||||
*/
|
||||
function fixStarterData(data: SystemSaveData) {
|
||||
for (const starterId of defaultStarterSpecies) {
|
||||
if (data.starterData[starterId]?.abilityAttr) {
|
||||
data.starterData[starterId].abilityAttr |= AbilityAttr.ABILITY_1;
|
||||
}
|
||||
if (data.dexData[starterId]?.caughtAttr) {
|
||||
data.dexData[starterId].caughtAttr |= DexAttr.FEMALE;
|
||||
}
|
||||
}
|
||||
}
|
||||
] as const;
|
||||
|
||||
export const settingsMigrators = [
|
||||
/**
|
||||
* Migrate from "REROLL_TARGET" property to {@linkcode
|
||||
* SettingKeys.Shop_Cursor_Target}.
|
||||
* @param data the `settings` object
|
||||
*/
|
||||
function fixRerollTarget(data: Object) {
|
||||
if (data.hasOwnProperty("REROLL_TARGET") && !data.hasOwnProperty(SettingKeys.Shop_Cursor_Target)) {
|
||||
data[SettingKeys.Shop_Cursor_Target] = data["REROLL_TARGET"];
|
||||
delete data["REROLL_TARGET"];
|
||||
localStorage.setItem("settings", JSON.stringify(data));
|
||||
}
|
||||
}
|
||||
] as const;
|
||||
|
||||
export const sessionMigrators = [
|
||||
/**
|
||||
* Converts old lapsing modifiers (battle items, lures, and Dire Hit) and
|
||||
* other miscellaneous modifiers (vitamins, White Herb) to any new class
|
||||
* names and/or change in reload arguments.
|
||||
* @param data {@linkcode SessionSaveData}
|
||||
*/
|
||||
function migrateModifiers(data: SessionSaveData) {
|
||||
data.modifiers.forEach((m) => {
|
||||
if (m.className === "PokemonBaseStatModifier") {
|
||||
m.className = "BaseStatModifier";
|
||||
} else if (m.className === "PokemonResetNegativeStatStageModifier") {
|
||||
m.className = "ResetNegativeStatStageModifier";
|
||||
} else if (m.className === "TempBattleStatBoosterModifier") {
|
||||
const maxBattles = 5;
|
||||
// Dire Hit no longer a part of the TempBattleStatBoosterModifierTypeGenerator
|
||||
if (m.typeId !== "DIRE_HIT") {
|
||||
m.className = "TempStatStageBoosterModifier";
|
||||
m.typeId = "TEMP_STAT_STAGE_BOOSTER";
|
||||
|
||||
// Migration from TempBattleStat to Stat
|
||||
const newStat = m.typePregenArgs[0] + 1;
|
||||
m.typePregenArgs[0] = newStat;
|
||||
|
||||
// From [ stat, battlesLeft ] to [ stat, maxBattles, battleCount ]
|
||||
m.args = [ newStat, maxBattles, Math.min(m.args[1], maxBattles) ];
|
||||
} else {
|
||||
m.className = "TempCritBoosterModifier";
|
||||
m.typePregenArgs = [];
|
||||
|
||||
// From [ stat, battlesLeft ] to [ maxBattles, battleCount ]
|
||||
m.args = [ maxBattles, Math.min(m.args[1], maxBattles) ];
|
||||
}
|
||||
} else if (m.className === "DoubleBattleChanceBoosterModifier" && m.args.length === 1) {
|
||||
let maxBattles: number;
|
||||
switch (m.typeId) {
|
||||
case "MAX_LURE":
|
||||
maxBattles = 30;
|
||||
break;
|
||||
case "SUPER_LURE":
|
||||
maxBattles = 15;
|
||||
break;
|
||||
default:
|
||||
maxBattles = 10;
|
||||
break;
|
||||
}
|
||||
|
||||
// From [ battlesLeft ] to [ maxBattles, battleCount ]
|
||||
m.args = [ maxBattles, Math.min(m.args[0], maxBattles) ];
|
||||
}
|
||||
});
|
||||
|
||||
data.enemyModifiers.forEach((m) => {
|
||||
if (m.className === "PokemonBaseStatModifier") {
|
||||
m.className = "BaseStatModifier";
|
||||
} else if (m.className === "PokemonResetNegativeStatStageModifier") {
|
||||
m.className = "ResetNegativeStatStageModifier";
|
||||
}
|
||||
});
|
||||
}
|
||||
] as const;
|
|
@ -1,7 +1,8 @@
|
|||
import { Abilities } from "#app/enums/abilities";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phase from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
|
@ -32,15 +33,16 @@ describe("Items - Lock Capsule", () => {
|
|||
});
|
||||
|
||||
it("doesn't set the cost of common tier items to 0", async () => {
|
||||
await game.startBattle();
|
||||
await game.classicMode.startBattle();
|
||||
game.scene.overridePhase(new SelectModifierPhase(game.scene, 0, undefined, { guaranteedModifierTiers: [ ModifierTier.COMMON, ModifierTier.COMMON, ModifierTier.COMMON ], fillRemaining: false }));
|
||||
|
||||
game.move.select(Moves.SURF);
|
||||
await game.phaseInterceptor.to(SelectModifierPhase, false);
|
||||
game.onNextPrompt("SelectModifierPhase", Mode.MODIFIER_SELECT, () => {
|
||||
const selectModifierPhase = game.scene.getCurrentPhase() as SelectModifierPhase;
|
||||
const rerollCost = selectModifierPhase.getRerollCost(true);
|
||||
expect(rerollCost).toBe(150);
|
||||
});
|
||||
|
||||
const rewards = game.scene.getCurrentPhase() as SelectModifierPhase;
|
||||
const potion = new ModifierTypeOption(modifierTypes.POTION(), 0, 40); // Common tier item
|
||||
const rerollCost = rewards.getRerollCost([ potion, potion, potion ], true);
|
||||
|
||||
expect(rerollCost).toBe(150);
|
||||
game.doSelectModifier();
|
||||
await game.phaseInterceptor.to("SelectModifierPhase");
|
||||
}, 20000);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,255 @@
|
|||
import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag";
|
||||
import { allMoves } from "#app/data/move";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import { PokemonInstantReviveModifier } from "#app/modifier/modifier";
|
||||
|
||||
|
||||
describe("Moves - Destiny Bond", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
const defaultParty = [ Species.BULBASAUR, Species.SQUIRTLE ];
|
||||
const enemyFirst = [ BattlerIndex.ENEMY, BattlerIndex.PLAYER ];
|
||||
const playerFirst = [ BattlerIndex.PLAYER, BattlerIndex.ENEMY ];
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override.battleType("single")
|
||||
.ability(Abilities.UNNERVE) // Pre-emptively prevent flakiness from opponent berries
|
||||
.enemySpecies(Species.RATTATA)
|
||||
.enemyAbility(Abilities.RUN_AWAY)
|
||||
.startingLevel(100) // Make sure tested moves KO
|
||||
.enemyLevel(5)
|
||||
.enemyMoveset(Moves.DESTINY_BOND);
|
||||
});
|
||||
|
||||
it("should KO the opponent on the same turn", async () => {
|
||||
const moveToUse = Moves.TACKLE;
|
||||
|
||||
game.override.moveset(moveToUse);
|
||||
await game.classicMode.startBattle(defaultParty);
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||
const playerPokemon = game.scene.getPlayerPokemon();
|
||||
|
||||
game.move.select(moveToUse);
|
||||
await game.setTurnOrder(enemyFirst);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(enemyPokemon?.isFainted()).toBe(true);
|
||||
expect(playerPokemon?.isFainted()).toBe(true);
|
||||
});
|
||||
|
||||
it("should KO the opponent on the next turn", async () => {
|
||||
const moveToUse = Moves.TACKLE;
|
||||
|
||||
game.override.moveset([ Moves.SPLASH, moveToUse ]);
|
||||
await game.classicMode.startBattle(defaultParty);
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||
const playerPokemon = game.scene.getPlayerPokemon();
|
||||
|
||||
// Turn 1: Enemy uses Destiny Bond and doesn't faint
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.setTurnOrder(playerFirst);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(enemyPokemon?.isFainted()).toBe(false);
|
||||
expect(playerPokemon?.isFainted()).toBe(false);
|
||||
|
||||
// Turn 2: Player KO's the enemy before the enemy's turn
|
||||
game.move.select(moveToUse);
|
||||
await game.setTurnOrder(playerFirst);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(enemyPokemon?.isFainted()).toBe(true);
|
||||
expect(playerPokemon?.isFainted()).toBe(true);
|
||||
});
|
||||
|
||||
it("should fail if used twice in a row", async () => {
|
||||
const moveToUse = Moves.TACKLE;
|
||||
|
||||
game.override.moveset([ Moves.SPLASH, moveToUse ]);
|
||||
await game.classicMode.startBattle(defaultParty);
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||
const playerPokemon = game.scene.getPlayerPokemon();
|
||||
|
||||
// Turn 1: Enemy uses Destiny Bond and doesn't faint
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.setTurnOrder(enemyFirst);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(enemyPokemon?.isFainted()).toBe(false);
|
||||
expect(playerPokemon?.isFainted()).toBe(false);
|
||||
|
||||
// Turn 2: Enemy should fail Destiny Bond then get KO'd
|
||||
game.move.select(moveToUse);
|
||||
await game.setTurnOrder(enemyFirst);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(enemyPokemon?.isFainted()).toBe(true);
|
||||
expect(playerPokemon?.isFainted()).toBe(false);
|
||||
});
|
||||
|
||||
it("should not KO the opponent if the user dies to weather", async () => {
|
||||
// Opponent will be reduced to 1 HP by False Swipe, then faint to Sandstorm
|
||||
const moveToUse = Moves.FALSE_SWIPE;
|
||||
|
||||
game.override.moveset(moveToUse)
|
||||
.ability(Abilities.SAND_STREAM);
|
||||
await game.classicMode.startBattle(defaultParty);
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||
const playerPokemon = game.scene.getPlayerPokemon();
|
||||
|
||||
game.move.select(moveToUse);
|
||||
await game.setTurnOrder(enemyFirst);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(enemyPokemon?.isFainted()).toBe(true);
|
||||
expect(playerPokemon?.isFainted()).toBe(false);
|
||||
});
|
||||
|
||||
it("should not KO the opponent if the user had another turn", async () => {
|
||||
const moveToUse = Moves.TACKLE;
|
||||
|
||||
game.override.moveset([ Moves.SPORE, moveToUse ]);
|
||||
await game.classicMode.startBattle(defaultParty);
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||
const playerPokemon = game.scene.getPlayerPokemon();
|
||||
|
||||
// Turn 1: Enemy uses Destiny Bond and doesn't faint
|
||||
game.move.select(Moves.SPORE);
|
||||
await game.setTurnOrder(enemyFirst);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(enemyPokemon?.isFainted()).toBe(false);
|
||||
expect(playerPokemon?.isFainted()).toBe(false);
|
||||
expect(enemyPokemon?.status?.effect).toBe(StatusEffect.SLEEP);
|
||||
|
||||
// Turn 2: Enemy should skip a turn due to sleep, then get KO'd
|
||||
game.move.select(moveToUse);
|
||||
await game.setTurnOrder(enemyFirst);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(enemyPokemon?.isFainted()).toBe(true);
|
||||
expect(playerPokemon?.isFainted()).toBe(false);
|
||||
});
|
||||
|
||||
it("should not KO an ally", async () => {
|
||||
game.override.moveset([ Moves.DESTINY_BOND, Moves.CRUNCH ])
|
||||
.battleType("double");
|
||||
await game.classicMode.startBattle([ Species.SHEDINJA, Species.BULBASAUR, Species.SQUIRTLE ]);
|
||||
|
||||
const enemyPokemon0 = game.scene.getEnemyField()[0];
|
||||
const enemyPokemon1 = game.scene.getEnemyField()[1];
|
||||
const playerPokemon0 = game.scene.getPlayerField()[0];
|
||||
const playerPokemon1 = game.scene.getPlayerField()[1];
|
||||
|
||||
// Shedinja uses Destiny Bond, then ally Bulbasaur KO's Shedinja with Crunch
|
||||
game.move.select(Moves.DESTINY_BOND, 0);
|
||||
game.move.select(Moves.CRUNCH, 1, BattlerIndex.PLAYER);
|
||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2 ]);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(enemyPokemon0?.isFainted()).toBe(false);
|
||||
expect(enemyPokemon1?.isFainted()).toBe(false);
|
||||
expect(playerPokemon0?.isFainted()).toBe(true);
|
||||
expect(playerPokemon1?.isFainted()).toBe(false);
|
||||
});
|
||||
|
||||
it("should not cause a crash if the user is KO'd by Ceaseless Edge", async () => {
|
||||
const moveToUse = Moves.CEASELESS_EDGE;
|
||||
vi.spyOn(allMoves[moveToUse], "accuracy", "get").mockReturnValue(100);
|
||||
|
||||
game.override.moveset(moveToUse);
|
||||
await game.classicMode.startBattle(defaultParty);
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||
const playerPokemon = game.scene.getPlayerPokemon();
|
||||
|
||||
game.move.select(moveToUse);
|
||||
await game.setTurnOrder(enemyFirst);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(enemyPokemon?.isFainted()).toBe(true);
|
||||
expect(playerPokemon?.isFainted()).toBe(true);
|
||||
|
||||
// Ceaseless Edge spikes effect should still activate
|
||||
const tagAfter = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag;
|
||||
expect(tagAfter.tagType).toBe(ArenaTagType.SPIKES);
|
||||
expect(tagAfter.layers).toBe(1);
|
||||
});
|
||||
|
||||
it("should not cause a crash if the user is KO'd by Pledge moves", async () => {
|
||||
game.override.moveset([ Moves.GRASS_PLEDGE, Moves.WATER_PLEDGE ])
|
||||
.battleType("double");
|
||||
await game.classicMode.startBattle(defaultParty);
|
||||
|
||||
const enemyPokemon0 = game.scene.getEnemyField()[0];
|
||||
const enemyPokemon1 = game.scene.getEnemyField()[1];
|
||||
const playerPokemon0 = game.scene.getPlayerField()[0];
|
||||
const playerPokemon1 = game.scene.getPlayerField()[1];
|
||||
|
||||
game.move.select(Moves.GRASS_PLEDGE, 0, BattlerIndex.ENEMY);
|
||||
game.move.select(Moves.WATER_PLEDGE, 1, BattlerIndex.ENEMY);
|
||||
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER, BattlerIndex.PLAYER_2 ]);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(enemyPokemon0?.isFainted()).toBe(true);
|
||||
expect(enemyPokemon1?.isFainted()).toBe(false);
|
||||
expect(playerPokemon0?.isFainted()).toBe(false);
|
||||
expect(playerPokemon1?.isFainted()).toBe(true);
|
||||
|
||||
// Pledge secondary effect should still activate
|
||||
const tagAfter = game.scene.arena.getTagOnSide(ArenaTagType.GRASS_WATER_PLEDGE, ArenaTagSide.ENEMY) as ArenaTrapTag;
|
||||
expect(tagAfter.tagType).toBe(ArenaTagType.GRASS_WATER_PLEDGE);
|
||||
});
|
||||
|
||||
/**
|
||||
* In particular, this should prevent something like
|
||||
* {@link https://github.com/pagefaultgames/pokerogue/issues/4219}
|
||||
* from occurring with fainting by KO'ing a Destiny Bond user with U-Turn.
|
||||
*/
|
||||
it("should not allow the opponent to revive via Reviver Seed", async () => {
|
||||
const moveToUse = Moves.TACKLE;
|
||||
|
||||
game.override.moveset(moveToUse)
|
||||
.startingHeldItems([{ name: "REVIVER_SEED" }]);
|
||||
await game.classicMode.startBattle(defaultParty);
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||
const playerPokemon = game.scene.getPlayerPokemon();
|
||||
|
||||
game.move.select(moveToUse);
|
||||
await game.setTurnOrder(enemyFirst);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(enemyPokemon?.isFainted()).toBe(true);
|
||||
expect(playerPokemon?.isFainted()).toBe(true);
|
||||
|
||||
// Check that the Tackle user's Reviver Seed did not activate
|
||||
const revSeeds = game.scene.getModifiers(PokemonInstantReviveModifier).filter(m => m.pokemonId === playerPokemon?.id);
|
||||
expect(revSeeds.length).toBe(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,89 @@
|
|||
import { Abilities } from "#enums/abilities";
|
||||
import { Biome } from "#enums/biome";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { allMoves, SecretPowerAttr } from "#app/data/move";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||
import { ArenaTagSide } from "#app/data/arena-tag";
|
||||
|
||||
describe("Moves - Secret Power", () => {
|
||||
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.SECRET_POWER ])
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.battleType("single")
|
||||
.disableCrits()
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyLevel(60)
|
||||
.enemyAbility(Abilities.BALL_FETCH);
|
||||
});
|
||||
|
||||
it("Secret Power checks for an active terrain first then looks at the biome for its secondary effect", async () => {
|
||||
game.override
|
||||
.startingBiome(Biome.VOLCANO)
|
||||
.enemyMoveset([ Moves.SPLASH, Moves.MISTY_TERRAIN ]);
|
||||
vi.spyOn(allMoves[Moves.SECRET_POWER], "chance", "get").mockReturnValue(100);
|
||||
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
|
||||
// No Terrain + Biome.VOLCANO --> Burn
|
||||
game.move.select(Moves.SECRET_POWER);
|
||||
await game.forceEnemyMove(Moves.SPLASH);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
expect(enemyPokemon.status?.effect).toBe(StatusEffect.BURN);
|
||||
|
||||
// Misty Terrain --> SpAtk -1
|
||||
game.move.select(Moves.SECRET_POWER);
|
||||
await game.forceEnemyMove(Moves.MISTY_TERRAIN);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(-1);
|
||||
});
|
||||
|
||||
it("the 'rainbow' effect of fire+water pledge does not double the chance of secret power's secondary effect",
|
||||
async () => {
|
||||
game.override
|
||||
.moveset([ Moves.FIRE_PLEDGE, Moves.WATER_PLEDGE, Moves.SECRET_POWER, Moves.SPLASH ])
|
||||
.enemyMoveset([ Moves.SPLASH ])
|
||||
.battleType("double");
|
||||
await game.classicMode.startBattle([ Species.BLASTOISE, Species.CHARIZARD ]);
|
||||
|
||||
const secretPowerAttr = allMoves[Moves.SECRET_POWER].getAttrs(SecretPowerAttr)[0];
|
||||
vi.spyOn(secretPowerAttr, "getMoveChance");
|
||||
|
||||
game.move.select(Moves.WATER_PLEDGE, 0, BattlerIndex.ENEMY);
|
||||
game.move.select(Moves.FIRE_PLEDGE, 1, BattlerIndex.ENEMY_2);
|
||||
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
expect(game.scene.arena.getTagOnSide(ArenaTagType.WATER_FIRE_PLEDGE, ArenaTagSide.PLAYER)).toBeDefined();
|
||||
|
||||
game.move.select(Moves.SECRET_POWER, 0, BattlerIndex.ENEMY);
|
||||
game.move.select(Moves.SPLASH, 1);
|
||||
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(secretPowerAttr.getMoveChance).toHaveLastReturnedWith(30);
|
||||
}
|
||||
);
|
||||
});
|
|
@ -124,10 +124,31 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("should start battle against the trainer", async () => {
|
||||
it("should start battle against the trainer with correctly loaded assets", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER, defaultParty);
|
||||
|
||||
let successfullyLoaded = false;
|
||||
vi.spyOn(scene, "getEnemyParty").mockImplementation(() => {
|
||||
const ace = scene.currentBattle?.enemyParty[0];
|
||||
if (ace) {
|
||||
// Pretend that loading assets takes an extra 500ms
|
||||
vi.spyOn(ace, "loadAssets").mockImplementation(() => new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
successfullyLoaded = true;
|
||||
resolve();
|
||||
}, 500);
|
||||
}));
|
||||
}
|
||||
|
||||
return scene.currentBattle?.enemyParty ?? [];
|
||||
});
|
||||
|
||||
await runMysteryEncounterToEnd(game, 1, undefined, true);
|
||||
|
||||
// Check that assets are successfully loaded
|
||||
expect(successfullyLoaded).toBe(true);
|
||||
|
||||
// Check usual battle stuff
|
||||
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
|
||||
expect(scene.currentBattle.trainer).toBeDefined();
|
||||
expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
|
||||
|
@ -182,10 +203,31 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("should start battle against the trainer", async () => {
|
||||
it("should start battle against the trainer with correctly loaded assets", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER, defaultParty);
|
||||
|
||||
let successfullyLoaded = false;
|
||||
vi.spyOn(scene, "getEnemyParty").mockImplementation(() => {
|
||||
const ace = scene.currentBattle?.enemyParty[0];
|
||||
if (ace) {
|
||||
// Pretend that loading assets takes an extra 500ms
|
||||
vi.spyOn(ace, "loadAssets").mockImplementation(() => new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
successfullyLoaded = true;
|
||||
resolve();
|
||||
}, 500);
|
||||
}));
|
||||
}
|
||||
|
||||
return scene.currentBattle?.enemyParty ?? [];
|
||||
});
|
||||
|
||||
await runMysteryEncounterToEnd(game, 2, undefined, true);
|
||||
|
||||
// Check that assets are successfully loaded
|
||||
expect(successfullyLoaded).toBe(true);
|
||||
|
||||
// Check usual battle stuff
|
||||
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
|
||||
expect(scene.currentBattle.trainer).toBeDefined();
|
||||
expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
|
||||
|
@ -240,10 +282,31 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("should start battle against the trainer", async () => {
|
||||
it("should start battle against the trainer with correctly loaded assets", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER, defaultParty);
|
||||
|
||||
let successfullyLoaded = false;
|
||||
vi.spyOn(scene, "getEnemyParty").mockImplementation(() => {
|
||||
const ace = scene.currentBattle?.enemyParty[0];
|
||||
if (ace) {
|
||||
// Pretend that loading assets takes an extra 500ms
|
||||
vi.spyOn(ace, "loadAssets").mockImplementation(() => new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
successfullyLoaded = true;
|
||||
resolve();
|
||||
}, 500);
|
||||
}));
|
||||
}
|
||||
|
||||
return scene.currentBattle?.enemyParty ?? [];
|
||||
});
|
||||
|
||||
await runMysteryEncounterToEnd(game, 3, undefined, true);
|
||||
|
||||
// Check that assets are successfully loaded
|
||||
expect(successfullyLoaded).toBe(true);
|
||||
|
||||
// Check usual battle stuff
|
||||
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
|
||||
expect(scene.currentBattle.trainer).toBeDefined();
|
||||
expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
|
||||
|
|
|
@ -63,11 +63,11 @@ describe("SelectModifierPhase", () => {
|
|||
new ModifierTypeOption(modifierTypes.REVIVE(), 0, 1000)
|
||||
];
|
||||
|
||||
const selectModifierPhase1 = new SelectModifierPhase(scene);
|
||||
const selectModifierPhase2 = new SelectModifierPhase(scene, 0, undefined, { rerollMultiplier: 2 });
|
||||
const selectModifierPhase1 = new SelectModifierPhase(scene, 0, undefined, { guaranteedModifierTypeOptions: options });
|
||||
const selectModifierPhase2 = new SelectModifierPhase(scene, 0, undefined, { guaranteedModifierTypeOptions: options, rerollMultiplier: 2 });
|
||||
|
||||
const cost1 = selectModifierPhase1.getRerollCost(options, false);
|
||||
const cost2 = selectModifierPhase2.getRerollCost(options, false);
|
||||
const cost1 = selectModifierPhase1.getRerollCost(false);
|
||||
const cost2 = selectModifierPhase2.getRerollCost(false);
|
||||
expect(cost2).toEqual(cost1 * 2);
|
||||
});
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import KeyboardPlugin = Phaser.Input.Keyboard.KeyboardPlugin;
|
|||
import GamepadPlugin = Phaser.Input.Gamepad.GamepadPlugin;
|
||||
import EventEmitter = Phaser.Events.EventEmitter;
|
||||
import UpdateList = Phaser.GameObjects.UpdateList;
|
||||
import { version } from "../../../package.json";
|
||||
|
||||
Object.defineProperty(window, "localStorage", {
|
||||
value: mockLocalStorage(),
|
||||
|
@ -101,6 +102,7 @@ export default class GameWrapper {
|
|||
injectMandatory() {
|
||||
this.game.config = {
|
||||
seed: ["test"],
|
||||
gameVersion: version
|
||||
};
|
||||
this.scene.game = this.game;
|
||||
this.game.renderer = {
|
||||
|
|
|
@ -16,7 +16,10 @@ import { ShopCursorTarget } from "#app/enums/shop-cursor-target";
|
|||
import { IntegerHolder } from "./../utils";
|
||||
import Phaser from "phaser";
|
||||
|
||||
export const SHOP_OPTIONS_ROW_LIMIT = 6;
|
||||
export const SHOP_OPTIONS_ROW_LIMIT = 7;
|
||||
const SINGLE_SHOP_ROW_YOFFSET = 12;
|
||||
const DOUBLE_SHOP_ROW_YOFFSET = 24;
|
||||
const OPTION_BUTTON_YPOSITION = -62;
|
||||
|
||||
export default class ModifierSelectUiHandler extends AwaitableUiHandler {
|
||||
private modifierContainer: Phaser.GameObjects.Container;
|
||||
|
@ -68,7 +71,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
|
|||
this.checkButtonWidth = context.measureText(i18next.t("modifierSelectUiHandler:checkTeam")).width;
|
||||
}
|
||||
|
||||
this.transferButtonContainer = this.scene.add.container((this.scene.game.canvas.width - this.checkButtonWidth) / 6 - 21, -64);
|
||||
this.transferButtonContainer = this.scene.add.container((this.scene.game.canvas.width - this.checkButtonWidth) / 6 - 21, OPTION_BUTTON_YPOSITION);
|
||||
this.transferButtonContainer.setName("transfer-btn");
|
||||
this.transferButtonContainer.setVisible(false);
|
||||
ui.add(this.transferButtonContainer);
|
||||
|
@ -78,7 +81,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
|
|||
transferButtonText.setOrigin(1, 0);
|
||||
this.transferButtonContainer.add(transferButtonText);
|
||||
|
||||
this.checkButtonContainer = this.scene.add.container((this.scene.game.canvas.width) / 6 - 1, -64);
|
||||
this.checkButtonContainer = this.scene.add.container((this.scene.game.canvas.width) / 6 - 1, OPTION_BUTTON_YPOSITION);
|
||||
this.checkButtonContainer.setName("use-btn");
|
||||
this.checkButtonContainer.setVisible(false);
|
||||
ui.add(this.checkButtonContainer);
|
||||
|
@ -88,7 +91,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
|
|||
checkButtonText.setOrigin(1, 0);
|
||||
this.checkButtonContainer.add(checkButtonText);
|
||||
|
||||
this.rerollButtonContainer = this.scene.add.container(16, -64);
|
||||
this.rerollButtonContainer = this.scene.add.container(16, OPTION_BUTTON_YPOSITION);
|
||||
this.rerollButtonContainer.setName("reroll-brn");
|
||||
this.rerollButtonContainer.setVisible(false);
|
||||
ui.add(this.rerollButtonContainer);
|
||||
|
@ -104,7 +107,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
|
|||
this.rerollCostText.setPositionRelative(rerollButtonText, rerollButtonText.displayWidth + 5, 1);
|
||||
this.rerollButtonContainer.add(this.rerollCostText);
|
||||
|
||||
this.lockRarityButtonContainer = this.scene.add.container(16, -64);
|
||||
this.lockRarityButtonContainer = this.scene.add.container(16, OPTION_BUTTON_YPOSITION);
|
||||
this.lockRarityButtonContainer.setVisible(false);
|
||||
ui.add(this.lockRarityButtonContainer);
|
||||
|
||||
|
@ -191,7 +194,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
|
|||
const shopTypeOptions = !removeHealShop
|
||||
? getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, baseShopCost.value)
|
||||
: [];
|
||||
const optionsYOffset = shopTypeOptions.length >= SHOP_OPTIONS_ROW_LIMIT ? -8 : -24;
|
||||
const optionsYOffset = shopTypeOptions.length > SHOP_OPTIONS_ROW_LIMIT ? -SINGLE_SHOP_ROW_YOFFSET : -DOUBLE_SHOP_ROW_YOFFSET;
|
||||
|
||||
for (let m = 0; m < typeOptions.length; m++) {
|
||||
const sliceWidth = (this.scene.game.canvas.width / 6) / (typeOptions.length + 2);
|
||||
|
@ -211,8 +214,8 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
|
|||
const row = m < SHOP_OPTIONS_ROW_LIMIT ? 0 : 1;
|
||||
const col = m < SHOP_OPTIONS_ROW_LIMIT ? m : m - SHOP_OPTIONS_ROW_LIMIT;
|
||||
const rowOptions = shopTypeOptions.slice(row ? SHOP_OPTIONS_ROW_LIMIT : 0, row ? undefined : SHOP_OPTIONS_ROW_LIMIT);
|
||||
const sliceWidth = (this.scene.game.canvas.width / SHOP_OPTIONS_ROW_LIMIT) / (rowOptions.length + 2);
|
||||
const option = new ModifierOption(this.scene, sliceWidth * (col + 1) + (sliceWidth * 0.5), ((-this.scene.game.canvas.height / 12) - (this.scene.game.canvas.height / 32) - (40 - (28 * row - 1))), shopTypeOptions[m]);
|
||||
const sliceWidth = (this.scene.game.canvas.width / 6) / (rowOptions.length + 2);
|
||||
const option = new ModifierOption(this.scene, sliceWidth * (col + 1) + (sliceWidth * 0.5), ((-this.scene.game.canvas.height / 12) - (this.scene.game.canvas.height / 32) - (42 - (28 * row - 1))), shopTypeOptions[m]);
|
||||
option.setScale(0.375);
|
||||
this.scene.add.existing(option);
|
||||
this.modifierContainer.add(option);
|
||||
|
@ -456,16 +459,18 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
|
|||
if (this.rowCursor === 1 && options.length === 0) {
|
||||
// Continue button when no shop items
|
||||
this.cursorObj.setScale(1.25);
|
||||
this.cursorObj.setPosition((this.scene.game.canvas.width / 18) + 23, (-this.scene.game.canvas.height / 12) - (this.shopOptionsRows.length > 1 ? 6 : 22));
|
||||
this.cursorObj.setPosition((this.scene.game.canvas.width / 18) + 23, (-this.scene.game.canvas.height / 12) - (this.shopOptionsRows.length > 1 ? SINGLE_SHOP_ROW_YOFFSET - 2 : DOUBLE_SHOP_ROW_YOFFSET - 2));
|
||||
ui.showText(i18next.t("modifierSelectUiHandler:continueNextWaveDescription"));
|
||||
return ret;
|
||||
}
|
||||
|
||||
const sliceWidth = (this.scene.game.canvas.width / 6) / (options.length + 2);
|
||||
if (this.rowCursor < 2) {
|
||||
this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 20, (-this.scene.game.canvas.height / 12) - (this.shopOptionsRows.length > 1 ? 6 : 22));
|
||||
// Cursor on free items
|
||||
this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 20, (-this.scene.game.canvas.height / 12) - (this.shopOptionsRows.length > 1 ? SINGLE_SHOP_ROW_YOFFSET - 2 : DOUBLE_SHOP_ROW_YOFFSET - 2));
|
||||
} else {
|
||||
this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 16, (-this.scene.game.canvas.height / 12 - this.scene.game.canvas.height / 32) - (-16 + 28 * (this.rowCursor - (this.shopOptionsRows.length - 1))));
|
||||
// Cursor on paying items
|
||||
this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 16, (-this.scene.game.canvas.height / 12 - this.scene.game.canvas.height / 32) - (-14 + 28 * (this.rowCursor - (this.shopOptionsRows.length - 1))));
|
||||
}
|
||||
|
||||
const type = options[this.cursor].modifierTypeOption.type;
|
||||
|
@ -475,16 +480,16 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
|
|||
this.moveInfoOverlay.show(allMoves[type.moveId]);
|
||||
}
|
||||
} else if (cursor === 0) {
|
||||
this.cursorObj.setPosition(6, this.lockRarityButtonContainer.visible ? -72 : -60);
|
||||
this.cursorObj.setPosition(6, this.lockRarityButtonContainer.visible ? OPTION_BUTTON_YPOSITION - 8 : OPTION_BUTTON_YPOSITION + 4);
|
||||
ui.showText(i18next.t("modifierSelectUiHandler:rerollDesc"));
|
||||
} else if (cursor === 1) {
|
||||
this.cursorObj.setPosition((this.scene.game.canvas.width - this.transferButtonWidth - this.checkButtonWidth) / 6 - 30, -60);
|
||||
this.cursorObj.setPosition((this.scene.game.canvas.width - this.transferButtonWidth - this.checkButtonWidth) / 6 - 30, OPTION_BUTTON_YPOSITION + 4);
|
||||
ui.showText(i18next.t("modifierSelectUiHandler:transferDesc"));
|
||||
} else if (cursor === 2) {
|
||||
this.cursorObj.setPosition((this.scene.game.canvas.width - this.checkButtonWidth) / 6 - 10, -60);
|
||||
this.cursorObj.setPosition((this.scene.game.canvas.width - this.checkButtonWidth) / 6 - 10, OPTION_BUTTON_YPOSITION + 4);
|
||||
ui.showText(i18next.t("modifierSelectUiHandler:checkTeamDesc"));
|
||||
} else {
|
||||
this.cursorObj.setPosition(6, -60);
|
||||
this.cursorObj.setPosition(6, OPTION_BUTTON_YPOSITION + 4);
|
||||
ui.showText(i18next.t("modifierSelectUiHandler:lockRaritiesDesc"));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
import BattleScene from "../battle-scene";
|
||||
import { ModalConfig, ModalUiHandler } from "./modal-ui-handler";
|
||||
import { addTextObject, TextStyle } from "./text";
|
||||
import { Mode } from "./ui";
|
||||
|
||||
export default class OutdatedModalUiHandler extends ModalUiHandler {
|
||||
constructor(scene: BattleScene, mode: Mode | null = null) {
|
||||
super(scene, mode);
|
||||
}
|
||||
|
||||
getModalTitle(): string {
|
||||
return "";
|
||||
}
|
||||
|
||||
getWidth(): number {
|
||||
return 160;
|
||||
}
|
||||
|
||||
getHeight(): number {
|
||||
return 64;
|
||||
}
|
||||
|
||||
getMargin(): [number, number, number, number] {
|
||||
return [ 0, 0, 48, 0 ];
|
||||
}
|
||||
|
||||
getButtonLabels(): string[] {
|
||||
return [ ];
|
||||
}
|
||||
|
||||
setup(): void {
|
||||
super.setup();
|
||||
|
||||
const label = addTextObject(this.scene, this.getWidth() / 2, this.getHeight() / 2, "Your client is currently outdated.\nPlease reload to update the game.\n\nIf this error persists, please clear your browser cache.", TextStyle.WINDOW, { fontSize: "48px", align: "center" });
|
||||
label.setOrigin(0.5, 0.5);
|
||||
|
||||
this.modalContainer.add(label);
|
||||
}
|
||||
|
||||
show(args: any[]): boolean {
|
||||
const config: ModalConfig = {
|
||||
buttonActions: []
|
||||
};
|
||||
|
||||
return super.show([ config ]);
|
||||
}
|
||||
}
|
|
@ -671,6 +671,9 @@ export default class PartyUiHandler extends MessageUiHandler {
|
|||
} else if (this.cursor === 6) {
|
||||
this.partyCancelButton.select();
|
||||
}
|
||||
if (this.lastCursor < 6 && this.lastCursor >= party.length) {
|
||||
this.lastCursor = party.length - 1;
|
||||
}
|
||||
|
||||
for (const p in party) {
|
||||
const slotIndex = parseInt(p);
|
||||
|
|
|
@ -34,7 +34,6 @@ import SaveSlotSelectUiHandler from "./save-slot-select-ui-handler";
|
|||
import TitleUiHandler from "./title-ui-handler";
|
||||
import SavingIconHandler from "./saving-icon-handler";
|
||||
import UnavailableModalUiHandler from "./unavailable-modal-ui-handler";
|
||||
import OutdatedModalUiHandler from "./outdated-modal-ui-handler";
|
||||
import SessionReloadModalUiHandler from "./session-reload-modal-ui-handler";
|
||||
import { Button } from "#enums/buttons";
|
||||
import i18next from "i18next";
|
||||
|
@ -90,7 +89,6 @@ export enum Mode {
|
|||
LOADING,
|
||||
SESSION_RELOAD,
|
||||
UNAVAILABLE,
|
||||
OUTDATED,
|
||||
CHALLENGE_SELECT,
|
||||
RENAME_POKEMON,
|
||||
RUN_HISTORY,
|
||||
|
@ -134,7 +132,6 @@ const noTransitionModes = [
|
|||
Mode.LOADING,
|
||||
Mode.SESSION_RELOAD,
|
||||
Mode.UNAVAILABLE,
|
||||
Mode.OUTDATED,
|
||||
Mode.RENAME_POKEMON,
|
||||
Mode.TEST_DIALOGUE,
|
||||
Mode.AUTO_COMPLETE,
|
||||
|
@ -200,7 +197,6 @@ export default class UI extends Phaser.GameObjects.Container {
|
|||
new LoadingModalUiHandler(scene),
|
||||
new SessionReloadModalUiHandler(scene),
|
||||
new UnavailableModalUiHandler(scene),
|
||||
new OutdatedModalUiHandler(scene),
|
||||
new GameChallengesUiHandler(scene),
|
||||
new RenameFormUiHandler(scene),
|
||||
new RunHistoryUiHandler(scene),
|
||||
|
|
Loading…
Reference in New Issue