mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-19 22:09:27 +02:00
Compare commits
17 Commits
5404d67e71
...
bcfc6038c3
Author | SHA1 | Date | |
---|---|---|---|
|
bcfc6038c3 | ||
|
46c78a0540 | ||
|
98809c28bd | ||
|
e0559e03ff | ||
|
f6b99780fb | ||
|
19af9bdb8b | ||
|
8e61b642a3 | ||
|
8383df1855 | ||
|
4de3226d99 | ||
|
57b8b466df | ||
|
3a491a6201 | ||
|
0a93c51c41 | ||
|
73993f25c9 | ||
|
a821fc2f80 | ||
|
dc849bcd08 | ||
|
2b72077af4 | ||
|
349c1d052f |
@ -1 +1 @@
|
|||||||
Subproject commit ab2716d5440c25f73986664aa3f3131821c3c392
|
Subproject commit 1ea8f865e30d1940caa0fceeabf37ae2e4689471
|
17
src/@types/typed-event-target.ts
Normal file
17
src/@types/typed-event-target.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Interface restricting the events emitted by an {@linkcode EventTarget} to a certain kind of {@linkcode Event}.
|
||||||
|
* @typeParam T - The type to restrict the interface's access; must extend from {@linkcode Event}
|
||||||
|
*/
|
||||||
|
export interface TypedEventTarget<T extends Event = never> extends EventTarget {
|
||||||
|
dispatchEvent(event: T): boolean;
|
||||||
|
addEventListener(
|
||||||
|
event: T["type"],
|
||||||
|
callback: EventListenerOrEventListenerObject | null,
|
||||||
|
options?: AddEventListenerOptions | boolean,
|
||||||
|
): void;
|
||||||
|
removeEventListener(
|
||||||
|
type: T["type"],
|
||||||
|
callback: EventListenerOrEventListenerObject | null,
|
||||||
|
options?: EventListenerOptions | boolean,
|
||||||
|
): void;
|
||||||
|
}
|
@ -19,6 +19,7 @@ import { MoveTarget } from "#enums/move-target";
|
|||||||
import { PokemonType } from "#enums/pokemon-type";
|
import { PokemonType } from "#enums/pokemon-type";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { StatusEffect } from "#enums/status-effect";
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
|
import { ArenaTagAddedEvent } from "#events/arena";
|
||||||
import type { Arena } from "#field/arena";
|
import type { Arena } from "#field/arena";
|
||||||
import type { Pokemon } from "#field/pokemon";
|
import type { Pokemon } from "#field/pokemon";
|
||||||
import type {
|
import type {
|
||||||
@ -729,7 +730,9 @@ export class IonDelugeTag extends ArenaTag {
|
|||||||
*/
|
*/
|
||||||
export abstract class ArenaTrapTag extends SerializableArenaTag {
|
export abstract class ArenaTrapTag extends SerializableArenaTag {
|
||||||
abstract readonly tagType: ArenaTrapTagType;
|
abstract readonly tagType: ArenaTrapTagType;
|
||||||
|
/** The tag's current number of layers. */
|
||||||
public layers: number;
|
public layers: number;
|
||||||
|
/** The maximum number of layers this tag can have. */
|
||||||
public maxLayers: number;
|
public maxLayers: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -749,11 +752,13 @@ export abstract class ArenaTrapTag extends SerializableArenaTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onOverlap(arena: Arena, _source: Pokemon | null): void {
|
onOverlap(arena: Arena, _source: Pokemon | null): void {
|
||||||
if (this.layers < this.maxLayers) {
|
if (this.layers === this.maxLayers) {
|
||||||
this.layers++;
|
return;
|
||||||
|
|
||||||
this.onAdd(arena);
|
|
||||||
}
|
}
|
||||||
|
// Add an extra layer of the current hazard, then
|
||||||
|
this.layers++;
|
||||||
|
this.onAdd(arena);
|
||||||
|
arena.eventTarget.dispatchEvent(new ArenaTagAddedEvent(this.tagType, this.side, 0, [this.layers, this.maxLayers]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -771,9 +776,13 @@ export abstract class ArenaTrapTag extends SerializableArenaTag {
|
|||||||
return this.activateTrap(pokemon, simulated);
|
return this.activateTrap(pokemon, simulated);
|
||||||
}
|
}
|
||||||
|
|
||||||
activateTrap(_pokemon: Pokemon, _simulated: boolean): boolean {
|
/**
|
||||||
return false;
|
* Trigger this trap's effects on any Pokemon switching into battle.
|
||||||
}
|
* @param _pokemon - The {@linkcode Pokemon} entering the field
|
||||||
|
* @param _simulated - Whether the switch is simulated
|
||||||
|
* @returns `true` if the effect succeeded
|
||||||
|
*/
|
||||||
|
abstract activateTrap(_pokemon: Pokemon, _simulated: boolean): boolean;
|
||||||
|
|
||||||
getMatchupScoreMultiplier(pokemon: Pokemon): number {
|
getMatchupScoreMultiplier(pokemon: Pokemon): number {
|
||||||
return pokemon.isGrounded()
|
return pokemon.isGrounded()
|
||||||
|
@ -45736,6 +45736,285 @@ export const tmSpecies: TmSpecies = {
|
|||||||
SpeciesId.HISUI_ARCANINE,
|
SpeciesId.HISUI_ARCANINE,
|
||||||
SpeciesId.HISUI_AVALUGG,
|
SpeciesId.HISUI_AVALUGG,
|
||||||
],
|
],
|
||||||
|
[MoveId.SHOCK_WAVE]: [
|
||||||
|
SpeciesId.RATTATA,
|
||||||
|
SpeciesId.RATICATE,
|
||||||
|
SpeciesId.PIKACHU,
|
||||||
|
SpeciesId.RAICHU,
|
||||||
|
SpeciesId.NIDORAN_F,
|
||||||
|
SpeciesId.NIDORINA,
|
||||||
|
SpeciesId.NIDOQUEEN,
|
||||||
|
SpeciesId.NIDORAN_M,
|
||||||
|
SpeciesId.NIDORINO,
|
||||||
|
SpeciesId.NIDOKING,
|
||||||
|
SpeciesId.CLEFAIRY,
|
||||||
|
SpeciesId.CLEFABLE,
|
||||||
|
SpeciesId.JIGGLYPUFF,
|
||||||
|
SpeciesId.WIGGLYTUFF,
|
||||||
|
SpeciesId.MEOWTH,
|
||||||
|
SpeciesId.PERSIAN,
|
||||||
|
SpeciesId.ABRA,
|
||||||
|
SpeciesId.KADABRA,
|
||||||
|
SpeciesId.ALAKAZAM,
|
||||||
|
SpeciesId.MAGNEMITE,
|
||||||
|
SpeciesId.MAGNETON,
|
||||||
|
SpeciesId.GRIMER,
|
||||||
|
SpeciesId.MUK,
|
||||||
|
SpeciesId.VOLTORB,
|
||||||
|
SpeciesId.ELECTRODE,
|
||||||
|
SpeciesId.LICKITUNG,
|
||||||
|
SpeciesId.KOFFING,
|
||||||
|
SpeciesId.WEEZING,
|
||||||
|
SpeciesId.RHYHORN,
|
||||||
|
SpeciesId.RHYDON,
|
||||||
|
SpeciesId.CHANSEY,
|
||||||
|
SpeciesId.TANGELA,
|
||||||
|
SpeciesId.KANGASKHAN,
|
||||||
|
SpeciesId.MR_MIME,
|
||||||
|
SpeciesId.ELECTABUZZ,
|
||||||
|
SpeciesId.TAUROS,
|
||||||
|
SpeciesId.LAPRAS,
|
||||||
|
SpeciesId.JOLTEON,
|
||||||
|
SpeciesId.PORYGON,
|
||||||
|
SpeciesId.SNORLAX,
|
||||||
|
SpeciesId.ZAPDOS,
|
||||||
|
SpeciesId.DRATINI,
|
||||||
|
SpeciesId.DRAGONAIR,
|
||||||
|
SpeciesId.DRAGONITE,
|
||||||
|
SpeciesId.MEWTWO,
|
||||||
|
SpeciesId.MEW,
|
||||||
|
SpeciesId.SENTRET,
|
||||||
|
SpeciesId.FURRET,
|
||||||
|
SpeciesId.CHINCHOU,
|
||||||
|
SpeciesId.LANTURN,
|
||||||
|
SpeciesId.PICHU,
|
||||||
|
SpeciesId.CLEFFA,
|
||||||
|
SpeciesId.IGGLYBUFF,
|
||||||
|
SpeciesId.TOGEPI,
|
||||||
|
SpeciesId.TOGETIC,
|
||||||
|
SpeciesId.MAREEP,
|
||||||
|
SpeciesId.FLAAFFY,
|
||||||
|
SpeciesId.AMPHAROS,
|
||||||
|
SpeciesId.AIPOM,
|
||||||
|
SpeciesId.MISDREAVUS,
|
||||||
|
SpeciesId.GIRAFARIG,
|
||||||
|
SpeciesId.DUNSPARCE,
|
||||||
|
SpeciesId.SNUBBULL,
|
||||||
|
SpeciesId.GRANBULL,
|
||||||
|
SpeciesId.QWILFISH,
|
||||||
|
SpeciesId.PORYGON2,
|
||||||
|
SpeciesId.STANTLER,
|
||||||
|
SpeciesId.ELEKID,
|
||||||
|
SpeciesId.MILTANK,
|
||||||
|
SpeciesId.BLISSEY,
|
||||||
|
SpeciesId.RAIKOU,
|
||||||
|
SpeciesId.TYRANITAR,
|
||||||
|
SpeciesId.LUGIA,
|
||||||
|
SpeciesId.HO_OH,
|
||||||
|
SpeciesId.CELEBI,
|
||||||
|
SpeciesId.ZIGZAGOON,
|
||||||
|
SpeciesId.LINOONE,
|
||||||
|
SpeciesId.WINGULL,
|
||||||
|
SpeciesId.PELIPPER,
|
||||||
|
SpeciesId.RALTS,
|
||||||
|
SpeciesId.KIRLIA,
|
||||||
|
SpeciesId.GARDEVOIR,
|
||||||
|
SpeciesId.SLAKOTH,
|
||||||
|
SpeciesId.VIGOROTH,
|
||||||
|
SpeciesId.SLAKING,
|
||||||
|
SpeciesId.WHISMUR,
|
||||||
|
SpeciesId.LOUDRED,
|
||||||
|
SpeciesId.EXPLOUD,
|
||||||
|
SpeciesId.NOSEPASS,
|
||||||
|
SpeciesId.SKITTY,
|
||||||
|
SpeciesId.DELCATTY,
|
||||||
|
SpeciesId.SABLEYE,
|
||||||
|
SpeciesId.ARON,
|
||||||
|
SpeciesId.LAIRON,
|
||||||
|
SpeciesId.AGGRON,
|
||||||
|
SpeciesId.ELECTRIKE,
|
||||||
|
SpeciesId.MANECTRIC,
|
||||||
|
SpeciesId.PLUSLE,
|
||||||
|
SpeciesId.MINUN,
|
||||||
|
SpeciesId.VOLBEAT,
|
||||||
|
SpeciesId.ILLUMISE,
|
||||||
|
SpeciesId.GULPIN,
|
||||||
|
SpeciesId.SWALOT,
|
||||||
|
SpeciesId.SPOINK,
|
||||||
|
SpeciesId.GRUMPIG,
|
||||||
|
SpeciesId.SPINDA,
|
||||||
|
SpeciesId.ZANGOOSE,
|
||||||
|
SpeciesId.CASTFORM,
|
||||||
|
SpeciesId.KECLEON,
|
||||||
|
SpeciesId.SHUPPET,
|
||||||
|
SpeciesId.BANETTE,
|
||||||
|
SpeciesId.CHIMECHO,
|
||||||
|
SpeciesId.ABSOL,
|
||||||
|
SpeciesId.REGIROCK,
|
||||||
|
SpeciesId.REGICE,
|
||||||
|
SpeciesId.REGISTEEL,
|
||||||
|
SpeciesId.LATIAS,
|
||||||
|
SpeciesId.LATIOS,
|
||||||
|
SpeciesId.KYOGRE,
|
||||||
|
SpeciesId.GROUDON,
|
||||||
|
SpeciesId.RAYQUAZA,
|
||||||
|
SpeciesId.JIRACHI,
|
||||||
|
SpeciesId.DEOXYS,
|
||||||
|
SpeciesId.BIDOOF,
|
||||||
|
SpeciesId.BIBAREL,
|
||||||
|
SpeciesId.SHINX,
|
||||||
|
SpeciesId.LUXIO,
|
||||||
|
SpeciesId.LUXRAY,
|
||||||
|
SpeciesId.CRANIDOS,
|
||||||
|
SpeciesId.RAMPARDOS,
|
||||||
|
SpeciesId.SHIELDON,
|
||||||
|
SpeciesId.BASTIODON,
|
||||||
|
SpeciesId.PACHIRISU,
|
||||||
|
SpeciesId.AMBIPOM,
|
||||||
|
SpeciesId.DRIFLOON,
|
||||||
|
SpeciesId.DRIFBLIM,
|
||||||
|
SpeciesId.BUNEARY,
|
||||||
|
SpeciesId.LOPUNNY,
|
||||||
|
SpeciesId.MISMAGIUS,
|
||||||
|
SpeciesId.GLAMEOW,
|
||||||
|
SpeciesId.PURUGLY,
|
||||||
|
SpeciesId.CHINGLING,
|
||||||
|
SpeciesId.MIME_JR,
|
||||||
|
SpeciesId.HAPPINY,
|
||||||
|
SpeciesId.SPIRITOMB,
|
||||||
|
SpeciesId.MUNCHLAX,
|
||||||
|
SpeciesId.MAGNEZONE,
|
||||||
|
SpeciesId.LICKILICKY,
|
||||||
|
SpeciesId.RHYPERIOR,
|
||||||
|
SpeciesId.TANGROWTH,
|
||||||
|
SpeciesId.ELECTIVIRE,
|
||||||
|
SpeciesId.TOGEKISS,
|
||||||
|
SpeciesId.PORYGON_Z,
|
||||||
|
SpeciesId.GALLADE,
|
||||||
|
SpeciesId.PROBOPASS,
|
||||||
|
SpeciesId.FROSLASS,
|
||||||
|
SpeciesId.ROTOM,
|
||||||
|
SpeciesId.UXIE,
|
||||||
|
SpeciesId.MESPRIT,
|
||||||
|
SpeciesId.AZELF,
|
||||||
|
SpeciesId.DIALGA,
|
||||||
|
SpeciesId.PALKIA,
|
||||||
|
SpeciesId.REGIGIGAS,
|
||||||
|
SpeciesId.GIRATINA,
|
||||||
|
SpeciesId.DARKRAI,
|
||||||
|
SpeciesId.ARCEUS,
|
||||||
|
SpeciesId.VICTINI,
|
||||||
|
SpeciesId.PATRAT,
|
||||||
|
SpeciesId.WATCHOG,
|
||||||
|
SpeciesId.LILLIPUP,
|
||||||
|
SpeciesId.HERDIER,
|
||||||
|
SpeciesId.STOUTLAND,
|
||||||
|
SpeciesId.MUNNA,
|
||||||
|
SpeciesId.MUSHARNA,
|
||||||
|
SpeciesId.BLITZLE,
|
||||||
|
SpeciesId.ZEBSTRIKA,
|
||||||
|
SpeciesId.WOOBAT,
|
||||||
|
SpeciesId.SWOOBAT,
|
||||||
|
SpeciesId.SIGILYPH,
|
||||||
|
SpeciesId.YAMASK,
|
||||||
|
SpeciesId.COFAGRIGUS,
|
||||||
|
SpeciesId.MINCCINO,
|
||||||
|
SpeciesId.CINCCINO,
|
||||||
|
SpeciesId.GOTHITA,
|
||||||
|
SpeciesId.GOTHORITA,
|
||||||
|
SpeciesId.GOTHITELLE,
|
||||||
|
SpeciesId.SOLOSIS,
|
||||||
|
SpeciesId.DUOSION,
|
||||||
|
SpeciesId.REUNICLUS,
|
||||||
|
SpeciesId.EMOLGA,
|
||||||
|
SpeciesId.FRILLISH,
|
||||||
|
SpeciesId.JELLICENT,
|
||||||
|
SpeciesId.JOLTIK,
|
||||||
|
SpeciesId.GALVANTULA,
|
||||||
|
SpeciesId.KLINK,
|
||||||
|
SpeciesId.KLANG,
|
||||||
|
SpeciesId.KLINKLANG,
|
||||||
|
SpeciesId.EELEKTRIK,
|
||||||
|
SpeciesId.EELEKTROSS,
|
||||||
|
SpeciesId.ELGYEM,
|
||||||
|
SpeciesId.BEHEEYEM,
|
||||||
|
SpeciesId.LITWICK,
|
||||||
|
SpeciesId.LAMPENT,
|
||||||
|
SpeciesId.CHANDELURE,
|
||||||
|
SpeciesId.AXEW,
|
||||||
|
SpeciesId.FRAXURE,
|
||||||
|
SpeciesId.HAXORUS,
|
||||||
|
SpeciesId.STUNFISK,
|
||||||
|
SpeciesId.DRUDDIGON,
|
||||||
|
SpeciesId.GOLETT,
|
||||||
|
SpeciesId.GOLURK,
|
||||||
|
SpeciesId.DEINO,
|
||||||
|
SpeciesId.ZWEILOUS,
|
||||||
|
SpeciesId.HYDREIGON,
|
||||||
|
SpeciesId.THUNDURUS,
|
||||||
|
SpeciesId.ZEKROM,
|
||||||
|
SpeciesId.MELOETTA,
|
||||||
|
SpeciesId.GENESECT,
|
||||||
|
SpeciesId.BRAIXEN,
|
||||||
|
SpeciesId.DELPHOX,
|
||||||
|
SpeciesId.ESPURR,
|
||||||
|
SpeciesId.MEOWSTIC,
|
||||||
|
SpeciesId.HONEDGE,
|
||||||
|
SpeciesId.DOUBLADE,
|
||||||
|
SpeciesId.AEGISLASH,
|
||||||
|
SpeciesId.SKRELP,
|
||||||
|
SpeciesId.DRAGALGE,
|
||||||
|
SpeciesId.HELIOPTILE,
|
||||||
|
SpeciesId.HELIOLISK,
|
||||||
|
SpeciesId.DEDENNE,
|
||||||
|
SpeciesId.GOOMY,
|
||||||
|
SpeciesId.SLIGGOO,
|
||||||
|
SpeciesId.GOODRA,
|
||||||
|
SpeciesId.ZYGARDE,
|
||||||
|
SpeciesId.HOOPA,
|
||||||
|
SpeciesId.YUNGOOS,
|
||||||
|
SpeciesId.GUMSHOOS,
|
||||||
|
SpeciesId.GRUBBIN,
|
||||||
|
SpeciesId.CHARJABUG,
|
||||||
|
SpeciesId.VIKAVOLT,
|
||||||
|
SpeciesId.PASSIMIAN,
|
||||||
|
SpeciesId.TURTONATOR,
|
||||||
|
SpeciesId.TOGEDEMARU,
|
||||||
|
SpeciesId.DRAMPA,
|
||||||
|
SpeciesId.KOMMO_O,
|
||||||
|
SpeciesId.TAPU_KOKO,
|
||||||
|
SpeciesId.SOLGALEO,
|
||||||
|
SpeciesId.LUNALA,
|
||||||
|
SpeciesId.PHEROMOSA,
|
||||||
|
SpeciesId.XURKITREE,
|
||||||
|
SpeciesId.CELESTEELA,
|
||||||
|
SpeciesId.GUZZLORD,
|
||||||
|
SpeciesId.NECROZMA,
|
||||||
|
SpeciesId.MAGEARNA,
|
||||||
|
SpeciesId.NAGANADEL,
|
||||||
|
SpeciesId.ZERAORA,
|
||||||
|
SpeciesId.TOXTRICITY,
|
||||||
|
SpeciesId.MR_RIME,
|
||||||
|
SpeciesId.REGIELEKI,
|
||||||
|
SpeciesId.WYRDEER,
|
||||||
|
SpeciesId.FARIGIRAF,
|
||||||
|
SpeciesId.DUDUNSPARCE,
|
||||||
|
SpeciesId.MIRAIDON,
|
||||||
|
SpeciesId.RAGING_BOLT,
|
||||||
|
SpeciesId.ALOLA_RATTATA,
|
||||||
|
SpeciesId.ALOLA_RATICATE,
|
||||||
|
SpeciesId.ALOLA_RAICHU,
|
||||||
|
SpeciesId.ALOLA_MEOWTH,
|
||||||
|
SpeciesId.ALOLA_PERSIAN,
|
||||||
|
SpeciesId.ALOLA_GRAVELER,
|
||||||
|
SpeciesId.ALOLA_GOLEM,
|
||||||
|
SpeciesId.ALOLA_GRIMER,
|
||||||
|
SpeciesId.ALOLA_MUK,
|
||||||
|
SpeciesId.GALAR_WEEZING,
|
||||||
|
SpeciesId.GALAR_MR_MIME,
|
||||||
|
SpeciesId.HISUI_SLIGGOO,
|
||||||
|
SpeciesId.HISUI_GOODRA,
|
||||||
|
],
|
||||||
[MoveId.WATER_PULSE]: [
|
[MoveId.WATER_PULSE]: [
|
||||||
SpeciesId.SQUIRTLE,
|
SpeciesId.SQUIRTLE,
|
||||||
SpeciesId.WARTORTLE,
|
SpeciesId.WARTORTLE,
|
||||||
@ -68747,6 +69026,7 @@ export const tmPoolTiers: TmPoolTiers = {
|
|||||||
[MoveId.LEAF_BLADE]: ModifierTier.ULTRA,
|
[MoveId.LEAF_BLADE]: ModifierTier.ULTRA,
|
||||||
[MoveId.DRAGON_DANCE]: ModifierTier.GREAT,
|
[MoveId.DRAGON_DANCE]: ModifierTier.GREAT,
|
||||||
[MoveId.ROCK_BLAST]: ModifierTier.GREAT,
|
[MoveId.ROCK_BLAST]: ModifierTier.GREAT,
|
||||||
|
[MoveId.SHOCK_WAVE]: ModifierTier.GREAT,
|
||||||
[MoveId.WATER_PULSE]: ModifierTier.GREAT,
|
[MoveId.WATER_PULSE]: ModifierTier.GREAT,
|
||||||
[MoveId.ROOST]: ModifierTier.GREAT,
|
[MoveId.ROOST]: ModifierTier.GREAT,
|
||||||
[MoveId.GRAVITY]: ModifierTier.COMMON,
|
[MoveId.GRAVITY]: ModifierTier.COMMON,
|
||||||
|
@ -99,6 +99,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE
|
|||||||
MysteryEncounterType.DARK_DEAL,
|
MysteryEncounterType.DARK_DEAL,
|
||||||
)
|
)
|
||||||
.withEncounterTier(MysteryEncounterTier.ROGUE)
|
.withEncounterTier(MysteryEncounterTier.ROGUE)
|
||||||
|
.withDisallowedChallenges(Challenges.HARDCORE)
|
||||||
.withIntroSpriteConfigs([
|
.withIntroSpriteConfigs([
|
||||||
{
|
{
|
||||||
spriteKey: "dark_deal_scientist",
|
spriteKey: "dark_deal_scientist",
|
||||||
|
@ -673,6 +673,8 @@ export async function catchPokemon(
|
|||||||
globalScene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs);
|
globalScene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs);
|
||||||
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
|
const addStatus = new BooleanHolder(true);
|
||||||
|
applyChallenges(ChallengeType.POKEMON_ADD_TO_PARTY, pokemon, addStatus);
|
||||||
const doPokemonCatchMenu = () => {
|
const doPokemonCatchMenu = () => {
|
||||||
const end = () => {
|
const end = () => {
|
||||||
// Ensure the pokemon is in the enemy party in all situations
|
// Ensure the pokemon is in the enemy party in all situations
|
||||||
@ -708,9 +710,7 @@ export async function catchPokemon(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
Promise.all([pokemon.hideInfo(), globalScene.gameData.setPokemonCaught(pokemon)]).then(() => {
|
Promise.all([pokemon.hideInfo(), globalScene.gameData.setPokemonCaught(pokemon)]).then(() => {
|
||||||
const addStatus = new BooleanHolder(true);
|
if (!(isObtain || addStatus.value)) {
|
||||||
applyChallenges(ChallengeType.POKEMON_ADD_TO_PARTY, pokemon, addStatus);
|
|
||||||
if (!addStatus.value) {
|
|
||||||
removePokemon();
|
removePokemon();
|
||||||
end();
|
end();
|
||||||
return;
|
return;
|
||||||
@ -807,10 +807,16 @@ export async function catchPokemon(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (showCatchObtainMessage) {
|
if (showCatchObtainMessage) {
|
||||||
|
let catchMessage: string;
|
||||||
|
if (isObtain) {
|
||||||
|
catchMessage = "battle:pokemonObtained";
|
||||||
|
} else if (addStatus.value) {
|
||||||
|
catchMessage = "battle:pokemonCaught";
|
||||||
|
} else {
|
||||||
|
catchMessage = "battle:pokemonCaughtButChallenge";
|
||||||
|
}
|
||||||
globalScene.ui.showText(
|
globalScene.ui.showText(
|
||||||
i18next.t(isObtain ? "battle:pokemonObtained" : "battle:pokemonCaught", {
|
i18next.t(catchMessage, { pokemonName: pokemon.getNameToRender() }),
|
||||||
pokemonName: pokemon.getNameToRender(),
|
|
||||||
}),
|
|
||||||
null,
|
null,
|
||||||
doPokemonCatchMenu,
|
doPokemonCatchMenu,
|
||||||
0,
|
0,
|
||||||
|
@ -5,16 +5,13 @@ import type { Constructor } from "#utils/common";
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the attributes of a {@linkcode PositionalTag}.
|
* Load the attributes of a {@linkcode PositionalTag}.
|
||||||
* @param tagType - The {@linkcode PositionalTagType} to create
|
* @param data - An object containing the {@linkcode PositionalTagType} to create,
|
||||||
* @param args - The arguments needed to instantize the given tag
|
* as well as the arguments needed to instantize the given tag
|
||||||
* @returns The newly created tag.
|
* @returns The newly created tag.
|
||||||
* @remarks
|
* @remarks
|
||||||
* This function does not perform any checking if the added tag is valid.
|
* This function does not perform any checking if the added tag is valid.
|
||||||
*/
|
*/
|
||||||
export function loadPositionalTag<T extends PositionalTagType>({
|
export function loadPositionalTag<T extends PositionalTagType>(data: serializedPosTagMap[T]): posTagInstanceMap[T];
|
||||||
tagType,
|
|
||||||
...args
|
|
||||||
}: serializedPosTagMap[T]): posTagInstanceMap[T];
|
|
||||||
/**
|
/**
|
||||||
* Load the attributes of a {@linkcode PositionalTag}.
|
* Load the attributes of a {@linkcode PositionalTag}.
|
||||||
* @param tag - The {@linkcode SerializedPositionalTag} to instantiate
|
* @param tag - The {@linkcode SerializedPositionalTag} to instantiate
|
||||||
@ -23,17 +20,10 @@ export function loadPositionalTag<T extends PositionalTagType>({
|
|||||||
* This function does not perform any checking if the added tag is valid.
|
* This function does not perform any checking if the added tag is valid.
|
||||||
*/
|
*/
|
||||||
export function loadPositionalTag(tag: SerializedPositionalTag): PositionalTag;
|
export function loadPositionalTag(tag: SerializedPositionalTag): PositionalTag;
|
||||||
export function loadPositionalTag<T extends PositionalTagType>({
|
export function loadPositionalTag({ tagType, ...rest }: SerializedPositionalTag): PositionalTag {
|
||||||
tagType,
|
const tagClass = posTagConstructorMap[tagType];
|
||||||
...rest
|
// @ts-expect-error - tagType always corresponds to the proper constructor for `rest`
|
||||||
}: serializedPosTagMap[T]): posTagInstanceMap[T] {
|
return new tagClass(rest);
|
||||||
// Note: We need 2 type assertions here:
|
|
||||||
// 1 because TS doesn't narrow the type of TagClass correctly based on `T`.
|
|
||||||
// It converts it into `new (DelayedAttackTag | WishTag) => DelayedAttackTag & WishTag`
|
|
||||||
const tagClass = posTagConstructorMap[tagType] as new (args: posTagParamMap[T]) => posTagInstanceMap[T];
|
|
||||||
// 2 because TS doesn't narrow the type of `rest` correctly
|
|
||||||
// (from `Omit<serializedPosTagParamMap[T], "tagType"> into `posTagParamMap[T]`)
|
|
||||||
return new tagClass(rest as unknown as posTagParamMap[T]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Const object mapping tag types to their constructors. */
|
/** Const object mapping tag types to their constructors. */
|
||||||
@ -42,7 +32,7 @@ const posTagConstructorMap = Object.freeze({
|
|||||||
[PositionalTagType.WISH]: WishTag,
|
[PositionalTagType.WISH]: WishTag,
|
||||||
}) satisfies {
|
}) satisfies {
|
||||||
// NB: This `satisfies` block ensures that all tag types have corresponding entries in the map.
|
// NB: This `satisfies` block ensures that all tag types have corresponding entries in the map.
|
||||||
[k in PositionalTagType]: Constructor<PositionalTag & { tagType: k }>;
|
[k in PositionalTagType]: Constructor<PositionalTag & { readonly tagType: k }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Type mapping positional tag types to their constructors. */
|
/** Type mapping positional tag types to their constructors. */
|
||||||
@ -59,11 +49,12 @@ type posTagParamMap = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type mapping all positional tag types to their constructors' parameters, alongside the `tagType` selector.
|
* Type mapping all positional tag types to their constructors' parameters, alongside the `tagType` selector. \
|
||||||
* Equivalent to their serialized representations.
|
* Equivalent to their serialized representations.
|
||||||
|
* @interface
|
||||||
*/
|
*/
|
||||||
export type serializedPosTagMap = {
|
export type serializedPosTagMap = {
|
||||||
[k in PositionalTagType]: posTagParamMap[k] & { tagType: k };
|
[k in PositionalTagType]: posTagParamMap[k] & Pick<posTagInstanceMap[k], "tagType">;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Union type containing all serialized {@linkcode PositionalTag}s. */
|
/** Union type containing all serialized {@linkcode PositionalTag}s. */
|
||||||
|
@ -2,7 +2,9 @@ import { globalScene } from "#app/global-scene";
|
|||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
// biome-ignore-start lint/correctness/noUnusedImports: TSDoc
|
// biome-ignore-start lint/correctness/noUnusedImports: TSDoc
|
||||||
import type { ArenaTag } from "#data/arena-tag";
|
import type { ArenaTag } from "#data/arena-tag";
|
||||||
|
import type { Stat } from "#enums/stat";
|
||||||
// biome-ignore-end lint/correctness/noUnusedImports: TSDoc
|
// biome-ignore-end lint/correctness/noUnusedImports: TSDoc
|
||||||
|
|
||||||
import { allMoves } from "#data/data-lists";
|
import { allMoves } from "#data/data-lists";
|
||||||
import type { BattlerIndex } from "#enums/battler-index";
|
import type { BattlerIndex } from "#enums/battler-index";
|
||||||
import type { MoveId } from "#enums/move-id";
|
import type { MoveId } from "#enums/move-id";
|
||||||
@ -30,7 +32,7 @@ export interface PositionalTagBaseArgs {
|
|||||||
/**
|
/**
|
||||||
* The {@linkcode BattlerIndex} targeted by this effect.
|
* The {@linkcode BattlerIndex} targeted by this effect.
|
||||||
*/
|
*/
|
||||||
targetIndex: BattlerIndex;
|
readonly targetIndex: BattlerIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,7 +41,7 @@ export interface PositionalTagBaseArgs {
|
|||||||
* Multiple tags of the same kind can stack with one another, provided they are affecting different targets.
|
* Multiple tags of the same kind can stack with one another, provided they are affecting different targets.
|
||||||
*/
|
*/
|
||||||
export abstract class PositionalTag implements PositionalTagBaseArgs {
|
export abstract class PositionalTag implements PositionalTagBaseArgs {
|
||||||
/** This tag's {@linkcode PositionalTagType | type} */
|
/** This tag's {@linkcode PositionalTagType | type}. */
|
||||||
public abstract readonly tagType: PositionalTagType;
|
public abstract readonly tagType: PositionalTagType;
|
||||||
// These arguments have to be public to implement the interface, but are functionally private
|
// These arguments have to be public to implement the interface, but are functionally private
|
||||||
// outside this and the tag manager.
|
// outside this and the tag manager.
|
||||||
@ -76,9 +78,9 @@ interface DelayedAttackArgs extends PositionalTagBaseArgs {
|
|||||||
/**
|
/**
|
||||||
* The {@linkcode Pokemon.id | PID} of the {@linkcode Pokemon} having created this effect.
|
* The {@linkcode Pokemon.id | PID} of the {@linkcode Pokemon} having created this effect.
|
||||||
*/
|
*/
|
||||||
sourceId: number;
|
readonly sourceId: number;
|
||||||
/** The {@linkcode MoveId} that created this attack. */
|
/** The {@linkcode MoveId} that created this attack. */
|
||||||
sourceMove: MoveId;
|
readonly sourceMove: MoveId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -133,9 +135,9 @@ export class DelayedAttackTag extends PositionalTag implements DelayedAttackArgs
|
|||||||
/** Interface containing arguments used to construct a {@linkcode WishTag}. */
|
/** Interface containing arguments used to construct a {@linkcode WishTag}. */
|
||||||
interface WishArgs extends PositionalTagBaseArgs {
|
interface WishArgs extends PositionalTagBaseArgs {
|
||||||
/** The amount of {@linkcode Stat.HP | HP} to heal; set to 50% of the user's max HP during move usage. */
|
/** The amount of {@linkcode Stat.HP | HP} to heal; set to 50% of the user's max HP during move usage. */
|
||||||
healHp: number;
|
readonly healHp: number;
|
||||||
/** The name of the {@linkcode Pokemon} having created the tag. */
|
/** The name of the {@linkcode Pokemon} having created the tag. */
|
||||||
pokemonName: string;
|
readonly pokemonName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
33
src/enums/arena-event-type.ts
Normal file
33
src/enums/arena-event-type.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import type { ArenaTag } from "#data/arena-tag";
|
||||||
|
import type { PositionalTag } from "#data/positional-tags/positional-tag";
|
||||||
|
import type { TerrainType } from "#data/terrain";
|
||||||
|
import type { WeatherType } from "#enums/weather-type";
|
||||||
|
import type { ArenaEvent } from "#events/arena";
|
||||||
|
import type { ObjectValues } from "#types/type-helpers";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum representing the types of all {@linkcode ArenaEvent}s that can be emitted.
|
||||||
|
* @eventProperty
|
||||||
|
* @enum
|
||||||
|
*/
|
||||||
|
export const ArenaEventType = {
|
||||||
|
/** Emitted when a {@linkcode WeatherType} is added, overlapped, or removed */
|
||||||
|
WEATHER_CHANGED: "onWeatherChanged",
|
||||||
|
/** Emitted when a {@linkcode TerrainType} is added, overlapped, or removed */
|
||||||
|
TERRAIN_CHANGED: "onTerrainChanged",
|
||||||
|
|
||||||
|
/** Emitted when a new {@linkcode ArenaTag} is added */
|
||||||
|
ARENA_TAG_ADDED: "onArenaTagAdded",
|
||||||
|
/** Emitted when an existing {@linkcode ArenaTag} is removed */
|
||||||
|
ARENA_TAG_REMOVED: "onArenaTagRemoved",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type ArenaEventType = ObjectValues<typeof ArenaEventType>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Doc comment removal prevention block
|
||||||
|
{@linkcode WeatherType}
|
||||||
|
{@linkcode TerrainType}
|
||||||
|
{@linkcode PositionalTag}
|
||||||
|
{@linkcode ArenaTag}
|
||||||
|
*/
|
@ -1,5 +1,18 @@
|
|||||||
|
import type { ArenaTag } from "#data/arena-tag";
|
||||||
|
import type { ArenaFlyout } from "#ui/arena-flyout";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum used to represent a given side of the field for the purposes of {@linkcode ArenaTag}s and
|
||||||
|
* the current {@linkcode ArenaFlyout}.
|
||||||
|
*/
|
||||||
export enum ArenaTagSide {
|
export enum ArenaTagSide {
|
||||||
|
/**
|
||||||
|
* The effect applies to both sides of the field (player & enemy).
|
||||||
|
* Also used for the purposes of displaying weather and other "field-based" effects in the flyout.
|
||||||
|
*/
|
||||||
BOTH,
|
BOTH,
|
||||||
|
/** The effect applies exclusively to the player's side of the field. */
|
||||||
PLAYER,
|
PLAYER,
|
||||||
|
/** The effect applies exclusively to the opposing side of the field. */
|
||||||
ENEMY
|
ENEMY
|
||||||
}
|
}
|
||||||
|
@ -1,109 +1,125 @@
|
|||||||
import type { TerrainType } from "#data/terrain";
|
import type { TerrainType } from "#data/terrain";
|
||||||
|
import { ArenaEventType } from "#enums/arena-event-type";
|
||||||
import type { ArenaTagSide } from "#enums/arena-tag-side";
|
import type { ArenaTagSide } from "#enums/arena-tag-side";
|
||||||
import type { ArenaTagType } from "#enums/arena-tag-type";
|
import type { ArenaTagType } from "#enums/arena-tag-type";
|
||||||
import type { WeatherType } from "#enums/weather-type";
|
import type { WeatherType } from "#enums/weather-type";
|
||||||
|
|
||||||
/** Alias for all {@linkcode ArenaEvent} type strings */
|
/**
|
||||||
export enum ArenaEventType {
|
* Abstract container class for all {@linkcode ArenaEventType} events.
|
||||||
/** Triggers when a {@linkcode WeatherType} is added, overlapped, or removed */
|
* @eventProperty
|
||||||
WEATHER_CHANGED = "onWeatherChanged",
|
*/
|
||||||
/** Triggers when a {@linkcode TerrainType} is added, overlapped, or removed */
|
abstract class ArenaEvent extends Event {
|
||||||
TERRAIN_CHANGED = "onTerrainChanged",
|
/** The {@linkcode ArenaEventType} being emitted. */
|
||||||
|
public declare abstract readonly type: ArenaEventType; // that's a mouthful!
|
||||||
/** Triggers when a {@linkcode ArenaTagType} is added */
|
// biome-ignore lint/complexity/noUselessConstructor: changes the type of the type field
|
||||||
TAG_ADDED = "onTagAdded",
|
constructor(type: ArenaEventType) {
|
||||||
/** Triggers when a {@linkcode ArenaTagType} is removed */
|
super(type);
|
||||||
TAG_REMOVED = "onTagRemoved",
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export type { ArenaEvent };
|
||||||
* Base container class for all {@linkcode ArenaEventType} events
|
|
||||||
* @extends Event
|
|
||||||
*/
|
|
||||||
export class ArenaEvent extends Event {
|
|
||||||
/** The total duration of the {@linkcode ArenaEventType} */
|
|
||||||
public duration: number;
|
|
||||||
constructor(eventType: ArenaEventType, duration: number) {
|
|
||||||
super(eventType);
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container class for {@linkcode ArenaEventType.WEATHER_CHANGED} events. \
|
||||||
|
* Emitted whenever a weather effect starts, ends or is replaced.
|
||||||
|
* @eventProperty
|
||||||
|
*/
|
||||||
|
export class WeatherChangedEvent extends ArenaEvent {
|
||||||
|
declare type: typeof ArenaEventType.WEATHER_CHANGED;
|
||||||
|
|
||||||
|
/** The new {@linkcode WeatherType} being set. */
|
||||||
|
public weatherType: WeatherType;
|
||||||
|
/**
|
||||||
|
* The new weather's initial duration.
|
||||||
|
* Unused if {@linkcode weatherType} is set to {@linkcode WeatherType.NONE}.
|
||||||
|
*/
|
||||||
|
public duration: number;
|
||||||
|
|
||||||
|
constructor(weatherType: WeatherType, duration: number) {
|
||||||
|
super(ArenaEventType.WEATHER_CHANGED);
|
||||||
|
|
||||||
|
this.weatherType = weatherType;
|
||||||
this.duration = duration;
|
this.duration = duration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Container class for {@linkcode ArenaEventType.WEATHER_CHANGED} events
|
|
||||||
* @extends ArenaEvent
|
|
||||||
*/
|
|
||||||
export class WeatherChangedEvent extends ArenaEvent {
|
|
||||||
/** The {@linkcode WeatherType} being overridden */
|
|
||||||
public oldWeatherType: WeatherType;
|
|
||||||
/** The {@linkcode WeatherType} being set */
|
|
||||||
public newWeatherType: WeatherType;
|
|
||||||
constructor(oldWeatherType: WeatherType, newWeatherType: WeatherType, duration: number) {
|
|
||||||
super(ArenaEventType.WEATHER_CHANGED, duration);
|
|
||||||
|
|
||||||
this.oldWeatherType = oldWeatherType;
|
|
||||||
this.newWeatherType = newWeatherType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* Container class for {@linkcode ArenaEventType.TERRAIN_CHANGED} events
|
* Container class for {@linkcode ArenaEventType.TERRAIN_CHANGED} events. \
|
||||||
* @extends ArenaEvent
|
* Emitted whenever a terrain effect starts, ends or is replaced.
|
||||||
|
* @eventProperty
|
||||||
*/
|
*/
|
||||||
export class TerrainChangedEvent extends ArenaEvent {
|
export class TerrainChangedEvent extends ArenaEvent {
|
||||||
/** The {@linkcode TerrainType} being overridden */
|
declare type: typeof ArenaEventType.TERRAIN_CHANGED;
|
||||||
public oldTerrainType: TerrainType;
|
|
||||||
/** The {@linkcode TerrainType} being set */
|
|
||||||
public newTerrainType: TerrainType;
|
|
||||||
constructor(oldTerrainType: TerrainType, newTerrainType: TerrainType, duration: number) {
|
|
||||||
super(ArenaEventType.TERRAIN_CHANGED, duration);
|
|
||||||
|
|
||||||
this.oldTerrainType = oldTerrainType;
|
/** The new {@linkcode TerrainType} being set. */
|
||||||
this.newTerrainType = newTerrainType;
|
public terrainType: TerrainType;
|
||||||
|
/**
|
||||||
|
* The new terrain's initial duration.
|
||||||
|
* Unused if {@linkcode terrainType} is set to {@linkcode TerrainType.NONE}.
|
||||||
|
*/
|
||||||
|
public duration: number;
|
||||||
|
|
||||||
|
constructor(terrainType: TerrainType, duration: number) {
|
||||||
|
super(ArenaEventType.TERRAIN_CHANGED);
|
||||||
|
|
||||||
|
this.terrainType = terrainType;
|
||||||
|
this.duration = duration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Container class for {@linkcode ArenaEventType.TAG_ADDED} events
|
* Container class for {@linkcode ArenaEventType.ARENA_TAG_ADDED} events. \
|
||||||
* @extends ArenaEvent
|
* Emitted whenever a new {@linkcode ArenaTag} is added to the arena, or whenever an existing
|
||||||
|
* {@linkcode ArenaTrapTag} overlaps and adds new layers.
|
||||||
|
* @eventProperty
|
||||||
*/
|
*/
|
||||||
export class TagAddedEvent extends ArenaEvent {
|
export class ArenaTagAddedEvent extends ArenaEvent {
|
||||||
/** The {@linkcode ArenaTagType} being added */
|
declare type: typeof ArenaEventType.ARENA_TAG_ADDED;
|
||||||
public arenaTagType: ArenaTagType;
|
|
||||||
/** The {@linkcode ArenaTagSide} the tag is being placed on */
|
/** The {@linkcode ArenaTagType} of the tag being added */
|
||||||
public arenaTagSide: ArenaTagSide;
|
public tagType: ArenaTagType;
|
||||||
/** The current number of layers of the arena trap. */
|
/** The {@linkcode ArenaTagSide} the tag is being added too */
|
||||||
public arenaTagLayers: number;
|
public side: ArenaTagSide;
|
||||||
/** The maximum amount of layers of the arena trap. */
|
/** The tag's initial duration. */
|
||||||
public arenaTagMaxLayers: number;
|
public duration: number;
|
||||||
|
/**
|
||||||
|
* A tuple containing the current and maximum number of layers of the current {@linkcode ArenaTrapTag},
|
||||||
|
* or `undefined` if the tag was not a trap.
|
||||||
|
*/
|
||||||
|
public trapLayers: [current: number, max: number] | undefined;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
arenaTagType: ArenaTagType,
|
side: ArenaTagType,
|
||||||
arenaTagSide: ArenaTagSide,
|
arenaTagSide: ArenaTagSide,
|
||||||
duration: number,
|
duration: number,
|
||||||
arenaTagLayers?: number,
|
trapLayers?: [current: number, max: number],
|
||||||
arenaTagMaxLayers?: number,
|
|
||||||
) {
|
) {
|
||||||
super(ArenaEventType.TAG_ADDED, duration);
|
super(ArenaEventType.ARENA_TAG_ADDED);
|
||||||
|
|
||||||
this.arenaTagType = arenaTagType;
|
this.tagType = side;
|
||||||
this.arenaTagSide = arenaTagSide;
|
this.side = arenaTagSide;
|
||||||
this.arenaTagLayers = arenaTagLayers!; // TODO: is this bang correct?
|
this.duration = duration;
|
||||||
this.arenaTagMaxLayers = arenaTagMaxLayers!; // TODO: is this bang correct?
|
this.trapLayers = trapLayers;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Container class for {@linkcode ArenaEventType.TAG_REMOVED} events
|
* Container class for {@linkcode ArenaEventType.ARENA_TAG_REMOVED} events. \
|
||||||
* @extends ArenaEvent
|
* Emitted whenever an {@linkcode ArenaTag} is removed from the field for any reason.
|
||||||
|
* @eventProperty
|
||||||
*/
|
*/
|
||||||
export class TagRemovedEvent extends ArenaEvent {
|
export class ArenaTagRemovedEvent extends ArenaEvent {
|
||||||
/** The {@linkcode ArenaTagType} being removed */
|
declare type: typeof ArenaEventType.ARENA_TAG_REMOVED;
|
||||||
public arenaTagType: ArenaTagType;
|
|
||||||
/** The {@linkcode ArenaTagSide} the tag was being placed on */
|
|
||||||
public arenaTagSide: ArenaTagSide;
|
|
||||||
constructor(arenaTagType: ArenaTagType, arenaTagSide: ArenaTagSide, duration: number) {
|
|
||||||
super(ArenaEventType.TAG_REMOVED, duration);
|
|
||||||
|
|
||||||
this.arenaTagType = arenaTagType;
|
/** The {@linkcode ArenaTagType} of the tag being removed. */
|
||||||
this.arenaTagSide = arenaTagSide;
|
public tagType: ArenaTagType;
|
||||||
|
/** The {@linkcode ArenaTagSide} the removed tag affected. */
|
||||||
|
public side: ArenaTagSide;
|
||||||
|
|
||||||
|
constructor(tagType: ArenaTagType, side: ArenaTagSide) {
|
||||||
|
super(ArenaEventType.ARENA_TAG_REMOVED);
|
||||||
|
|
||||||
|
this.tagType = tagType;
|
||||||
|
this.side = side;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,11 +31,18 @@ import { SpeciesId } from "#enums/species-id";
|
|||||||
import { TimeOfDay } from "#enums/time-of-day";
|
import { TimeOfDay } from "#enums/time-of-day";
|
||||||
import { TrainerType } from "#enums/trainer-type";
|
import { TrainerType } from "#enums/trainer-type";
|
||||||
import { WeatherType } from "#enums/weather-type";
|
import { WeatherType } from "#enums/weather-type";
|
||||||
import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#events/arena";
|
import {
|
||||||
|
type ArenaEvent,
|
||||||
|
ArenaTagAddedEvent,
|
||||||
|
ArenaTagRemovedEvent,
|
||||||
|
TerrainChangedEvent,
|
||||||
|
WeatherChangedEvent,
|
||||||
|
} from "#events/arena";
|
||||||
import type { Pokemon } from "#field/pokemon";
|
import type { Pokemon } from "#field/pokemon";
|
||||||
import { FieldEffectModifier } from "#modifiers/modifier";
|
import { FieldEffectModifier } from "#modifiers/modifier";
|
||||||
import type { Move } from "#moves/move";
|
import type { Move } from "#moves/move";
|
||||||
import type { AbstractConstructor } from "#types/type-helpers";
|
import type { AbstractConstructor } from "#types/type-helpers";
|
||||||
|
import type { TypedEventTarget } from "#types/typed-event-target";
|
||||||
import { type Constructor, isNullOrUndefined, NumberHolder, randSeedInt } from "#utils/common";
|
import { type Constructor, isNullOrUndefined, NumberHolder, randSeedInt } from "#utils/common";
|
||||||
import { getPokemonSpecies } from "#utils/pokemon-utils";
|
import { getPokemonSpecies } from "#utils/pokemon-utils";
|
||||||
|
|
||||||
@ -45,10 +52,7 @@ export class Arena {
|
|||||||
public terrain: Terrain | null;
|
public terrain: Terrain | null;
|
||||||
/** All currently-active {@linkcode ArenaTag}s on both sides of the field. */
|
/** All currently-active {@linkcode ArenaTag}s on both sides of the field. */
|
||||||
public tags: ArenaTag[] = [];
|
public tags: ArenaTag[] = [];
|
||||||
/**
|
/** A manager for the currently-active {@linkcode PositionalTag}s on both sides of the field. */
|
||||||
* All currently-active {@linkcode PositionalTag}s on both sides of the field,
|
|
||||||
* sorted by tag type.
|
|
||||||
*/
|
|
||||||
public positionalTagManager: PositionalTagManager = new PositionalTagManager();
|
public positionalTagManager: PositionalTagManager = new PositionalTagManager();
|
||||||
|
|
||||||
public bgm: string;
|
public bgm: string;
|
||||||
@ -66,7 +70,11 @@ export class Arena {
|
|||||||
private pokemonPool: PokemonPools;
|
private pokemonPool: PokemonPools;
|
||||||
private trainerPool: BiomeTierTrainerPools;
|
private trainerPool: BiomeTierTrainerPools;
|
||||||
|
|
||||||
public readonly eventTarget: EventTarget = new EventTarget();
|
/**
|
||||||
|
* Event dispatcher for various {@linkcode ArenaEvent}s.
|
||||||
|
* Used primarily to update the arena flyout.
|
||||||
|
*/
|
||||||
|
public readonly eventTarget: TypedEventTarget<ArenaEvent> = new EventTarget();
|
||||||
|
|
||||||
constructor(biome: BiomeId, playerFaints = 0) {
|
constructor(biome: BiomeId, playerFaints = 0) {
|
||||||
this.biomeType = biome;
|
this.biomeType = biome;
|
||||||
@ -344,9 +352,7 @@ export class Arena {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.weather = weather ? new Weather(weather, weatherDuration.value) : null;
|
this.weather = weather ? new Weather(weather, weatherDuration.value) : null;
|
||||||
this.eventTarget.dispatchEvent(
|
this.eventTarget.dispatchEvent(new WeatherChangedEvent(this.getWeatherType(), weatherDuration.value));
|
||||||
new WeatherChangedEvent(oldWeatherType, this.weather?.weatherType!, this.weather?.turnsLeft!),
|
|
||||||
); // TODO: is this bang correct?
|
|
||||||
|
|
||||||
if (this.weather) {
|
if (this.weather) {
|
||||||
globalScene.phaseManager.unshiftNew(
|
globalScene.phaseManager.unshiftNew(
|
||||||
@ -432,9 +438,7 @@ export class Arena {
|
|||||||
|
|
||||||
this.terrain = terrain ? new Terrain(terrain, terrainDuration.value) : null;
|
this.terrain = terrain ? new Terrain(terrain, terrainDuration.value) : null;
|
||||||
|
|
||||||
this.eventTarget.dispatchEvent(
|
this.eventTarget.dispatchEvent(new TerrainChangedEvent(this.getTerrainType(), terrainDuration.value));
|
||||||
new TerrainChangedEvent(oldTerrainType, this.terrain?.terrainType!, this.terrain?.turnsLeft!),
|
|
||||||
); // TODO: are those bangs correct?
|
|
||||||
|
|
||||||
if (this.terrain) {
|
if (this.terrain) {
|
||||||
if (!ignoreAnim) {
|
if (!ignoreAnim) {
|
||||||
@ -708,26 +712,25 @@ export class Arena {
|
|||||||
const existingTag = this.getTagOnSide(tagType, side);
|
const existingTag = this.getTagOnSide(tagType, side);
|
||||||
if (existingTag) {
|
if (existingTag) {
|
||||||
existingTag.onOverlap(this, globalScene.getPokemonById(sourceId));
|
existingTag.onOverlap(this, globalScene.getPokemonById(sourceId));
|
||||||
|
|
||||||
if (existingTag instanceof ArenaTrapTag) {
|
|
||||||
const { tagType, side, turnCount, layers, maxLayers } = existingTag as ArenaTrapTag;
|
|
||||||
this.eventTarget.dispatchEvent(new TagAddedEvent(tagType, side, turnCount, layers, maxLayers));
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// creates a new tag object
|
// creates a new tag object
|
||||||
const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId, side);
|
const newTag = getArenaTag(tagType, turnCount, sourceMove, sourceId, side);
|
||||||
if (newTag) {
|
if (!newTag) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
newTag.onAdd(this, quiet);
|
newTag.onAdd(this, quiet);
|
||||||
this.tags.push(newTag);
|
this.tags.push(newTag);
|
||||||
|
|
||||||
const { layers = 0, maxLayers = 0 } = newTag instanceof ArenaTrapTag ? newTag : {};
|
// Dispatch a TagAddedEvent to update the flyout.
|
||||||
|
if (newTag instanceof ArenaTrapTag) {
|
||||||
this.eventTarget.dispatchEvent(
|
globalScene.arena.eventTarget.dispatchEvent(
|
||||||
new TagAddedEvent(newTag.tagType, newTag.side, newTag.turnCount, layers, maxLayers),
|
new ArenaTagAddedEvent(tagType, side, turnCount, [newTag.layers, newTag.maxLayers]),
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
globalScene.arena.eventTarget.dispatchEvent(new ArenaTagAddedEvent(tagType, side, turnCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -807,7 +810,7 @@ export class Arena {
|
|||||||
t.onRemove(this);
|
t.onRemove(this);
|
||||||
this.tags.splice(this.tags.indexOf(t), 1);
|
this.tags.splice(this.tags.indexOf(t), 1);
|
||||||
|
|
||||||
this.eventTarget.dispatchEvent(new TagRemovedEvent(t.tagType, t.side, t.turnCount));
|
this.eventTarget.dispatchEvent(new ArenaTagRemovedEvent(t.tagType, t.side));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -818,7 +821,7 @@ export class Arena {
|
|||||||
tag.onRemove(this);
|
tag.onRemove(this);
|
||||||
tags.splice(tags.indexOf(tag), 1);
|
tags.splice(tags.indexOf(tag), 1);
|
||||||
|
|
||||||
this.eventTarget.dispatchEvent(new TagRemovedEvent(tag.tagType, tag.side, tag.turnCount));
|
this.eventTarget.dispatchEvent(new ArenaTagRemovedEvent(tag.tagType, tag.side));
|
||||||
}
|
}
|
||||||
return !!tag;
|
return !!tag;
|
||||||
}
|
}
|
||||||
@ -829,20 +832,17 @@ export class Arena {
|
|||||||
tag.onRemove(this, quiet);
|
tag.onRemove(this, quiet);
|
||||||
this.tags.splice(this.tags.indexOf(tag), 1);
|
this.tags.splice(this.tags.indexOf(tag), 1);
|
||||||
|
|
||||||
this.eventTarget.dispatchEvent(new TagRemovedEvent(tag.tagType, tag.side, tag.turnCount));
|
this.eventTarget.dispatchEvent(new ArenaTagRemovedEvent(tag.tagType, tag.side));
|
||||||
}
|
}
|
||||||
return !!tag;
|
return !!tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeAllTags(): void {
|
removeAllTags(): void {
|
||||||
while (this.tags.length) {
|
for (const tag of this.tags) {
|
||||||
this.tags[0].onRemove(this);
|
tag.onRemove(this);
|
||||||
this.eventTarget.dispatchEvent(
|
this.eventTarget.dispatchEvent(new ArenaTagRemovedEvent(tag.tagType, tag.side));
|
||||||
new TagRemovedEvent(this.tags[0].tagType, this.tags[0].side, this.tags[0].turnCount),
|
|
||||||
);
|
|
||||||
|
|
||||||
this.tags.splice(0, 1);
|
|
||||||
}
|
}
|
||||||
|
this.tags = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -253,8 +253,11 @@ export class AttemptCapturePhase extends PokemonPhase {
|
|||||||
|
|
||||||
globalScene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs);
|
globalScene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs);
|
||||||
|
|
||||||
|
const addStatus = new BooleanHolder(true);
|
||||||
|
applyChallenges(ChallengeType.POKEMON_ADD_TO_PARTY, pokemon, addStatus);
|
||||||
|
|
||||||
globalScene.ui.showText(
|
globalScene.ui.showText(
|
||||||
i18next.t("battle:pokemonCaught", {
|
i18next.t(addStatus.value ? "battle:pokemonCaught" : "battle:pokemonCaughtButChallenge", {
|
||||||
pokemonName: getPokemonNameWithAffix(pokemon),
|
pokemonName: getPokemonNameWithAffix(pokemon),
|
||||||
}),
|
}),
|
||||||
null,
|
null,
|
||||||
@ -290,8 +293,6 @@ export class AttemptCapturePhase extends PokemonPhase {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
Promise.all([pokemon.hideInfo(), globalScene.gameData.setPokemonCaught(pokemon)]).then(() => {
|
Promise.all([pokemon.hideInfo(), globalScene.gameData.setPokemonCaught(pokemon)]).then(() => {
|
||||||
const addStatus = new BooleanHolder(true);
|
|
||||||
applyChallenges(ChallengeType.POKEMON_ADD_TO_PARTY, pokemon, addStatus);
|
|
||||||
if (!addStatus.value) {
|
if (!addStatus.value) {
|
||||||
removePokemon();
|
removePokemon();
|
||||||
end();
|
end();
|
||||||
|
@ -16,8 +16,10 @@ export class SelectBiomePhase extends BattlePhase {
|
|||||||
|
|
||||||
globalScene.resetSeed();
|
globalScene.resetSeed();
|
||||||
|
|
||||||
|
const gameMode = globalScene.gameMode;
|
||||||
const currentBiome = globalScene.arena.biomeType;
|
const currentBiome = globalScene.arena.biomeType;
|
||||||
const nextWaveIndex = globalScene.currentBattle.waveIndex + 1;
|
const currentWaveIndex = globalScene.currentBattle.waveIndex;
|
||||||
|
const nextWaveIndex = currentWaveIndex + 1;
|
||||||
|
|
||||||
const setNextBiome = (nextBiome: BiomeId) => {
|
const setNextBiome = (nextBiome: BiomeId) => {
|
||||||
if (nextWaveIndex % 10 === 1) {
|
if (nextWaveIndex % 10 === 1) {
|
||||||
@ -26,6 +28,15 @@ export class SelectBiomePhase extends BattlePhase {
|
|||||||
applyChallenges(ChallengeType.PARTY_HEAL, healStatus);
|
applyChallenges(ChallengeType.PARTY_HEAL, healStatus);
|
||||||
if (healStatus.value) {
|
if (healStatus.value) {
|
||||||
globalScene.phaseManager.unshiftNew("PartyHealPhase", false);
|
globalScene.phaseManager.unshiftNew("PartyHealPhase", false);
|
||||||
|
} else {
|
||||||
|
globalScene.phaseManager.unshiftNew(
|
||||||
|
"SelectModifierPhase",
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
gameMode.isFixedBattle(currentWaveIndex)
|
||||||
|
? gameMode.getFixedBattle(currentWaveIndex).customModifierRewardSettings
|
||||||
|
: undefined,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
globalScene.phaseManager.unshiftNew("SwitchBiomePhase", nextBiome);
|
globalScene.phaseManager.unshiftNew("SwitchBiomePhase", nextBiome);
|
||||||
@ -33,12 +44,12 @@ export class SelectBiomePhase extends BattlePhase {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(globalScene.gameMode.isClassic && globalScene.gameMode.isWaveFinal(nextWaveIndex + 9)) ||
|
(gameMode.isClassic && gameMode.isWaveFinal(nextWaveIndex + 9)) ||
|
||||||
(globalScene.gameMode.isDaily && globalScene.gameMode.isWaveFinal(nextWaveIndex)) ||
|
(gameMode.isDaily && gameMode.isWaveFinal(nextWaveIndex)) ||
|
||||||
(globalScene.gameMode.hasShortBiomes && !(nextWaveIndex % 50))
|
(gameMode.hasShortBiomes && !(nextWaveIndex % 50))
|
||||||
) {
|
) {
|
||||||
setNextBiome(BiomeId.END);
|
setNextBiome(BiomeId.END);
|
||||||
} else if (globalScene.gameMode.hasRandomBiomes) {
|
} else if (gameMode.hasRandomBiomes) {
|
||||||
setNextBiome(this.generateNextBiome(nextWaveIndex));
|
setNextBiome(this.generateNextBiome(nextWaveIndex));
|
||||||
} else if (Array.isArray(biomeLinks[currentBiome])) {
|
} else if (Array.isArray(biomeLinks[currentBiome])) {
|
||||||
const biomes: BiomeId[] = (biomeLinks[currentBiome] as (BiomeId | [BiomeId, number])[])
|
const biomes: BiomeId[] = (biomeLinks[currentBiome] as (BiomeId | [BiomeId, number])[])
|
||||||
@ -73,9 +84,6 @@ export class SelectBiomePhase extends BattlePhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
generateNextBiome(waveIndex: number): BiomeId {
|
generateNextBiome(waveIndex: number): BiomeId {
|
||||||
if (!(waveIndex % 50)) {
|
return waveIndex % 50 === 0 ? BiomeId.END : globalScene.generateRandomBiome(waveIndex);
|
||||||
return BiomeId.END;
|
|
||||||
}
|
|
||||||
return globalScene.generateRandomBiome(waveIndex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,9 @@ import { globalScene } from "#app/global-scene";
|
|||||||
import { modifierTypes } from "#data/data-lists";
|
import { modifierTypes } from "#data/data-lists";
|
||||||
import { BattleType } from "#enums/battle-type";
|
import { BattleType } from "#enums/battle-type";
|
||||||
import type { BattlerIndex } from "#enums/battler-index";
|
import type { BattlerIndex } from "#enums/battler-index";
|
||||||
import { ChallengeType } from "#enums/challenge-type";
|
|
||||||
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
|
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
|
||||||
import type { CustomModifierSettings } from "#modifiers/modifier-type";
|
|
||||||
import { handleMysteryEncounterVictory } from "#mystery-encounters/encounter-phase-utils";
|
import { handleMysteryEncounterVictory } from "#mystery-encounters/encounter-phase-utils";
|
||||||
import { PokemonPhase } from "#phases/pokemon-phase";
|
import { PokemonPhase } from "#phases/pokemon-phase";
|
||||||
import { applyChallenges } from "#utils/challenge-utils";
|
|
||||||
import { BooleanHolder } from "#utils/common";
|
|
||||||
|
|
||||||
export class VictoryPhase extends PokemonPhase {
|
export class VictoryPhase extends PokemonPhase {
|
||||||
public readonly phaseName = "VictoryPhase";
|
public readonly phaseName = "VictoryPhase";
|
||||||
@ -49,15 +45,19 @@ export class VictoryPhase extends PokemonPhase {
|
|||||||
if (globalScene.currentBattle.battleType === BattleType.TRAINER) {
|
if (globalScene.currentBattle.battleType === BattleType.TRAINER) {
|
||||||
globalScene.phaseManager.pushNew("TrainerVictoryPhase");
|
globalScene.phaseManager.pushNew("TrainerVictoryPhase");
|
||||||
}
|
}
|
||||||
if (globalScene.gameMode.isEndless || !globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex)) {
|
|
||||||
|
const gameMode = globalScene.gameMode;
|
||||||
|
const currentWaveIndex = globalScene.currentBattle.waveIndex;
|
||||||
|
|
||||||
|
if (gameMode.isEndless || !gameMode.isWaveFinal(currentWaveIndex)) {
|
||||||
globalScene.phaseManager.pushNew("EggLapsePhase");
|
globalScene.phaseManager.pushNew("EggLapsePhase");
|
||||||
if (globalScene.gameMode.isClassic) {
|
if (gameMode.isClassic) {
|
||||||
switch (globalScene.currentBattle.waveIndex) {
|
switch (currentWaveIndex) {
|
||||||
case ClassicFixedBossWaves.RIVAL_1:
|
case ClassicFixedBossWaves.RIVAL_1:
|
||||||
case ClassicFixedBossWaves.RIVAL_2:
|
case ClassicFixedBossWaves.RIVAL_2:
|
||||||
// Get event modifiers for this wave
|
// Get event modifiers for this wave
|
||||||
timedEventManager
|
timedEventManager
|
||||||
.getFixedBattleEventRewards(globalScene.currentBattle.waveIndex)
|
.getFixedBattleEventRewards(currentWaveIndex)
|
||||||
.map(r => globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes[r]));
|
.map(r => globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes[r]));
|
||||||
break;
|
break;
|
||||||
case ClassicFixedBossWaves.EVIL_BOSS_2:
|
case ClassicFixedBossWaves.EVIL_BOSS_2:
|
||||||
@ -66,59 +66,53 @@ export class VictoryPhase extends PokemonPhase {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const healStatus = new BooleanHolder(globalScene.currentBattle.waveIndex % 10 === 0);
|
if (currentWaveIndex % 10) {
|
||||||
applyChallenges(ChallengeType.PARTY_HEAL, healStatus);
|
|
||||||
if (!healStatus.value) {
|
|
||||||
globalScene.phaseManager.pushNew(
|
globalScene.phaseManager.pushNew(
|
||||||
"SelectModifierPhase",
|
"SelectModifierPhase",
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
this.getFixedBattleCustomModifiers(),
|
gameMode.isFixedBattle(currentWaveIndex)
|
||||||
|
? gameMode.getFixedBattle(currentWaveIndex).customModifierRewardSettings
|
||||||
|
: undefined,
|
||||||
);
|
);
|
||||||
} else if (globalScene.gameMode.isDaily) {
|
} else if (gameMode.isDaily) {
|
||||||
globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes.EXP_CHARM);
|
globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes.EXP_CHARM);
|
||||||
if (
|
if (currentWaveIndex > 10 && !gameMode.isWaveFinal(currentWaveIndex)) {
|
||||||
globalScene.currentBattle.waveIndex > 10 &&
|
|
||||||
!globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex)
|
|
||||||
) {
|
|
||||||
globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes.GOLDEN_POKEBALL);
|
globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes.GOLDEN_POKEBALL);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const superExpWave = !globalScene.gameMode.isEndless ? (globalScene.offsetGym ? 0 : 20) : 10;
|
const superExpWave = !gameMode.isEndless ? (globalScene.offsetGym ? 0 : 20) : 10;
|
||||||
if (globalScene.gameMode.isEndless && globalScene.currentBattle.waveIndex === 10) {
|
if (gameMode.isEndless && currentWaveIndex === 10) {
|
||||||
globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes.EXP_SHARE);
|
globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes.EXP_SHARE);
|
||||||
}
|
}
|
||||||
if (
|
if (currentWaveIndex <= 750 && (currentWaveIndex <= 500 || currentWaveIndex % 30 === superExpWave)) {
|
||||||
globalScene.currentBattle.waveIndex <= 750 &&
|
|
||||||
(globalScene.currentBattle.waveIndex <= 500 || globalScene.currentBattle.waveIndex % 30 === superExpWave)
|
|
||||||
) {
|
|
||||||
globalScene.phaseManager.pushNew(
|
globalScene.phaseManager.pushNew(
|
||||||
"ModifierRewardPhase",
|
"ModifierRewardPhase",
|
||||||
globalScene.currentBattle.waveIndex % 30 !== superExpWave || globalScene.currentBattle.waveIndex > 250
|
currentWaveIndex % 30 !== superExpWave || currentWaveIndex > 250
|
||||||
? modifierTypes.EXP_CHARM
|
? modifierTypes.EXP_CHARM
|
||||||
: modifierTypes.SUPER_EXP_CHARM,
|
: modifierTypes.SUPER_EXP_CHARM,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (globalScene.currentBattle.waveIndex <= 150 && !(globalScene.currentBattle.waveIndex % 50)) {
|
if (currentWaveIndex <= 150 && !(currentWaveIndex % 50)) {
|
||||||
globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes.GOLDEN_POKEBALL);
|
globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes.GOLDEN_POKEBALL);
|
||||||
}
|
}
|
||||||
if (globalScene.gameMode.isEndless && !(globalScene.currentBattle.waveIndex % 50)) {
|
if (gameMode.isEndless && !(currentWaveIndex % 50)) {
|
||||||
globalScene.phaseManager.pushNew(
|
globalScene.phaseManager.pushNew(
|
||||||
"ModifierRewardPhase",
|
"ModifierRewardPhase",
|
||||||
!(globalScene.currentBattle.waveIndex % 250) ? modifierTypes.VOUCHER_PREMIUM : modifierTypes.VOUCHER_PLUS,
|
!(currentWaveIndex % 250) ? modifierTypes.VOUCHER_PREMIUM : modifierTypes.VOUCHER_PLUS,
|
||||||
);
|
);
|
||||||
globalScene.phaseManager.pushNew("AddEnemyBuffModifierPhase");
|
globalScene.phaseManager.pushNew("AddEnemyBuffModifierPhase");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) {
|
if (gameMode.hasRandomBiomes || globalScene.isNewBiome()) {
|
||||||
globalScene.phaseManager.pushNew("SelectBiomePhase");
|
globalScene.phaseManager.pushNew("SelectBiomePhase");
|
||||||
}
|
}
|
||||||
|
|
||||||
globalScene.phaseManager.pushNew("NewBattlePhase");
|
globalScene.phaseManager.pushNew("NewBattlePhase");
|
||||||
} else {
|
} else {
|
||||||
globalScene.currentBattle.battleType = BattleType.CLEAR;
|
globalScene.currentBattle.battleType = BattleType.CLEAR;
|
||||||
globalScene.score += globalScene.gameMode.getClearScoreBonus();
|
globalScene.score += gameMode.getClearScoreBonus();
|
||||||
globalScene.updateScoreText();
|
globalScene.updateScoreText();
|
||||||
globalScene.phaseManager.pushNew("GameOverPhase", true);
|
globalScene.phaseManager.pushNew("GameOverPhase", true);
|
||||||
}
|
}
|
||||||
@ -126,18 +120,4 @@ export class VictoryPhase extends PokemonPhase {
|
|||||||
|
|
||||||
this.end();
|
this.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* If this wave is a fixed battle with special custom modifier rewards,
|
|
||||||
* will pass those settings to the upcoming {@linkcode SelectModifierPhase}`.
|
|
||||||
*/
|
|
||||||
getFixedBattleCustomModifiers(): CustomModifierSettings | undefined {
|
|
||||||
const gameMode = globalScene.gameMode;
|
|
||||||
const waveIndex = globalScene.currentBattle.waveIndex;
|
|
||||||
if (gameMode.isFixedBattle(waveIndex)) {
|
|
||||||
return gameMode.getFixedBattle(waveIndex).customModifierRewardSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -448,6 +448,8 @@ export function getAchievementDescription(localizationKey: string): string {
|
|||||||
return i18next.t("achv:FLIP_STATS.description", { context: genderStr });
|
return i18next.t("achv:FLIP_STATS.description", { context: genderStr });
|
||||||
case "FLIP_INVERSE":
|
case "FLIP_INVERSE":
|
||||||
return i18next.t("achv:FLIP_INVERSE.description", { context: genderStr });
|
return i18next.t("achv:FLIP_INVERSE.description", { context: genderStr });
|
||||||
|
case "NUZLOCKE":
|
||||||
|
return i18next.t("achv:NUZLOCKE.description", { context: genderStr });
|
||||||
case "BREEDERS_IN_SPACE":
|
case "BREEDERS_IN_SPACE":
|
||||||
return i18next.t("achv:BREEDERS_IN_SPACE.description", {
|
return i18next.t("achv:BREEDERS_IN_SPACE.description", {
|
||||||
context: genderStr,
|
context: genderStr,
|
||||||
|
@ -35,7 +35,7 @@ import { TrainerVariant } from "#enums/trainer-variant";
|
|||||||
import { UiMode } from "#enums/ui-mode";
|
import { UiMode } from "#enums/ui-mode";
|
||||||
import { Unlockables } from "#enums/unlockables";
|
import { Unlockables } from "#enums/unlockables";
|
||||||
import { WeatherType } from "#enums/weather-type";
|
import { WeatherType } from "#enums/weather-type";
|
||||||
import { TagAddedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#events/arena";
|
import { ArenaTagAddedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#events/arena";
|
||||||
import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon";
|
import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon";
|
||||||
// biome-ignore lint/performance/noNamespaceImport: Something weird is going on here and I don't want to touch it
|
// biome-ignore lint/performance/noNamespaceImport: Something weird is going on here and I don't want to touch it
|
||||||
import * as Modifier from "#modifiers/modifier";
|
import * as Modifier from "#modifiers/modifier";
|
||||||
@ -1113,36 +1113,30 @@ export class GameData {
|
|||||||
});
|
});
|
||||||
|
|
||||||
globalScene.arena.weather = sessionData.arena.weather;
|
globalScene.arena.weather = sessionData.arena.weather;
|
||||||
|
if (globalScene.arena.getWeatherType() !== WeatherType.NONE) {
|
||||||
globalScene.arena.eventTarget.dispatchEvent(
|
globalScene.arena.eventTarget.dispatchEvent(
|
||||||
new WeatherChangedEvent(
|
new WeatherChangedEvent(globalScene.arena.getWeatherType(), globalScene.arena.weather?.turnsLeft!),
|
||||||
WeatherType.NONE,
|
);
|
||||||
globalScene.arena.weather?.weatherType!,
|
}
|
||||||
globalScene.arena.weather?.turnsLeft!,
|
|
||||||
),
|
|
||||||
); // TODO: is this bang correct?
|
|
||||||
|
|
||||||
globalScene.arena.terrain = sessionData.arena.terrain;
|
globalScene.arena.terrain = sessionData.arena.terrain;
|
||||||
|
if (globalScene.arena.getTerrainType() !== TerrainType.NONE) {
|
||||||
globalScene.arena.eventTarget.dispatchEvent(
|
globalScene.arena.eventTarget.dispatchEvent(
|
||||||
new TerrainChangedEvent(
|
new TerrainChangedEvent(globalScene.arena.getTerrainType(), globalScene.arena.terrain?.turnsLeft!),
|
||||||
TerrainType.NONE,
|
);
|
||||||
globalScene.arena.terrain?.terrainType!,
|
}
|
||||||
globalScene.arena.terrain?.turnsLeft!,
|
|
||||||
),
|
|
||||||
); // TODO: is this bang correct?
|
|
||||||
|
|
||||||
globalScene.arena.playerTerasUsed = sessionData.arena.playerTerasUsed;
|
globalScene.arena.playerTerasUsed = sessionData.arena.playerTerasUsed;
|
||||||
|
|
||||||
globalScene.arena.tags = sessionData.arena.tags;
|
globalScene.arena.tags = sessionData.arena.tags;
|
||||||
if (globalScene.arena.tags) {
|
|
||||||
for (const tag of globalScene.arena.tags) {
|
for (const tag of globalScene.arena.tags) {
|
||||||
if (tag instanceof ArenaTrapTag) {
|
if (tag instanceof ArenaTrapTag) {
|
||||||
const { tagType, side, turnCount, layers, maxLayers } = tag as ArenaTrapTag;
|
const { tagType, side, turnCount, layers, maxLayers } = tag;
|
||||||
globalScene.arena.eventTarget.dispatchEvent(
|
globalScene.arena.eventTarget.dispatchEvent(
|
||||||
new TagAddedEvent(tagType, side, turnCount, layers, maxLayers),
|
new ArenaTagAddedEvent(tagType, side, turnCount, [layers, maxLayers]),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
globalScene.arena.eventTarget.dispatchEvent(new TagAddedEvent(tag.tagType, tag.side, tag.turnCount));
|
globalScene.arena.eventTarget.dispatchEvent(new ArenaTagAddedEvent(tag.tagType, tag.side, tag.turnCount));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,61 +1,68 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { ArenaTrapTag } from "#data/arena-tag";
|
// biome-ignore-start lint/correctness/noUnusedImports: TSDocs
|
||||||
import { TerrainType } from "#data/terrain";
|
import type { ArenaTag } from "#data/arena-tag";
|
||||||
|
import { type Terrain, TerrainType } from "#data/terrain";
|
||||||
|
import type { Weather } from "#data/weather";
|
||||||
|
import { ArenaEventType } from "#enums/arena-event-type";
|
||||||
|
// biome-ignore-end lint/correctness/noUnusedImports: TSDocs
|
||||||
import { ArenaTagSide } from "#enums/arena-tag-side";
|
import { ArenaTagSide } from "#enums/arena-tag-side";
|
||||||
import { ArenaTagType } from "#enums/arena-tag-type";
|
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||||
import { TextStyle } from "#enums/text-style";
|
import { TextStyle } from "#enums/text-style";
|
||||||
import { WeatherType } from "#enums/weather-type";
|
import { WeatherType } from "#enums/weather-type";
|
||||||
import type { ArenaEvent } from "#events/arena";
|
import type { ArenaTagAddedEvent, ArenaTagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#events/arena";
|
||||||
import {
|
|
||||||
ArenaEventType,
|
|
||||||
TagAddedEvent,
|
|
||||||
TagRemovedEvent,
|
|
||||||
TerrainChangedEvent,
|
|
||||||
WeatherChangedEvent,
|
|
||||||
} from "#events/arena";
|
|
||||||
import type { TurnEndEvent } from "#events/battle-scene";
|
|
||||||
import { BattleSceneEventType } from "#events/battle-scene";
|
import { BattleSceneEventType } from "#events/battle-scene";
|
||||||
import { addTextObject } from "#ui/text";
|
import { addTextObject } from "#ui/text";
|
||||||
import { TimeOfDayWidget } from "#ui/time-of-day-widget";
|
import { TimeOfDayWidget } from "#ui/time-of-day-widget";
|
||||||
import { addWindow, WindowVariant } from "#ui/ui-theme";
|
import { addWindow, WindowVariant } from "#ui/ui-theme";
|
||||||
import { fixedInt } from "#utils/common";
|
import { fixedInt } from "#utils/common";
|
||||||
import { toCamelCase, toTitleCase } from "#utils/strings";
|
import { toCamelCase } from "#utils/strings";
|
||||||
import type { ParseKeys } from "i18next";
|
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
|
||||||
/** Enum used to differentiate {@linkcode Arena} effects */
|
// #region Interfaces
|
||||||
enum ArenaEffectType {
|
|
||||||
PLAYER,
|
|
||||||
WEATHER,
|
|
||||||
TERRAIN,
|
|
||||||
FIELD,
|
|
||||||
ENEMY,
|
|
||||||
}
|
|
||||||
/** Container for info about an {@linkcode Arena}'s effects */
|
|
||||||
interface ArenaEffectInfo {
|
|
||||||
/** The enum string representation of the effect */
|
|
||||||
name: string;
|
|
||||||
/** {@linkcode ArenaEffectType} type of effect */
|
|
||||||
effectType: ArenaEffectType;
|
|
||||||
|
|
||||||
/** The maximum duration set by the effect */
|
/** Base container for info about the currently active {@linkcode Weather}. */
|
||||||
|
interface WeatherInfo {
|
||||||
|
/** The localized name of the weather. */
|
||||||
|
name: string;
|
||||||
|
/** The initial duration of the weather effect, or `0` if it should last indefinitely. */
|
||||||
maxDuration: number;
|
maxDuration: number;
|
||||||
/** The current duration left on the effect */
|
/** The current duration left on the weather. */
|
||||||
duration: number;
|
duration: number;
|
||||||
/** The arena tag type being added */
|
/** The current {@linkcode WeatherType}. */
|
||||||
|
weatherType: WeatherType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Base container for info about the currently active {@linkcode Terrain}. */
|
||||||
|
interface TerrainInfo {
|
||||||
|
/** The localized name of the terrain. */
|
||||||
|
name: string;
|
||||||
|
/** The initial duration of the terrain effect, or `0` if it should last indefinitely. */
|
||||||
|
maxDuration: number;
|
||||||
|
/** The current duration left on the terrain. */
|
||||||
|
duration: number;
|
||||||
|
/** The current {@linkcode TerrainType}. */
|
||||||
|
terrainType: TerrainType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Interface for info about an {@linkcode ArenaTag}'s effects */
|
||||||
|
interface ArenaTagInfo {
|
||||||
|
/** The localized name of the tag. */
|
||||||
|
name: string;
|
||||||
|
/** The {@linkcode ArenaTagSide} that the tag applies to. */
|
||||||
|
side: ArenaTagSide;
|
||||||
|
/** The maximum duration of the tag, or `0` if it should last indefinitely. */
|
||||||
|
maxDuration: number;
|
||||||
|
/** The current duration left on the tag. */
|
||||||
|
duration: number;
|
||||||
|
/** The tag's {@linkcode ArenaTagType}. */
|
||||||
tagType?: ArenaTagType;
|
tagType?: ArenaTagType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFieldEffectText(arenaTagType: string): string {
|
// #endregion interfaces
|
||||||
if (!arenaTagType || arenaTagType === ArenaTagType.NONE) {
|
|
||||||
return arenaTagType;
|
|
||||||
}
|
|
||||||
const effectName = toCamelCase(arenaTagType);
|
|
||||||
const i18nKey = `arenaFlyout:${effectName}` as ParseKeys;
|
|
||||||
const resultName = i18next.t(i18nKey);
|
|
||||||
return !resultName || resultName === i18nKey ? toTitleCase(arenaTagType) : resultName;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to display and update the on-screen arena flyout.
|
||||||
|
*/
|
||||||
export class ArenaFlyout extends Phaser.GameObjects.Container {
|
export class ArenaFlyout extends Phaser.GameObjects.Container {
|
||||||
/** The restricted width of the flyout which should be drawn to */
|
/** The restricted width of the flyout which should be drawn to */
|
||||||
private flyoutWidth = 170;
|
private flyoutWidth = 170;
|
||||||
@ -98,14 +105,20 @@ export class ArenaFlyout extends Phaser.GameObjects.Container {
|
|||||||
/** The {@linkcode Phaser.GameObjects.Text} used to indicate field effects */
|
/** The {@linkcode Phaser.GameObjects.Text} used to indicate field effects */
|
||||||
private flyoutTextField: Phaser.GameObjects.Text;
|
private flyoutTextField: Phaser.GameObjects.Text;
|
||||||
|
|
||||||
/** Container for all field effects observed by this object */
|
/** Holds info about the current active {@linkcode Weather}, if any are active. */
|
||||||
private readonly fieldEffectInfo: ArenaEffectInfo[] = [];
|
private weatherInfo?: WeatherInfo;
|
||||||
|
/** Holds info about the current active {@linkcode Terrain}, if any are active. */
|
||||||
|
private terrainInfo?: TerrainInfo;
|
||||||
|
|
||||||
// Stores callbacks in a variable so they can be unsubscribed from when destroyed
|
/** Container for all {@linkcode ArenaTag}s observed by this object. */
|
||||||
private readonly onNewArenaEvent = (event: Event) => this.onNewArena(event);
|
private arenaTags: ArenaTagInfo[] = [];
|
||||||
private readonly onTurnEndEvent = (event: Event) => this.onTurnEnd(event);
|
|
||||||
|
|
||||||
private readonly onFieldEffectChangedEvent = (event: Event) => this.onFieldEffectChanged(event);
|
private readonly onNewArenaEvent = () => this.onNewArena();
|
||||||
|
private readonly onTurnEndEvent = () => this.onTurnEnd();
|
||||||
|
private readonly onWeatherChangedEvent = (event: WeatherChangedEvent) => this.onWeatherChanged(event);
|
||||||
|
private readonly onTerrainChangedEvent = (event: TerrainChangedEvent) => this.onTerrainChanged(event);
|
||||||
|
private readonly onArenaTagAddedEvent = (event: ArenaTagAddedEvent) => this.onArenaTagAdded(event);
|
||||||
|
private readonly onArenaTagRemovedEvent = (event: ArenaTagRemovedEvent) => this.onArenaTagRemoved(event);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(globalScene, 0, 0);
|
super(globalScene, 0, 0);
|
||||||
@ -213,202 +226,143 @@ export class ArenaFlyout extends Phaser.GameObjects.Container {
|
|||||||
this.name = "Fight Flyout";
|
this.name = "Fight Flyout";
|
||||||
this.flyoutParent.name = "Fight Flyout Parent";
|
this.flyoutParent.name = "Fight Flyout Parent";
|
||||||
|
|
||||||
// Subscribes to required events available on game start
|
// Subscribe to required events available on game start
|
||||||
globalScene.eventTarget.addEventListener(BattleSceneEventType.NEW_ARENA, this.onNewArenaEvent);
|
globalScene.eventTarget.addEventListener(BattleSceneEventType.NEW_ARENA, this.onNewArenaEvent);
|
||||||
globalScene.eventTarget.addEventListener(BattleSceneEventType.TURN_END, this.onTurnEndEvent);
|
globalScene.eventTarget.addEventListener(BattleSceneEventType.TURN_END, this.onTurnEndEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onNewArena(_event: Event) {
|
/**
|
||||||
this.fieldEffectInfo.length = 0;
|
* Initialize listeners upon creating a new arena.
|
||||||
|
*/
|
||||||
|
private onNewArena() {
|
||||||
|
this.arenaTags = [];
|
||||||
|
|
||||||
// Subscribes to required events available on battle start
|
// Subscribe to required events available on battle start
|
||||||
globalScene.arena.eventTarget.addEventListener(ArenaEventType.WEATHER_CHANGED, this.onFieldEffectChangedEvent);
|
globalScene.arena.eventTarget.addEventListener(ArenaEventType.WEATHER_CHANGED, this.onWeatherChangedEvent);
|
||||||
globalScene.arena.eventTarget.addEventListener(ArenaEventType.TERRAIN_CHANGED, this.onFieldEffectChangedEvent);
|
globalScene.arena.eventTarget.addEventListener(ArenaEventType.TERRAIN_CHANGED, this.onTerrainChangedEvent);
|
||||||
globalScene.arena.eventTarget.addEventListener(ArenaEventType.TAG_ADDED, this.onFieldEffectChangedEvent);
|
globalScene.arena.eventTarget.addEventListener(ArenaEventType.ARENA_TAG_ADDED, this.onArenaTagAddedEvent);
|
||||||
globalScene.arena.eventTarget.addEventListener(ArenaEventType.TAG_REMOVED, this.onFieldEffectChangedEvent);
|
globalScene.arena.eventTarget.addEventListener(ArenaEventType.ARENA_TAG_REMOVED, this.onArenaTagRemovedEvent);
|
||||||
}
|
|
||||||
|
|
||||||
/** Clears out the current string stored in all arena effect texts */
|
|
||||||
private clearText() {
|
|
||||||
this.flyoutTextPlayer.text = "";
|
|
||||||
this.flyoutTextField.text = "";
|
|
||||||
this.flyoutTextEnemy.text = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Parses through all set Arena Effects and puts them into the proper {@linkcode Phaser.GameObjects.Text} object */
|
|
||||||
private updateFieldText() {
|
|
||||||
this.clearText();
|
|
||||||
|
|
||||||
this.fieldEffectInfo.sort((infoA, infoB) => infoA.duration - infoB.duration);
|
|
||||||
|
|
||||||
for (let i = 0; i < this.fieldEffectInfo.length; i++) {
|
|
||||||
const fieldEffectInfo = this.fieldEffectInfo[i];
|
|
||||||
|
|
||||||
// Creates a proxy object to decide which text object needs to be updated
|
|
||||||
let textObject: Phaser.GameObjects.Text;
|
|
||||||
switch (fieldEffectInfo.effectType) {
|
|
||||||
case ArenaEffectType.PLAYER:
|
|
||||||
textObject = this.flyoutTextPlayer;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ArenaEffectType.WEATHER:
|
|
||||||
case ArenaEffectType.TERRAIN:
|
|
||||||
case ArenaEffectType.FIELD:
|
|
||||||
textObject = this.flyoutTextField;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ArenaEffectType.ENEMY:
|
|
||||||
textObject = this.flyoutTextEnemy;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
textObject.text += fieldEffectInfo.name;
|
|
||||||
|
|
||||||
if (fieldEffectInfo.maxDuration !== 0) {
|
|
||||||
textObject.text += " " + fieldEffectInfo.duration + "/" + fieldEffectInfo.maxDuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
textObject.text += "\n";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the {@linkcode Event} being passed and updates the state of the fieldEffectInfo array
|
* Iterate through all currently present tags effects and decrement their durations.
|
||||||
* @param event {@linkcode Event} being sent
|
|
||||||
*/
|
*/
|
||||||
private onFieldEffectChanged(event: Event) {
|
private onTurnEnd() {
|
||||||
const arenaEffectChangedEvent = event as ArenaEvent;
|
// Remove all objects with positive max durations and whose durations have expired.
|
||||||
if (!arenaEffectChangedEvent) {
|
this.arenaTags = this.arenaTags.filter(info => info.maxDuration === 0 || --info.duration >= 0);
|
||||||
|
|
||||||
|
this.updateFieldText();
|
||||||
|
}
|
||||||
|
|
||||||
|
// #region ArenaTags
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a recently-created {@linkcode ArenaTag} to the flyout.
|
||||||
|
* @param event - The {@linkcode ArenaTagAddedEvent} having been emitted
|
||||||
|
*/
|
||||||
|
private onArenaTagAdded(event: ArenaTagAddedEvent): void {
|
||||||
|
const name = this.localizeEffectName(ArenaTagType[event.tagType]);
|
||||||
|
// Ternary used to avoid unneeded find
|
||||||
|
const existingTrapTag =
|
||||||
|
event.trapLayers !== undefined
|
||||||
|
? this.arenaTags.find(e => e.tagType === event.tagType && e.side === event.side)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
// If we got signalled for a layer count update, update the existing trap's name.
|
||||||
|
// Otherwise, push it to the array.
|
||||||
|
if (event.trapLayers !== undefined && existingTrapTag) {
|
||||||
|
this.updateTrapLayers(existingTrapTag, event.trapLayers, name);
|
||||||
|
} else {
|
||||||
|
this.arenaTags.push({
|
||||||
|
name,
|
||||||
|
side: event.side,
|
||||||
|
maxDuration: event.duration,
|
||||||
|
duration: event.duration,
|
||||||
|
tagType: event.tagType,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.updateFieldText();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an existing trap tag with an updated layer count whenever one is overlapped.
|
||||||
|
* @param existingTag - The existing {@linkcode ArenaTagInfo} being updated
|
||||||
|
* @param layers - The base number of layers of the new tag
|
||||||
|
* @param maxLayers - The maximum number of layers of the new tag; will not show layer count if `<=0`
|
||||||
|
* @param name - The name of the tag.
|
||||||
|
*/
|
||||||
|
private updateTrapLayers(existingTag: ArenaTagInfo, [layers, maxLayers]: [number, number], name: string): void {
|
||||||
|
const layerStr = maxLayers > 1 ? ` (${layers})` : "";
|
||||||
|
existingTag.name = `${name}${layerStr}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a recently-culled {@linkcode ArenaTag} from the flyout.
|
||||||
|
* @param event - The {@linkcode ArenaTagRemovedEvent} having been emitted
|
||||||
|
*/
|
||||||
|
private onArenaTagRemoved(event: ArenaTagRemovedEvent): void {
|
||||||
|
const foundIndex = this.arenaTags.findIndex(info => info.tagType === event.tagType && info.side === event.side);
|
||||||
|
|
||||||
|
if (foundIndex > -1) {
|
||||||
|
// If the tag was being tracked, remove it
|
||||||
|
this.arenaTags.splice(foundIndex, 1);
|
||||||
|
this.updateFieldText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #endregion ArenaTags
|
||||||
|
|
||||||
|
// #region Weather/Terrain
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the current weather text when the weather changes.
|
||||||
|
* @param event - The {@linkcode WeatherChangedEvent} having been emitted
|
||||||
|
*/
|
||||||
|
private onWeatherChanged(event: WeatherChangedEvent) {
|
||||||
|
// If weather was reset, clear the current data.
|
||||||
|
if (event.weatherType === WeatherType.NONE) {
|
||||||
|
this.weatherInfo = undefined;
|
||||||
|
this.updateFieldText();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let foundIndex: number;
|
this.weatherInfo = {
|
||||||
switch (arenaEffectChangedEvent.constructor) {
|
name: this.localizeEffectName(WeatherType[event.weatherType]),
|
||||||
case TagAddedEvent: {
|
maxDuration: event.duration,
|
||||||
const tagAddedEvent = arenaEffectChangedEvent as TagAddedEvent;
|
duration: event.duration,
|
||||||
const isArenaTrapTag = globalScene.arena.getTag(tagAddedEvent.arenaTagType) instanceof ArenaTrapTag;
|
weatherType: event.weatherType,
|
||||||
let arenaEffectType: ArenaEffectType;
|
|
||||||
|
|
||||||
if (tagAddedEvent.arenaTagSide === ArenaTagSide.BOTH) {
|
|
||||||
arenaEffectType = ArenaEffectType.FIELD;
|
|
||||||
} else if (tagAddedEvent.arenaTagSide === ArenaTagSide.PLAYER) {
|
|
||||||
arenaEffectType = ArenaEffectType.PLAYER;
|
|
||||||
} else {
|
|
||||||
arenaEffectType = ArenaEffectType.ENEMY;
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingTrapTagIndex = isArenaTrapTag
|
|
||||||
? this.fieldEffectInfo.findIndex(
|
|
||||||
e => tagAddedEvent.arenaTagType === e.tagType && arenaEffectType === e.effectType,
|
|
||||||
)
|
|
||||||
: -1;
|
|
||||||
let name: string = getFieldEffectText(ArenaTagType[tagAddedEvent.arenaTagType]);
|
|
||||||
|
|
||||||
if (isArenaTrapTag) {
|
|
||||||
if (existingTrapTagIndex !== -1) {
|
|
||||||
const layers = tagAddedEvent.arenaTagMaxLayers > 1 ? ` (${tagAddedEvent.arenaTagLayers})` : "";
|
|
||||||
this.fieldEffectInfo[existingTrapTagIndex].name = `${name}${layers}`;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (tagAddedEvent.arenaTagMaxLayers > 1) {
|
|
||||||
name = `${name} (${tagAddedEvent.arenaTagLayers})`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.fieldEffectInfo.push({
|
|
||||||
name,
|
|
||||||
effectType: arenaEffectType,
|
|
||||||
maxDuration: tagAddedEvent.duration,
|
|
||||||
duration: tagAddedEvent.duration,
|
|
||||||
tagType: tagAddedEvent.arenaTagType,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case TagRemovedEvent: {
|
|
||||||
const tagRemovedEvent = arenaEffectChangedEvent as TagRemovedEvent;
|
|
||||||
foundIndex = this.fieldEffectInfo.findIndex(info => info.tagType === tagRemovedEvent.arenaTagType);
|
|
||||||
|
|
||||||
if (foundIndex !== -1) {
|
|
||||||
// If the tag was being tracked, remove it
|
|
||||||
this.fieldEffectInfo.splice(foundIndex, 1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case WeatherChangedEvent:
|
|
||||||
case TerrainChangedEvent: {
|
|
||||||
const fieldEffectChangedEvent = arenaEffectChangedEvent as WeatherChangedEvent | TerrainChangedEvent;
|
|
||||||
|
|
||||||
// Stores the old Weather/Terrain name in case it's in the array already
|
|
||||||
const oldName = getFieldEffectText(
|
|
||||||
fieldEffectChangedEvent instanceof WeatherChangedEvent
|
|
||||||
? WeatherType[fieldEffectChangedEvent.oldWeatherType]
|
|
||||||
: TerrainType[fieldEffectChangedEvent.oldTerrainType],
|
|
||||||
);
|
|
||||||
// Stores the new Weather/Terrain info
|
|
||||||
const newInfo = {
|
|
||||||
name: getFieldEffectText(
|
|
||||||
fieldEffectChangedEvent instanceof WeatherChangedEvent
|
|
||||||
? WeatherType[fieldEffectChangedEvent.newWeatherType]
|
|
||||||
: TerrainType[fieldEffectChangedEvent.newTerrainType],
|
|
||||||
),
|
|
||||||
effectType:
|
|
||||||
fieldEffectChangedEvent instanceof WeatherChangedEvent ? ArenaEffectType.WEATHER : ArenaEffectType.TERRAIN,
|
|
||||||
maxDuration: fieldEffectChangedEvent.duration,
|
|
||||||
duration: fieldEffectChangedEvent.duration,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
foundIndex = this.fieldEffectInfo.findIndex(info => [newInfo.name, oldName].includes(info.name));
|
|
||||||
if (foundIndex === -1) {
|
|
||||||
if (newInfo.name !== undefined) {
|
|
||||||
this.fieldEffectInfo.push(newInfo); // Adds the info to the array if it doesn't already exist and is defined
|
|
||||||
}
|
|
||||||
} else if (!newInfo.name) {
|
|
||||||
this.fieldEffectInfo.splice(foundIndex, 1); // Removes the old info if the new one is undefined
|
|
||||||
} else {
|
|
||||||
this.fieldEffectInfo[foundIndex] = newInfo; // Otherwise, replace the old info
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateFieldText();
|
this.updateFieldText();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iterates through the fieldEffectInfo array and decrements the duration of each item
|
* Update the current terrain text when the terrain changes.
|
||||||
* @param event {@linkcode Event} being sent
|
* @param event - The {@linkcode TerrainChangedEvent} having been emitted
|
||||||
*/
|
*/
|
||||||
private onTurnEnd(event: Event) {
|
private onTerrainChanged(event: TerrainChangedEvent) {
|
||||||
const turnEndEvent = event as TurnEndEvent;
|
// If terrain was reset, clear the current data.
|
||||||
if (!turnEndEvent) {
|
if (event.terrainType === TerrainType.NONE) {
|
||||||
|
this.terrainInfo = undefined;
|
||||||
|
this.updateFieldText();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fieldEffectInfo: ArenaEffectInfo[] = [];
|
this.terrainInfo = {
|
||||||
this.fieldEffectInfo.forEach(i => fieldEffectInfo.push(i));
|
name: this.localizeEffectName(TerrainType[event.terrainType]),
|
||||||
|
maxDuration: event.duration,
|
||||||
for (let i = 0; i < fieldEffectInfo.length; i++) {
|
duration: event.duration,
|
||||||
const info = fieldEffectInfo[i];
|
terrainType: event.terrainType,
|
||||||
|
};
|
||||||
if (info.maxDuration === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
--info.duration;
|
|
||||||
if (info.duration <= 0) {
|
|
||||||
// Removes the item if the duration has expired
|
|
||||||
this.fieldEffectInfo.splice(this.fieldEffectInfo.indexOf(info), 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateFieldText();
|
this.updateFieldText();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #endregion Weather/Terrain
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Animates the flyout to either show or hide it by applying a fade and translation
|
* Animate the flyout to either show or hide the modal.
|
||||||
* @param visible Should the flyout be shown?
|
* @param visible - Whether the the flyout should be shown
|
||||||
*/
|
*/
|
||||||
public toggleFlyout(visible: boolean): void {
|
public toggleFlyout(visible: boolean): void {
|
||||||
globalScene.tweens.add({
|
globalScene.tweens.add({
|
||||||
@ -421,15 +375,96 @@ export class ArenaFlyout extends Phaser.GameObjects.Container {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Destroy this element and remove all associated listeners. */
|
||||||
public destroy(fromScene?: boolean): void {
|
public destroy(fromScene?: boolean): void {
|
||||||
globalScene.eventTarget.removeEventListener(BattleSceneEventType.NEW_ARENA, this.onNewArenaEvent);
|
globalScene.eventTarget.removeEventListener(BattleSceneEventType.NEW_ARENA, this.onNewArenaEvent);
|
||||||
globalScene.eventTarget.removeEventListener(BattleSceneEventType.TURN_END, this.onTurnEndEvent);
|
globalScene.eventTarget.removeEventListener(BattleSceneEventType.TURN_END, this.onTurnEndEvent);
|
||||||
|
|
||||||
globalScene.arena.eventTarget.removeEventListener(ArenaEventType.WEATHER_CHANGED, this.onFieldEffectChangedEvent);
|
globalScene.arena.eventTarget.removeEventListener(ArenaEventType.WEATHER_CHANGED, this.onWeatherChanged);
|
||||||
globalScene.arena.eventTarget.removeEventListener(ArenaEventType.TERRAIN_CHANGED, this.onFieldEffectChangedEvent);
|
globalScene.arena.eventTarget.removeEventListener(ArenaEventType.TERRAIN_CHANGED, this.onTerrainChanged);
|
||||||
globalScene.arena.eventTarget.removeEventListener(ArenaEventType.TAG_ADDED, this.onFieldEffectChangedEvent);
|
globalScene.arena.eventTarget.removeEventListener(ArenaEventType.ARENA_TAG_ADDED, this.onArenaTagAddedEvent);
|
||||||
globalScene.arena.eventTarget.removeEventListener(ArenaEventType.TAG_REMOVED, this.onFieldEffectChangedEvent);
|
globalScene.arena.eventTarget.removeEventListener(ArenaEventType.ARENA_TAG_REMOVED, this.onArenaTagRemovedEvent);
|
||||||
|
|
||||||
super.destroy(fromScene);
|
super.destroy(fromScene);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Clear out the contents of all arena texts. */
|
||||||
|
private clearText() {
|
||||||
|
this.flyoutTextPlayer.text = "";
|
||||||
|
this.flyoutTextField.text = "";
|
||||||
|
this.flyoutTextEnemy.text = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// #region Text display functions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterate over all field effects and update the corresponding {@linkcode Phaser.GameObjects.Text} object.
|
||||||
|
*/
|
||||||
|
private updateFieldText(): void {
|
||||||
|
this.clearText();
|
||||||
|
|
||||||
|
// Weather and terrain go first
|
||||||
|
if (this.weatherInfo) {
|
||||||
|
this.flyoutTextField.text += this.getTagText(this.weatherInfo);
|
||||||
|
}
|
||||||
|
if (this.terrainInfo) {
|
||||||
|
this.flyoutTextField.text += this.getTagText(this.terrainInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort and update all arena tag text
|
||||||
|
this.arenaTags.sort((infoA, infoB) => infoA.duration - infoB.duration);
|
||||||
|
for (const tag of this.arenaTags) {
|
||||||
|
this.getArenaTagTargetObj(tag.side).text += this.getTagText(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to retrieve the flyout text for a given effect's info.
|
||||||
|
* @param info - The {@linkcode ArenaTagInfo}, {@linkcode TerrainInfo} or {@linkcode WeatherInfo} being updated
|
||||||
|
* @returns The text to be added to the container
|
||||||
|
*/
|
||||||
|
private getTagText(info: ArenaTagInfo | WeatherInfo | TerrainInfo): string {
|
||||||
|
let text = info.name;
|
||||||
|
|
||||||
|
if (info.maxDuration > 0) {
|
||||||
|
text += ` ${info.duration}/${info.maxDuration}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
text += "\n";
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to select the text object needing to be updated depending on the current tag's side.
|
||||||
|
* @param side - The {@linkcode ArenaTagSide} of the tag being updated
|
||||||
|
* @returns The {@linkcode Phaser.GameObjects.Text} to be updated.
|
||||||
|
*/
|
||||||
|
private getArenaTagTargetObj(side: ArenaTagSide): Phaser.GameObjects.Text {
|
||||||
|
switch (side) {
|
||||||
|
case ArenaTagSide.PLAYER:
|
||||||
|
return this.flyoutTextPlayer;
|
||||||
|
case ArenaTagSide.ENEMY:
|
||||||
|
return this.flyoutTextEnemy;
|
||||||
|
case ArenaTagSide.BOTH:
|
||||||
|
return this.flyoutTextField;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// # endregion Text display functions
|
||||||
|
|
||||||
|
// #region Utilities
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the localized text for a given effect.
|
||||||
|
* @param text - The raw text of the effect; assumed to be in `UPPER_SNAKE_CASE` from a reverse mapping.
|
||||||
|
* @returns The localized text for the effect.
|
||||||
|
*/
|
||||||
|
private localizeEffectName(text: string): string {
|
||||||
|
const effectName = toCamelCase(text);
|
||||||
|
const i18nKey = `arenaFlyout:${effectName}`;
|
||||||
|
const resultName = i18next.t(i18nKey);
|
||||||
|
return resultName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// #endregion Utility emthods
|
||||||
}
|
}
|
||||||
|
@ -208,6 +208,26 @@ export class PokedexMonContainer extends Phaser.GameObjects.Container {
|
|||||||
);
|
);
|
||||||
this.checkIconId(defaultProps.female, defaultProps.formIndex, defaultProps.shiny, defaultProps.variant);
|
this.checkIconId(defaultProps.female, defaultProps.formIndex, defaultProps.shiny, defaultProps.variant);
|
||||||
this.add(this.icon);
|
this.add(this.icon);
|
||||||
|
|
||||||
|
[
|
||||||
|
this.hiddenAbilityIcon,
|
||||||
|
this.favoriteIcon,
|
||||||
|
this.classicWinIcon,
|
||||||
|
this.candyUpgradeIcon,
|
||||||
|
this.candyUpgradeOverlayIcon,
|
||||||
|
this.eggMove1Icon,
|
||||||
|
this.tmMove1Icon,
|
||||||
|
this.eggMove2Icon,
|
||||||
|
this.tmMove2Icon,
|
||||||
|
this.passive1Icon,
|
||||||
|
this.passive2Icon,
|
||||||
|
this.passive1OverlayIcon,
|
||||||
|
this.passive2OverlayIcon,
|
||||||
|
].forEach(icon => {
|
||||||
|
if (icon) {
|
||||||
|
this.bringToTop(icon);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
checkIconId(female, formIndex, shiny, variant) {
|
checkIconId(female, formIndex, shiny, variant) {
|
||||||
|
@ -410,6 +410,11 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
new DropDownLabel(i18next.t("filterBar:hasHiddenAbility"), undefined, DropDownState.ON),
|
new DropDownLabel(i18next.t("filterBar:hasHiddenAbility"), undefined, DropDownState.ON),
|
||||||
new DropDownLabel(i18next.t("filterBar:noHiddenAbility"), undefined, DropDownState.EXCLUDE),
|
new DropDownLabel(i18next.t("filterBar:noHiddenAbility"), undefined, DropDownState.EXCLUDE),
|
||||||
];
|
];
|
||||||
|
const seenSpeciesLabels = [
|
||||||
|
new DropDownLabel(i18next.t("filterBar:seenSpecies"), undefined, DropDownState.OFF),
|
||||||
|
new DropDownLabel(i18next.t("filterBar:isSeen"), undefined, DropDownState.ON),
|
||||||
|
new DropDownLabel(i18next.t("filterBar:isUnseen"), undefined, DropDownState.EXCLUDE),
|
||||||
|
];
|
||||||
const eggLabels = [
|
const eggLabels = [
|
||||||
new DropDownLabel(i18next.t("filterBar:egg"), undefined, DropDownState.OFF),
|
new DropDownLabel(i18next.t("filterBar:egg"), undefined, DropDownState.OFF),
|
||||||
new DropDownLabel(i18next.t("filterBar:eggPurchasable"), undefined, DropDownState.ON),
|
new DropDownLabel(i18next.t("filterBar:eggPurchasable"), undefined, DropDownState.ON),
|
||||||
@ -423,6 +428,7 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
new DropDownOption("FAVORITE", favoriteLabels),
|
new DropDownOption("FAVORITE", favoriteLabels),
|
||||||
new DropDownOption("WIN", winLabels),
|
new DropDownOption("WIN", winLabels),
|
||||||
new DropDownOption("HIDDEN_ABILITY", hiddenAbilityLabels),
|
new DropDownOption("HIDDEN_ABILITY", hiddenAbilityLabels),
|
||||||
|
new DropDownOption("SEEN_SPECIES", seenSpeciesLabels),
|
||||||
new DropDownOption("EGG", eggLabels),
|
new DropDownOption("EGG", eggLabels),
|
||||||
new DropDownOption("POKERUS", pokerusLabels),
|
new DropDownOption("POKERUS", pokerusLabels),
|
||||||
];
|
];
|
||||||
@ -792,14 +798,16 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
this.starterSelectMessageBoxContainer.setVisible(!!text?.length);
|
this.starterSelectMessageBoxContainer.setVisible(!!text?.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
isSeen(species: PokemonSpecies, dexEntry: DexEntry): boolean {
|
isSeen(species: PokemonSpecies, dexEntry: DexEntry, seenFilter?: boolean): boolean {
|
||||||
if (dexEntry?.seenAttr) {
|
if (dexEntry?.seenAttr) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (!seenFilter) {
|
||||||
const starterDexEntry = globalScene.gameData.dexData[this.getStarterSpeciesId(species.speciesId)];
|
const starterDexEntry = globalScene.gameData.dexData[this.getStarterSpeciesId(species.speciesId)];
|
||||||
return !!starterDexEntry?.caughtAttr;
|
return !!starterDexEntry?.caughtAttr;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if 'Icon' based upgrade notifications should be shown
|
* Determines if 'Icon' based upgrade notifications should be shown
|
||||||
@ -1617,6 +1625,21 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Seen Filter
|
||||||
|
const dexEntry = globalScene.gameData.dexData[species.speciesId];
|
||||||
|
const isItSeen = this.isSeen(species, dexEntry, true);
|
||||||
|
const fitsSeen = this.filterBar.getVals(DropDownColumn.MISC).some(misc => {
|
||||||
|
if (misc.val === "SEEN_SPECIES" && misc.state === DropDownState.ON) {
|
||||||
|
return isItSeen;
|
||||||
|
}
|
||||||
|
if (misc.val === "SEEN_SPECIES" && misc.state === DropDownState.EXCLUDE) {
|
||||||
|
return !isItSeen;
|
||||||
|
}
|
||||||
|
if (misc.val === "SEEN_SPECIES" && misc.state === DropDownState.OFF) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Egg Purchasable Filter
|
// Egg Purchasable Filter
|
||||||
const isEggPurchasable = this.isSameSpeciesEggAvailable(species.speciesId);
|
const isEggPurchasable = this.isSameSpeciesEggAvailable(species.speciesId);
|
||||||
const fitsEgg = this.filterBar.getVals(DropDownColumn.MISC).some(misc => {
|
const fitsEgg = this.filterBar.getVals(DropDownColumn.MISC).some(misc => {
|
||||||
@ -1658,6 +1681,7 @@ export class PokedexUiHandler extends MessageUiHandler {
|
|||||||
fitsFavorite &&
|
fitsFavorite &&
|
||||||
fitsWin &&
|
fitsWin &&
|
||||||
fitsHA &&
|
fitsHA &&
|
||||||
|
fitsSeen &&
|
||||||
fitsEgg &&
|
fitsEgg &&
|
||||||
fitsPokerus
|
fitsPokerus
|
||||||
) {
|
) {
|
||||||
|
@ -210,7 +210,8 @@ export class RunInfoUiHandler extends UiHandler {
|
|||||||
this.runContainer.add(headerText);
|
this.runContainer.add(headerText);
|
||||||
const runName = addTextObject(0, 0, this.runInfo.name, TextStyle.WINDOW);
|
const runName = addTextObject(0, 0, this.runInfo.name, TextStyle.WINDOW);
|
||||||
runName.setOrigin(0, 0);
|
runName.setOrigin(0, 0);
|
||||||
runName.setPositionRelative(headerBg, 60, 4);
|
const runNameX = headerText.width / 6 + headerText.x + 4;
|
||||||
|
runName.setPositionRelative(headerBg, runNameX, 4);
|
||||||
this.runContainer.add(runName);
|
this.runContainer.add(runName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { AbilityId } from "#enums/ability-id";
|
import { AbilityId } from "#enums/ability-id";
|
||||||
import { Challenges } from "#enums/challenges";
|
import { Challenges } from "#enums/challenges";
|
||||||
import { MoveId } from "#enums/move-id";
|
import { MoveId } from "#enums/move-id";
|
||||||
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import { PokeballType } from "#enums/pokeball";
|
import { PokeballType } from "#enums/pokeball";
|
||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
|
import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils";
|
||||||
import { GameManager } from "#test/test-utils/game-manager";
|
import { GameManager } from "#test/test-utils/game-manager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
@ -52,4 +54,18 @@ describe("Challenges - Limited Catch", () => {
|
|||||||
|
|
||||||
expect(game.scene.getPlayerParty()).toHaveLength(1);
|
expect(game.scene.getPlayerParty()).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should allow gift Pokémon from Mystery Encounters to be added to party", async () => {
|
||||||
|
game.override
|
||||||
|
.mysteryEncounterChance(100)
|
||||||
|
.mysteryEncounter(MysteryEncounterType.THE_POKEMON_SALESMAN)
|
||||||
|
.startingWave(12);
|
||||||
|
game.scene.money = 20000;
|
||||||
|
|
||||||
|
await game.challengeMode.runToSummon([SpeciesId.NUZLEAF]);
|
||||||
|
|
||||||
|
await runMysteryEncounterToEnd(game, 1);
|
||||||
|
|
||||||
|
expect(game.scene.getPlayerParty()).toHaveLength(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -3,6 +3,7 @@ import { Challenges } from "#enums/challenges";
|
|||||||
import { MoveId } from "#enums/move-id";
|
import { MoveId } from "#enums/move-id";
|
||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
import { UiMode } from "#enums/ui-mode";
|
import { UiMode } from "#enums/ui-mode";
|
||||||
|
import { ExpBoosterModifier } from "#modifiers/modifier";
|
||||||
import { GameManager } from "#test/test-utils/game-manager";
|
import { GameManager } from "#test/test-utils/game-manager";
|
||||||
import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler";
|
import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
@ -75,6 +76,7 @@ describe("Challenges - Limited Support", () => {
|
|||||||
await game.doKillOpponents();
|
await game.doKillOpponents();
|
||||||
await game.toNextWave();
|
await game.toNextWave();
|
||||||
|
|
||||||
|
expect(game.scene.getModifiers(ExpBoosterModifier)).toHaveLength(1);
|
||||||
expect(playerPokemon).not.toHaveFullHp();
|
expect(playerPokemon).not.toHaveFullHp();
|
||||||
|
|
||||||
game.move.use(MoveId.SPLASH);
|
game.move.use(MoveId.SPLASH);
|
||||||
|
@ -77,15 +77,13 @@ describe("Moves - Heal Block", () => {
|
|||||||
game.move.use(MoveId.WISH);
|
game.move.use(MoveId.WISH);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
expect(game.scene.arena.positionalTagManager.tags.filter(t => t.tagType === PositionalTagType.WISH)) //
|
expect(game).toHavePositionalTag(PositionalTagType.WISH);
|
||||||
.toHaveLength(1);
|
|
||||||
|
|
||||||
game.move.use(MoveId.SPLASH);
|
game.move.use(MoveId.SPLASH);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
// wish triggered, but did NOT heal the player
|
// wish triggered, but did NOT heal the player
|
||||||
expect(game.scene.arena.positionalTagManager.tags.filter(t => t.tagType === PositionalTagType.WISH)) //
|
expect(game).toHavePositionalTag(PositionalTagType.WISH, 0);
|
||||||
.toHaveLength(0);
|
|
||||||
expect(player.hp).toBe(1);
|
expect(player.hp).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
// biome-ignore-start lint/correctness/noUnusedImports: TSDoc
|
// biome-ignore-start lint/correctness/noUnusedImports: TSDoc
|
||||||
|
|
||||||
|
import type { PositionalTag } from "#data/positional-tags/positional-tag";
|
||||||
import type { GameManager } from "#test/test-utils/game-manager";
|
import type { GameManager } from "#test/test-utils/game-manager";
|
||||||
// biome-ignore-end lint/correctness/noUnusedImports: TSDoc
|
// biome-ignore-end lint/correctness/noUnusedImports: TSDoc
|
||||||
|
|
||||||
@ -17,8 +19,8 @@ export type toHavePositionalTagOptions<P extends PositionalTagType> = OneOther<s
|
|||||||
/**
|
/**
|
||||||
* Matcher to check if the {@linkcode Arena} has a certain number of {@linkcode PositionalTag}s active.
|
* Matcher to check if the {@linkcode Arena} has a certain number of {@linkcode PositionalTag}s active.
|
||||||
* @param received - The object to check. Should be the current {@linkcode GameManager}
|
* @param received - The object to check. Should be the current {@linkcode GameManager}
|
||||||
* @param expectedTag - The {@linkcode PositionalTagType} of the desired tag, or a partially-filled {@linkcode PositionalTag}
|
* @param expectedTag - The {@linkcode PositionalTagType} of the desired tag, or a partially-filled
|
||||||
* containing the desired properties
|
* {@linkcode PositionalTag} containing the desired properties
|
||||||
* @param count - The number of tags that should be active; defaults to `1` and must be within the range `[0, 4]`
|
* @param count - The number of tags that should be active; defaults to `1` and must be within the range `[0, 4]`
|
||||||
* @returns The result of the matching
|
* @returns The result of the matching
|
||||||
*/
|
*/
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import { Pokemon } from "#field/pokemon";
|
import { Pokemon } from "#field/pokemon";
|
||||||
import type { GameManager } from "#test/test-utils/game-manager";
|
import type { GameManager } from "#test/test-utils/game-manager";
|
||||||
import i18next, { type ParseKeys } from "i18next";
|
import i18next, { type ParseKeys } from "i18next";
|
||||||
import { vi } from "vitest";
|
import { type MockInstance, vi } from "vitest";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up the i18next mock.
|
* Mock i18next's {@linkcode t} function to only produce the raw key.
|
||||||
* Includes a i18next.t mocked implementation only returning the raw key (`(key) => key`)
|
|
||||||
*
|
*
|
||||||
* @returns A spy/mock of i18next
|
* @returns A {@linkcode MockInstance} for `i18next.t`
|
||||||
*/
|
*/
|
||||||
export function mockI18next() {
|
export function mockI18next(): MockInstance<(typeof i18next)["t"]> {
|
||||||
return vi.spyOn(i18next, "t").mockImplementation((key: ParseKeys) => key);
|
return vi.spyOn(i18next, "t").mockImplementation((key: ParseKeys) => key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,25 +1,24 @@
|
|||||||
import type { SerializedPositionalTag, serializedPosTagMap } from "#data/positional-tags/load-positional-tag";
|
import type { SerializedPositionalTag, serializedPosTagMap } from "#data/positional-tags/load-positional-tag";
|
||||||
import type { DelayedAttackTag, WishTag } from "#data/positional-tags/positional-tag";
|
import type { DelayedAttackTag, WishTag } from "#data/positional-tags/positional-tag";
|
||||||
import type { PositionalTagType } from "#enums/positional-tag-type";
|
import type { PositionalTagType } from "#enums/positional-tag-type";
|
||||||
import type { Mutable, NonFunctionPropertiesRecursive } from "#types/type-helpers";
|
import type { NonFunctionPropertiesRecursive } from "#types/type-helpers";
|
||||||
import { describe, expectTypeOf, it } from "vitest";
|
import { describe, expectTypeOf, it } from "vitest";
|
||||||
|
|
||||||
// Needed to get around properties being readonly in certain classes
|
|
||||||
type NonFunctionMutable<T> = Mutable<NonFunctionPropertiesRecursive<T>>;
|
|
||||||
|
|
||||||
describe("serializedPositionalTagMap", () => {
|
describe("serializedPositionalTagMap", () => {
|
||||||
it("should contain representations of each tag's serialized form", () => {
|
it("should contain representations of each tag's serialized form", () => {
|
||||||
expectTypeOf<serializedPosTagMap[PositionalTagType.DELAYED_ATTACK]>().branded.toEqualTypeOf<
|
expectTypeOf<serializedPosTagMap[PositionalTagType.DELAYED_ATTACK]>().branded.toEqualTypeOf<
|
||||||
NonFunctionMutable<DelayedAttackTag>
|
NonFunctionPropertiesRecursive<DelayedAttackTag>
|
||||||
|
>();
|
||||||
|
expectTypeOf<serializedPosTagMap[PositionalTagType.WISH]>().branded.toEqualTypeOf<
|
||||||
|
NonFunctionPropertiesRecursive<WishTag>
|
||||||
>();
|
>();
|
||||||
expectTypeOf<serializedPosTagMap[PositionalTagType.WISH]>().branded.toEqualTypeOf<NonFunctionMutable<WishTag>>();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("SerializedPositionalTag", () => {
|
describe("SerializedPositionalTag", () => {
|
||||||
it("should accept a union of all serialized tag forms", () => {
|
it("should accept a union of all serialized tag forms", () => {
|
||||||
expectTypeOf<SerializedPositionalTag>().branded.toEqualTypeOf<
|
expectTypeOf<SerializedPositionalTag>().branded.toEqualTypeOf<
|
||||||
NonFunctionMutable<DelayedAttackTag> | NonFunctionMutable<WishTag>
|
NonFunctionPropertiesRecursive<DelayedAttackTag> | NonFunctionPropertiesRecursive<WishTag>
|
||||||
>();
|
>();
|
||||||
});
|
});
|
||||||
it("should accept a union of all unserialized tag forms", () => {
|
it("should accept a union of all unserialized tag forms", () => {
|
||||||
|
67
test/ui/flyouts/arena-flyout.test.ts
Normal file
67
test/ui/flyouts/arena-flyout.test.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { AbilityId } from "#enums/ability-id";
|
||||||
|
import { MoveId } from "#enums/move-id";
|
||||||
|
import { SpeciesId } from "#enums/species-id";
|
||||||
|
import { GameManager } from "#test/test-utils/game-manager";
|
||||||
|
import { mockI18next } from "#test/test-utils/test-utils";
|
||||||
|
import type { ArenaFlyout } from "#ui/arena-flyout";
|
||||||
|
import type i18next from "i18next";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterAll, beforeAll, beforeEach, describe, expect, it, type MockInstance } from "vitest";
|
||||||
|
|
||||||
|
describe("UI - Arena Flyout", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
let flyout: ArenaFlyout;
|
||||||
|
let tSpy: MockInstance<(typeof i18next)["t"]>;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
game.override
|
||||||
|
.ability(AbilityId.BALL_FETCH)
|
||||||
|
.battleStyle("double")
|
||||||
|
.criticalHits(false)
|
||||||
|
.enemySpecies(SpeciesId.MAGIKARP)
|
||||||
|
.enemyAbility(AbilityId.BALL_FETCH)
|
||||||
|
.enemyMoveset(MoveId.SPLASH)
|
||||||
|
.startingLevel(100)
|
||||||
|
.enemyLevel(100);
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
||||||
|
|
||||||
|
flyout = game.scene.arenaFlyout;
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Reset i18n mock before each test
|
||||||
|
tSpy = mockI18next();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("localizeEffectName", () => {
|
||||||
|
it("should retrieve locales from an effect name", () => {
|
||||||
|
const name = flyout["localizeEffectName"]("STEALTH_ROCK");
|
||||||
|
expect(name).toBe("arenaFlyout:stealthRock");
|
||||||
|
expect(tSpy).toHaveBeenCalledExactlyOnceWith("arenaFlyout:stealthRock");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Helper type to get around unexportedness
|
||||||
|
type infoType = Parameters<(typeof flyout)["getTagText"]>[0];
|
||||||
|
|
||||||
|
describe("getTagText", () => {
|
||||||
|
it.each<{ info: Pick<infoType, "name" | "duration" | "maxDuration">; text: string }>([
|
||||||
|
{ info: { name: "Spikes (1)", duration: 0, maxDuration: 0 }, text: "Spikes (1)\n" },
|
||||||
|
{ info: { name: "Grassy Terrain", duration: 1, maxDuration: 5 }, text: "Grassy Terrain (1/5)\n" },
|
||||||
|
])("should get the name of an arena effect", ({ info, text }) => {
|
||||||
|
const got = flyout["getTagText"](info as infoType);
|
||||||
|
expect(got).toBe(text);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user