Merge pull request #37 from flx-sta/event/getting-lost-at-the-sea
Implement `Lost at sea` (Common)
This commit is contained in:
commit
7a24f68769
|
@ -0,0 +1,19 @@
|
|||
{ "frames": [
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"frame": { "x": 0, "y": 0, "w": 46, "h": 60 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 46, "h": 60 },
|
||||
"sourceSize": { "w": 46, "h": 60 }
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.aseprite.org/",
|
||||
"version": "1.3.7-x64",
|
||||
"image": "buoy-sheet.png",
|
||||
"format": "RGBA8888",
|
||||
"size": { "w": 46, "h": 60 },
|
||||
"scale": "1"
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
|
@ -2678,7 +2678,7 @@ export default class BattleScene extends SceneBase {
|
|||
let availableEncounters: IMysteryEncounter[] = [];
|
||||
// New encounter will never be the same as the most recent encounter
|
||||
const previousEncounter = this.mysteryEncounterData.encounteredEvents?.length > 0 ? this.mysteryEncounterData.encounteredEvents[this.mysteryEncounterData.encounteredEvents.length - 1][0] : null;
|
||||
const biomeMysteryEncounters = mysteryEncountersByBiome.get(this.arena.biomeType);
|
||||
const biomeMysteryEncounters = mysteryEncountersByBiome.get(this.arena.biomeType) ?? [];
|
||||
// If no valid encounters exist at tier, checks next tier down, continuing until there are some encounters available
|
||||
while (availableEncounters.length === 0 && tier >= 0) {
|
||||
availableEncounters = biomeMysteryEncounters
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
import { getPokemonSpecies } from "#app/data/pokemon-species.js";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { Species } from "#app/enums/species.js";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "../../../battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier } from "../mystery-encounter";
|
||||
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||
import { applyDamageToPokemon, leaveEncounterWithoutBattle, setEncounterExp } from "../utils/encounter-phase-utils";
|
||||
|
||||
const OPTION_1_REQUIRED_MOVE = Moves.SURF;
|
||||
const OPTION_2_REQUIRED_MOVE = Moves.FLY;
|
||||
/**
|
||||
* Damage percentage taken when wandering aimlessly.
|
||||
* Can be a number between `0` - `100`.
|
||||
* The higher the more damage taken (100% = instant KO).
|
||||
*/
|
||||
const DAMAGE_PERCENTAGE: number = 25;
|
||||
/** The i18n namespace for the encounter */
|
||||
const namepsace = "mysteryEncounter:lostAtSea";
|
||||
|
||||
/**
|
||||
* Lost at sea encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/9 | GitHub Issue #9}
|
||||
* @see For biome requirements check [mysteryEncountersByBiome](../mystery-encounters.ts)
|
||||
*/
|
||||
export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.LOST_AT_SEA)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(11, 179)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
fileRoot: "mystery-encounters",
|
||||
spriteKey: "buoy",
|
||||
hasShadow: false,
|
||||
x: 20,
|
||||
y: 3,
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([{ text: `${namepsace}:intro` }])
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
const { mysteryEncounter } = scene.currentBattle;
|
||||
|
||||
mysteryEncounter.setDialogueToken("damagePercentage", String(DAMAGE_PERCENTAGE));
|
||||
mysteryEncounter.setDialogueToken("option1RequiredMove", Moves[OPTION_1_REQUIRED_MOVE]);
|
||||
mysteryEncounter.setDialogueToken("option2RequiredMove", Moves[OPTION_2_REQUIRED_MOVE]);
|
||||
|
||||
return true;
|
||||
})
|
||||
.withTitle(`${namepsace}:title`)
|
||||
.withDescription(`${namepsace}:description`)
|
||||
.withQuery(`${namepsace}:query`)
|
||||
.withOption(
|
||||
// Option 1: Use a (non fainted) pokemon that can learn Surf to guide you back/
|
||||
new MysteryEncounterOptionBuilder()
|
||||
.withPokemonCanLearnMoveRequirement(OPTION_1_REQUIRED_MOVE)
|
||||
.withOptionMode(EncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namepsace}:option:1:label`,
|
||||
disabledButtonLabel: `${namepsace}:option:1:label_disabled`,
|
||||
buttonTooltip: `${namepsace}:option:1:tooltip`,
|
||||
disabledButtonTooltip: `${namepsace}:option:1:tooltip_disabled`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namepsace}:option:1:selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => handlePokemonGuidingYouPhase(scene))
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
//Option 2: Use a (non fainted) pokemon that can learn fly to guide you back.
|
||||
new MysteryEncounterOptionBuilder()
|
||||
.withPokemonCanLearnMoveRequirement(OPTION_2_REQUIRED_MOVE)
|
||||
.withOptionMode(EncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namepsace}:option:2:label`,
|
||||
disabledButtonLabel: `${namepsace}:option:2:label_disabled`,
|
||||
buttonTooltip: `${namepsace}:option:2:tooltip`,
|
||||
disabledButtonTooltip: `${namepsace}:option:2:tooltip_disabled`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namepsace}:option:2:selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => handlePokemonGuidingYouPhase(scene))
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
// Option 3: Wander aimlessly
|
||||
{
|
||||
buttonLabel: `${namepsace}:option:3:label`,
|
||||
buttonTooltip: `${namepsace}:option:3:tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namepsace}:option:3:selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
const allowedPokemon = scene.getParty().filter((p) => p.isAllowedInBattle());
|
||||
|
||||
for (const pkm of allowedPokemon) {
|
||||
const percentage = DAMAGE_PERCENTAGE / 100;
|
||||
const damage = Math.floor(pkm.getMaxHp() * percentage);
|
||||
applyDamageToPokemon(scene, pkm, damage);
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
text: `${namepsace}:outro`,
|
||||
},
|
||||
])
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Generic handler for using a guiding pokemon to guide you back.
|
||||
*
|
||||
* @param scene Battle scene
|
||||
* @param guidePokemon pokemon choosen as a guide
|
||||
*/
|
||||
function handlePokemonGuidingYouPhase(scene: BattleScene) {
|
||||
const laprasSpecies = getPokemonSpecies(Species.LAPRAS);
|
||||
const { mysteryEncounter } = scene.currentBattle;
|
||||
|
||||
if (mysteryEncounter.selectedOption) {
|
||||
setEncounterExp(scene, mysteryEncounter.selectedOption.primaryPokemon.id, laprasSpecies.baseExp, true);
|
||||
} else {
|
||||
console.warn("Lost at sea: No guide pokemon found but pokemon guides player. huh!?");
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
return true;
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import {
|
||||
leaveEncounterWithoutBattle,
|
||||
setEncounterRewards
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { getHighestLevelPlayerPokemon, koPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
import { GameOverPhase } from "#app/phases";
|
||||
import { randSeedInt } from "#app/utils";
|
||||
import { randSeedInt } from "#app/utils.js";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "../../../battle-scene";
|
||||
import IMysteryEncounter, {
|
||||
|
@ -12,8 +13,6 @@ import IMysteryEncounter, {
|
|||
MysteryEncounterTier,
|
||||
} from "../mystery-encounter";
|
||||
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { getHighestLevelPlayerPokemon, koPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
|
||||
export const MysteriousChestEncounter: IMysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(
|
||||
|
@ -115,16 +114,9 @@ export const MysteriousChestEncounter: IMysteryEncounter =
|
|||
scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", highestLevelPokemon.name);
|
||||
// Show which Pokemon was KOed, then leave encounter with no rewards
|
||||
// Does this synchronously so that game over doesn't happen over result message
|
||||
await showEncounterText(scene, "mysteryEncounter:mysterious_chest_option_1_bad_result")
|
||||
.then(() => {
|
||||
if (scene.getParty().filter((p) => p.isAllowedInBattle()).length === 0) {
|
||||
// All pokemon fainted, game over
|
||||
scene.clearPhaseQueue();
|
||||
scene.unshiftPhase(new GameOverPhase(scene));
|
||||
} else {
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
}
|
||||
});
|
||||
await showEncounterText(scene, "mysteryEncounter:mysterious_chest_option_1_bad_result").then(() => {
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
});
|
||||
}
|
||||
})
|
||||
.build()
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import { OptionTextDisplay } from "#app/data/mystery-encounters/mystery-encounter-dialogue";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { PlayerPokemon } from "#app/field/pokemon";
|
||||
import BattleScene from "../../battle-scene";
|
||||
import * as Utils from "../../utils";
|
||||
import { EncounterPokemonRequirement, EncounterSceneRequirement, MoneyRequirement } from "./mystery-encounter-requirements";
|
||||
import { Type } from "../type";
|
||||
import { EncounterPokemonRequirement, EncounterSceneRequirement, MoneyRequirement, TypeRequirement } from "./mystery-encounter-requirements";
|
||||
import { CanLearnMoveRequirement, CanLearnMoveRequirementOptions } from "./requirements/can-learn-move-requirement";
|
||||
|
||||
export enum EncounterOptionMode {
|
||||
/** Default style */
|
||||
|
@ -65,7 +68,6 @@ export default class MysteryEncounterOption implements MysteryEncounterOption {
|
|||
}
|
||||
let qualified: PlayerPokemon[] = scene.getParty();
|
||||
for (const req of this.primaryPokemonRequirements) {
|
||||
console.log(req);
|
||||
if (req.meetsRequirement(scene)) {
|
||||
if (req instanceof EncounterPokemonRequirement) {
|
||||
qualified = qualified.filter(pkmn => req.queryParty(scene.getParty()).includes(pkmn));
|
||||
|
@ -183,12 +185,42 @@ export class MysteryEncounterOptionBuilder implements Partial<MysteryEncounterOp
|
|||
return Object.assign(this, { primaryPokemonRequirements: this.primaryPokemonRequirements });
|
||||
}
|
||||
|
||||
/**
|
||||
* Player is required to have certain type/s of pokemon in his party (with optional min number of pokemons with that type)
|
||||
*
|
||||
* @param type the required type/s
|
||||
* @param excludeFainted whether to exclude fainted pokemon
|
||||
* @param minNumberOfPokemon number of pokemons to have that type
|
||||
* @param invertQuery
|
||||
* @returns
|
||||
*/
|
||||
withPokemonTypeRequirement(type: Type | Type[], excludeFainted?: boolean, minNumberOfPokemon?: number, invertQuery?: boolean) {
|
||||
return this.withPrimaryPokemonRequirement(new TypeRequirement(type, excludeFainted, minNumberOfPokemon, invertQuery));
|
||||
}
|
||||
|
||||
/**
|
||||
* Player is required to have a pokemon that can learn a certain move/moveset
|
||||
*
|
||||
* @param move the required move/moves
|
||||
* @param options see {@linkcode CanLearnMoveRequirementOptions}
|
||||
* @returns
|
||||
*/
|
||||
withPokemonCanLearnMoveRequirement(move: Moves | Moves[], options?: CanLearnMoveRequirementOptions) {
|
||||
return this.withPrimaryPokemonRequirement(new CanLearnMoveRequirement(move, options));
|
||||
}
|
||||
|
||||
withSecondaryPokemonRequirement(requirement: EncounterPokemonRequirement, excludePrimaryFromSecondaryRequirements?: boolean): this & Required<Pick<MysteryEncounterOption, "secondaryPokemonRequirements">> {
|
||||
this.secondaryPokemonRequirements.push(requirement);
|
||||
this.excludePrimaryFromSecondaryRequirements = excludePrimaryFromSecondaryRequirements;
|
||||
return Object.assign(this, { secondaryPokemonRequirements: this.secondaryPokemonRequirements });
|
||||
}
|
||||
|
||||
/**
|
||||
* Se the full dialogue object to the option. Will override anything already set
|
||||
*
|
||||
* @param dialogue see {@linkcode OptionTextDisplay}
|
||||
* @returns
|
||||
*/
|
||||
withDialogue(dialogue: OptionTextDisplay) {
|
||||
this.dialogue = dialogue;
|
||||
return this;
|
||||
|
|
|
@ -31,12 +31,10 @@ export abstract class EncounterSceneRequirement implements EncounterRequirement
|
|||
}
|
||||
|
||||
export abstract class EncounterPokemonRequirement implements EncounterRequirement {
|
||||
minNumberOfPokemon: number;
|
||||
invertQuery: boolean;
|
||||
public minNumberOfPokemon: number;
|
||||
public invertQuery: boolean;
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
abstract meetsRequirement(scene: BattleScene): boolean;
|
||||
|
||||
/**
|
||||
* Returns all party members that are compatible with this requirement. For non pokemon related requirements, the entire party is returned.
|
||||
|
@ -331,11 +329,13 @@ export class NatureRequirement extends EncounterPokemonRequirement {
|
|||
|
||||
export class TypeRequirement extends EncounterPokemonRequirement {
|
||||
requiredType: Type[];
|
||||
excludeFainted: boolean;
|
||||
minNumberOfPokemon: number;
|
||||
invertQuery: boolean;
|
||||
|
||||
constructor(type: Type | Type[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
||||
constructor(type: Type | Type[], excludeFainted: boolean = true, minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
||||
super();
|
||||
this.excludeFainted = excludeFainted;
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
if (type instanceof Array) {
|
||||
|
@ -347,10 +347,16 @@ export class TypeRequirement extends EncounterPokemonRequirement {
|
|||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
const partyPokemon = scene.getParty();
|
||||
let partyPokemon = scene.getParty();
|
||||
|
||||
if (isNullOrUndefined(partyPokemon) || this?.requiredType?.length < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.excludeFainted) {
|
||||
partyPokemon = partyPokemon.filter((pokemon) => !pokemon.isFainted());
|
||||
}
|
||||
|
||||
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
||||
}
|
||||
|
||||
|
@ -946,3 +952,5 @@ export class WeightRequirement extends EncounterPokemonRequirement {
|
|||
return ["weight", pokemon.getWeight().toString()];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -198,7 +198,6 @@ export default class IMysteryEncounter implements IMysteryEncounter {
|
|||
}
|
||||
let qualified: PlayerPokemon[] = scene.getParty();
|
||||
for (const req of this.primaryPokemonRequirements) {
|
||||
console.log(req);
|
||||
if (req.meetsRequirement(scene)) {
|
||||
if (req instanceof EncounterPokemonRequirement) {
|
||||
qualified = qualified.filter(pkmn => req.queryParty(scene.getParty()).includes(pkmn));
|
||||
|
@ -382,8 +381,11 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Defines an option for the encounter
|
||||
* There should be at least 2 options defined and no more than 4
|
||||
* Defines an option for the encounter.
|
||||
* Use for complex options.
|
||||
* There should be at least 2 options defined and no more than 4.
|
||||
* If easy/streamlined use {@linkcode MysteryEncounterBuilder.withOptionPhase}
|
||||
*
|
||||
* @param option - MysteryEncounterOption to add, can use MysteryEncounterOptionBuilder to create instance
|
||||
* @returns
|
||||
*/
|
||||
|
@ -399,8 +401,10 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Adds a streamlined option phase.
|
||||
* Only use if no pre-/post-options or condtions necessary.
|
||||
* Defines an option + phasefor the encounter.
|
||||
* Use for easy/streamlined options.
|
||||
* There should be at least 2 options defined and no more than 4.
|
||||
* If complex use {@linkcode MysteryEncounterBuilder.withOption}
|
||||
*
|
||||
* @param dialogue - {@linkcode OptionTextDisplay}
|
||||
* @param callback - {@linkcode OptionPhaseCallback}
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import IMysteryEncounter from "./mystery-encounter";
|
||||
import { Biome } from "#enums/biome";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { DarkDealEncounter } from "./encounters/dark-deal-encounter";
|
||||
import { DepartmentStoreSaleEncounter } from "./encounters/department-store-sale-encounter";
|
||||
import { FieldTripEncounter } from "./encounters/field-trip-encounter";
|
||||
import { FightOrFlightEncounter } from "./encounters/fight-or-flight-encounter";
|
||||
import { LostAtSeaEncounter } from "./encounters/lost-at-sea-encounter";
|
||||
import { MysteriousChallengersEncounter } from "./encounters/mysterious-challengers-encounter";
|
||||
import { MysteriousChestEncounter } from "./encounters/mysterious-chest-encounter";
|
||||
import { FightOrFlightEncounter } from "./encounters/fight-or-flight-encounter";
|
||||
import { TrainingSessionEncounter } from "./encounters/training-session-encounter";
|
||||
import { Biome } from "#enums/biome";
|
||||
import { SleepingSnorlaxEncounter } from "./encounters/sleeping-snorlax-encounter";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { DepartmentStoreSaleEncounter } from "./encounters/department-store-sale-encounter";
|
||||
import { ShadyVitaminDealerEncounter } from "./encounters/shady-vitamin-dealer-encounter";
|
||||
import { FieldTripEncounter } from "./encounters/field-trip-encounter";
|
||||
import { SleepingSnorlaxEncounter } from "./encounters/sleeping-snorlax-encounter";
|
||||
import { TrainingSessionEncounter } from "./encounters/training-session-encounter";
|
||||
import IMysteryEncounter from "./mystery-encounter";
|
||||
import { SafariZoneEncounter } from "#app/data/mystery-encounters/encounters/safari-zone-encounter";
|
||||
|
||||
// Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / 256
|
||||
|
@ -166,7 +167,10 @@ export const mysteryEncountersByBiome = new Map<Biome, MysteryEncounterType[]>([
|
|||
MysteryEncounterType.SLEEPING_SNORLAX,
|
||||
MysteryEncounterType.SAFARI_ZONE
|
||||
]],
|
||||
[Biome.SEA, []],
|
||||
|
||||
[Biome.SEA, [
|
||||
MysteryEncounterType.LOST_AT_SEA
|
||||
]],
|
||||
[Biome.SWAMP, [
|
||||
MysteryEncounterType.SAFARI_ZONE
|
||||
]],
|
||||
|
@ -215,6 +219,7 @@ export function initMysteryEncounters() {
|
|||
allMysteryEncounters[MysteryEncounterType.SHADY_VITAMIN_DEALER] = ShadyVitaminDealerEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.FIELD_TRIP] = FieldTripEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.SAFARI_ZONE] = SafariZoneEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.LOST_AT_SEA] = LostAtSeaEncounter;
|
||||
|
||||
// Add extreme encounters to biome map
|
||||
extremeBiomeEncounters.forEach(encounter => {
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
import BattleScene from "#app/battle-scene";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { PlayerPokemon } from "#app/field/pokemon";
|
||||
import { isNullOrUndefined } from "#app/utils";
|
||||
import { EncounterPokemonRequirement } from "../mystery-encounter-requirements";
|
||||
|
||||
/**
|
||||
* {@linkcode CanLearnMoveRequirement} options
|
||||
*/
|
||||
export interface CanLearnMoveRequirementOptions {
|
||||
excludeLevelMoves?: boolean;
|
||||
excludeTmMoves?: boolean;
|
||||
excludeEggMoves?: boolean;
|
||||
includeFainted?: boolean;
|
||||
minNumberOfPokemon?: number;
|
||||
invertQuery?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requires that a pokemon can learn a specific move/moveset.
|
||||
*/
|
||||
export class CanLearnMoveRequirement extends EncounterPokemonRequirement {
|
||||
private readonly requiredMoves: Moves[];
|
||||
private readonly excludeLevelMoves?: boolean;
|
||||
private readonly excludeTmMoves?: boolean;
|
||||
private readonly excludeEggMoves?: boolean;
|
||||
private readonly includeFainted?: boolean;
|
||||
|
||||
constructor(requiredMoves: Moves | Moves[], options: CanLearnMoveRequirementOptions = {}) {
|
||||
super();
|
||||
this.requiredMoves = Array.isArray(requiredMoves) ? requiredMoves : [requiredMoves];
|
||||
|
||||
const { excludeLevelMoves, excludeTmMoves, excludeEggMoves, includeFainted, minNumberOfPokemon, invertQuery } = options;
|
||||
|
||||
this.excludeLevelMoves = excludeLevelMoves ?? false;
|
||||
this.excludeTmMoves = excludeTmMoves ?? false;
|
||||
this.excludeEggMoves = excludeEggMoves ?? false;
|
||||
this.includeFainted = includeFainted ?? false;
|
||||
this.minNumberOfPokemon = minNumberOfPokemon ?? 1;
|
||||
this.invertQuery = invertQuery;
|
||||
}
|
||||
|
||||
override meetsRequirement(scene: BattleScene): boolean {
|
||||
const partyPokemon = scene.getParty().filter((pkm) => (this.includeFainted ? pkm.isAllowed() : pkm.isAllowedInBattle()));
|
||||
|
||||
if (isNullOrUndefined(partyPokemon) || this?.requiredMoves?.length < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
||||
}
|
||||
|
||||
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter((pokemon) =>
|
||||
// every required move should be included
|
||||
this.requiredMoves.every((requiredMove) => this.getAllPokemonMoves(pokemon).includes(requiredMove))
|
||||
);
|
||||
} else {
|
||||
return partyPokemon.filter(
|
||||
(pokemon) =>
|
||||
// none of the "required" moves should be included
|
||||
!this.requiredMoves.some((requiredMove) => this.getAllPokemonMoves(pokemon).includes(requiredMove))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
override getDialogueToken(_scene: BattleScene, _pokemon?: PlayerPokemon): [string, string] {
|
||||
return ["requiredMoves", this.requiredMoves.join(", ")];
|
||||
}
|
||||
|
||||
private getPokemonLevelMoves(pkm: PlayerPokemon): Moves[] {
|
||||
return pkm.getLevelMoves().map(([_level, move]) => move);
|
||||
}
|
||||
|
||||
private getAllPokemonMoves(pkm: PlayerPokemon): Moves[] {
|
||||
const allPokemonMoves: Moves[] = [];
|
||||
|
||||
if (!this.excludeLevelMoves) {
|
||||
allPokemonMoves.push(...(this.getPokemonLevelMoves(pkm) ?? []));
|
||||
}
|
||||
|
||||
if (!this.excludeTmMoves) {
|
||||
allPokemonMoves.push(...(pkm.compatibleTms ?? []));
|
||||
}
|
||||
|
||||
if (!this.excludeEggMoves) {
|
||||
allPokemonMoves.push(...(pkm.getEggMoves() ?? []));
|
||||
}
|
||||
|
||||
return allPokemonMoves;
|
||||
}
|
||||
}
|
|
@ -1,30 +1,31 @@
|
|||
import i18next from "i18next";
|
||||
import { BattleType } from "#app/battle";
|
||||
import BattleScene from "../../../battle-scene";
|
||||
import PokemonSpecies from "../../pokemon-species";
|
||||
import { MysteryEncounterVariant } from "../mystery-encounter";
|
||||
import { Status, StatusEffect } from "../../status-effect";
|
||||
import { TrainerConfig, trainerConfigs, TrainerSlot } from "../../trainer-config";
|
||||
import { biomeLinks } from "#app/data/biomes";
|
||||
import MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { WIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters";
|
||||
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import Pokemon, { FieldPosition, PlayerPokemon } from "#app/field/pokemon";
|
||||
import Trainer, { TrainerVariant } from "../../../field/trainer";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { ExpBalanceModifier, ExpShareModifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier } from "#app/modifier/modifier";
|
||||
import { CustomModifierSettings, getModifierPoolForType, ModifierPoolType, ModifierType, ModifierTypeFunc, ModifierTypeGenerator, ModifierTypeOption, modifierTypes, PokemonHeldItemModifierType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
|
||||
import { BattleEndPhase, EggLapsePhase, ExpPhase, ModifierRewardPhase, SelectModifierPhase, ShowPartyExpBarPhase, TrainerVictoryPhase } from "#app/phases";
|
||||
import { MysteryEncounterBattlePhase, MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phase";
|
||||
import * as Utils from "../../../utils";
|
||||
import { isNullOrUndefined } from "#app/utils";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import PokemonData from "#app/system/pokemon-data";
|
||||
import { Biome } from "#enums/biome";
|
||||
import { biomeLinks } from "#app/data/biomes";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler";
|
||||
import { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||
import { WIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters";
|
||||
import * as Overrides from "#app/overrides";
|
||||
import MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { BattleEndPhase, EggLapsePhase, ExpPhase, GameOverPhase, ModifierRewardPhase, SelectModifierPhase, ShowPartyExpBarPhase, TrainerVictoryPhase } from "#app/phases";
|
||||
import { MysteryEncounterBattlePhase, MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phase";
|
||||
import PokemonData from "#app/system/pokemon-data";
|
||||
import { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||
import { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import { isNullOrUndefined } from "#app/utils";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { Biome } from "#enums/biome";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import i18next from "i18next";
|
||||
import BattleScene from "../../../battle-scene";
|
||||
import Trainer, { TrainerVariant } from "../../../field/trainer";
|
||||
import * as Utils from "../../../utils";
|
||||
import PokemonSpecies from "../../pokemon-species";
|
||||
import { Status, StatusEffect } from "../../status-effect";
|
||||
import { TrainerConfig, trainerConfigs, TrainerSlot } from "../../trainer-config";
|
||||
import { MysteryEncounterVariant } from "../mystery-encounter";
|
||||
|
||||
export class EnemyPokemonConfig {
|
||||
species: PokemonSpecies;
|
||||
|
@ -385,7 +386,7 @@ export function setEncounterRewards(scene: BattleScene, customShopRewards?: Cust
|
|||
* Will initialize exp phases into the phase queue (these are in addition to any combat or other exp earned)
|
||||
* Exp Share and Exp Balance will still function as normal
|
||||
* @param scene - Battle Scene
|
||||
* @param participantIds - ids of party pokemon that get full exp value. Other party members will receive Exp Share amounts
|
||||
* @param participantId - id/s of party pokemon that get full exp value. Other party members will receive Exp Share amounts
|
||||
* @param baseExpValue - gives exp equivalent to a pokemon of the wave index's level.
|
||||
* Guidelines:
|
||||
* 36 - Sunkern (lowest in game)
|
||||
|
@ -399,7 +400,9 @@ export function setEncounterRewards(scene: BattleScene, customShopRewards?: Cust
|
|||
* https://bulbapedia.bulbagarden.net/wiki/List_of_Pok%C3%A9mon_by_effort_value_yield_(Generation_IX)
|
||||
* @param useWaveIndex - set to false when directly passing the the full exp value instead of baseExpValue
|
||||
*/
|
||||
export function setEncounterExp(scene: BattleScene, participantIds: integer[], baseExpValue: number, useWaveIndex: boolean = true) {
|
||||
export function setEncounterExp(scene: BattleScene, participantId: integer | integer[], baseExpValue: number, useWaveIndex: boolean = true) {
|
||||
const participantIds = Array.isArray(participantId) ? participantId : [participantId];
|
||||
|
||||
scene.currentBattle.mysteryEncounter.doEncounterExp = (scene: BattleScene) => {
|
||||
const party = scene.getParty();
|
||||
const expShareModifier = scene.findModifier(m => m instanceof ExpShareModifier) as ExpShareModifier;
|
||||
|
@ -519,6 +522,14 @@ export function leaveEncounterWithoutBattle(scene: BattleScene, addHealPhase: bo
|
|||
}
|
||||
|
||||
export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase: boolean = false) {
|
||||
const allowedPkm = scene.getParty().filter((pkm) => pkm.isAllowedInBattle());
|
||||
|
||||
if (allowedPkm.length === 0) {
|
||||
scene.clearPhaseQueue();
|
||||
scene.unshiftPhase(new GameOverPhase(scene));
|
||||
return;
|
||||
}
|
||||
|
||||
if (scene.currentBattle.mysteryEncounter.encounterVariant === MysteryEncounterVariant.SAFARI_BATTLE) {
|
||||
scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase));
|
||||
} else if (scene.currentBattle.mysteryEncounter.encounterVariant === MysteryEncounterVariant.NO_BATTLE) {
|
||||
|
@ -673,3 +684,65 @@ export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: n
|
|||
|
||||
console.log(`Starting weight: ${baseSpawnWeight}\nAverage MEs per run: ${totalMean}\nStandard Deviation: ${totalStd}\nAvg Commons: ${commonMean}\nAvg Uncommons: ${uncommonMean}\nAvg Rares: ${rareMean}\nAvg Super Rares: ${superRareMean}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes care of handling player pokemon KO (with all its side effects)
|
||||
*
|
||||
* @param scene the battle scene
|
||||
* @param pokemon the player pokemon to KO
|
||||
*/
|
||||
export function koPlayerPokemon(scene: BattleScene, pokemon: PlayerPokemon) {
|
||||
pokemon.hp = 0;
|
||||
pokemon.trySetStatus(StatusEffect.FAINT);
|
||||
pokemon.updateInfo();
|
||||
queueEncounterMessage(scene, i18next.t("battle:fainted", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles applying hp changes to a player pokemon.
|
||||
* Takes care of not going below `0`, above max-hp, adding `FNT` status correctly and updating the pokemon info.
|
||||
* TODO: handle special cases like wonder-guard/ninjask
|
||||
* @param scene the battle scene
|
||||
* @param pokemon the player pokemon to apply the hp change to
|
||||
* @param value the hp change amount. Positive for heal. Negative for damage
|
||||
*
|
||||
*/
|
||||
function applyHpChangeToPokemon(scene: BattleScene, pokemon: PlayerPokemon, value: number) {
|
||||
const hpChange = Math.round(pokemon.hp + value);
|
||||
const nextHp = Math.max(Math.min(hpChange, pokemon.getMaxHp()), 0);
|
||||
if (nextHp === 0) {
|
||||
koPlayerPokemon(scene, pokemon);
|
||||
} else {
|
||||
pokemon.hp = nextHp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles applying damage to a player pokemon
|
||||
* @param scene the battle scene
|
||||
* @param pokemon the player pokemon to apply damage to
|
||||
* @param damage the amount of damage to apply
|
||||
* @see {@linkcode applyHpChangeToPokemon}
|
||||
*/
|
||||
export function applyDamageToPokemon(scene: BattleScene, pokemon: PlayerPokemon, damage: number) {
|
||||
if (damage <= 0) {
|
||||
console.warn("Healing pokemon with `applyDamageToPokemon` is not recommended! Please use `applyHealToPokemon` instead.");
|
||||
}
|
||||
|
||||
applyHpChangeToPokemon(scene, pokemon, -damage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles applying heal to a player pokemon
|
||||
* @param scene the battle scene
|
||||
* @param pokemon the player pokemon to apply heal to
|
||||
* @param heal the amount of heal to apply
|
||||
* @see {@linkcode applyHpChangeToPokemon}
|
||||
*/
|
||||
export function applyHealToPokemon(scene: BattleScene, pokemon: PlayerPokemon, heal: number) {
|
||||
if (heal <= 0) {
|
||||
console.warn("Damaging pokemong with `applyHealToPokemon` is not recommended! Please use `applyDamageToPokemon` instead.");
|
||||
}
|
||||
|
||||
applyHpChangeToPokemon(scene, pokemon, heal);
|
||||
}
|
||||
|
|
|
@ -8,5 +8,6 @@ export enum MysteryEncounterType {
|
|||
DEPARTMENT_STORE_SALE,
|
||||
SHADY_VITAMIN_DEALER,
|
||||
FIELD_TRIP,
|
||||
SAFARI_ZONE
|
||||
SAFARI_ZONE,
|
||||
LOST_AT_SEA //might be generalized later on
|
||||
}
|
||||
|
|
|
@ -2,18 +2,50 @@ import { GameObjects } from "phaser";
|
|||
import BattleScene from "../battle-scene";
|
||||
import IMysteryEncounter from "../data/mystery-encounters/mystery-encounter";
|
||||
|
||||
type KnownFileRoot =
|
||||
| "trainer"
|
||||
| "pokemon"
|
||||
| "arenas"
|
||||
| "battle_anims"
|
||||
| "cg"
|
||||
| "character"
|
||||
| "effect"
|
||||
| "egg"
|
||||
| "events"
|
||||
| "inputs"
|
||||
| "items"
|
||||
| "mystery-encounters"
|
||||
| "pokeball"
|
||||
| "pokemon"
|
||||
| "statuses"
|
||||
| "trainer"
|
||||
| "ui";
|
||||
|
||||
export class MysteryEncounterSpriteConfig {
|
||||
spriteKey: string; // e.g. "ace_trainer_f"
|
||||
fileRoot: string; // "trainer" for trainer sprites, "pokemon" for pokemon, etc. Refer to /public/images directory for the folder name
|
||||
hasShadow?: boolean = false; // Spawns shadow underneath sprite
|
||||
disableAnimation?: boolean = false; // Animates frames or not
|
||||
repeat?: boolean = false; // Cycles animation
|
||||
/** The sprite key (which is the image file name). e.g. "ace_trainer_f" */
|
||||
spriteKey: string;
|
||||
/** Refer to [/public/images](../../public/images) directorty for all folder names */
|
||||
fileRoot: KnownFileRoot & string;
|
||||
/** Enable shadow. Defaults to `false` */
|
||||
hasShadow?: boolean = false;
|
||||
/** Disable animation. Defaults to `false` */
|
||||
disableAnimation?: boolean = false;
|
||||
/** Repeat the animation. Defaults to `false` */
|
||||
repeat?: boolean = false;
|
||||
/** Tint color. `0` - `1`. Higher means darker tint. */
|
||||
tint?: number;
|
||||
x?: number; // X offset
|
||||
y?: number; // Y offset
|
||||
/** X offset */
|
||||
x?: number;
|
||||
/** Y offset */
|
||||
y?: number;
|
||||
/** Y shadow offset */
|
||||
yShadowOffset?: number;
|
||||
/** Sprite scale. `0` - `n` */
|
||||
scale?: number;
|
||||
isItem?: boolean; // For item sprites, set to true
|
||||
/** If you are using an item sprite, set to `true` */
|
||||
isItem?: boolean;
|
||||
/** The sprites alpha. `0` - `1` The lower the number, the more transparent */
|
||||
alpha?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,32 +92,35 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||
const spacingValue = Math.round((maxX - minX) / Math.max(this.spriteConfigs.filter(s => !s.x && !s.y).length, 1));
|
||||
|
||||
this.spriteConfigs?.forEach((config) => {
|
||||
const { spriteKey, isItem, hasShadow, scale, x, y, yShadowOffset, alpha } = config;
|
||||
|
||||
let sprite: GameObjects.Sprite;
|
||||
let tintSprite: GameObjects.Sprite;
|
||||
if (!config.isItem) {
|
||||
sprite = getSprite(config.spriteKey, config.hasShadow, config.yShadowOffset);
|
||||
tintSprite = getSprite(config.spriteKey);
|
||||
|
||||
if (!isItem) {
|
||||
sprite = getSprite(spriteKey, hasShadow, yShadowOffset);
|
||||
tintSprite = getSprite(spriteKey);
|
||||
} else {
|
||||
sprite = getItemSprite(config.spriteKey);
|
||||
tintSprite = getItemSprite(config.spriteKey);
|
||||
sprite = getItemSprite(spriteKey);
|
||||
tintSprite = getItemSprite(spriteKey);
|
||||
}
|
||||
|
||||
tintSprite.setVisible(false);
|
||||
|
||||
if (config.scale) {
|
||||
sprite.setScale(config.scale);
|
||||
tintSprite.setScale(config.scale);
|
||||
if (scale) {
|
||||
sprite.setScale(scale);
|
||||
tintSprite.setScale(scale);
|
||||
}
|
||||
|
||||
// Sprite offset from origin
|
||||
if (config.x || config.y) {
|
||||
if (config.x) {
|
||||
sprite.setPosition(origin + config.x, sprite.y);
|
||||
tintSprite.setPosition(origin + config.x, tintSprite.y);
|
||||
if (x || y) {
|
||||
if (x) {
|
||||
sprite.setPosition(origin + x, sprite.y);
|
||||
tintSprite.setPosition(origin + x, tintSprite.y);
|
||||
}
|
||||
if (config.y) {
|
||||
sprite.setPosition(sprite.x, sprite.y + config.y);
|
||||
tintSprite.setPosition(tintSprite.x, tintSprite.y + config.y);
|
||||
if (y) {
|
||||
sprite.setPosition(sprite.x, sprite.y + y);
|
||||
tintSprite.setPosition(tintSprite.x, tintSprite.y + y);
|
||||
}
|
||||
} else {
|
||||
// Single sprite
|
||||
|
@ -100,6 +135,11 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||
}
|
||||
}
|
||||
|
||||
if (alpha) {
|
||||
sprite.setAlpha(alpha);
|
||||
tintSprite.setAlpha(alpha);
|
||||
}
|
||||
|
||||
this.add(sprite);
|
||||
this.add(tintSprite);
|
||||
});
|
||||
|
|
|
@ -274,9 +274,18 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
* @returns {boolean} True if pokemon is allowed in battle
|
||||
*/
|
||||
isAllowedInBattle(): boolean {
|
||||
return !this.isFainted() && this.isAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this pokemon is allowed (no challenge exclusion)
|
||||
* This is frequently a better alternative to {@link isFainted}
|
||||
* @returns {boolean} True if pokemon is allowed in battle
|
||||
*/
|
||||
isAllowed(): boolean {
|
||||
const challengeAllowed = new Utils.BooleanHolder(true);
|
||||
applyChallenges(this.scene.gameMode, ChallengeType.POKEMON_IN_BATTLE, this, challengeAllowed);
|
||||
return !this.isFainted() && challengeAllowed.value;
|
||||
return challengeAllowed.value;
|
||||
}
|
||||
|
||||
isActive(onField?: boolean): boolean {
|
||||
|
@ -1319,6 +1328,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all egg moves
|
||||
*
|
||||
* @returns list of egg moves
|
||||
*/
|
||||
getEggMoves() : Moves[] {
|
||||
return speciesEggMoves[this.species.speciesId];
|
||||
}
|
||||
|
||||
setMove(moveIndex: integer, moveId: Moves): void {
|
||||
const move = moveId ? new PokemonMove(moveId) : null;
|
||||
this.moveset[moveIndex] = move;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { SimpleTranslationEntries } from "#app/interfaces/locales";
|
||||
import { lostAtSea } from "./mystery-encounters/lost-at-sea";
|
||||
|
||||
/**
|
||||
* Patterns that can be used:
|
||||
|
@ -13,7 +13,7 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales";
|
|||
* Any '(+)' or '(-)' type of tooltip will auto-color to green/blue respectively. THIS ONLY OCCURS FOR OPTION TOOLTIPS, NOWHERE ELSE
|
||||
* Other types of '(...)' tooltips will have to specify the text color manually by using '@[SUMMARY_GREEN]{<text>}' pattern
|
||||
*/
|
||||
export const mysteryEncounter: SimpleTranslationEntries = {
|
||||
export const mysteryEncounter = {
|
||||
// DO NOT REMOVE
|
||||
"unit_test_dialogue": "{{test}}{{test}} {{test{{test}}}} {{test1}} {{test\}} {{test\\}} {{test\\\}} {test}}",
|
||||
|
||||
|
@ -240,4 +240,5 @@ export const mysteryEncounter: SimpleTranslationEntries = {
|
|||
$@s{item_fanfare}You gained a Berry!`,
|
||||
"sleeping_snorlax_option_3_good_result": "Your {{option3PrimaryName}} uses {{option3PrimaryMove}}! @s{item_fanfare}It steals Leftovers off the sleeping Snorlax and you make out like bandits!",
|
||||
|
||||
lostAtSea,
|
||||
} as const;
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
export const lostAtSea = {
|
||||
intro: "Wandering aimlessly, you effectively get nowhere.",
|
||||
title: "Lost at sea",
|
||||
description: "The sea is turbulent in this area, and you seem to be running out of fuel.\nThis is bad. Is there a way out of the situation?",
|
||||
query: "What will you do?",
|
||||
option: {
|
||||
1: {
|
||||
label: "{{option1PrimaryName}} can help",
|
||||
label_disabled: "Can't {{option1RequiredMove}}",
|
||||
tooltip: "(+) {{option1PrimaryName}} saves you.\n(+) {{option1PrimaryName}} gains some EXP.",
|
||||
tooltip_disabled: "You have no Pokémon to {{option1RequiredMove}} on",
|
||||
selected:
|
||||
"{{option1PrimaryName}} swims ahead, guiding you back on track.\n{{option1PrimaryName}} seems to also have gotten stronger in this time of need.",
|
||||
},
|
||||
2: {
|
||||
label: "{{option2PrimaryName}} can help",
|
||||
label_disabled: "Can't {{option2RequiredMove}}",
|
||||
tooltip: "(+) {{option2PrimaryName}} saves you.\n(+) {{option2PrimaryName}} gains some EXP.",
|
||||
tooltip_disabled: "You have no Pokémon to {{option2RequiredMove}} with",
|
||||
selected:
|
||||
"{{option2PrimaryName}} flies ahead of your boat, guiding you back on track.\n{{option2PrimaryName}} seems to also have gotten stronger in this time of need.",
|
||||
},
|
||||
3: {
|
||||
label: "Wander aimlessly",
|
||||
tooltip: "(-) Each of your Pokémon lose {{damagePercentage}}% of their total HP.",
|
||||
selected: `You float about in the boat, steering it aimlessly until you finally get back on track.
|
||||
$You and your Pokémon get very fatigued during the whole ordeal.`,
|
||||
},
|
||||
},
|
||||
outro: "You are back on track."
|
||||
};
|
|
@ -4,7 +4,7 @@ import { Phase } from "../phase";
|
|||
import { Mode } from "../ui/ui";
|
||||
import { hideMysteryEncounterIntroVisuals, OptionSelectSettings } from "../data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { CheckSwitchPhase, NewBattlePhase, ReturnPhase, ScanIvsPhase, SelectModifierPhase, SummonPhase, ToggleDoublePositionPhase } from "../phases";
|
||||
import MysteryEncounterOption from "../data/mystery-encounters/mystery-encounter-option";
|
||||
import MysteryEncounterOption, { OptionPhaseCallback } from "../data/mystery-encounters/mystery-encounter-option";
|
||||
import { MysteryEncounterVariant } from "../data/mystery-encounters/mystery-encounter";
|
||||
import { getCharVariantFromDialogue } from "../data/dialogue";
|
||||
import { TrainerSlot } from "../data/trainer-config";
|
||||
|
@ -138,7 +138,7 @@ export class MysteryEncounterPhase extends Phase {
|
|||
* Any phase that is meant to follow this one MUST be queued via the onOptionSelect() logic of the selected option
|
||||
*/
|
||||
export class MysteryEncounterOptionSelectedPhase extends Phase {
|
||||
onOptionSelect: (scene: BattleScene) => Promise<boolean | void>;
|
||||
onOptionSelect: OptionPhaseCallback;
|
||||
|
||||
constructor(scene: BattleScene) {
|
||||
super(scene);
|
||||
|
@ -394,7 +394,7 @@ export class MysteryEncounterRewardsPhase extends Phase {
|
|||
* - Queuing of the next wave
|
||||
*/
|
||||
export class PostMysteryEncounterPhase extends Phase {
|
||||
onPostOptionSelect: (scene: BattleScene) => Promise<void | boolean>;
|
||||
onPostOptionSelect: OptionPhaseCallback;
|
||||
|
||||
constructor(scene: BattleScene) {
|
||||
super(scene);
|
||||
|
|
|
@ -52,7 +52,7 @@ describe("Abilities - Quick Draw", () => {
|
|||
expect(pokemon.battleData.abilitiesApplied).contain(Abilities.QUICK_DRAW);
|
||||
}, 20000);
|
||||
|
||||
test("does not triggered by non damage moves", async () => {
|
||||
test("does not triggered by non damage moves", { timeout: 20000, retry: 5 }, async () => {
|
||||
await game.startBattle([Species.SLOWBRO]);
|
||||
|
||||
const pokemon = game.scene.getPlayerPokemon();
|
||||
|
@ -67,7 +67,7 @@ describe("Abilities - Quick Draw", () => {
|
|||
expect(pokemon.isFainted()).toBe(true);
|
||||
expect(enemy.isFainted()).toBe(false);
|
||||
expect(pokemon.battleData.abilitiesApplied).not.contain(Abilities.QUICK_DRAW);
|
||||
}, 20000);
|
||||
});
|
||||
|
||||
test("does not increase priority", async () => {
|
||||
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue(Array(4).fill(Moves.EXTREME_SPEED));
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
import { Button } from "#app/enums/buttons";
|
||||
import { MessagePhase } from "#app/phases";
|
||||
import { MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phase";
|
||||
import MysteryEncounterUiHandler from "#app/ui/mystery-encounter-ui-handler";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import GameManager from "../utils/gameManager";
|
||||
|
||||
export async function runSelectMysteryEncounterOption(game: GameManager, optionNo: number) {
|
||||
if (game.isCurrentPhase(MessagePhase)) {
|
||||
// Handle eventual weather messages (e.g. a downpour started!)
|
||||
game.onNextPrompt("MessagePhase", Mode.MESSAGE, () => {
|
||||
const uiHandler = game.scene.ui.getHandler<MysteryEncounterUiHandler>();
|
||||
uiHandler.processInput(Button.ACTION);
|
||||
});
|
||||
await game.phaseInterceptor.run(MessagePhase);
|
||||
}
|
||||
|
||||
// dispose of intro messages
|
||||
game.onNextPrompt("MysteryEncounterPhase", Mode.MESSAGE, () => {
|
||||
const uiHandler = game.scene.ui.getHandler<MysteryEncounterUiHandler>();
|
||||
uiHandler.processInput(Button.ACTION);
|
||||
});
|
||||
// select the desired option
|
||||
game.onNextPrompt("MysteryEncounterPhase", Mode.MYSTERY_ENCOUNTER, () => {
|
||||
const uiHandler = game.scene.ui.getHandler<MysteryEncounterUiHandler>();
|
||||
uiHandler.unblockInput(); // input are blocked by 1s to prevent accidental input. Tests need to handle that
|
||||
|
||||
switch (optionNo) {
|
||||
case 1:
|
||||
// no movement needed. Default cursor position
|
||||
break;
|
||||
case 2:
|
||||
uiHandler.processInput(Button.RIGHT);
|
||||
break;
|
||||
case 3:
|
||||
uiHandler.processInput(Button.DOWN);
|
||||
break;
|
||||
case 4:
|
||||
uiHandler.processInput(Button.RIGHT);
|
||||
uiHandler.processInput(Button.DOWN);
|
||||
break;
|
||||
}
|
||||
|
||||
uiHandler.processInput(Button.ACTION);
|
||||
});
|
||||
await game.phaseInterceptor.run(MysteryEncounterPhase);
|
||||
|
||||
// run the selected options phase
|
||||
game.onNextPrompt("MysteryEncounterOptionSelectedPhase", Mode.MESSAGE, () => {
|
||||
const uiHandler = game.scene.ui.getHandler<MysteryEncounterUiHandler>();
|
||||
uiHandler.processInput(Button.ACTION);
|
||||
});
|
||||
await game.phaseInterceptor.to(MysteryEncounterRewardsPhase);
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
import Battle from "#app/battle";
|
||||
import { LostAtSeaEncounter } from "#app/data/mystery-encounters/encounters/lost-at-sea-encounter";
|
||||
import { EncounterOptionMode } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
|
||||
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species.js";
|
||||
import { Biome } from "#app/enums/biome";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { MysteryEncounterType } from "#app/enums/mystery-encounter-type";
|
||||
import { Species } from "#app/enums/species";
|
||||
import GameManager from "#app/test/utils/gameManager";
|
||||
import { workaround_reInitSceneWithOverrides } from "#app/test/utils/testUtils";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { runSelectMysteryEncounterOption } from "../encounterTestUtils";
|
||||
|
||||
const namepsace = "mysteryEncounter:lostAtSea";
|
||||
/** Blastoise for surf. Pidgeot for fly. Abra for none. */
|
||||
const defaultParty = [Species.BLASTOISE, Species.PIDGEOT, Species.ABRA];
|
||||
const defaultBiome = Biome.SEA;
|
||||
const defaultWave = 33;
|
||||
|
||||
describe("Lost at Sea - Mystery Encounter", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({ type: Phaser.HEADLESS });
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override.mysteryEncounterChance(100);
|
||||
game.override.startingBiome(defaultBiome);
|
||||
game.override.startingWave(defaultWave);
|
||||
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
||||
new Map<Biome, MysteryEncounterType[]>([
|
||||
[Biome.SEA, [MysteryEncounterType.LOST_AT_SEA]],
|
||||
[Biome.MOUNTAIN, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]],
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
it("should have the correct properties", async () => {
|
||||
await workaround_reInitSceneWithOverrides(game);
|
||||
await game.runToMysteryEncounter(defaultParty);
|
||||
|
||||
expect(LostAtSeaEncounter.encounterType).toBe(MysteryEncounterType.LOST_AT_SEA);
|
||||
expect(LostAtSeaEncounter.dialogue).toBeDefined();
|
||||
expect(LostAtSeaEncounter.dialogue.intro).toStrictEqual([{ text: `${namepsace}:intro` }]);
|
||||
expect(LostAtSeaEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namepsace}:title`);
|
||||
expect(LostAtSeaEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namepsace}:description`);
|
||||
expect(LostAtSeaEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namepsace}:query`);
|
||||
expect(LostAtSeaEncounter.options.length).toBe(3);
|
||||
});
|
||||
|
||||
it("should not spawn outside of sea biome", async () => {
|
||||
game.override.startingBiome(Biome.MOUNTAIN);
|
||||
await workaround_reInitSceneWithOverrides(game);
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(game.scene.currentBattle.mysteryEncounter.encounterType).not.toBe(MysteryEncounterType.LOST_AT_SEA);
|
||||
});
|
||||
|
||||
it("should not run below wave 11", async () => {
|
||||
game.override.startingWave(10);
|
||||
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(game.scene.currentBattle.mysteryEncounter).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should not run above wave 179", async () => {
|
||||
game.override.startingWave(180);
|
||||
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(game.scene.currentBattle.mysteryEncounter).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should set the correct dialog tokens during initialization", () => {
|
||||
vi.spyOn(game.scene, "currentBattle", "get").mockReturnValue({ mysteryEncounter: LostAtSeaEncounter } as Battle);
|
||||
|
||||
const { onInit } = LostAtSeaEncounter;
|
||||
|
||||
expect(LostAtSeaEncounter.onInit).toBeDefined();
|
||||
|
||||
const onInitResult = onInit(game.scene);
|
||||
|
||||
expect(LostAtSeaEncounter.dialogueTokens?.damagePercentage).toBe("25");
|
||||
expect(LostAtSeaEncounter.dialogueTokens?.option1RequiredMove).toBe(Moves[Moves.SURF]);
|
||||
expect(LostAtSeaEncounter.dialogueTokens?.option2RequiredMove).toBe(Moves[Moves.FLY]);
|
||||
expect(onInitResult).toBe(true);
|
||||
});
|
||||
|
||||
describe("Option 1 - Surf", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option1 = LostAtSeaEncounter.options[0];
|
||||
expect(option1.optionMode).toBe(EncounterOptionMode.DISABLED_OR_DEFAULT);
|
||||
expect(option1.dialogue).toBeDefined();
|
||||
expect(option1.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namepsace}:option:1:label`,
|
||||
disabledButtonLabel: `${namepsace}:option:1:label_disabled`,
|
||||
buttonTooltip: `${namepsace}:option:1:tooltip`,
|
||||
disabledButtonTooltip: `${namepsace}:option:1:tooltip_disabled`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namepsace}:option:1:selected`,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should award exp to surfable PKM (Blastoise)", async () => {
|
||||
const laprasSpecies = getPokemonSpecies(Species.LAPRAS);
|
||||
|
||||
await workaround_reInitSceneWithOverrides(game);
|
||||
await game.runToMysteryEncounter(defaultParty);
|
||||
const party = game.scene.getParty();
|
||||
const blastoise = party.find((pkm) => pkm.species.speciesId === Species.PIDGEOT);
|
||||
const expBefore = blastoise.exp;
|
||||
|
||||
await runSelectMysteryEncounterOption(game, 2);
|
||||
|
||||
expect(blastoise.exp).toBe(expBefore + laprasSpecies.baseExp * defaultWave);
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
game.override.startingWave(33);
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await workaround_reInitSceneWithOverrides(game);
|
||||
await game.runToMysteryEncounter(defaultParty);
|
||||
await runSelectMysteryEncounterOption(game, 1);
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
|
||||
it("should be disabled if no surfable PKM is in party", async () => {
|
||||
// TODO
|
||||
});
|
||||
});
|
||||
|
||||
describe("Option 2 - Fly", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option2 = LostAtSeaEncounter.options[1];
|
||||
|
||||
expect(option2.optionMode).toBe(EncounterOptionMode.DISABLED_OR_DEFAULT);
|
||||
expect(option2.dialogue).toBeDefined();
|
||||
expect(option2.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namepsace}:option:2:label`,
|
||||
disabledButtonLabel: `${namepsace}:option:2:label_disabled`,
|
||||
buttonTooltip: `${namepsace}:option:2:tooltip`,
|
||||
disabledButtonTooltip: `${namepsace}:option:2:tooltip_disabled`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namepsace}:option:2:selected`,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should award exp to flyable PKM (Pidgeot)", async () => {
|
||||
const laprasBaseExp = 187;
|
||||
const wave = 33;
|
||||
game.override.startingWave(wave);
|
||||
|
||||
await workaround_reInitSceneWithOverrides(game);
|
||||
await game.runToMysteryEncounter(defaultParty);
|
||||
const party = game.scene.getParty();
|
||||
const pidgeot = party.find((pkm) => pkm.species.speciesId === Species.PIDGEOT);
|
||||
const expBefore = pidgeot.exp;
|
||||
|
||||
await runSelectMysteryEncounterOption(game, 2);
|
||||
|
||||
expect(pidgeot.exp).toBe(expBefore + laprasBaseExp * wave);
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
game.override.startingWave(33);
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await workaround_reInitSceneWithOverrides(game);
|
||||
await game.runToMysteryEncounter(defaultParty);
|
||||
await runSelectMysteryEncounterOption(game, 2);
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
|
||||
it("should be disabled if no flyable PKM is in party", async () => {
|
||||
// TODO
|
||||
});
|
||||
});
|
||||
|
||||
describe("Option 3 - Wander aimlessy", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option3 = LostAtSeaEncounter.options[2];
|
||||
|
||||
expect(option3.optionMode).toBe(EncounterOptionMode.DEFAULT);
|
||||
expect(option3.dialogue).toBeDefined();
|
||||
expect(option3.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namepsace}:option:3:label`,
|
||||
buttonTooltip: `${namepsace}:option:3:tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namepsace}:option:3:selected`,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should damage all (allowed in battle) party PKM by 25%", async () => {
|
||||
game.override.startingWave(33);
|
||||
|
||||
await workaround_reInitSceneWithOverrides(game);
|
||||
await game.runToMysteryEncounter(defaultParty);
|
||||
|
||||
const party = game.scene.getParty();
|
||||
const abra = party.find((pkm) => pkm.species.speciesId === Species.ABRA);
|
||||
vi.spyOn(abra, "isAllowedInBattle").mockReturnValue(false);
|
||||
|
||||
await runSelectMysteryEncounterOption(game, 3);
|
||||
|
||||
const allowedPkm = party.filter((pkm) => pkm.isAllowedInBattle());
|
||||
const notAllowedPkm = party.filter((pkm) => !pkm.isAllowedInBattle());
|
||||
allowedPkm.forEach((pkm) =>
|
||||
expect(pkm.hp, `${pkm.name} should have receivd 25% damage: ${pkm.hp} / ${pkm.getMaxHp()} HP`).toBe(pkm.getMaxHp() - Math.floor(pkm.getMaxHp() * 0.25))
|
||||
);
|
||||
|
||||
notAllowedPkm.forEach((pkm) => expect(pkm.hp, `${pkm.name} should be full hp: ${pkm.hp} / ${pkm.getMaxHp()} HP`).toBe(pkm.getMaxHp()));
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
game.override.startingWave(33);
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
workaround_reInitSceneWithOverrides(game);
|
||||
await game.runToMysteryEncounter(defaultParty);
|
||||
await runSelectMysteryEncounterOption(game, 3);
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -35,6 +35,7 @@ import { BattlerIndex } from "#app/battle.js";
|
|||
import TargetSelectUiHandler from "#app/ui/target-select-ui-handler.js";
|
||||
import BattleMessageUiHandler from "#app/ui/battle-message-ui-handler";
|
||||
import {MysteryEncounterPhase} from "#app/phases/mystery-encounter-phase";
|
||||
import { OverridesHelper } from "./overridesHelper";
|
||||
|
||||
/**
|
||||
* Class to manage the game state and transitions between phases.
|
||||
|
@ -45,6 +46,7 @@ export default class GameManager {
|
|||
public phaseInterceptor: PhaseInterceptor;
|
||||
public textInterceptor: TextInterceptor;
|
||||
public inputsHandler: InputsHandler;
|
||||
public readonly override: OverridesHelper;
|
||||
|
||||
/**
|
||||
* Creates an instance of GameManager.
|
||||
|
@ -60,6 +62,7 @@ export default class GameManager {
|
|||
this.phaseInterceptor = new PhaseInterceptor(this.scene);
|
||||
this.textInterceptor = new TextInterceptor(this.scene);
|
||||
this.gameWrapper.setScene(this.scene);
|
||||
this.override = new OverridesHelper();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
import { Weather, WeatherType } from "#app/data/weather";
|
||||
import { Biome } from "#app/enums/biome";
|
||||
import * as Overrides from "#app/overrides";
|
||||
import { vi } from "vitest";
|
||||
|
||||
/**
|
||||
* Helper to handle overrides in tests
|
||||
*/
|
||||
export class OverridesHelper {
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* Override the encounter chance for a mystery encounter.
|
||||
* @param percentage the encounter chance in %
|
||||
*/
|
||||
mysteryEncounterChance(percentage: number) {
|
||||
const maxRate: number = 256; // 100%
|
||||
const rate = maxRate * (percentage / 100);
|
||||
vi.spyOn(Overrides, "MYSTERY_ENCOUNTER_RATE_OVERRIDE", "get").mockReturnValue(rate);
|
||||
this.log(`Mystery encounter chance set to ${percentage}% (=${rate})!`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the starting biome
|
||||
* @warning The biome will not be overridden unless you call `workaround_reInitSceneWithOverrides()` (testUtils)
|
||||
* @param biome the biome to set
|
||||
*/
|
||||
startingBiome(biome: Biome) {
|
||||
vi.spyOn(Overrides, "STARTING_BIOME_OVERRIDE", "get").mockReturnValue(biome);
|
||||
this.log(`Starting biome set to ${Biome[biome]} (=${biome})!`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the starting wave (index)
|
||||
* @param wave the wave (index) to set. Classic: `1`-`200`
|
||||
*/
|
||||
startingWave(wave: number) {
|
||||
vi.spyOn(Overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(wave);
|
||||
this.log(`Starting wave set to ${wave}!`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the weather (type)
|
||||
* @param type weather type to set
|
||||
*/
|
||||
weather(type: WeatherType) {
|
||||
vi.spyOn(Overrides, "WEATHER_OVERRIDE", "get").mockReturnValue(type);
|
||||
this.log(`Weather set to ${Weather[type]} (=${type})!`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the seed
|
||||
* @warning The seed will not be overridden unless you call `workaround_reInitSceneWithOverrides()` (testUtils)
|
||||
* @param seed the seed to set
|
||||
*/
|
||||
seed(seed: string) {
|
||||
vi.spyOn(Overrides, "SEED_OVERRIDE", "get").mockReturnValue(seed);
|
||||
this.log(`Seed set to "${seed}"!`);
|
||||
}
|
||||
|
||||
private log(...params: any[]) {
|
||||
console.log("Overrides:", ...params);
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import {
|
|||
EncounterPhase,
|
||||
EnemyCommandPhase,
|
||||
FaintPhase,
|
||||
LearnMovePhase,
|
||||
LoginPhase,
|
||||
MessagePhase,
|
||||
MoveEffectPhase,
|
||||
|
@ -41,6 +42,7 @@ import {
|
|||
MysteryEncounterBattlePhase,
|
||||
MysteryEncounterOptionSelectedPhase,
|
||||
MysteryEncounterPhase,
|
||||
MysteryEncounterRewardsPhase,
|
||||
PostMysteryEncounterPhase
|
||||
} from "#app/phases/mystery-encounter-phase";
|
||||
|
||||
|
@ -100,11 +102,13 @@ export default class PhaseInterceptor {
|
|||
[MysteryEncounterPhase, this.startPhase],
|
||||
[MysteryEncounterOptionSelectedPhase, this.startPhase],
|
||||
[MysteryEncounterBattlePhase, this.startPhase],
|
||||
[PostMysteryEncounterPhase, this.startPhase]
|
||||
[MysteryEncounterRewardsPhase, this.startPhase],
|
||||
[PostMysteryEncounterPhase, this.startPhase],
|
||||
[LearnMovePhase, this.startPhase]
|
||||
];
|
||||
|
||||
private endBySetMode = [
|
||||
TitlePhase, SelectGenderPhase, CommandPhase, SelectModifierPhase
|
||||
TitlePhase, SelectGenderPhase, CommandPhase, SelectModifierPhase, PostMysteryEncounterPhase
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import i18next, { type ParseKeys } from "i18next";
|
||||
import { vi } from "vitest";
|
||||
import GameManager from "./gameManager";
|
||||
|
||||
/**
|
||||
* Sets up the i18next mock.
|
||||
|
@ -21,3 +22,15 @@ export function mockI18next() {
|
|||
export function arrayOfRange(start: integer, end: integer) {
|
||||
return Array.from({ length: end - start }, (_v, k) => k + start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Woraround to reinitialize the game scene with overrides being set properly.
|
||||
* By default the scene is initialized without all overrides even having a chance to be applied.
|
||||
* @warning USE AT YOUR OWN RISK! Might be deleted in the future
|
||||
* @param game The game manager
|
||||
* @deprecated
|
||||
*/
|
||||
export async function workaround_reInitSceneWithOverrides(game: GameManager) {
|
||||
await game.runToTitle();
|
||||
game.gameWrapper.setScene(game.scene);
|
||||
}
|
||||
|
|
|
@ -135,7 +135,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
|||
// TODO: If we need to handle cancel option? Maybe default logic to leave/run from encounter idk
|
||||
}
|
||||
} else {
|
||||
switch (this.optionsContainer.length) {
|
||||
switch (this.optionsContainer.list.length) {
|
||||
case 3:
|
||||
success = this.handleTwoOptionMoveInput(button);
|
||||
break;
|
||||
|
|
|
@ -234,8 +234,8 @@ export default class UI extends Phaser.GameObjects.Container {
|
|||
(this.scene as BattleScene).uiContainer.add(this.tooltipContainer);
|
||||
}
|
||||
|
||||
getHandler(): UiHandler {
|
||||
return this.handlers[this.mode];
|
||||
getHandler<H extends UiHandler = UiHandler>(): H {
|
||||
return this.handlers[this.mode] as H;
|
||||
}
|
||||
|
||||
getMessageHandler(): BattleMessageUiHandler {
|
||||
|
|
Loading…
Reference in New Issue