From 7698e0f7bec1497576e49b49f979c1503794379a Mon Sep 17 00:00:00 2001 From: ImperialSympathizer Date: Sat, 7 Sep 2024 10:48:02 -0400 Subject: [PATCH] clean up GTS Encounter and add unit tests --- .../images/mystery-encounters/teleporter.json | 18 +- .../images/mystery-encounters/teleporter.png | Bin 661 -> 1303 bytes .../global-trade-system-encounter.ts | 417 ++++++++---------- .../encounters/safari-zone-encounter.ts | 2 +- .../teleporting-hijinks-encounter.ts | 4 +- src/field/pokemon.ts | 46 +- .../global-trade-system-dialogue.json | 6 +- src/overrides.ts | 4 +- src/system/pokemon-data.ts | 11 +- .../mystery-encounter/encounter-test-utils.ts | 8 +- .../global-trade-system-encounter.test.ts | 270 ++++++++++++ .../the-strong-stuff-encounter.test.ts | 4 +- .../mystery-encounter-utils.test.ts | 2 +- 13 files changed, 535 insertions(+), 257 deletions(-) create mode 100644 src/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts diff --git a/public/images/mystery-encounters/teleporter.json b/public/images/mystery-encounters/teleporter.json index e267c9a3dde..4fe45807be2 100644 --- a/public/images/mystery-encounters/teleporter.json +++ b/public/images/mystery-encounters/teleporter.json @@ -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$" } } diff --git a/public/images/mystery-encounters/teleporter.png b/public/images/mystery-encounters/teleporter.png index e71170ff184bdc93c8b35519b885800840af1dd3..9a049c30ab17f5288048e2104bffabce13bece3d 100644 GIT binary patch delta 1299 zcmV+u1?>8j1(ynciBL{Q4GJ0x0000DNk~Le0000=0000_2m=5B0DHru@Bjb+QBX`& zMNDaNCL%rFt1IK144jBCN8sP*Ykk*Z&Kx005}r|1OSL|1eDm8tVX&QWk#>!~g&e!~vBn4jTXf z1Yb!+K~z}7o!ARwn;;MX;2=juD=yk3?fUhExRw#eb2?U`s&sY!qU-=L@lLk#OEK*rQ{t;EfmSvT+uHG zCkew_*l2L>&J;>_FHuSav2&2{*B}YS*nqv#?v4_65UD+vB6O6-GAXf$LTTs1ZdiG& zSCj%&Nev1aqz072go;UJ(wC1UwJ3Kd3SjY6Eg|v82mXK9r?6{e(TwFDLg-$S{uJ?L zI}}O#zC{TsNve?c-Erhrq_#-y6lpqxBt^3zNc(QBb(a2d$*QPbq$RA2V^SnBva@BA zRZ#=5R8S2mQprkFG+4_{gBLA`NflC}qLr3lOly${)RtH3izVkg@L>;Y2Z8E2lgpF_Rw!7OcmFo(*)u&AAUqPkHUrVa2)@Ea|h)VsFxk-*o zg=eZ^EhVLmfjB*}J`y(yQLVdHlvrNsP?8lndk6=fI$A;MQPL&1`;;iFf=X7DGAdht zF3eA);s{16Ps`ekf{t!+=_Cr$2qJZ_sbtv{n{Izl3gQ&qscJGCSTf4yT`Z)jrqtvq z=a-<%D7408<&sHvU%d5!4OFUDIF}@%j2gXVOtLl_wSw~3Ngb?2iG&R%B_&d*qUEEI z(8?%pMM|U@EvAy?!<5toY@}vv3*fKE7QBSiBotfJ-57^`ijC4dbCrEuy~LKkCCWzx zr`mt(sZyoYgqwzq~QB^t*k2WmtmYkY&kSU>YP zP`u1urAq1lsP7;8K2qupir0KUt2ZcFrcC{O_o&i>CrJ^7&Pv07kUUjbXB0tyiTc2J zPBpEJB1i`++kw~pgvk7XrE9Tm#*fo zxlNVQctZW+*V>@iQiXJu6;=K5bChbzo@S+KQvFt?@;xjrGX1Q$>Ik(4@JQMIwGXn@ zjD39cvPyS~bBd?>qnj05>BbDtW++u^oiVA;xMU8L%>$lMSSsuu_VP1dHvdEUnrDBI z=T@eSd4aUlAcg<>1#_&9i(Rz?PS)zWZ;a#&7o4^;-?{> z0S`3Htm;8I&Ky%g%HiBL{Q4GJ0x0000DNk~Le0000$0000^2m=5B0H?j+oB#j-Bv4FL zMF0Q*0000G5D_ClOfEV?FhEE#z1~V%V{3$zl%>6(u*cr!_oz~`-;sV7e+v@;01FcV z0GgZ_0006CNkl6|NjmMAE5*Y?dC1#Iq&TIn6@dpetrE- zp{d36yD7aPr5B8ToQ!GyJ*i0(VBLmVYyvs`j3pVE=ATvR1)fj4up}VoftM@)6bPlj z@~@D85F-gtH?bM$Qy7G`v?!44+B;K8x1YM z_8`rW^}F!u0wOlE!WuwLyH0G{8M%X6H~~KG9;34`0j!P2BESa8 z&k!4=0}8-xRYo~xGjI$iAvSOb7h}Abj#iL>y;%-oepdrosFnlce+3<2@{&dj<#j;F z8`CbT#spyoxZ||bwx?y?tOBO~VsT}}edq&ZgxW&+SM7Ni0^Znotpj!b6M!GqJAltm zfm%S*00&O_2vovtfC>KsIG=@e;4}v^&^wR(Y90aIt4RTD!^^P+n*0ZV@iCkOle0Lv zn$$pN4%#_t^TR>-e{}#tehTnaecP@8c@E_KaF!Dggid}NeLH5re>*QUkO23)0(nW< zMF7$izy%}V-$VFAO`vYX#hc}XHb>4EV#jwV_HT2H7a)IH5YlThkbqLy4+fy5TOc8T z1kgLDty_R^T$i|A571V16tCv*1J{MJqFf@XTM4-F@z6# => { + 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); @@ -199,96 +276,10 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = await showEncounterText(scene, `${namespace}.trade_received`, 0, true, 4000); scene.playBgm("mystery_encounter_gts"); await hideTradeBackground(scene); + tradedPokemon.destroy(); - setEncounterRewards(scene, { fillRemaining: true }); - leaveEncounterWithoutBattle(scene); - }) - .build() - ) - .withOption( - MysteryEncounterOptionBuilder - .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) - .withDialogue({ - buttonLabel: `${namespace}.option.2.label`, - buttonTooltip: `${namespace}.option.2.tooltip`, - selected: [ - { - text: `${namespace}.option.2.selected`, - speaker: `${namespace}.speaker` - }, - { - text: `${namespace}.option.2.selected_2`, - }, - { - text: `${namespace}.option.2.selected_3`, - speaker: `${namespace}.speaker` - }, - ], - }) - .withPreOptionPhase(async (scene: BattleScene) => { - // Swap player's items on pokemon with the most items - // Item comparisons look at whichever Pokemon has the greatest number of TRANSFERABLE, non-berry items - // So Vitamins, form change items, etc. are not included - const encounter = scene.currentBattle.mysteryEncounter!; - - const party = scene.getParty(); - let mostHeldItemsPokemon = party[0]; - let count = mostHeldItemsPokemon.getHeldItems() - .filter(m => m.isTransferrable && !(m instanceof BerryModifier)) - .reduce((v, m) => v + m.stackCount, 0); - - party.forEach(pokemon => { - const nextCount = pokemon.getHeldItems() - .filter(m => m.isTransferrable && !(m instanceof BerryModifier)) - .reduce((v, m) => v + m.stackCount, 0); - if (nextCount > count) { - mostHeldItemsPokemon = pokemon; - count = nextCount; - } - }); - - encounter.setDialogueToken("switchPokemon", mostHeldItemsPokemon.getNameToRender()); - - const items = mostHeldItemsPokemon.getHeldItems(); - - // Shuffles Berries (if they have any) - let numBerries = 0; - items.filter(m => m instanceof BerryModifier) - .forEach(m => { - numBerries += m.stackCount; - scene.removeModifier(m); - }); - - generateItemsOfTier(scene, mostHeldItemsPokemon, numBerries, "Berries"); - - // Shuffle Transferable held items in the same tier (only shuffles Ultra and Rogue atm) - let numUltra = 0; - let numRogue = 0; - items.filter(m => m.isTransferrable && !(m instanceof BerryModifier)) - .forEach(m => { - const type = m.type.withTierFromPool(); - const tier = type.tier ?? ModifierTier.ULTRA; - if (type.id === "LUCKY_EGG" || tier === ModifierTier.ULTRA) { - numUltra += m.stackCount; - scene.removeModifier(m); - } else if (type.id === "GOLDEN_EGG" || tier === ModifierTier.ROGUE) { - numRogue += m.stackCount; - scene.removeModifier(m); - } - }); - - generateItemsOfTier(scene, mostHeldItemsPokemon, numUltra, ModifierTier.ULTRA); - generateItemsOfTier(scene, mostHeldItemsPokemon, numRogue, ModifierTier.ROGUE); - }) - .withOptionPhase(async (scene: BattleScene) => { leaveEncounterWithoutBattle(scene, true); }) - .withPostOptionPhase(async (scene: BattleScene) => { - // Play animations - const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon()!, scene.getPlayerPokemon()); - background.playWithoutTargets(scene, 230, 40, 2); - await transitionMysteryEncounterIntroVisuals(scene); - }) .build() ) .withOption( @@ -297,74 +288,108 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = .withDialogue({ buttonLabel: `${namespace}.option.3.label`, buttonTooltip: `${namespace}.option.3.tooltip`, - selected: [ - { - text: `${namespace}.option.3.selected`, - speaker: `${namespace}.speaker` - }, - { - text: `${namespace}.option.3.selected_2`, - }, - { - text: `${namespace}.option.3.selected_3`, - speaker: `${namespace}.speaker` - }, - ], + secondOptionPrompt: `${namespace}.option.3.trade_options_prompt`, }) - .withPreOptionPhase(async (scene: BattleScene) => { - // Swap player's types on all party pokemon - // If a Pokemon had a single type prior, they will still have a single type after - for (const pokemon of scene.getParty()) { - const originalTypes = pokemon.getTypes(false, false, true); + .withPreOptionPhase(async (scene: BattleScene): Promise => { + 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; + }); - // If the Pokemon has non-status moves that don't match the Pokemon's type, prioritizes those as the new type - // Makes the "randomness" of the shuffle slightly less punishing - let priorityTypes = pokemon.moveset - .filter(move => move && !originalTypes.includes(move.getMove().type) && move.getMove().category !== MoveCategory.STATUS) - .map(move => move!.getMove().type); - if (priorityTypes?.length > 0) { - priorityTypes = [...new Set(priorityTypes)]; - randSeedShuffle(priorityTypes); + 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; } - let newTypes: Type[]; - if (!originalTypes || originalTypes.length < 1) { - newTypes = priorityTypes && priorityTypes.length > 0 ? [priorityTypes.pop()!] : [(randSeedInt(18) as Type)]; - } else { - newTypes = originalTypes.map(m => { - if (priorityTypes && priorityTypes.length > 0) { - const ret = priorityTypes.pop()!; - randSeedShuffle(priorityTypes); - return ret; - } + return null; + }; - return randSeedInt(18) as Type; - }); - } - - if (!pokemon.mysteryEncounterData) { - pokemon.mysteryEncounterData = new MysteryEncounterPokemonData(undefined, undefined, undefined, newTypes); - } else { - pokemon.mysteryEncounterData.types = newTypes; - } - } + return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter); }) .withOptionPhase(async (scene: BattleScene) => { - leaveEncounterWithoutBattle(scene, true); - }) - .withPostOptionPhase(async (scene: BattleScene) => { - // Play animations - const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon()!, scene.getPlayerPokemon()); - background.playWithoutTargets(scene, 230, 40, 2); - await transitionMysteryEncounterIntroVisuals(scene); + 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() ) - .withOutroDialogue([ + .withSimpleOption( { - text: `${namespace}.outro`, + 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 { @@ -386,7 +411,7 @@ function getPokemonTradeOptions(scene: BattleScene): Map const tradeOptions: PokemonSpecies[] = []; for (let i = 0; i < 3; i++) { - const speciesTradeOption = generateTradeOption(originalBst, alreadyUsedSpecies); + const speciesTradeOption = generateTradeOption(alreadyUsedSpecies, originalBst); alreadyUsedSpecies.push(speciesTradeOption); tradeOptions.push(speciesTradeOption); } @@ -401,11 +426,15 @@ function getPokemonTradeOptions(scene: BattleScene): Map return tradeOptionsMap; } -function generateTradeOption(originalBst: number, alreadyUsedSpecies: PokemonSpecies[]): PokemonSpecies { +function generateTradeOption(alreadyUsedSpecies: PokemonSpecies[], originalBst?: number): PokemonSpecies { let newSpecies: PokemonSpecies | undefined; while (isNullOrUndefined(newSpecies)) { - let bstCap = originalBst + 100; - let bstMin = originalBst - 100; + 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 @@ -433,69 +462,6 @@ function generateTradeOption(originalBst: number, alreadyUsedSpecies: PokemonSpe return newSpecies!; } -function generateItemsOfTier(scene: BattleScene, pokemon: PlayerPokemon, numItems: integer, tier: ModifierTier | "Berries") { - // These pools have to be defined at runtime so that modifierTypes exist - // Pools have instances of the modifier type equal to the max stacks that modifier can be applied to any one pokemon - // This is to prevent "over-generating" a random item of a certain type during item swaps - const ultraPool = [ - [modifierTypes.REVIVER_SEED, 1], - [modifierTypes.GOLDEN_PUNCH, 5], - [modifierTypes.ATTACK_TYPE_BOOSTER, 99], - [modifierTypes.QUICK_CLAW, 3], - [modifierTypes.WIDE_LENS, 3] - ]; - - const roguePool = [ - [modifierTypes.LEFTOVERS, 4], - [modifierTypes.SHELL_BELL, 4], - [modifierTypes.SOUL_DEW, 10], - [modifierTypes.SOOTHE_BELL, 3], - [modifierTypes.SCOPE_LENS, 1], - [modifierTypes.BATON, 1], - [modifierTypes.FOCUS_BAND, 5], - [modifierTypes.KINGS_ROCK, 3], - [modifierTypes.GRIP_CLAW, 5] - ]; - - const berryPool = [ - [BerryType.APICOT, 3], - [BerryType.ENIGMA, 2], - [BerryType.GANLON, 3], - [BerryType.LANSAT, 3], - [BerryType.LEPPA, 2], - [BerryType.LIECHI, 3], - [BerryType.LUM, 2], - [BerryType.PETAYA, 3], - [BerryType.SALAC, 2], - [BerryType.SITRUS, 2], - [BerryType.STARF, 3] - ]; - - let pool: any[]; - if (tier === "Berries") { - pool = berryPool; - } else { - pool = tier === ModifierTier.ULTRA ? ultraPool : roguePool; - } - - for (let i = 0; i < numItems; i++) { - const randIndex = randSeedInt(pool.length); - const newItemType = pool[randIndex]; - let newMod; - if (tier === "Berries") { - newMod = generateModifierType(scene, modifierTypes.BERRY, [newItemType[0]]) as PokemonHeldItemModifierType; - } else { - newMod = generateModifierType(scene, newItemType[0]) as PokemonHeldItemModifierType; - } - applyModifierTypeToPlayerPokemon(scene, pokemon, newMod); - // Decrement max stacks and remove from pool if at max - newItemType[1]--; - if (newItemType[1] <= 0) { - pool.splice(randIndex, 1); - } - } -} - function showTradeBackground(scene: BattleScene) { return new Promise(resolve => { const tradeContainer = scene.add.container(0, -scene.game.canvas.height / 6); @@ -846,7 +812,12 @@ function doTradeReceivedSequence(scene: BattleScene, receivedPokemon: PlayerPoke function generateRandomTraderName() { const length = Object.keys(trainerNamePools).length; - const trainerTypePool = trainerNamePools[randInt(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 diff --git a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts index c07d952579c..e9eb7503c51 100644 --- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts +++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts @@ -256,7 +256,7 @@ async function summonSafariPokemon(scene: BattleScene) { // Roll shiny twice if (!pokemon.shiny) { - pokemon.trySetShiny(); + pokemon.trySetShinySeed(); } // Roll HA twice diff --git a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts index 2e99c32af9c..fc216f4d34f 100644 --- a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts +++ b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts @@ -45,7 +45,9 @@ export const TeleportingHijinksEncounter: MysteryEncounter = spriteKey: "teleporter", fileRoot: "mystery-encounters", hasShadow: true, - y: 4 + x: 4, + y: 4, + yShadow: 1 } ]) .withIntroDialogue([ diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index cd1d0daca0b..2a0c63d331b 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -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) diff --git a/src/locales/en/mystery-encounters/global-trade-system-dialogue.json b/src/locales/en/mystery-encounters/global-trade-system-dialogue.json index 2f6ce4993d3..1cc420355b7 100644 --- a/src/locales/en/mystery-encounters/global-trade-system-dialogue.json +++ b/src/locales/en/mystery-encounters/global-trade-system-dialogue.json @@ -15,16 +15,18 @@ }, "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 try your luck today!\nYou continue on." + "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": "{{tradedPokemon}} will be sent to {{tradeTrainerName}}", + "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}}!" } \ No newline at end of file diff --git a/src/overrides.ts b/src/overrides.ts index 046fc8f640c..709587e8481 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -141,9 +141,9 @@ class DefaultOverrides { // ------------------------- /** 1 to 256, set to null to ignore */ - readonly MYSTERY_ENCOUNTER_RATE_OVERRIDE: number | null = 256; + readonly MYSTERY_ENCOUNTER_RATE_OVERRIDE: number | null = null; readonly MYSTERY_ENCOUNTER_TIER_OVERRIDE: MysteryEncounterTier | null = null; - readonly MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType | null = MysteryEncounterType.GLOBAL_TRADE_SYSTEM; + readonly MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType | null = null; // ------------------------- // MODIFIER / ITEM OVERRIDES diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index c0fd88cd9dd..650b7d1950d 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -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; - } } } diff --git a/src/test/mystery-encounter/encounter-test-utils.ts b/src/test/mystery-encounter/encounter-test-utils.ts index 9b0e6c426b7..4f2bb6fe45b 100644 --- a/src/test/mystery-encounter/encounter-test-utils.ts +++ b/src/test/mystery-encounter/encounter-test-utils.ts @@ -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(); + encounterUiHandler.processInput(Button.ACTION); + await vi.waitFor(() => expect(partyUiHandler.show).toHaveBeenCalled()); for (let i = 1; i < pokemonNo; i++) { diff --git a/src/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts b/src/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts new file mode 100644 index 00000000000..f22244272fb --- /dev/null +++ b/src/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts @@ -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.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(); + }); + }); +}); diff --git a/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts b/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts index 45d032ce75c..edaf80f86c7 100644 --- a/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts @@ -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), diff --git a/src/test/mystery-encounter/mystery-encounter-utils.test.ts b/src/test/mystery-encounter/mystery-encounter-utils.test.ts index d15a34afa59..61eb1eaffd1 100644 --- a/src/test/mystery-encounter/mystery-encounter-utils.test.ts +++ b/src/test/mystery-encounter/mystery-encounter-utils.test.ts @@ -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); }); });