mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-01-31 13:17:21 +00:00
Merge pull request #4084 from ben-lear/global-trade-system
Global Trade System Mystery Encounter
This commit is contained in:
commit
51d6e0ec02
BIN
public/audio/bgm/mystery_encounter_gen_5_gts.mp3
Normal file
BIN
public/audio/bgm/mystery_encounter_gen_5_gts.mp3
Normal file
Binary file not shown.
BIN
public/audio/bgm/mystery_encounter_gen_6_gts.mp3
Normal file
BIN
public/audio/bgm/mystery_encounter_gen_6_gts.mp3
Normal file
Binary file not shown.
41
public/images/mystery-encounters/gts_placeholder.json
Normal file
41
public/images/mystery-encounters/gts_placeholder.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "gts_placeholder.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 47,
|
||||
"h": 79
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0000.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 17,
|
||||
"y": 1,
|
||||
"w": 47,
|
||||
"h": 79
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 47,
|
||||
"h": 79
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:4e95cf5cd2b0329629c40dfe871e5ae0:cf1cd6aef867fcde2439177ebb561178:39ec800be807afcf5dd13b9cc59fc386$"
|
||||
}
|
||||
}
|
BIN
public/images/mystery-encounters/gts_placeholder.png
Normal file
BIN
public/images/mystery-encounters/gts_placeholder.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 707 B |
@ -4,8 +4,8 @@
|
||||
"image": "teleporter.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 64,
|
||||
"h": 78
|
||||
"w": 74,
|
||||
"h": 79
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
@ -14,20 +14,20 @@
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 64,
|
||||
"h": 78
|
||||
"w": 74,
|
||||
"h": 79
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 64,
|
||||
"h": 78
|
||||
"w": 74,
|
||||
"h": 79
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 64,
|
||||
"h": 78
|
||||
"w": 74,
|
||||
"h": 79
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -36,6 +36,6 @@
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:a8e006630c2838130468b0d5c9aeb8a6:684c1813cb6c86e395c18027a593ed28:ce1615396ce7b0a146766d50b319bb81$"
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:937d8502b98f79720118061b6021e108:2b4f9db00d5b0997b42a5466f808509b:ce1615396ce7b0a146766d50b319bb81$"
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 661 B After Width: | Height: | Size: 1.3 KiB |
@ -0,0 +1,826 @@
|
||||
import { leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { TrainerSlot, } from "#app/data/trainer-config";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
import { getPlayerModifierTypeOptions, ModifierPoolType, ModifierTypeOption, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { Species } from "#enums/species";
|
||||
import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { getTypeRgb } from "#app/data/type";
|
||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { IntegerHolder, isNullOrUndefined, randInt, randSeedInt, randSeedShuffle } from "#app/utils";
|
||||
import Pokemon, { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
|
||||
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, SpeciesStatBoosterModifier } from "#app/modifier/modifier";
|
||||
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||
import PokemonData from "#app/system/pokemon-data";
|
||||
import i18next from "i18next";
|
||||
import { Gender, getGenderSymbol } from "#app/data/gender";
|
||||
import { getNatureName } from "#app/data/nature";
|
||||
import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball";
|
||||
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { trainerNamePools } from "#app/data/trainer-names";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:globalTradeSystem";
|
||||
|
||||
const LEGENDARY_TRADE_POOLS = {
|
||||
1: [Species.RATTATA, Species.PIDGEY, Species.WEEDLE],
|
||||
2: [Species.SENTRET, Species.HOOTHOOT, Species.LEDYBA],
|
||||
3: [Species.POOCHYENA, Species.ZIGZAGOON, Species.TAILLOW],
|
||||
4: [Species.BIDOOF, Species.STARLY, Species.KRICKETOT],
|
||||
5: [Species.PATRAT, Species.PURRLOIN, Species.PIDOVE],
|
||||
6: [Species.BUNNELBY, Species.LITLEO, Species.SCATTERBUG],
|
||||
7: [Species.PIKIPEK, Species.YUNGOOS, Species.ROCKRUFF],
|
||||
8: [Species.SKWOVET, Species.WOOLOO, Species.ROOKIDEE],
|
||||
9: [Species.LECHONK, Species.FIDOUGH, Species.TAROUNTULA]
|
||||
};
|
||||
|
||||
/** Exclude Paradox mons as they aren't considered legendary/mythical */
|
||||
const EXCLUDED_TRADE_SPECIES = [
|
||||
Species.GREAT_TUSK,
|
||||
Species.SCREAM_TAIL,
|
||||
Species.BRUTE_BONNET,
|
||||
Species.FLUTTER_MANE,
|
||||
Species.SLITHER_WING,
|
||||
Species.SANDY_SHOCKS,
|
||||
Species.ROARING_MOON,
|
||||
Species.WALKING_WAKE,
|
||||
Species.GOUGING_FIRE,
|
||||
Species.RAGING_BOLT,
|
||||
Species.IRON_TREADS,
|
||||
Species.IRON_BUNDLE,
|
||||
Species.IRON_HANDS,
|
||||
Species.IRON_JUGULIS,
|
||||
Species.IRON_MOTH,
|
||||
Species.IRON_THORNS,
|
||||
Species.IRON_VALIANT,
|
||||
Species.IRON_LEAVES,
|
||||
Species.IRON_BOULDER,
|
||||
Species.IRON_CROWN
|
||||
];
|
||||
|
||||
/**
|
||||
* Global Trade System encounter.
|
||||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3812 | GitHub Issue #3812}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const GlobalTradeSystemEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.GLOBAL_TRADE_SYSTEM)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(10, 180)
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "gts_placeholder",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: false,
|
||||
disableAnimation: true
|
||||
}
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
}
|
||||
])
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
|
||||
// Load bgm
|
||||
if (scene.musicPreference === 0) {
|
||||
scene.loadBgm("mystery_encounter_gts", "mystery_encounter_gen_5_gts.mp3");
|
||||
} else {
|
||||
// Mixed option
|
||||
scene.loadBgm("mystery_encounter_gts", "mystery_encounter_gen_6_gts.mp3");
|
||||
}
|
||||
|
||||
// Load possible trade options
|
||||
// Maps current party member's id to 3 EnemyPokemon objects
|
||||
// None of the trade options can be the same species
|
||||
const tradeOptionsMap: Map<number, EnemyPokemon[]> = getPokemonTradeOptions(scene);
|
||||
encounter.misc = {
|
||||
tradeOptionsMap
|
||||
};
|
||||
|
||||
return true;
|
||||
})
|
||||
.withOnVisualsStart((scene: BattleScene) => {
|
||||
// Change the bgm
|
||||
scene.fadeOutBgm(1500, false);
|
||||
scene.time.delayedCall(1500, () => {
|
||||
scene.playBgm("mystery_encounter_gts");
|
||||
});
|
||||
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
secondOptionPrompt: `${namespace}.option.1.trade_options_prompt`,
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Get the trade species options for the selected pokemon
|
||||
const tradeOptionsMap: Map<number, EnemyPokemon[]> = encounter.misc.tradeOptionsMap;
|
||||
const tradeOptions = tradeOptionsMap.get(pokemon.id);
|
||||
if (!tradeOptions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return tradeOptions.map((tradePokemon: EnemyPokemon) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: tradePokemon.getNameToRender(),
|
||||
handler: () => {
|
||||
// Pokemon trade selected
|
||||
encounter.setDialogueToken("tradedPokemon", pokemon.getNameToRender());
|
||||
encounter.setDialogueToken("received", tradePokemon.getNameToRender());
|
||||
encounter.misc = {
|
||||
tradedPokemon: pokemon,
|
||||
receivedPokemon: tradePokemon,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
onHover: () => {
|
||||
const formName = tradePokemon.species.forms?.[pokemon.formIndex]?.formName;
|
||||
const line1 = i18next.t("pokemonInfoContainer:ability") + " " + tradePokemon.getAbility().name + (tradePokemon.getGender() !== Gender.GENDERLESS ? " | " + i18next.t("pokemonInfoContainer:gender") + " " + getGenderSymbol(tradePokemon.getGender()) : "");
|
||||
const line2 = i18next.t("pokemonInfoContainer:nature") + " " + getNatureName(tradePokemon.getNature()) + (formName ? " | " + i18next.t("pokemonInfoContainer:form") + " " + formName : "");
|
||||
scene.ui.showText(`${line1}\n${line2}`, 0);
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected);
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
const tradedPokemon: PlayerPokemon = encounter.misc.tradedPokemon;
|
||||
const receivedPokemonData: EnemyPokemon = encounter.misc.receivedPokemon;
|
||||
const modifiers = tradedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier) && !(m instanceof SpeciesStatBoosterModifier));
|
||||
|
||||
// Generate a trainer name
|
||||
const traderName = generateRandomTraderName();
|
||||
encounter.setDialogueToken("tradeTrainerName", traderName.trim());
|
||||
|
||||
// Remove the original party member from party
|
||||
scene.removePokemonFromPlayerParty(tradedPokemon, false);
|
||||
|
||||
// Set data properly, then generate the new Pokemon's assets
|
||||
receivedPokemonData.passive = tradedPokemon.passive;
|
||||
receivedPokemonData.pokeball = randSeedInt(5);
|
||||
const dataSource = new PokemonData(receivedPokemonData);
|
||||
const newPlayerPokemon = scene.addPlayerPokemon(receivedPokemonData.species, receivedPokemonData.level, dataSource.abilityIndex, dataSource.formIndex, dataSource.gender, dataSource.shiny, dataSource.variant, dataSource.ivs, dataSource.nature, dataSource);
|
||||
scene.getParty().push(newPlayerPokemon);
|
||||
await newPlayerPokemon.loadAssets();
|
||||
|
||||
for (const mod of modifiers) {
|
||||
mod.pokemonId = newPlayerPokemon.id;
|
||||
scene.addModifier(mod, true, false, false, true);
|
||||
}
|
||||
|
||||
// Show the trade animation
|
||||
await showTradeBackground(scene);
|
||||
await doPokemonTradeSequence(scene, tradedPokemon, newPlayerPokemon);
|
||||
await showEncounterText(scene, `${namespace}.trade_received`, 0, true, 4000);
|
||||
scene.playBgm("mystery_encounter_gts");
|
||||
await hideTradeBackground(scene);
|
||||
tradedPokemon.destroy();
|
||||
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Randomly generate a Wonder Trade pokemon
|
||||
const randomTradeOption = generateTradeOption(scene.getParty().map(p => p.species));
|
||||
const tradePokemon = new EnemyPokemon(scene, randomTradeOption, pokemon.level, TrainerSlot.NONE, false);
|
||||
// Extra shiny roll at 1/128 odds (boosted by events and charms)
|
||||
if (!tradePokemon.shiny) {
|
||||
// 512/65536 -> 1/128
|
||||
tradePokemon.trySetShinySeed(512, true);
|
||||
}
|
||||
|
||||
// Extra HA roll at base 1/64 odds (boosted by events and charms)
|
||||
if (pokemon.species.abilityHidden) {
|
||||
const hiddenIndex = pokemon.species.ability2 ? 2 : 1;
|
||||
if (pokemon.abilityIndex < hiddenIndex) {
|
||||
const hiddenAbilityChance = new IntegerHolder(64);
|
||||
scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
|
||||
|
||||
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
|
||||
|
||||
if (hasHiddenAbility) {
|
||||
pokemon.abilityIndex = hiddenIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
encounter.setDialogueToken("tradedPokemon", pokemon.getNameToRender());
|
||||
encounter.setDialogueToken("received", tradePokemon.getNameToRender());
|
||||
encounter.misc = {
|
||||
tradedPokemon: pokemon,
|
||||
receivedPokemon: tradePokemon,
|
||||
};
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected);
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
const tradedPokemon: PlayerPokemon = encounter.misc.tradedPokemon;
|
||||
const receivedPokemonData: EnemyPokemon = encounter.misc.receivedPokemon;
|
||||
const modifiers = tradedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier) && !(m instanceof SpeciesStatBoosterModifier));
|
||||
|
||||
// Generate a trainer name
|
||||
const traderName = generateRandomTraderName();
|
||||
encounter.setDialogueToken("tradeTrainerName", traderName.trim());
|
||||
|
||||
// Remove the original party member from party
|
||||
scene.removePokemonFromPlayerParty(tradedPokemon, false);
|
||||
|
||||
// Set data properly, then generate the new Pokemon's assets
|
||||
receivedPokemonData.passive = tradedPokemon.passive;
|
||||
receivedPokemonData.pokeball = randSeedInt(5);
|
||||
const dataSource = new PokemonData(receivedPokemonData);
|
||||
const newPlayerPokemon = scene.addPlayerPokemon(receivedPokemonData.species, receivedPokemonData.level, undefined, undefined, undefined, undefined, undefined, undefined, undefined, dataSource);
|
||||
scene.getParty().push(newPlayerPokemon);
|
||||
await newPlayerPokemon.loadAssets();
|
||||
|
||||
for (const mod of modifiers) {
|
||||
mod.pokemonId = newPlayerPokemon.id;
|
||||
scene.addModifier(mod, true, false, false, true);
|
||||
}
|
||||
|
||||
// Show the trade animation
|
||||
await showTradeBackground(scene);
|
||||
await doPokemonTradeSequence(scene, tradedPokemon, newPlayerPokemon);
|
||||
await showEncounterText(scene, `${namespace}.trade_received`, 0, true, 4000);
|
||||
scene.playBgm("mystery_encounter_gts");
|
||||
await hideTradeBackground(scene);
|
||||
tradedPokemon.destroy();
|
||||
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.3.label`,
|
||||
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||
secondOptionPrompt: `${namespace}.option.3.trade_options_prompt`,
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Get Pokemon held items and filter for valid ones
|
||||
const validItems = pokemon.getHeldItems().filter((it) => {
|
||||
return it.isTransferrable;
|
||||
});
|
||||
|
||||
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: modifier.type.name,
|
||||
handler: () => {
|
||||
// Pokemon and item selected
|
||||
encounter.setDialogueToken("chosenItem", modifier.type.name);
|
||||
encounter.misc = {
|
||||
chosenModifier: modifier,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
|
||||
// Only Pokemon that can gain benefits are above 1/3rd HP with no status
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon has items to trade
|
||||
const meetsReqs = pokemon.getHeldItems().filter((it) => {
|
||||
return it.isTransferrable;
|
||||
}).length > 0;
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}.option.3.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
const modifier = encounter.misc.chosenModifier;
|
||||
|
||||
// Check tier of the traded item, the received item will be one tier up
|
||||
const type = modifier.type.withTierFromPool();
|
||||
let tier = type.tier ?? ModifierTier.GREAT;
|
||||
// Eggs and White Herb are not in the pool
|
||||
if (type.id === "WHITE_HERB") {
|
||||
tier = ModifierTier.GREAT;
|
||||
} else if (type.id === "LUCKY_EGG") {
|
||||
tier = ModifierTier.ULTRA;
|
||||
} else if (type.id === "GOLDEN_EGG") {
|
||||
tier = ModifierTier.ROGUE;
|
||||
}
|
||||
// Increment tier by 1
|
||||
if (tier < ModifierTier.MASTER) {
|
||||
tier++;
|
||||
}
|
||||
|
||||
regenerateModifierPoolThresholds(scene.getParty(), ModifierPoolType.PLAYER, 0);
|
||||
let item: ModifierTypeOption | null = null;
|
||||
// TMs excluded from possible rewards
|
||||
while (!item || item.type.id.includes("TM_")) {
|
||||
item = getPlayerModifierTypeOptions(1, scene.getParty(), [], { guaranteedModifierTiers: [tier], allowLuckUpgrades: false })[0];
|
||||
}
|
||||
|
||||
encounter.setDialogueToken("itemName", item.type.name);
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeOptions: [item], fillRemaining: false });
|
||||
|
||||
// Remove the chosen modifier if its stacks go to 0
|
||||
modifier.stackCount -= 1;
|
||||
if (modifier.stackCount === 0) {
|
||||
scene.removeModifier(modifier);
|
||||
}
|
||||
scene.updateModifiers(true, true);
|
||||
|
||||
// Generate a trainer name
|
||||
const traderName = generateRandomTraderName();
|
||||
encounter.setDialogueToken("tradeTrainerName", traderName.trim());
|
||||
await showEncounterText(scene, `${namespace}.item_trade_selected`);
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.4.label`,
|
||||
buttonTooltip: `${namespace}.option.4.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.4.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.build();
|
||||
|
||||
function getPokemonTradeOptions(scene: BattleScene): Map<number, EnemyPokemon[]> {
|
||||
const tradeOptionsMap: Map<number, EnemyPokemon[]> = new Map<number, EnemyPokemon[]>();
|
||||
// Starts by filtering out any current party members as valid resulting species
|
||||
const alreadyUsedSpecies: PokemonSpecies[] = scene.getParty().map(p => p.species);
|
||||
|
||||
scene.getParty().forEach(pokemon => {
|
||||
// If the party member is legendary/mythical, the only trade options available are always pulled from generation-specific legendary trade pools
|
||||
if (pokemon.species.legendary || pokemon.species.subLegendary || pokemon.species.mythical) {
|
||||
const generation = pokemon.species.generation;
|
||||
const tradeOptions: EnemyPokemon[] = LEGENDARY_TRADE_POOLS[generation].map(s => {
|
||||
const pokemonSpecies = getPokemonSpecies(s);
|
||||
return new EnemyPokemon(scene, pokemonSpecies, 5, TrainerSlot.NONE, false);
|
||||
});
|
||||
tradeOptionsMap.set(pokemon.id, tradeOptions);
|
||||
} else {
|
||||
const originalBst = pokemon.calculateBaseStats().reduce((a, b) => a + b, 0);
|
||||
|
||||
const tradeOptions: PokemonSpecies[] = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const speciesTradeOption = generateTradeOption(alreadyUsedSpecies, originalBst);
|
||||
alreadyUsedSpecies.push(speciesTradeOption);
|
||||
tradeOptions.push(speciesTradeOption);
|
||||
}
|
||||
|
||||
// Add trade options to map
|
||||
tradeOptionsMap.set(pokemon.id, tradeOptions.map(s => {
|
||||
return new EnemyPokemon(scene, s, pokemon.level, TrainerSlot.NONE, false);
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
return tradeOptionsMap;
|
||||
}
|
||||
|
||||
function generateTradeOption(alreadyUsedSpecies: PokemonSpecies[], originalBst?: number): PokemonSpecies {
|
||||
let newSpecies: PokemonSpecies | undefined;
|
||||
while (isNullOrUndefined(newSpecies)) {
|
||||
let bstCap = 9999;
|
||||
let bstMin = 0;
|
||||
if (originalBst) {
|
||||
bstCap = originalBst + 100;
|
||||
bstMin = originalBst - 100;
|
||||
}
|
||||
|
||||
// Get all non-legendary species that fall within the Bst range requirements
|
||||
let validSpecies = allSpecies
|
||||
.filter(s => {
|
||||
const isLegendaryOrMythical = s.legendary || s.subLegendary || s.mythical;
|
||||
const speciesBst = s.getBaseStatTotal();
|
||||
const bstInRange = speciesBst >= bstMin && speciesBst <= bstCap;
|
||||
return !isLegendaryOrMythical && bstInRange && !EXCLUDED_TRADE_SPECIES.includes(s.speciesId);
|
||||
});
|
||||
|
||||
// There must be at least 20 species available before it will choose one
|
||||
if (validSpecies?.length > 20) {
|
||||
validSpecies = randSeedShuffle(validSpecies);
|
||||
newSpecies = validSpecies.pop();
|
||||
while (isNullOrUndefined(newSpecies) || alreadyUsedSpecies.includes(newSpecies!)) {
|
||||
newSpecies = validSpecies.pop();
|
||||
}
|
||||
} else {
|
||||
// Expands search range until at least 20 are in the pool
|
||||
bstMin -= 10;
|
||||
bstCap += 10;
|
||||
}
|
||||
}
|
||||
|
||||
return newSpecies!;
|
||||
}
|
||||
|
||||
function showTradeBackground(scene: BattleScene) {
|
||||
return new Promise<void>(resolve => {
|
||||
const tradeContainer = scene.add.container(0, -scene.game.canvas.height / 6);
|
||||
tradeContainer.setName("Trade Background");
|
||||
|
||||
const flyByStaticBg = scene.add.rectangle(0, 0, scene.game.canvas.width / 6, scene.game.canvas.height / 6, 0);
|
||||
flyByStaticBg.setName("Black Background");
|
||||
flyByStaticBg.setOrigin(0, 0);
|
||||
flyByStaticBg.setVisible(false);
|
||||
tradeContainer.add(flyByStaticBg);
|
||||
|
||||
const tradeBaseBg = scene.add.image(0, 0, "default_bg");
|
||||
tradeBaseBg.setName("Trade Background Image");
|
||||
tradeBaseBg.setOrigin(0, 0);
|
||||
tradeContainer.add(tradeBaseBg);
|
||||
|
||||
scene.fieldUI.add(tradeContainer);
|
||||
scene.fieldUI.bringToTop(tradeContainer);
|
||||
tradeContainer.setVisible(true);
|
||||
tradeContainer.alpha = 0;
|
||||
|
||||
scene.tweens.add({
|
||||
targets: tradeContainer,
|
||||
alpha: 1,
|
||||
duration: 500,
|
||||
ease: "Sine.easeInOut",
|
||||
onComplete: () => {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function hideTradeBackground(scene: BattleScene) {
|
||||
return new Promise<void>(resolve => {
|
||||
const transformationContainer = scene.fieldUI.getByName("Trade Background");
|
||||
|
||||
scene.tweens.add({
|
||||
targets: transformationContainer,
|
||||
alpha: 0,
|
||||
duration: 1000,
|
||||
ease: "Sine.easeInOut",
|
||||
onComplete: () => {
|
||||
scene.fieldUI.remove(transformationContainer, true);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates an "evolution-like" animation to transform a previousPokemon (presumably from the player's party) into a new one, not necessarily an evolution species.
|
||||
* @param scene
|
||||
* @param tradedPokemon
|
||||
* @param receivedPokemon
|
||||
*/
|
||||
function doPokemonTradeSequence(scene: BattleScene, tradedPokemon: PlayerPokemon, receivedPokemon: PlayerPokemon) {
|
||||
return new Promise<void>(resolve => {
|
||||
const tradeContainer = scene.fieldUI.getByName("Trade Background") as Phaser.GameObjects.Container;
|
||||
const tradeBaseBg = tradeContainer.getByName("Trade Background Image") as Phaser.GameObjects.Image;
|
||||
|
||||
let tradedPokemonSprite: Phaser.GameObjects.Sprite;
|
||||
let tradedPokemonTintSprite: Phaser.GameObjects.Sprite;
|
||||
let receivedPokemonSprite: Phaser.GameObjects.Sprite;
|
||||
let receivedPokemonTintSprite: Phaser.GameObjects.Sprite;
|
||||
|
||||
const getPokemonSprite = () => {
|
||||
const ret = scene.addPokemonSprite(tradedPokemon, tradeBaseBg.displayWidth / 2, tradeBaseBg.displayHeight / 2, "pkmn__sub");
|
||||
ret.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true });
|
||||
return ret;
|
||||
};
|
||||
|
||||
tradeContainer.add((tradedPokemonSprite = getPokemonSprite()));
|
||||
tradeContainer.add((tradedPokemonTintSprite = getPokemonSprite()));
|
||||
tradeContainer.add((receivedPokemonSprite = getPokemonSprite()));
|
||||
tradeContainer.add((receivedPokemonTintSprite = getPokemonSprite()));
|
||||
|
||||
tradedPokemonSprite.setAlpha(0);
|
||||
tradedPokemonTintSprite.setAlpha(0);
|
||||
tradedPokemonTintSprite.setTintFill(getPokeballTintColor(tradedPokemon.pokeball));
|
||||
receivedPokemonSprite.setVisible(false);
|
||||
receivedPokemonTintSprite.setVisible(false);
|
||||
receivedPokemonTintSprite.setTintFill(getPokeballTintColor(receivedPokemon.pokeball));
|
||||
|
||||
[ tradedPokemonSprite, tradedPokemonTintSprite ].map(sprite => {
|
||||
sprite.play(tradedPokemon.getSpriteKey(true));
|
||||
sprite.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()) });
|
||||
sprite.setPipelineData("ignoreTimeTint", true);
|
||||
sprite.setPipelineData("spriteKey", tradedPokemon.getSpriteKey());
|
||||
sprite.setPipelineData("shiny", tradedPokemon.shiny);
|
||||
sprite.setPipelineData("variant", tradedPokemon.variant);
|
||||
[ "spriteColors", "fusionSpriteColors" ].map(k => {
|
||||
if (tradedPokemon.summonData?.speciesForm) {
|
||||
k += "Base";
|
||||
}
|
||||
sprite.pipelineData[k] = tradedPokemon.getSprite().pipelineData[k];
|
||||
});
|
||||
});
|
||||
|
||||
[ receivedPokemonSprite, receivedPokemonTintSprite ].map(sprite => {
|
||||
sprite.play(receivedPokemon.getSpriteKey(true));
|
||||
sprite.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()) });
|
||||
sprite.setPipelineData("ignoreTimeTint", true);
|
||||
sprite.setPipelineData("spriteKey", receivedPokemon.getSpriteKey());
|
||||
sprite.setPipelineData("shiny", receivedPokemon.shiny);
|
||||
sprite.setPipelineData("variant", receivedPokemon.variant);
|
||||
[ "spriteColors", "fusionSpriteColors" ].map(k => {
|
||||
if (receivedPokemon.summonData?.speciesForm) {
|
||||
k += "Base";
|
||||
}
|
||||
sprite.pipelineData[k] = receivedPokemon.getSprite().pipelineData[k];
|
||||
});
|
||||
});
|
||||
|
||||
// Traded pokemon pokeball
|
||||
const tradedPbAtlasKey = getPokeballAtlasKey(tradedPokemon.pokeball);
|
||||
const tradedPokeball: Phaser.GameObjects.Sprite = scene.add.sprite(tradeBaseBg.displayWidth / 2, tradeBaseBg.displayHeight / 2, "pb", tradedPbAtlasKey);
|
||||
tradedPokeball.setVisible(false);
|
||||
tradeContainer.add(tradedPokeball);
|
||||
|
||||
// Received pokemon pokeball
|
||||
const receivedPbAtlasKey = getPokeballAtlasKey(receivedPokemon.pokeball);
|
||||
const receivedPokeball: Phaser.GameObjects.Sprite = scene.add.sprite(tradeBaseBg.displayWidth / 2, tradeBaseBg.displayHeight / 2, "pb", receivedPbAtlasKey);
|
||||
receivedPokeball.setVisible(false);
|
||||
tradeContainer.add(receivedPokeball);
|
||||
|
||||
scene.tweens.add({
|
||||
targets: tradedPokemonSprite,
|
||||
alpha: 1,
|
||||
ease: "Cubic.easeInOut",
|
||||
duration: 500,
|
||||
onComplete: async () => {
|
||||
scene.fadeOutBgm(1000, false);
|
||||
await showEncounterText(scene, `${namespace}.pokemon_trade_selected`);
|
||||
tradedPokemon.cry();
|
||||
scene.playBgm("evolution");
|
||||
await showEncounterText(scene, `${namespace}.pokemon_trade_goodbye`);
|
||||
|
||||
tradedPokeball.setAlpha(0);
|
||||
tradedPokeball.setVisible(true);
|
||||
scene.tweens.add({
|
||||
targets: tradedPokeball,
|
||||
alpha: 1,
|
||||
ease: "Cubic.easeInOut",
|
||||
duration: 250,
|
||||
onComplete: () => {
|
||||
tradedPokeball.setTexture("pb", `${tradedPbAtlasKey}_opening`);
|
||||
scene.time.delayedCall(17, () => tradedPokeball.setTexture("pb", `${tradedPbAtlasKey}_open`));
|
||||
scene.playSound("se/pb_rel");
|
||||
tradedPokemonTintSprite.setVisible(true);
|
||||
|
||||
// TODO: need to add particles to fieldUI instead of field
|
||||
// addPokeballOpenParticles(scene, tradedPokemon.x, tradedPokemon.y, tradedPokemon.pokeball);
|
||||
|
||||
scene.tweens.add({
|
||||
targets: [tradedPokemonTintSprite, tradedPokemonSprite],
|
||||
duration: 500,
|
||||
ease: "Sine.easeIn",
|
||||
scale: 0.25,
|
||||
onComplete: () => {
|
||||
tradedPokemonSprite.setVisible(false);
|
||||
tradedPokeball.setTexture("pb", `${tradedPbAtlasKey}_opening`);
|
||||
tradedPokemonTintSprite.setVisible(false);
|
||||
scene.playSound("se/pb_catch");
|
||||
scene.time.delayedCall(17, () => tradedPokeball.setTexture("pb", `${tradedPbAtlasKey}`));
|
||||
|
||||
scene.tweens.add({
|
||||
targets: tradedPokeball,
|
||||
y: "+=10",
|
||||
duration: 200,
|
||||
delay: 250,
|
||||
ease: "Cubic.easeIn",
|
||||
onComplete: () => {
|
||||
scene.playSound("se/pb_bounce_1");
|
||||
|
||||
scene.tweens.add({
|
||||
targets: tradedPokeball,
|
||||
y: "-=100",
|
||||
duration: 200,
|
||||
delay: 1000,
|
||||
ease: "Cubic.easeInOut",
|
||||
onStart: () => {
|
||||
scene.playSound("se/pb_throw");
|
||||
},
|
||||
onComplete: async () => {
|
||||
await doPokemonTradeFlyBySequence(scene, tradedPokemonSprite, receivedPokemonSprite);
|
||||
await doTradeReceivedSequence(scene, receivedPokemon, receivedPokemonSprite, receivedPokemonTintSprite, receivedPokeball, receivedPbAtlasKey);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function doPokemonTradeFlyBySequence(scene: BattleScene, tradedPokemonSprite: Phaser.GameObjects.Sprite, receivedPokemonSprite: Phaser.GameObjects.Sprite) {
|
||||
return new Promise<void>(resolve => {
|
||||
const tradeContainer = scene.fieldUI.getByName("Trade Background") as Phaser.GameObjects.Container;
|
||||
const tradeBaseBg = tradeContainer.getByName("Trade Background Image") as Phaser.GameObjects.Image;
|
||||
const flyByStaticBg = tradeContainer.getByName("Black Background") as Phaser.GameObjects.Rectangle;
|
||||
flyByStaticBg.setVisible(true);
|
||||
tradeContainer.bringToTop(tradedPokemonSprite);
|
||||
tradeContainer.bringToTop(receivedPokemonSprite);
|
||||
|
||||
tradedPokemonSprite.x = tradeBaseBg.displayWidth / 4;
|
||||
tradedPokemonSprite.y = 200;
|
||||
tradedPokemonSprite.scale = 1;
|
||||
tradedPokemonSprite.setVisible(true);
|
||||
receivedPokemonSprite.x = tradeBaseBg.displayWidth * 3 / 4;
|
||||
receivedPokemonSprite.y = -200;
|
||||
receivedPokemonSprite.scale = 1;
|
||||
receivedPokemonSprite.setVisible(true);
|
||||
|
||||
const FADE_DELAY = 300;
|
||||
const ANIM_DELAY = 750;
|
||||
const BASE_ANIM_DURATION = 1000;
|
||||
|
||||
// Fade out trade background
|
||||
scene.tweens.add({
|
||||
targets: tradeBaseBg,
|
||||
alpha: 0,
|
||||
ease: "Cubic.easeInOut",
|
||||
duration: FADE_DELAY,
|
||||
onComplete: () => {
|
||||
scene.tweens.add({
|
||||
targets: [receivedPokemonSprite, tradedPokemonSprite],
|
||||
y: tradeBaseBg.displayWidth / 2 - 100,
|
||||
ease: "Cubic.easeInOut",
|
||||
duration: BASE_ANIM_DURATION * 3,
|
||||
onComplete: () => {
|
||||
scene.tweens.add({
|
||||
targets: receivedPokemonSprite,
|
||||
x: tradeBaseBg.displayWidth / 4,
|
||||
ease: "Cubic.easeInOut",
|
||||
duration: BASE_ANIM_DURATION / 2,
|
||||
delay: ANIM_DELAY
|
||||
});
|
||||
scene.tweens.add({
|
||||
targets: tradedPokemonSprite,
|
||||
x: tradeBaseBg.displayWidth * 3 / 4,
|
||||
ease: "Cubic.easeInOut",
|
||||
duration: BASE_ANIM_DURATION / 2,
|
||||
delay: ANIM_DELAY,
|
||||
onComplete: () => {
|
||||
scene.tweens.add({
|
||||
targets: receivedPokemonSprite,
|
||||
y: "+=200",
|
||||
ease: "Cubic.easeInOut",
|
||||
duration: BASE_ANIM_DURATION * 2,
|
||||
delay: ANIM_DELAY,
|
||||
});
|
||||
scene.tweens.add({
|
||||
targets: tradedPokemonSprite,
|
||||
y: "-=200",
|
||||
ease: "Cubic.easeInOut",
|
||||
duration: BASE_ANIM_DURATION * 2,
|
||||
delay: ANIM_DELAY,
|
||||
onComplete: () => {
|
||||
scene.tweens.add({
|
||||
targets: tradeBaseBg,
|
||||
alpha: 1,
|
||||
ease: "Cubic.easeInOut",
|
||||
duration: FADE_DELAY,
|
||||
onComplete: () => {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function doTradeReceivedSequence(scene: BattleScene, receivedPokemon: PlayerPokemon, receivedPokemonSprite: Phaser.GameObjects.Sprite, receivedPokemonTintSprite: Phaser.GameObjects.Sprite, receivedPokeballSprite: Phaser.GameObjects.Sprite, receivedPbAtlasKey: string) {
|
||||
return new Promise<void>(resolve => {
|
||||
const tradeContainer = scene.fieldUI.getByName("Trade Background") as Phaser.GameObjects.Container;
|
||||
const tradeBaseBg = tradeContainer.getByName("Trade Background Image") as Phaser.GameObjects.Image;
|
||||
|
||||
receivedPokemonSprite.setVisible(false);
|
||||
receivedPokemonSprite.x = tradeBaseBg.displayWidth / 2;
|
||||
receivedPokemonSprite.y = tradeBaseBg.displayHeight / 2;
|
||||
receivedPokemonTintSprite.setVisible(false);
|
||||
receivedPokemonTintSprite.x = tradeBaseBg.displayWidth / 2;
|
||||
receivedPokemonTintSprite.y = tradeBaseBg.displayHeight / 2;
|
||||
|
||||
receivedPokeballSprite.setVisible(true);
|
||||
receivedPokeballSprite.x = tradeBaseBg.displayWidth / 2;
|
||||
receivedPokeballSprite.y = tradeBaseBg.displayHeight / 2 - 100;
|
||||
|
||||
const BASE_ANIM_DURATION = 1000;
|
||||
|
||||
// Pokeball falls to the screen
|
||||
scene.playSound("se/pb_throw");
|
||||
scene.tweens.add({
|
||||
targets: receivedPokeballSprite,
|
||||
y: "+=100",
|
||||
ease: "Cubic.easeInOut",
|
||||
duration: BASE_ANIM_DURATION,
|
||||
onComplete: () => {
|
||||
scene.playSound("se/pb_bounce_1");
|
||||
scene.time.delayedCall(100, () => scene.playSound("se/pb_bounce_1"));
|
||||
|
||||
scene.time.delayedCall(2000, () => {
|
||||
scene.playSound("se/pb_rel");
|
||||
scene.fadeOutBgm(500, false);
|
||||
receivedPokemon.cry();
|
||||
receivedPokemonTintSprite.scale = 0.25;
|
||||
receivedPokemonTintSprite.alpha = 1;
|
||||
receivedPokemonSprite.setVisible(true);
|
||||
receivedPokemonSprite.scale = 0.25;
|
||||
receivedPokemonTintSprite.alpha = 1;
|
||||
receivedPokemonTintSprite.setVisible(true);
|
||||
receivedPokeballSprite.setTexture("pb", `${receivedPbAtlasKey}_opening`);
|
||||
scene.time.delayedCall(17, () => receivedPokeballSprite.setTexture("pb", `${receivedPbAtlasKey}_open`));
|
||||
scene.tweens.add({
|
||||
targets: receivedPokemonSprite,
|
||||
duration: 250,
|
||||
ease: "Sine.easeOut",
|
||||
scale: 1
|
||||
});
|
||||
scene.tweens.add({
|
||||
targets: receivedPokemonTintSprite,
|
||||
duration: 250,
|
||||
ease: "Sine.easeOut",
|
||||
scale: 1,
|
||||
alpha: 0,
|
||||
onComplete: () => {
|
||||
receivedPokeballSprite.destroy();
|
||||
scene.time.delayedCall(2000, () => resolve());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function generateRandomTraderName() {
|
||||
const length = Object.keys(trainerNamePools).length;
|
||||
// +1 avoids TrainerType.UNKNOWN
|
||||
let trainerTypePool = trainerNamePools[randInt(length) + 1];
|
||||
while (!trainerTypePool) {
|
||||
trainerTypePool = trainerNamePools[randInt(length) + 1];
|
||||
}
|
||||
// Some trainers have 2 gendered pools, some do not
|
||||
const genderedPool = trainerTypePool[randInt(trainerTypePool.length)];
|
||||
const trainerNameString = genderedPool instanceof Array ? genderedPool[randInt(genderedPool.length)] : genderedPool;
|
||||
// Some names have an '&' symbol and need to be trimmed to a single name instead of a double name
|
||||
const trainerNames = trainerNameString.split(" & ");
|
||||
return trainerNames[randInt(trainerNames.length)];
|
||||
}
|
@ -256,7 +256,7 @@ async function summonSafariPokemon(scene: BattleScene) {
|
||||
|
||||
// Roll shiny twice
|
||||
if (!pokemon.shiny) {
|
||||
pokemon.trySetShiny();
|
||||
pokemon.trySetShinySeed();
|
||||
}
|
||||
|
||||
// Roll HA twice
|
||||
|
@ -45,7 +45,9 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
|
||||
spriteKey: "teleporter",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
y: 4
|
||||
x: 4,
|
||||
y: 4,
|
||||
yShadow: 1
|
||||
}
|
||||
])
|
||||
.withIntroDialogue([
|
||||
|
@ -7,10 +7,10 @@ import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||
import { leaveEncounterWithoutBattle, setEncounterRewards, } from "../utils/encounter-phase-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||
import { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||
import { IntegerHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils";
|
||||
import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { HiddenAbilityRateBoosterModifier, PokemonBaseStatTotalModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
|
||||
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
|
||||
import { achvs } from "#app/system/achv";
|
||||
import { speciesEggMoves } from "#app/data/egg-moves";
|
||||
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
||||
@ -247,7 +247,7 @@ function getTeamTransformations(scene: BattleScene): PokemonTransformation[] {
|
||||
pokemonTransformations[index].heldItems = removed.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier));
|
||||
scene.removePokemonFromPlayerParty(removed, false);
|
||||
|
||||
const bst = getOriginalBst(scene, removed);
|
||||
const bst = removed.calculateBaseStats().reduce((a, b) => a + b, 0);
|
||||
let newBstRange;
|
||||
if (i < 2) {
|
||||
newBstRange = HIGH_BST_TRANSFORM_BASE_VALUES;
|
||||
@ -415,22 +415,6 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
|
||||
}
|
||||
}
|
||||
|
||||
function getOriginalBst(scene: BattleScene, pokemon: Pokemon) {
|
||||
const baseStats = pokemon.getSpeciesForm().baseStats.slice(0);
|
||||
scene.applyModifiers(PokemonBaseStatTotalModifier, true, pokemon, baseStats);
|
||||
if (pokemon.fusionSpecies) {
|
||||
const fusionBaseStats = pokemon.getFusionSpeciesForm().baseStats;
|
||||
for (let s = 0; s < pokemon.stats.length; s++) {
|
||||
baseStats[s] = Math.ceil((baseStats[s] + fusionBaseStats[s]) / 2);
|
||||
}
|
||||
} else if (scene.gameMode.isSplicedOnly) {
|
||||
for (let s = 0; s < pokemon.stats.length; s++) {
|
||||
baseStats[s] = Math.ceil(baseStats[s] / 2);
|
||||
}
|
||||
}
|
||||
return baseStats.reduce((a, b) => a + b, 0);
|
||||
}
|
||||
|
||||
function getTransformedSpecies(originalBst: number, bstSearchRange: [number, number], hasPokemonBstHigherThan600: boolean, hasPokemonBstBetween570And600: boolean, alreadyUsedSpecies: PokemonSpecies[]): PokemonSpecies {
|
||||
let newSpecies: PokemonSpecies | undefined;
|
||||
while (isNullOrUndefined(newSpecies)) {
|
||||
|
@ -30,6 +30,7 @@ import { TeleportingHijinksEncounter } from "#app/data/mystery-encounters/encoun
|
||||
import { BugTypeSuperfanEncounter } from "#app/data/mystery-encounters/encounters/bug-type-superfan-encounter";
|
||||
import { FunAndGamesEncounter } from "#app/data/mystery-encounters/encounters/fun-and-games-encounter";
|
||||
import { UncommonBreedEncounter } from "#app/data/mystery-encounters/encounters/uncommon-breed-encounter";
|
||||
import { GlobalTradeSystemEncounter } from "#app/data/mystery-encounters/encounters/global-trade-system-encounter";
|
||||
|
||||
/**
|
||||
* Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / 256
|
||||
@ -184,7 +185,8 @@ const humanTransitableBiomeEncounters: MysteryEncounterType[] = [
|
||||
const civilizationBiomeEncounters: MysteryEncounterType[] = [
|
||||
MysteryEncounterType.DEPARTMENT_STORE_SALE,
|
||||
MysteryEncounterType.PART_TIMER,
|
||||
MysteryEncounterType.FUN_AND_GAMES
|
||||
MysteryEncounterType.FUN_AND_GAMES,
|
||||
MysteryEncounterType.GLOBAL_TRADE_SYSTEM
|
||||
];
|
||||
|
||||
/**
|
||||
@ -311,6 +313,7 @@ export function initMysteryEncounters() {
|
||||
allMysteryEncounters[MysteryEncounterType.BUG_TYPE_SUPERFAN] = BugTypeSuperfanEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.FUN_AND_GAMES] = FunAndGamesEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.UNCOMMON_BREED] = UncommonBreedEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.GLOBAL_TRADE_SYSTEM] = GlobalTradeSystemEncounter;
|
||||
|
||||
// Add extreme encounters to biome map
|
||||
extremeBiomeEncounters.forEach(encounter => {
|
||||
|
@ -53,11 +53,12 @@ export function queueEncounterMessage(scene: BattleScene, contentKey: string): v
|
||||
* @param contentKey
|
||||
* @param prompt
|
||||
* @param callbackDelay
|
||||
* @param promptDelay
|
||||
*/
|
||||
export function showEncounterText(scene: BattleScene, contentKey: string, callbackDelay: number = 0, prompt: boolean = true): Promise<void> {
|
||||
export function showEncounterText(scene: BattleScene, contentKey: string, callbackDelay: number = 0, prompt: boolean = true, promptDelay: number | null = null): Promise<void> {
|
||||
return new Promise<void>(resolve => {
|
||||
const text: string | null = getEncounterText(scene, contentKey);
|
||||
scene.ui.showText(text ?? "", null, () => resolve(), callbackDelay, prompt);
|
||||
scene.ui.showText(text ?? "", null, () => resolve(), callbackDelay, prompt, promptDelay);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -27,5 +27,6 @@ export enum MysteryEncounterType {
|
||||
TELEPORTING_HIJINKS,
|
||||
BUG_TYPE_SUPERFAN,
|
||||
FUN_AND_GAMES,
|
||||
UNCOMMON_BREED
|
||||
UNCOMMON_BREED,
|
||||
GLOBAL_TRADE_SYSTEM
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import { variantData } from "#app/data/variant";
|
||||
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "../ui/battle-info";
|
||||
import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr } from "../data/move";
|
||||
import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species";
|
||||
import { Constructor, isNullOrUndefined } from "#app/utils";
|
||||
import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils";
|
||||
import * as Utils from "../utils";
|
||||
import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type";
|
||||
import { getLevelTotalExp } from "../data/exp";
|
||||
@ -201,7 +201,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
this.fusionGender = dataSource.fusionGender;
|
||||
this.fusionLuck = dataSource.fusionLuck;
|
||||
this.usedTMs = dataSource.usedTMs ?? [];
|
||||
this.mysteryEncounterData = dataSource.mysteryEncounterData;
|
||||
this.mysteryEncounterData = dataSource.mysteryEncounterData ?? new MysteryEncounterPokemonData();
|
||||
} else {
|
||||
this.id = Utils.randSeedInt(4294967296);
|
||||
this.ivs = ivs || Utils.getIvsFromId(this.id);
|
||||
@ -577,8 +577,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
const formKey = this.getFormKey();
|
||||
if (formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1 || formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1) {
|
||||
return 1.5;
|
||||
} else if (!isNullOrUndefined(this.mysteryEncounterData?.spriteScale)) {
|
||||
return this.mysteryEncounterData.spriteScale;
|
||||
} else if (!isNullOrUndefined(this.mysteryEncounterData.spriteScale) && this.mysteryEncounterData.spriteScale !== 0) {
|
||||
return this.mysteryEncounterData.spriteScale!;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
@ -1082,7 +1082,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
if (!types.length || !includeTeraType) {
|
||||
if (this.mysteryEncounterData?.types && this.mysteryEncounterData.types.length > 0) {
|
||||
if (this.mysteryEncounterData.types && this.mysteryEncounterData.types.length > 0) {
|
||||
// "Permanent" override for a Pokemon's normal types, currently only used by Mystery Encounters
|
||||
this.mysteryEncounterData.types.forEach(t => types.push(t));
|
||||
} else if (!ignoreOverride && this.summonData?.types && this.summonData.types.length > 0) {
|
||||
@ -1716,6 +1716,42 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
return this.shiny;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that tries to set a Pokemon shiny based on seed.
|
||||
* For manual use only, usually to roll a Pokemon's shiny chance a second time.
|
||||
*
|
||||
* The base shiny odds are {@linkcode baseShinyChance} / 65536
|
||||
* @param thresholdOverride number that is divided by 2^16 (65536) to get the shiny chance, overrides {@linkcode shinyThreshold} if set (bypassing shiny rate modifiers such as Shiny Charm)
|
||||
* @param applyModifiersToOverride If {@linkcode thresholdOverride} is set and this is true, will apply Shiny Charm and event modifiers to {@linkcode thresholdOverride}
|
||||
* @returns true if the Pokemon has been set as a shiny, false otherwise
|
||||
*/
|
||||
trySetShinySeed(thresholdOverride?: integer, applyModifiersToOverride?: boolean): boolean {
|
||||
/** `64/65536 -> 1/1024` */
|
||||
const baseShinyChance = 64;
|
||||
const shinyThreshold = new Utils.IntegerHolder(baseShinyChance);
|
||||
if (thresholdOverride === undefined || applyModifiersToOverride) {
|
||||
if (thresholdOverride !== undefined && applyModifiersToOverride) {
|
||||
shinyThreshold.value = thresholdOverride;
|
||||
}
|
||||
if (this.scene.eventManager.isEventActive()) {
|
||||
shinyThreshold.value *= this.scene.eventManager.getShinyMultiplier();
|
||||
}
|
||||
if (!this.hasTrainer()) {
|
||||
this.scene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold);
|
||||
}
|
||||
} else {
|
||||
shinyThreshold.value = thresholdOverride;
|
||||
}
|
||||
|
||||
this.shiny = randSeedInt(65536) < shinyThreshold.value;
|
||||
|
||||
if (this.shiny) {
|
||||
this.initShinySparkle();
|
||||
}
|
||||
|
||||
return this.shiny;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a variant
|
||||
* Has a 10% of returning 2 (epic variant)
|
||||
|
@ -27,6 +27,7 @@ import teleportingHijinks from "#app/locales/en/mystery-encounters/teleporting-h
|
||||
import bugTypeSuperfan from "#app/locales/en/mystery-encounters/bug-type-superfan-dialogue.json";
|
||||
import funAndGames from "#app/locales/en/mystery-encounters/fun-and-games-dialogue.json";
|
||||
import uncommonBreed from "#app/locales/en/mystery-encounters/uncommon-breed-dialogue.json";
|
||||
import globalTradeSystem from "#app/locales/en/mystery-encounters/global-trade-system-dialogue.json";
|
||||
|
||||
/**
|
||||
* Injection patterns that can be used:
|
||||
@ -76,4 +77,5 @@ export const mysteryEncounter = {
|
||||
bugTypeSuperfan,
|
||||
funAndGames,
|
||||
uncommonBreed,
|
||||
globalTradeSystem
|
||||
} as const;
|
||||
|
@ -0,0 +1,32 @@
|
||||
{
|
||||
"intro": "It's an interface for the Global Trade System!",
|
||||
"title": "The GTS",
|
||||
"description": "Ah, the GTS! A technological wonder, you can connect with anyone else around the globe to trade Pokémon with them! Will fortune smile upon your trade today?",
|
||||
"query": "What will you do?",
|
||||
"option": {
|
||||
"1": {
|
||||
"label": "Check Trade Offers",
|
||||
"tooltip": "(+) Select a trade offer for one of your Pokémon",
|
||||
"trade_options_prompt": "Select a Pokémon to receive through trade."
|
||||
},
|
||||
"2": {
|
||||
"label": "Wonder Trade",
|
||||
"tooltip": "(+) Send one of your Pokémon to the GTS and get a random Pokémon in return"
|
||||
},
|
||||
"3": {
|
||||
"label": "Trade an Item",
|
||||
"trade_options_prompt": "Select an item to send.",
|
||||
"invalid_selection": "This Pokémon doesn't have legal items to trade.",
|
||||
"tooltip": "(+) Send one of your Items to the GTS and get a random new Item"
|
||||
},
|
||||
"4": {
|
||||
"label": "Leave",
|
||||
"tooltip": "(-) No Rewards",
|
||||
"selected": "No time to trade today!\nYou continue on."
|
||||
}
|
||||
},
|
||||
"pokemon_trade_selected": "{{tradedPokemon}} will be sent to {{tradeTrainerName}}.",
|
||||
"pokemon_trade_goodbye": "Goodbye, {{tradedPokemon}}!",
|
||||
"item_trade_selected": "{{chosenItem}} will be sent to {{tradeTrainerName}}.$.@d{64}.@d{64}.@d{64}\n@s{level_up_fanfare}Trade complete!$You received a {{itemName}} from {{tradeTrainerName}}!",
|
||||
"trade_received": "@s{evolution_fanfare}{{tradeTrainerName}} sent over {{received}}!"
|
||||
}
|
@ -103,6 +103,8 @@ export default class PokemonData {
|
||||
this.fusionLuck = source.fusionLuck !== undefined ? source.fusionLuck : (source.fusionShiny ? source.fusionVariant + 1 : 0);
|
||||
this.usedTMs = source.usedTMs ?? [];
|
||||
|
||||
this.mysteryEncounterData = source.mysteryEncounterData ?? new MysteryEncounterPokemonData();
|
||||
|
||||
if (!forHistory) {
|
||||
this.boss = (source instanceof EnemyPokemon && !!source.bossSegments) || (!this.player && !!source.boss);
|
||||
this.bossSegments = source.bossSegments;
|
||||
@ -114,7 +116,6 @@ export default class PokemonData {
|
||||
this.status = sourcePokemon.status;
|
||||
if (this.player) {
|
||||
this.summonData = sourcePokemon.summonData;
|
||||
this.mysteryEncounterData = sourcePokemon.mysteryEncounterData;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -143,14 +144,6 @@ export default class PokemonData {
|
||||
this.summonData.tags = [];
|
||||
}
|
||||
}
|
||||
|
||||
this.mysteryEncounterData = new MysteryEncounterPokemonData();
|
||||
if (!forHistory && source.mysteryEncounterData) {
|
||||
this.mysteryEncounterData.spriteScale = source.mysteryEncounterData.spriteScale;
|
||||
this.mysteryEncounterData.ability = source.mysteryEncounterData.ability;
|
||||
this.mysteryEncounterData.passive = source.mysteryEncounterData.passive;
|
||||
this.mysteryEncounterData.types = source.mysteryEncounterData.types;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,10 +113,10 @@ export async function runSelectMysteryEncounterOption(game: GameManager, optionN
|
||||
break;
|
||||
}
|
||||
|
||||
uiHandler.processInput(Button.ACTION);
|
||||
|
||||
if (!isNullOrUndefined(secondaryOptionSelect?.pokemonNo)) {
|
||||
await handleSecondaryOptionSelect(game, secondaryOptionSelect!.pokemonNo, secondaryOptionSelect!.optionNo);
|
||||
} else {
|
||||
uiHandler.processInput(Button.ACTION);
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,6 +124,10 @@ async function handleSecondaryOptionSelect(game: GameManager, pokemonNo: number,
|
||||
// Handle secondary option selections
|
||||
const partyUiHandler = game.scene.ui.handlers[Mode.PARTY] as PartyUiHandler;
|
||||
vi.spyOn(partyUiHandler, "show");
|
||||
|
||||
const encounterUiHandler = game.scene.ui.getHandler<MysteryEncounterUiHandler>();
|
||||
encounterUiHandler.processInput(Button.ACTION);
|
||||
|
||||
await vi.waitFor(() => expect(partyUiHandler.show).toHaveBeenCalled());
|
||||
|
||||
for (let i = 1; i < pokemonNo; i++) {
|
||||
|
@ -0,0 +1,270 @@
|
||||
import { Biome } from "#app/enums/biome";
|
||||
import { MysteryEncounterType } from "#app/enums/mystery-encounter-type";
|
||||
import { Species } from "#app/enums/species";
|
||||
import GameManager from "#app/test/utils/gameManager";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
|
||||
import { PokemonNatureWeightModifier } from "#app/modifier/modifier";
|
||||
import { generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { GlobalTradeSystemEncounter } from "#app/data/mystery-encounters/encounters/global-trade-system-encounter";
|
||||
import { CIVILIZATION_ENCOUNTER_BIOMES } from "#app/data/mystery-encounters/mystery-encounters";
|
||||
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
|
||||
const namespace = "mysteryEncounter:globalTradeSystem";
|
||||
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
|
||||
const defaultBiome = Biome.CAVE;
|
||||
const defaultWave = 45;
|
||||
|
||||
describe("Global Trade System - Mystery Encounter", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
let scene: BattleScene;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({ type: Phaser.HEADLESS });
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
game = new GameManager(phaserGame);
|
||||
scene = game.scene;
|
||||
game.override.mysteryEncounterChance(100);
|
||||
game.override.startingWave(defaultWave);
|
||||
game.override.startingBiome(defaultBiome);
|
||||
game.override.disableTrainerWaves();
|
||||
|
||||
const biomeMap = new Map<Biome, MysteryEncounterType[]>([
|
||||
[Biome.VOLCANO, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]],
|
||||
]);
|
||||
CIVILIZATION_ENCOUNTER_BIOMES.forEach(biome => {
|
||||
biomeMap.set(biome, [MysteryEncounterType.GLOBAL_TRADE_SYSTEM]);
|
||||
});
|
||||
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(biomeMap);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
vi.clearAllMocks();
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
it("should have the correct properties", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.GLOBAL_TRADE_SYSTEM, defaultParty);
|
||||
|
||||
expect(GlobalTradeSystemEncounter.encounterType).toBe(MysteryEncounterType.GLOBAL_TRADE_SYSTEM);
|
||||
expect(GlobalTradeSystemEncounter.encounterTier).toBe(MysteryEncounterTier.COMMON);
|
||||
expect(GlobalTradeSystemEncounter.dialogue).toBeDefined();
|
||||
expect(GlobalTradeSystemEncounter.dialogue.intro).toStrictEqual([{ text: `${namespace}.intro` }]);
|
||||
expect(GlobalTradeSystemEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`);
|
||||
expect(GlobalTradeSystemEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}.description`);
|
||||
expect(GlobalTradeSystemEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}.query`);
|
||||
expect(GlobalTradeSystemEncounter.options.length).toBe(4);
|
||||
});
|
||||
|
||||
it("should not run below wave 10", async () => {
|
||||
game.override.startingWave(9);
|
||||
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.GLOBAL_TRADE_SYSTEM);
|
||||
});
|
||||
|
||||
it("should not run above wave 179", async () => {
|
||||
game.override.startingWave(181);
|
||||
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(scene.currentBattle.mysteryEncounter).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should not spawn outside of CIVILIZATION_ENCOUNTER_BIOMES", async () => {
|
||||
game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON);
|
||||
game.override.startingBiome(Biome.VOLCANO);
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.GLOBAL_TRADE_SYSTEM);
|
||||
});
|
||||
|
||||
describe("Option 1 - Check Trade Offers", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option = GlobalTradeSystemEncounter.options[0];
|
||||
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
|
||||
expect(option.dialogue).toBeDefined();
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
secondOptionPrompt: `${namespace}.option.1.trade_options_prompt`,
|
||||
});
|
||||
});
|
||||
|
||||
it("Should trade a Pokemon from the player's party for the first of 3 Pokemon options", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.GLOBAL_TRADE_SYSTEM, defaultParty);
|
||||
|
||||
const speciesBefore = scene.getParty()[0].species.speciesId;
|
||||
await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1, optionNo: 1 });
|
||||
|
||||
const speciesAfter = scene.getParty().at(-1)?.species.speciesId;
|
||||
|
||||
expect(speciesAfter).toBeDefined();
|
||||
expect(speciesBefore).not.toBe(speciesAfter);
|
||||
expect(defaultParty.includes(speciesAfter!)).toBeFalsy();
|
||||
});
|
||||
|
||||
it("Should trade a Pokemon from the player's party for the second of 3 Pokemon options", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.GLOBAL_TRADE_SYSTEM, defaultParty);
|
||||
|
||||
const speciesBefore = scene.getParty()[1].species.speciesId;
|
||||
await runMysteryEncounterToEnd(game, 1, { pokemonNo: 2, optionNo: 2 });
|
||||
|
||||
const speciesAfter = scene.getParty().at(-1)?.species.speciesId;
|
||||
|
||||
expect(speciesAfter).toBeDefined();
|
||||
expect(speciesBefore).not.toBe(speciesAfter);
|
||||
expect(defaultParty.includes(speciesAfter!)).toBeFalsy();
|
||||
});
|
||||
|
||||
it("Should trade a Pokemon from the player's party for the third of 3 Pokemon options", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.GLOBAL_TRADE_SYSTEM, defaultParty);
|
||||
|
||||
const speciesBefore = scene.getParty()[2].species.speciesId;
|
||||
await runMysteryEncounterToEnd(game, 1, { pokemonNo: 3, optionNo: 3 });
|
||||
|
||||
const speciesAfter = scene.getParty().at(-1)?.species.speciesId;
|
||||
|
||||
expect(speciesAfter).toBeDefined();
|
||||
expect(speciesBefore).not.toBe(speciesAfter);
|
||||
expect(defaultParty.includes(speciesAfter!)).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.GLOBAL_TRADE_SYSTEM, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1, optionNo: 1 });
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Option 2 - Wonder Trade", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option = GlobalTradeSystemEncounter.options[1];
|
||||
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
|
||||
expect(option.dialogue).toBeDefined();
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`
|
||||
});
|
||||
});
|
||||
|
||||
it("Should trade a Pokemon from the player's party for the a random wonder trade Pokemon", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.GLOBAL_TRADE_SYSTEM, defaultParty);
|
||||
|
||||
const speciesBefore = scene.getParty()[2].species.speciesId;
|
||||
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1 });
|
||||
|
||||
const speciesAfter = scene.getParty().at(-1)?.species.speciesId;
|
||||
|
||||
expect(speciesAfter).toBeDefined();
|
||||
expect(speciesBefore).not.toBe(speciesAfter);
|
||||
expect(defaultParty.includes(speciesAfter!)).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.GLOBAL_TRADE_SYSTEM, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 2 });
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Option 3 - Trade an Item", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option = GlobalTradeSystemEncounter.options[2];
|
||||
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
|
||||
expect(option.dialogue).toBeDefined();
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}.option.3.label`,
|
||||
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||
secondOptionPrompt: `${namespace}.option.3.trade_options_prompt`,
|
||||
});
|
||||
});
|
||||
|
||||
it("should decrease item stacks of chosen item and have a tiered up item in rewards", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.GLOBAL_TRADE_SYSTEM, defaultParty);
|
||||
|
||||
// Set 2 Soul Dew on party lead
|
||||
scene.modifiers = [];
|
||||
const soulDew = generateModifierType(scene, modifierTypes.SOUL_DEW)!;
|
||||
const modifier = soulDew.newModifier(scene.getParty()[0]) as PokemonNatureWeightModifier;
|
||||
modifier.stackCount = 2;
|
||||
await scene.addModifier(modifier, true, false, false, true);
|
||||
await scene.updateModifiers(true);
|
||||
|
||||
await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1});
|
||||
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
|
||||
await game.phaseInterceptor.run(SelectModifierPhase);
|
||||
|
||||
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
|
||||
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
|
||||
expect(modifierSelectHandler.options.length).toEqual(1);
|
||||
expect(modifierSelectHandler.options[0].modifierTypeOption.type.tier).toBe(ModifierTier.MASTER);
|
||||
const soulDewAfter = scene.findModifier(m => m instanceof PokemonNatureWeightModifier);
|
||||
expect(soulDewAfter?.stackCount).toBe(1);
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.GLOBAL_TRADE_SYSTEM, defaultParty);
|
||||
|
||||
// Set 1 Soul Dew on party lead
|
||||
scene.modifiers = [];
|
||||
const soulDew = generateModifierType(scene, modifierTypes.SOUL_DEW)!;
|
||||
const modifier = soulDew.newModifier(scene.getParty()[0]) as PokemonNatureWeightModifier;
|
||||
modifier.stackCount = 1;
|
||||
await scene.addModifier(modifier, true, false, false, true);
|
||||
await scene.updateModifiers(true);
|
||||
|
||||
await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1});
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Option 4 - Leave", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option = GlobalTradeSystemEncounter.options[3];
|
||||
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
|
||||
expect(option.dialogue).toBeDefined();
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}.option.4.label`,
|
||||
buttonTooltip: `${namespace}.option.4.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.4.selected`,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.GLOBAL_TRADE_SYSTEM, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 4);
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
@ -66,7 +66,7 @@ describe("The Strong Stuff - Mystery Encounter", () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF, defaultParty);
|
||||
|
||||
expect(TheStrongStuffEncounter.encounterType).toBe(MysteryEncounterType.THE_STRONG_STUFF);
|
||||
expect(TheStrongStuffEncounter.encounterTier).toBe(MysteryEncounterTier.COMMON);
|
||||
expect(TheStrongStuffEncounter.encounterTier).toBe(MysteryEncounterTier.GREAT);
|
||||
expect(TheStrongStuffEncounter.dialogue).toBeDefined();
|
||||
expect(TheStrongStuffEncounter.dialogue.intro).toStrictEqual([{ text: `${namespace}.intro` }]);
|
||||
expect(TheStrongStuffEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`);
|
||||
@ -121,7 +121,7 @@ describe("The Strong Stuff - Mystery Encounter", () => {
|
||||
species: getPokemonSpecies(Species.SHUCKLE),
|
||||
isBoss: true,
|
||||
bossSegments: 5,
|
||||
mysteryEncounterData: new MysteryEncounterPokemonData(1.5),
|
||||
mysteryEncounterData: new MysteryEncounterPokemonData(1.25),
|
||||
nature: Nature.BOLD,
|
||||
moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER],
|
||||
modifierConfigs: expect.any(Array),
|
||||
|
@ -287,7 +287,7 @@ describe("Mystery Encounter Utils", () => {
|
||||
const spy = vi.spyOn(game.scene.ui, "showText");
|
||||
|
||||
await showEncounterText(scene, "mysteryEncounter:unit_test_dialogue");
|
||||
expect(spy).toHaveBeenCalledWith("valuevalue {{testvalue}} {{test1}} {{test}} {{test\\}} {{test\\}} {test}}", null, expect.any(Function), 0, true);
|
||||
expect(spy).toHaveBeenCalledWith("valuevalue {{testvalue}} {{test1}} {{test}} {{test\\}} {{test\\}} {test}}", null, expect.any(Function), 0, true, null);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -138,6 +138,7 @@ const noTransitionModes = [
|
||||
Mode.TEST_DIALOGUE,
|
||||
Mode.AUTO_COMPLETE,
|
||||
Mode.ADMIN,
|
||||
Mode.MYSTERY_ENCOUNTER
|
||||
];
|
||||
|
||||
export default class UI extends Phaser.GameObjects.Container {
|
||||
|
Loading…
x
Reference in New Issue
Block a user