commit latest beta merge updates

This commit is contained in:
ImperialSympathizer 2024-08-20 18:01:47 -04:00
parent 8899fce571
commit 0b698a04a2
8 changed files with 85 additions and 126 deletions

View File

@ -111,6 +111,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER)); new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER));
clownConfig.setPartyTemplates(clownPartyTemplate); clownConfig.setPartyTemplates(clownPartyTemplate);
clownConfig.setDoubleOnly(); clownConfig.setDoubleOnly();
// @ts-ignore
clownConfig.partyTemplateFunc = null; // Overrides party template func if it exists clownConfig.partyTemplateFunc = null; // Overrides party template func if it exists
// Generate random ability for Blacephalon from pool // Generate random ability for Blacephalon from pool
@ -415,7 +416,7 @@ function onYesAbilitySwap(scene: BattleScene, resolve) {
const onPokemonSelected = (pokemon: PlayerPokemon) => { const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Do ability swap // Do ability swap
if (!pokemon.mysteryEncounterData) { if (!pokemon.mysteryEncounterData) {
pokemon.mysteryEncounterData = new MysteryEncounterPokemonData(null, Abilities.AERILATE); pokemon.mysteryEncounterData = new MysteryEncounterPokemonData(undefined, Abilities.AERILATE);
} }
pokemon.mysteryEncounterData.ability = scene.currentBattle.mysteryEncounter.misc.ability; pokemon.mysteryEncounterData.ability = scene.currentBattle.mysteryEncounter.misc.ability;
scene.currentBattle.mysteryEncounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender()); scene.currentBattle.mysteryEncounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender());

View File

@ -54,8 +54,8 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
.withOption( .withOption(
// Option 1: Use a (non fainted) pokemon that can learn Surf to guide you back/ // Option 1: Use a (non fainted) pokemon that can learn Surf to guide you back/
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.withPokemonCanLearnMoveRequirement(OPTION_1_REQUIRED_MOVE)
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withPokemonCanLearnMoveRequirement(OPTION_1_REQUIRED_MOVE)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}.option.1.label`,
disabledButtonLabel: `${namespace}.option.1.label_disabled`, disabledButtonLabel: `${namespace}.option.1.label_disabled`,
@ -73,8 +73,8 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
.withOption( .withOption(
//Option 2: Use a (non fainted) pokemon that can learn fly to guide you back. //Option 2: Use a (non fainted) pokemon that can learn fly to guide you back.
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.withPokemonCanLearnMoveRequirement(OPTION_2_REQUIRED_MOVE)
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withPokemonCanLearnMoveRequirement(OPTION_2_REQUIRED_MOVE)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}.option.2.label`,
disabledButtonLabel: `${namespace}.option.2.label_disabled`, disabledButtonLabel: `${namespace}.option.2.label_disabled`,
@ -131,7 +131,7 @@ async function handlePokemonGuidingYouPhase(scene: BattleScene) {
const laprasSpecies = getPokemonSpecies(Species.LAPRAS); const laprasSpecies = getPokemonSpecies(Species.LAPRAS);
const { mysteryEncounter } = scene.currentBattle; const { mysteryEncounter } = scene.currentBattle;
if (mysteryEncounter.selectedOption) { if (mysteryEncounter.selectedOption?.primaryPokemon?.id) {
setEncounterExp(scene, mysteryEncounter.selectedOption.primaryPokemon.id, laprasSpecies.baseExp, true); setEncounterExp(scene, mysteryEncounter.selectedOption.primaryPokemon.id, laprasSpecies.baseExp, true);
} else { } else {
console.warn("Lost at sea: No guide pokemon found but pokemon guides player. huh!?"); console.warn("Lost at sea: No guide pokemon found but pokemon guides player. huh!?");

View File

@ -8,6 +8,7 @@ import { EncounterPokemonRequirement, EncounterSceneRequirement, MoneyRequiremen
import { CanLearnMoveRequirement, CanLearnMoveRequirementOptions } from "./requirements/can-learn-move-requirement"; import { CanLearnMoveRequirement, CanLearnMoveRequirementOptions } from "./requirements/can-learn-move-requirement";
import { isNullOrUndefined } from "#app/utils"; import { isNullOrUndefined } from "#app/utils";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
export type OptionPhaseCallback = (scene: BattleScene) => Promise<void | boolean>; export type OptionPhaseCallback = (scene: BattleScene) => Promise<void | boolean>;
@ -39,7 +40,7 @@ export default class MysteryEncounterOption {
/** Executes after the encounter is over. Usually this will be for calculating dialogueTokens or performing data updates */ /** Executes after the encounter is over. Usually this will be for calculating dialogueTokens or performing data updates */
onPostOptionPhase?: OptionPhaseCallback; onPostOptionPhase?: OptionPhaseCallback;
constructor(option: MysteryEncounterOption) { constructor(option: MysteryEncounterOption | null) {
Object.assign(this, option); Object.assign(this, option);
this.hasDexProgress = !isNullOrUndefined(this.hasDexProgress) ? this.hasDexProgress : false; this.hasDexProgress = !isNullOrUndefined(this.hasDexProgress) ? this.hasDexProgress : false;
this.requirements = this.requirements ? this.requirements : []; this.requirements = this.requirements ? this.requirements : [];
@ -137,41 +138,27 @@ export default class MysteryEncounterOption {
} }
} }
const baseOption = {
optionMode: MysteryEncounterOptionMode.DEFAULT,
hasDexProgress: false,
requirements: [],
primaryPokemonRequirements: [],
secondaryPokemonRequirements: [],
excludePrimaryFromSecondaryRequirements: true,
};
/* Picks non-optional fields from an "object" type alias and returns a union of literal types (keys) */
type PickNonOptionalFieldsKeys<T> = Exclude<{ [K in Keys<T>]: T extends Record<K, T[K]> ? K : never; }[Keys<T>], undefined>;
// type NonFunctionFieldsKeys<T> = { [K in Keys<T>]: T[K] extends Function ? never : K; }[Keys<T>];
// type FunctionPropertyKeys<T> = { [K in Keys<T>]: T[K] extends Function ? K : never; }[Keys<T>];
/* Extracts keys from T */
type Keys<T> = keyof T;
/* Filters out all optional fields from an "object" type alias, and all function fields */
type PickNonOptionalFields<T> = Pick<T, PickNonOptionalFieldsKeys<T>>;
/* Omits keys that exist in the base object and optional keys on MysteryEncounter, as well as functions on MysteryEncounter */
type OmitBaseAndOptionalKeys<T, B> = Omit<PickNonOptionalFields<T>, Keys<B>>;
export class MysteryEncounterOptionBuilder implements Partial<MysteryEncounterOption> { export class MysteryEncounterOptionBuilder implements Partial<MysteryEncounterOption> {
optionMode: MysteryEncounterOptionMode; optionMode: MysteryEncounterOptionMode = MysteryEncounterOptionMode.DEFAULT;
requirements: EncounterSceneRequirement[] = []; requirements: EncounterSceneRequirement[] = [];
primaryPokemonRequirements: EncounterPokemonRequirement[] = []; primaryPokemonRequirements: EncounterPokemonRequirement[] = [];
secondaryPokemonRequirements: EncounterPokemonRequirement[] = []; secondaryPokemonRequirements: EncounterPokemonRequirement[] = [];
excludePrimaryFromSecondaryRequirements: boolean; excludePrimaryFromSecondaryRequirements: boolean = false;
isDisabledOnRequirementsNotMet: boolean; isDisabledOnRequirementsNotMet: boolean = true;
hasDexProgress: boolean; hasDexProgress: boolean = false;
onPreOptionPhase?: OptionPhaseCallback; onPreOptionPhase?: OptionPhaseCallback;
onOptionPhase: OptionPhaseCallback; onOptionPhase: OptionPhaseCallback;
onPostOptionPhase?: OptionPhaseCallback; onPostOptionPhase?: OptionPhaseCallback;
dialogue: OptionTextDisplay; dialogue: OptionTextDisplay;
static newOptionWithMode(optionMode: MysteryEncounterOptionMode): MysteryEncounterOptionBuilder & OmitBaseAndOptionalKeys<MysteryEncounterOption, typeof baseOption> & Pick<MysteryEncounterOption, "optionMode"> { hasRequirements = MysteryEncounter.prototype["hasRequirements"];
return Object.assign(new MysteryEncounterOptionBuilder(), { ...baseOption, optionMode }); meetsRequirements = MysteryEncounter.prototype["meetsRequirements"];
pokemonMeetsPrimaryRequirements = MysteryEncounter.prototype["pokemonMeetsPrimaryRequirements"];
meetsPrimaryRequirementAndPrimaryPokemonSelected = MysteryEncounter.prototype["meetsPrimaryRequirementAndPrimaryPokemonSelected"];
meetsSupportingRequirementAndSupportingPokemonSelected = MysteryEncounter.prototype["meetsSupportingRequirementAndSupportingPokemonSelected"];
static newOptionWithMode(optionMode: MysteryEncounterOptionMode): MysteryEncounterOptionBuilder & Pick<MysteryEncounterOption, "optionMode"> {
return Object.assign(new MysteryEncounterOptionBuilder(), { optionMode });
} }
withHasDexProgress(hasDexProgress: boolean): this & Required<Pick<MysteryEncounterOption, "hasDexProgress">> { withHasDexProgress(hasDexProgress: boolean): this & Required<Pick<MysteryEncounterOption, "hasDexProgress">> {

View File

@ -2,15 +2,15 @@ import { Abilities } from "#enums/abilities";
import { Type } from "#app/data/type"; import { Type } from "#app/data/type";
export class MysteryEncounterPokemonData { export class MysteryEncounterPokemonData {
public spriteScale: number; public spriteScale: number | undefined;
public ability: Abilities; public ability: Abilities | undefined;
public passive: Abilities; public passive: Abilities | undefined;
public types: Type[] = []; public types: Type[];
constructor(spriteScale?: number, ability?: Abilities, passive?: Abilities, types?: Type[]) { constructor(spriteScale?: number, ability?: Abilities, passive?: Abilities, types?: Type[]) {
this.spriteScale = spriteScale; this.spriteScale = spriteScale;
this.ability = ability; this.ability = ability;
this.passive = passive; this.passive = passive;
this.types = types; this.types = types ?? [];
} }
} }

View File

@ -48,7 +48,7 @@ export class CombinationSceneRequirement extends EncounterSceneRequirement {
} }
} }
return null; return this.orRequirements[0].getDialogueToken(scene, pokemon);
} }
} }
@ -104,7 +104,7 @@ export class CombinationPokemonRequirement extends EncounterPokemonRequirement {
} }
} }
return null; return this.orRequirements[0].getDialogueToken(scene, pokemon);
} }
} }
@ -125,7 +125,7 @@ export class PreviousEncounterRequirement extends EncounterSceneRequirement {
} }
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
return ["previousEncounter", scene.mysteryEncounterData.encounteredEvents.find(e => e[0] === this.previousEncounterRequirement)[0].toString()]; return ["previousEncounter", scene.mysteryEncounterData.encounteredEvents.find(e => e[0] === this.previousEncounterRequirement)?.[0].toString() ?? ""];
} }
} }
@ -158,7 +158,7 @@ export class WaveRangeRequirement extends EncounterSceneRequirement {
} }
export class TimeOfDayRequirement extends EncounterSceneRequirement { export class TimeOfDayRequirement extends EncounterSceneRequirement {
requiredTimeOfDay?: TimeOfDay[]; requiredTimeOfDay: TimeOfDay[];
constructor(timeOfDay: TimeOfDay | TimeOfDay[]) { constructor(timeOfDay: TimeOfDay | TimeOfDay[]) {
super(); super();
@ -180,7 +180,7 @@ export class TimeOfDayRequirement extends EncounterSceneRequirement {
} }
export class WeatherRequirement extends EncounterSceneRequirement { export class WeatherRequirement extends EncounterSceneRequirement {
requiredWeather?: WeatherType[]; requiredWeather: WeatherType[];
constructor(weather: WeatherType | WeatherType[]) { constructor(weather: WeatherType | WeatherType[]) {
super(); super();
@ -188,8 +188,8 @@ export class WeatherRequirement extends EncounterSceneRequirement {
} }
meetsRequirement(scene: BattleScene): boolean { meetsRequirement(scene: BattleScene): boolean {
const currentWeather = scene.arena?.weather?.weatherType; const currentWeather = scene.arena.weather?.weatherType;
if (!isNullOrUndefined(currentWeather) && this?.requiredWeather?.length > 0 && !this.requiredWeather.includes(currentWeather)) { if (!isNullOrUndefined(currentWeather) && this?.requiredWeather?.length > 0 && !this.requiredWeather.includes(currentWeather!)) {
return false; return false;
} }
@ -197,7 +197,12 @@ export class WeatherRequirement extends EncounterSceneRequirement {
} }
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
return ["weather", WeatherType[scene.arena?.weather?.weatherType].replace("_", " ").toLocaleLowerCase()]; const currentWeather = scene.arena.weather?.weatherType;
let token = "";
if (!isNullOrUndefined(currentWeather)) {
token = WeatherType[currentWeather!].replace("_", " ").toLocaleLowerCase();
}
return ["weather", token];
} }
} }
@ -262,11 +267,8 @@ export class PersistentModifierRequirement extends EncounterSceneRequirement {
} }
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
if (this.requiredHeldItemModifiers.length > 0) {
return ["requiredItem", this.requiredHeldItemModifiers[0]]; return ["requiredItem", this.requiredHeldItemModifiers[0]];
} }
return null;
}
} }
export class MoneyRequirement extends EncounterSceneRequirement { export class MoneyRequirement extends EncounterSceneRequirement {
@ -327,10 +329,10 @@ export class SpeciesRequirement extends EncounterPokemonRequirement {
} }
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
if (this.requiredSpecies.includes(pokemon.species.speciesId)) { if (pokemon?.species.speciesId && this.requiredSpecies.includes(pokemon.species.speciesId)) {
return ["species", Species[pokemon.species.speciesId]]; return ["species", Species[pokemon.species.speciesId]];
} }
return null; return ["species", ""];
} }
} }
@ -410,11 +412,11 @@ export class TypeRequirement extends EncounterPokemonRequirement {
} }
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
const includedTypes = this.requiredType.filter((ty) => pokemon.getTypes().includes(ty)); const includedTypes = this.requiredType.filter((ty) => pokemon?.getTypes().includes(ty));
if (includedTypes.length > 0) { if (includedTypes.length > 0) {
return ["type", Type[includedTypes[0]]]; return ["type", Type[includedTypes[0]]];
} }
return null; return ["type", ""];
} }
} }
@ -441,19 +443,19 @@ export class MoveRequirement extends EncounterPokemonRequirement {
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) { if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredMoves.filter((reqMove) => pokemon.moveset.filter((move) => move.moveId === reqMove).length > 0).length > 0); return partyPokemon.filter((pokemon) => this.requiredMoves.filter((reqMove) => pokemon.moveset.filter((move) => move?.moveId === reqMove).length > 0).length > 0);
} else { } else {
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed moves // for an inverted query, we only want to get the pokemon that don't have ANY of the listed moves
return partyPokemon.filter((pokemon) => this.requiredMoves.filter((reqMove) => pokemon.moveset.filter((move) => move.moveId === reqMove).length === 0).length === 0); return partyPokemon.filter((pokemon) => this.requiredMoves.filter((reqMove) => pokemon.moveset.filter((move) => move?.moveId === reqMove).length === 0).length === 0);
} }
} }
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
const includedMoves = pokemon.moveset.filter((move) => this.requiredMoves.includes(move.moveId)); const includedMoves = pokemon?.moveset.filter((move) => move?.moveId && this.requiredMoves.includes(move.moveId));
if (includedMoves.length > 0) { if (includedMoves && includedMoves.length > 0 && includedMoves[0]) {
return ["move", includedMoves[0].getName()]; return ["move", includedMoves[0].getName()];
} }
return null; return ["move", ""];
} }
} }
@ -485,19 +487,19 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement {
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) { if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredMoves.filter((learnableMove) => pokemon.compatibleTms.filter(tm => !pokemon.moveset.find(m => m.moveId === tm)).includes(learnableMove)).length > 0); return partyPokemon.filter((pokemon) => this.requiredMoves.filter((learnableMove) => pokemon.compatibleTms.filter(tm => !pokemon.moveset.find(m => m?.moveId === tm)).includes(learnableMove)).length > 0);
} else { } else {
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed learnableMoves // for an inverted query, we only want to get the pokemon that don't have ANY of the listed learnableMoves
return partyPokemon.filter((pokemon) => this.requiredMoves.filter((learnableMove) => pokemon.compatibleTms.filter(tm => !pokemon.moveset.find(m => m.moveId === tm)).includes(learnableMove)).length === 0); return partyPokemon.filter((pokemon) => this.requiredMoves.filter((learnableMove) => pokemon.compatibleTms.filter(tm => !pokemon.moveset.find(m => m?.moveId === tm)).includes(learnableMove)).length === 0);
} }
} }
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
const includedCompatMoves = this.requiredMoves.filter((reqMove) => pokemon.compatibleTms.filter((tm) => !pokemon.moveset.find(m => m.moveId === tm)).includes(reqMove)); const includedCompatMoves = this.requiredMoves.filter((reqMove) => pokemon?.compatibleTms.filter((tm) => !pokemon.moveset.find(m => m?.moveId === tm)).includes(reqMove));
if (includedCompatMoves.length > 0) { if (includedCompatMoves.length > 0) {
return ["compatibleMove", Moves[includedCompatMoves[0]]]; return ["compatibleMove", Moves[includedCompatMoves[0]]];
} }
return null; return ["compatibleMove", ""];
} }
} }
@ -572,10 +574,10 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
} }
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
if (this.requiredAbilities.some(a => pokemon.getAbility().id === a)) { if (pokemon?.getAbility().id && this.requiredAbilities.some(a => pokemon.getAbility().id === a)) {
return ["ability", pokemon.getAbility().name]; return ["ability", pokemon.getAbility().name];
} }
return null; return ["ability", ""];
} }
} }
@ -607,7 +609,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
return this.requiredStatusEffect.some((statusEffect) => { return this.requiredStatusEffect.some((statusEffect) => {
if (statusEffect === StatusEffect.NONE) { if (statusEffect === StatusEffect.NONE) {
// StatusEffect.NONE also checks for null or undefined status // StatusEffect.NONE also checks for null or undefined status
return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status.effect) || pokemon.status?.effect === statusEffect; return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status!.effect) || pokemon.status?.effect === statusEffect;
} else { } else {
return pokemon.status?.effect === statusEffect; return pokemon.status?.effect === statusEffect;
} }
@ -639,7 +641,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
if (reqStatus.length > 0) { if (reqStatus.length > 0) {
return ["status", StatusEffect[reqStatus[0]]]; return ["status", StatusEffect[reqStatus[0]]];
} }
return null; return ["status", ""];
} }
} }

View File

@ -1,6 +1,6 @@
import { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import { isNullOrUndefined } from "#app/utils"; import { capitalizeFirstLetter, isNullOrUndefined } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene"; import BattleScene from "#app/battle-scene";
import MysteryEncounterIntroVisuals, { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro"; import MysteryEncounterIntroVisuals, { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro";
@ -125,17 +125,17 @@ export default class MysteryEncounter implements IMysteryEncounter {
/** /**
* Dialogue object containing all the dialogue, messages, tooltips, etc. for an encounter * Dialogue object containing all the dialogue, messages, tooltips, etc. for an encounter
*/ */
dialogue?: MysteryEncounterDialogue; dialogue: MysteryEncounterDialogue;
/** /**
* Data used for setting up/initializing enemy party in battles * Data used for setting up/initializing enemy party in battles
* Can store multiple configs so that one can be chosen based on option selected * Can store multiple configs so that one can be chosen based on option selected
* Should usually be defined in `onInit()` or `onPreOptionPhase()` * Should usually be defined in `onInit()` or `onPreOptionPhase()`
*/ */
enemyPartyConfigs?: EnemyPartyConfig[]; enemyPartyConfigs: EnemyPartyConfig[];
/** /**
* Object instance containing sprite data for an encounter when it is being spawned * Object instance containing sprite data for an encounter when it is being spawned
* Otherwise, will be undefined * Otherwise, will be undefined
* You probably shouldn't do anything with this unless you have a very specific need * You probably shouldn't do anything directly with this unless you have a very specific need
*/ */
introVisuals?: MysteryEncounterIntroVisuals; introVisuals?: MysteryEncounterIntroVisuals;
@ -233,7 +233,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
return !this.primaryPokemonRequirements.some(req => !req.queryParty(scene.getParty()).map(p => p.id).includes(pokemon.id)); return !this.primaryPokemonRequirements.some(req => !req.queryParty(scene.getParty()).map(p => p.id).includes(pokemon.id));
} }
private meetsPrimaryRequirementAndPrimaryPokemonSelected(scene: BattleScene): boolean { meetsPrimaryRequirementAndPrimaryPokemonSelected(scene: BattleScene): boolean {
if (this.primaryPokemonRequirements.length === 0) { if (this.primaryPokemonRequirements.length === 0) {
const activeMon = scene.getParty().filter(p => p.isActive(true)); const activeMon = scene.getParty().filter(p => p.isActive(true));
if (activeMon.length > 0) { if (activeMon.length > 0) {
@ -290,7 +290,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
} }
} }
private meetsSecondaryRequirementAndSecondaryPokemonSelected(scene: BattleScene): boolean { meetsSecondaryRequirementAndSecondaryPokemonSelected(scene: BattleScene): boolean {
if (!this.secondaryPokemonRequirements) { if (!this.secondaryPokemonRequirements) {
this.secondaryPokemon = []; this.secondaryPokemon = [];
return true; return true;
@ -338,7 +338,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
if (!req.invertQuery) { if (!req.invertQuery) {
const value = req.getDialogueToken(scene, this.primaryPokemon); const value = req.getDialogueToken(scene, this.primaryPokemon);
if (value?.length === 2) { if (value?.length === 2) {
this.setDialogueToken("primary" + this.capitalizeFirstLetter(value[0]), value[1]); this.setDialogueToken("primary" + capitalizeFirstLetter(value[0]), value[1]);
} }
} }
} }
@ -349,9 +349,9 @@ export default class MysteryEncounter implements IMysteryEncounter {
if (!req.invertQuery) { if (!req.invertQuery) {
const value = req.getDialogueToken(scene, this.secondaryPokemon[0]); const value = req.getDialogueToken(scene, this.secondaryPokemon[0]);
if (value?.length === 2) { if (value?.length === 2) {
this.setDialogueToken("primary" + this.capitalizeFirstLetter(value[0]), value[1]); this.setDialogueToken("primary" + capitalizeFirstLetter(value[0]), value[1]);
} }
this.setDialogueToken("secondary" + this.capitalizeFirstLetter(value[0]), value[1]); this.setDialogueToken("secondary" + capitalizeFirstLetter(value[0]), value[1]);
} }
} }
} }
@ -365,7 +365,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
for (const req of opt.requirements) { for (const req of opt.requirements) {
const dialogueToken = req.getDialogueToken(scene); const dialogueToken = req.getDialogueToken(scene);
if (dialogueToken?.length === 2) { if (dialogueToken?.length === 2) {
this.setDialogueToken("option" + j + this.capitalizeFirstLetter(dialogueToken[0]), dialogueToken[1]); this.setDialogueToken("option" + j + capitalizeFirstLetter(dialogueToken[0]), dialogueToken[1]);
} }
} }
} }
@ -375,7 +375,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
if (!req.invertQuery) { if (!req.invertQuery) {
const value = req.getDialogueToken(scene, opt.primaryPokemon); const value = req.getDialogueToken(scene, opt.primaryPokemon);
if (value?.length === 2) { if (value?.length === 2) {
this.setDialogueToken("option" + j + "Primary" + this.capitalizeFirstLetter(value[0]), value[1]); this.setDialogueToken("option" + j + "Primary" + capitalizeFirstLetter(value[0]), value[1]);
} }
} }
} }
@ -386,7 +386,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
if (!req.invertQuery) { if (!req.invertQuery) {
const value = req.getDialogueToken(scene, opt.secondaryPokemon[0]); const value = req.getDialogueToken(scene, opt.secondaryPokemon[0]);
if (value?.length === 2) { if (value?.length === 2) {
this.setDialogueToken("option" + j + "Secondary" + this.capitalizeFirstLetter(value[0]), value[1]); this.setDialogueToken("option" + j + "Secondary" + capitalizeFirstLetter(value[0]), value[1]);
} }
} }
} }
@ -418,42 +418,8 @@ export default class MysteryEncounter implements IMysteryEncounter {
const currentOffset = this.seedOffset ?? scene.currentBattle.waveIndex * 1000; const currentOffset = this.seedOffset ?? scene.currentBattle.waveIndex * 1000;
this.seedOffset = currentOffset + 512; this.seedOffset = currentOffset + 512;
} }
private capitalizeFirstLetter(str: string) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
} }
const baseMysteryEncounter = {
hideBattleIntroMessage: false,
autoHideIntroVisuals: true,
enterIntroVisualsFromRight: false,
catchAllowed: false,
continuousEncounter: false,
maxAllowedEncounters: 3,
requirements: [],
primaryPokemonRequirements: [],
secondaryPokemonRequirements: [],
excludePrimaryFromSupportRequirements: true,
dialogueTokens: new Map<string, string>(),
encounterMode: MysteryEncounterMode.DEFAULT,
lockEncounterRewardTiers: false,
startOfBattleEffectsComplete: false,
expMultiplier: 1,
};
/* Picks non-optional fields from an "object" type alias and returns a union of literal types (keys) */
type PickNonOptionalFieldsKeys<T> = Exclude<{ [K in Keys<T>]: T extends Record<K, T[K]> ? K : never; }[Keys<T>], undefined>;
// type NonFunctionFieldsKeys<T> = { [K in Keys<T>]: T[K] extends Function ? never : K; }[Keys<T>];
// type FunctionPropertyKeys<T> = { [K in Keys<T>]: T[K] extends Function ? K : never; }[Keys<T>];
/* Extracts keys from T */
type Keys<T> = keyof T;
/* Filters out all optional fields from an "object" type alias, and all function fields */
type PickNonOptionalFields<T> = Pick<T, PickNonOptionalFieldsKeys<T>>;
/* Omits keys that exist in the base object and optional keys on MysteryEncounter, as well as functions on MysteryEncounter */
type OmitBaseAndOptionalKeys<T, B> = Omit<PickNonOptionalFields<T>, Keys<B>>;
// type OmitBaseSuppliedAndOptionalKeys<T, B, S> = Omit<OmitBaseAndOptionalKeys<T, B>, Keys<S>>;
/** /**
* Builder class for creating a MysteryEncounter * Builder class for creating a MysteryEncounter
* must call `build()` at the end after specifying all params for the MysteryEncounter * must call `build()` at the end after specifying all params for the MysteryEncounter
@ -461,8 +427,9 @@ type OmitBaseAndOptionalKeys<T, B> = Omit<PickNonOptionalFields<T>, Keys<B>>;
export class MysteryEncounterBuilder implements Partial<MysteryEncounter> { export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
encounterType: MysteryEncounterType; encounterType: MysteryEncounterType;
encounterMode: MysteryEncounterMode; encounterMode: MysteryEncounterMode;
options: [MysteryEncounterOption, MysteryEncounterOption, ...MysteryEncounterOption[]]; options: [MysteryEncounterOption, MysteryEncounterOption, ...MysteryEncounterOption[]] = [new MysteryEncounterOption(null), new MysteryEncounterOption(null)];
spriteConfigs: MysteryEncounterSpriteConfig[]; spriteConfigs: MysteryEncounterSpriteConfig[];
enemyPartyConfigs: EnemyPartyConfig[] = [];
dialogue: MysteryEncounterDialogue = {}; dialogue: MysteryEncounterDialogue = {};
encounterTier: MysteryEncounterTier; encounterTier: MysteryEncounterTier;
@ -478,15 +445,15 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
onInit?: (scene: BattleScene) => boolean; onInit?: (scene: BattleScene) => boolean;
onVisualsStart?: (scene: BattleScene) => boolean; onVisualsStart?: (scene: BattleScene) => boolean;
hideBattleIntroMessage: boolean; hideBattleIntroMessage: boolean = false;
hideIntroVisuals: boolean; autoHideIntroVisuals: boolean = true;
enterIntroVisualsFromRight: boolean; enterIntroVisualsFromRight: boolean = false;
continuousEncounter: boolean; continuousEncounter: boolean = false;
catchAllowed: boolean; catchAllowed: boolean = false;
lockEncounterRewardTiers: boolean; lockEncounterRewardTiers: boolean = false;
startOfBattleEffectsComplete: boolean; startOfBattleEffectsComplete: boolean = false;
maxAllowedEncounters: number; maxAllowedEncounters: number = 3;
expMultiplier: number; expMultiplier: number = 1;
/** /**
* Builder class has to re-declare the {@link MysteryEncounter} class functions so * Builder class has to re-declare the {@link MysteryEncounter} class functions so
@ -502,7 +469,6 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
updateSeedOffset = MysteryEncounter.prototype["updateSeedOffset"]; updateSeedOffset = MysteryEncounter.prototype["updateSeedOffset"];
meetsPrimaryRequirementAndPrimaryPokemonSelected = MysteryEncounter.prototype["meetsPrimaryRequirementAndPrimaryPokemonSelected"]; meetsPrimaryRequirementAndPrimaryPokemonSelected = MysteryEncounter.prototype["meetsPrimaryRequirementAndPrimaryPokemonSelected"];
meetsSecondaryRequirementAndSecondaryPokemonSelected = MysteryEncounter.prototype["meetsSecondaryRequirementAndSecondaryPokemonSelected"]; meetsSecondaryRequirementAndSecondaryPokemonSelected = MysteryEncounter.prototype["meetsSecondaryRequirementAndSecondaryPokemonSelected"];
capitalizeFirstLetter = MysteryEncounter.prototype["capitalizeFirstLetter"];
/** /**
* REQUIRED * REQUIRED
@ -514,15 +480,14 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
* @param encounterType * @param encounterType
* @returns this * @returns this
*/ */
static withEncounterType(encounterType: MysteryEncounterType): MysteryEncounterBuilder & OmitBaseAndOptionalKeys<MysteryEncounter, typeof baseMysteryEncounter> & Pick<MysteryEncounter, "encounterType"> { static withEncounterType(encounterType: MysteryEncounterType): MysteryEncounterBuilder & Pick<MysteryEncounter, "encounterType"> {
return Object.assign(new MysteryEncounterBuilder(), { ...baseMysteryEncounter, encounterType }); return Object.assign(new MysteryEncounterBuilder(), { encounterType });
} }
/** /**
* Defines an option for the encounter. * Defines an option for the encounter.
* Use for complex options. * Use for complex options.
* There should be at least 2 options defined and no more than 4. * 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 * @param option - MysteryEncounterOption to add, can use MysteryEncounterOptionBuilder to create instance
* @returns * @returns

View File

@ -552,7 +552,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
duration: 750, duration: 750,
onComplete: () => { onComplete: () => {
this.dexProgressContainer.on("pointerover", () => { this.dexProgressContainer.on("pointerover", () => {
(this.scene as BattleScene).ui.showTooltip(null, i18next.t("mysteryEncounter:affects_pokedex"), true); (this.scene as BattleScene).ui.showTooltip("", i18next.t("mysteryEncounter:affects_pokedex"), true);
}); });
this.dexProgressContainer.on("pointerout", () => { this.dexProgressContainer.on("pointerout", () => {
(this.scene as BattleScene).ui.hideTooltip(); (this.scene as BattleScene).ui.hideTooltip();

View File

@ -560,3 +560,7 @@ export function capitalizeString(str: string, sep: string, lowerFirstChar: boole
export function isNullOrUndefined(object: any): boolean { export function isNullOrUndefined(object: any): boolean {
return null === object || undefined === object; return null === object || undefined === object;
} }
export function capitalizeFirstLetter(str: string) {
return str.charAt(0).toUpperCase() + str.slice(1);
}