From 810c50cda27b5359add24eabc752beff37df0ffb Mon Sep 17 00:00:00 2001 From: ImperialSympathizer Date: Thu, 11 Jul 2024 11:40:07 -0400 Subject: [PATCH 1/5] new Field Trip encounter and add EXP gains to other encounters --- public/images/mystery-encounters/teacher.json | 41 ++++ public/images/mystery-encounters/teacher.png | Bin 0 -> 727 bytes .../dialogue/field-trip-dialogue.ts | 50 ++++ .../dialogue/shady-vitamin-dealer.ts | 4 +- .../encounters/department-store-sale.ts | 3 +- .../encounters/field-trip-encounter.ts | 220 ++++++++++++++++++ .../encounters/shady-vitamin-dealer.ts | 97 ++++++-- .../encounters/sleeping-snorlax.ts | 23 +- .../mystery-encounter-dialogue.ts | 2 + .../mystery-encounter-option.ts | 24 +- .../mystery-encounter-utils.ts | 7 +- .../mystery-encounters/mystery-encounter.ts | 7 +- .../mystery-encounters/mystery-encounters.ts | 2 + src/enums/mystery-encounter-type.ts | 3 +- src/locales/en/mystery-encounter.ts | 35 ++- src/overrides.ts | 7 +- src/phases.ts | 1 + src/ui/mystery-encounter-ui-handler.ts | 33 ++- 18 files changed, 495 insertions(+), 64 deletions(-) create mode 100644 public/images/mystery-encounters/teacher.json create mode 100644 public/images/mystery-encounters/teacher.png create mode 100644 src/data/mystery-encounters/dialogue/field-trip-dialogue.ts create mode 100644 src/data/mystery-encounters/encounters/field-trip-encounter.ts diff --git a/public/images/mystery-encounters/teacher.json b/public/images/mystery-encounters/teacher.json new file mode 100644 index 00000000000..881386d7228 --- /dev/null +++ b/public/images/mystery-encounters/teacher.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "teacher.png", + "format": "RGBA8888", + "size": { + "w": 43, + "h": 74 + }, + "scale": 1, + "frames": [ + { + "filename": "Spr_HGSS_Teacher.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 19, + "y": 8, + "w": 41, + "h": 72 + }, + "frame": { + "x": 1, + "y": 1, + "w": 41, + "h": 72 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:506e5a4ce79c134a7b4af90a90aef244:1b81d3d84bf12cedc419805eaff82548:59bc5dd000b5e72588320b473e31c312$" + } +} diff --git a/public/images/mystery-encounters/teacher.png b/public/images/mystery-encounters/teacher.png new file mode 100644 index 0000000000000000000000000000000000000000..b4332bc0032ccd93ec4319f4a1172e5bb676bcf7 GIT binary patch literal 727 zcmV;|0x127P)z5oCJY(`#A00001 zbW%=J06^y0W&i*H32;bRa{vGf6951U69E94oEQKA0zgSbK~zY`z1EGAqaY9jU>gJk zg1rCRZqI<4Tq2sfy`yS38~wH!ej=Hs|JY`2_vErH{B^14O-h?Fq|Gc_!)50uSbD2h`;Pajl)9EFxrdi0=*R8iKopcYvc zS-o>xN|GXCO>-*qD0c#T*Miq|Nk#Y2`P#*euBG4;jWIq2%h=?nQQ8o~LF?mzBe+!z zbV-}@hT}upjzL`_6+~L+XhXG)u8$a>?37tj%Kbh2cQqWAvmf$qYlvC7_G`t~PHEE` z<4#Sep+;S2uj6vL)@+Z_a6Ek`jQ2G+T8#yBpJKPsl-Q_Yifo`%s{w-M+Lz@*&NZkd ztsa&%vkzF8bAGW?U`O8cSg}5n8V|<)K?MhIB#8A^I9TwF;15>r@RrwCwO+3v18=*h zV`#XAwR*vWf4x~WYlw|Gf?@N!)nLP2K*`awXT_YLlVD!dR7;s9nem^Bn2Zj>i)9I# zM002ov JPDHLkV1m$-S=|5t literal 0 HcmV?d00001 diff --git a/src/data/mystery-encounters/dialogue/field-trip-dialogue.ts b/src/data/mystery-encounters/dialogue/field-trip-dialogue.ts new file mode 100644 index 00000000000..dca3f48f5bf --- /dev/null +++ b/src/data/mystery-encounters/dialogue/field-trip-dialogue.ts @@ -0,0 +1,50 @@ +import MysteryEncounterDialogue from "#app/data/mystery-encounters/mystery-encounter-dialogue"; + +export const FieldTripDialogue: MysteryEncounterDialogue = { + intro: [ + { + text: "mysteryEncounter:field_trip_intro_message" + }, + { + text: "mysteryEncounter:field_trip_intro_dialogue", + speaker: "mysteryEncounter:field_trip_speaker" + } + ], + encounterOptionsDialogue: { + title: "mysteryEncounter:field_trip_title", + description: "mysteryEncounter:field_trip_description", + query: "mysteryEncounter:field_trip_query", + options: [ + { + buttonLabel: "mysteryEncounter:field_trip_option_1_label", + buttonTooltip: "mysteryEncounter:field_trip_option_1_tooltip", + secondOptionPrompt: "mysteryEncounter:field_trip_second_option_prompt", + selected: [ + { + text: "mysteryEncounter:field_trip_option_selected" + } + ] + }, + { + buttonLabel: "mysteryEncounter:field_trip_option_2_label", + buttonTooltip: "mysteryEncounter:field_trip_option_2_tooltip", + secondOptionPrompt: "mysteryEncounter:field_trip_second_option_prompt", + selected: [ + { + text: "mysteryEncounter:field_trip_option_selected" + } + ] + }, + { + buttonLabel: "mysteryEncounter:field_trip_option_3_label", + buttonTooltip: "mysteryEncounter:field_trip_option_3_tooltip", + secondOptionPrompt: "mysteryEncounter:field_trip_second_option_prompt", + selected: [ + { + text: "mysteryEncounter:field_trip_option_selected" + } + ] + } + ] + } +}; diff --git a/src/data/mystery-encounters/dialogue/shady-vitamin-dealer.ts b/src/data/mystery-encounters/dialogue/shady-vitamin-dealer.ts index cb0c5ac130e..52b9741caf8 100644 --- a/src/data/mystery-encounters/dialogue/shady-vitamin-dealer.ts +++ b/src/data/mystery-encounters/dialogue/shady-vitamin-dealer.ts @@ -21,7 +21,7 @@ export const ShadyVitaminDealerDialogue: MysteryEncounterDialogue = { selected: [ { text: "mysteryEncounter:shady_vitamin_dealer_option_selected" - }, + } ] }, { @@ -30,7 +30,7 @@ export const ShadyVitaminDealerDialogue: MysteryEncounterDialogue = { selected: [ { text: "mysteryEncounter:shady_vitamin_dealer_option_selected" - }, + } ] }, { diff --git a/src/data/mystery-encounters/encounters/department-store-sale.ts b/src/data/mystery-encounters/encounters/department-store-sale.ts index 1d44c2b3af8..19269a774f3 100644 --- a/src/data/mystery-encounters/encounters/department-store-sale.ts +++ b/src/data/mystery-encounters/encounters/department-store-sale.ts @@ -1,5 +1,5 @@ import { - leaveEncounterWithoutBattle, setEncounterExp, + leaveEncounterWithoutBattle, setEncounterRewards, } from "#app/data/mystery-encounters/mystery-encounter-utils"; import { modifierTypes } from "#app/modifier/modifier-type"; @@ -46,7 +46,6 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBu i++; } - setEncounterExp(scene, scene.getParty().map(p => p.id), 300); setEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false }); leaveEncounterWithoutBattle(scene); }) diff --git a/src/data/mystery-encounters/encounters/field-trip-encounter.ts b/src/data/mystery-encounters/encounters/field-trip-encounter.ts new file mode 100644 index 00000000000..410df127b6e --- /dev/null +++ b/src/data/mystery-encounters/encounters/field-trip-encounter.ts @@ -0,0 +1,220 @@ +import { generateModifierTypeOption, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, setEncounterRewards, } from "#app/data/mystery-encounters/mystery-encounter-utils"; +import { modifierTypes } from "#app/modifier/modifier-type"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import BattleScene from "../../../battle-scene"; +import MysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier } from "../mystery-encounter"; +import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; +import { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; +import { MoveCategory } from "#app/data/move"; +import { TempBattleStat } from "#app/data/temp-battle-stat"; + +export const FieldTripEncounter: MysteryEncounter = MysteryEncounterBuilder + .withEncounterType(MysteryEncounterType.FIELD_TRIP) + .withEncounterTier(MysteryEncounterTier.COMMON) + .withIntroSpriteConfigs([ + { + spriteKey: "preschooler_m", + fileRoot: "trainer", + hasShadow: true + }, + { + spriteKey: "teacher", + fileRoot: "mystery-encounters", + hasShadow: true + }, + { + spriteKey: "preschooler_f", + fileRoot: "trainer", + hasShadow: true + }, + ]) + .withHideIntroVisuals(false) + .withSceneWaveRangeRequirement(10, 180) + .withOption(new MysteryEncounterOptionBuilder() + .withPreOptionPhase(async (scene: BattleScene): Promise => { + const encounter = scene.currentBattle.mysteryEncounter; + const onPokemonSelected = (pokemon: PlayerPokemon) => { + // Return the options for Pokemon move valid for this option + return pokemon.moveset.map((move: PokemonMove) => { + const option: OptionSelectItem = { + label: move.getName(), + handler: () => { + // Pokemon and move selected + const correctMove = move.getMove().category === MoveCategory.PHYSICAL; + encounter.setDialogueToken("moveCategory", "Physical"); + if (!correctMove) { + encounter.dialogue.encounterOptionsDialogue.options[0].selected = [ + { + text: "mysteryEncounter:field_trip_option_incorrect", + speaker: "mysteryEncounter:field_trip_speaker" + }, + { + text: "mysteryEncounter:field_trip_lesson_learned", + } + ]; + setEncounterExp(scene, scene.getParty().map(p => p.id), 50); + } else { + encounter.setDialogueToken("pokeName", pokemon.name); + encounter.setDialogueToken("move", move.getName()); + encounter.dialogue.encounterOptionsDialogue.options[0].selected = [ + { + text: "mysteryEncounter:field_trip_option_selected" + } + ]; + setEncounterExp(scene, [pokemon.id], 100); + } + encounter.misc = { + correctMove: correctMove + }; + return true; + } + }; + return option; + }); + }; + + return selectPokemonForOption(scene, onPokemonSelected); + }) + .withOptionPhase(async (scene: BattleScene) => { + const encounter = scene.currentBattle.mysteryEncounter; + if (encounter.misc.correctMove) { + const modifiers = [ + generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.ATK]), + generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.DEF]), + generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.SPD]), + generateModifierTypeOption(scene, modifierTypes.DIRE_HIT) + ]; + + setEncounterRewards(scene, { guaranteedModifierTypeOptions: modifiers, fillRemaining: false }); + } + + leaveEncounterWithoutBattle(scene, !encounter.misc.correctMove); + }) + .build() + ) + .withOption(new MysteryEncounterOptionBuilder() + .withPreOptionPhase(async (scene: BattleScene): Promise => { + const encounter = scene.currentBattle.mysteryEncounter; + const onPokemonSelected = (pokemon: PlayerPokemon) => { + // Return the options for Pokemon move valid for this option + return pokemon.moveset.map((move: PokemonMove) => { + const option: OptionSelectItem = { + label: move.getName(), + handler: () => { + // Pokemon and move selected + const correctMove = move.getMove().category === MoveCategory.SPECIAL; + encounter.setDialogueToken("moveCategory", "Special"); + if (!correctMove) { + encounter.dialogue.encounterOptionsDialogue.options[1].selected = [ + { + text: "mysteryEncounter:field_trip_option_incorrect", + speaker: "mysteryEncounter:field_trip_speaker" + }, + { + text: "mysteryEncounter:field_trip_lesson_learned", + } + ]; + setEncounterExp(scene, scene.getParty().map(p => p.id), 50); + } else { + encounter.setDialogueToken("pokeName", pokemon.name); + encounter.setDialogueToken("move", move.getName()); + encounter.dialogue.encounterOptionsDialogue.options[1].selected = [ + { + text: "mysteryEncounter:field_trip_option_selected" + } + ]; + setEncounterExp(scene, [pokemon.id], 100); + } + encounter.misc = { + correctMove: correctMove + }; + return true; + } + }; + return option; + }); + }; + + return selectPokemonForOption(scene, onPokemonSelected); + }) + .withOptionPhase(async (scene: BattleScene) => { + const encounter = scene.currentBattle.mysteryEncounter; + if (encounter.misc.correctMove) { + const modifiers = [ + generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.SPATK]), + generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.SPDEF]), + generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.SPD]), + generateModifierTypeOption(scene, modifierTypes.DIRE_HIT) + ]; + + setEncounterRewards(scene, { guaranteedModifierTypeOptions: modifiers, fillRemaining: false }); + } + + leaveEncounterWithoutBattle(scene, !encounter.misc.correctMove); + }) + .build() + ) + .withOption(new MysteryEncounterOptionBuilder() + .withPreOptionPhase(async (scene: BattleScene): Promise => { + const encounter = scene.currentBattle.mysteryEncounter; + const onPokemonSelected = (pokemon: PlayerPokemon) => { + // Return the options for Pokemon move valid for this option + return pokemon.moveset.map((move: PokemonMove) => { + const option: OptionSelectItem = { + label: move.getName(), + handler: () => { + // Pokemon and move selected + const correctMove = move.getMove().category === MoveCategory.STATUS; + encounter.setDialogueToken("moveCategory", "Status"); + if (!correctMove) { + encounter.dialogue.encounterOptionsDialogue.options[2].selected = [ + { + text: "mysteryEncounter:field_trip_option_incorrect", + speaker: "mysteryEncounter:field_trip_speaker" + }, + { + text: "mysteryEncounter:field_trip_lesson_learned", + } + ]; + setEncounterExp(scene, scene.getParty().map(p => p.id), 50); + } else { + encounter.setDialogueToken("pokeName", pokemon.name); + encounter.setDialogueToken("move", move.getName()); + encounter.dialogue.encounterOptionsDialogue.options[2].selected = [ + { + text: "mysteryEncounter:field_trip_option_selected" + } + ]; + setEncounterExp(scene, [pokemon.id], 100); + } + encounter.misc = { + correctMove: correctMove + }; + return true; + } + }; + return option; + }); + }; + + return selectPokemonForOption(scene, onPokemonSelected); + }) + .withOptionPhase(async (scene: BattleScene) => { + const encounter = scene.currentBattle.mysteryEncounter; + if (encounter.misc.correctMove) { + const modifiers = [ + generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.ACC]), + generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.SPD]), + generateModifierTypeOption(scene, modifierTypes.GREAT_BALL), + generateModifierTypeOption(scene, modifierTypes.IV_SCANNER) + ]; + + setEncounterRewards(scene, { guaranteedModifierTypeOptions: modifiers, fillRemaining: false }); + } + + leaveEncounterWithoutBattle(scene, !encounter.misc.correctMove); + }) + .build() + ) + .build(); diff --git a/src/data/mystery-encounters/encounters/shady-vitamin-dealer.ts b/src/data/mystery-encounters/encounters/shady-vitamin-dealer.ts index d58bc44daf7..21e1b199e04 100644 --- a/src/data/mystery-encounters/encounters/shady-vitamin-dealer.ts +++ b/src/data/mystery-encounters/encounters/shady-vitamin-dealer.ts @@ -1,9 +1,8 @@ import { - generateModifierType, + generateModifierTypeOption, leaveEncounterWithoutBattle, queueEncounterMessage, - selectPokemonForOption, - setEncounterRewards, + selectPokemonForOption, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/mystery-encounter-utils"; import { StatusEffect } from "#app/data/status-effect"; @@ -18,6 +17,7 @@ import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; import { MoneyRequirement } from "../mystery-encounter-requirements"; +import i18next from "i18next"; export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBuilder .withEncounterType(MysteryEncounterType.SHADY_VITAMIN_DEALER) @@ -43,7 +43,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui .withPrimaryPokemonStatusEffectRequirement([StatusEffect.NONE]) // Pokemon must not have status .withPrimaryPokemonHealthRatioRequirement([0.34, 1]) // Pokemon must have above 1/3rd HP .withOption(new MysteryEncounterOptionBuilder() - .withSceneMoneyRequirement(0, 2) // Wave scaling multiplier of 2 for cost + .withSceneMoneyRequirement(0, 2) // Wave scaling money multiplier of 2 .withPreOptionPhase(async (scene: BattleScene): Promise => { const encounter = scene.currentBattle.mysteryEncounter; const onPokemonSelected = (pokemon: PlayerPokemon) => { @@ -51,8 +51,8 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney); // Calculate modifiers and dialogue tokens const modifiers = [ - generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER), - generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER) + generateModifierTypeOption(scene, modifierTypes.BASE_STAT_BOOSTER).type, + generateModifierTypeOption(scene, modifierTypes.BASE_STAT_BOOSTER).type ]; encounter.setDialogueToken("boost1", modifiers[0].name); encounter.setDialogueToken("boost2", modifiers[1].name); @@ -62,12 +62,12 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui }; }; - // Only Pokemon that can gain benefits are unfainted with no status + // Only Pokemon that can gain benefits are above 1/3rd HP with no status const selectableFilter = (pokemon: Pokemon) => { // If pokemon meets primary pokemon reqs, it can be selected const meetsReqs = encounter.pokemonMeetsPrimaryRequirements(scene, pokemon); if (!meetsReqs) { - return "Pokémon must be healthy enough."; + return i18next.t("mysteryEncounter:shady_vitamin_dealer_invalid_selection"); } return null; @@ -99,7 +99,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui chosenPokemon.hp = Math.max(chosenPokemon.hp - damage, 0); // Roll for poison (80%) - if (randSeedInt(10) < 10) { + if (randSeedInt(10) < 8) { if (chosenPokemon.trySetStatus(StatusEffect.TOXIC)) { // Toxic applied queueEncounterMessage(scene, "mysteryEncounter:shady_vitamin_dealer_bad_poison"); @@ -111,32 +111,81 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui queueEncounterMessage(scene, "mysteryEncounter:shady_vitamin_dealer_damage_only"); } + setEncounterExp(scene, [chosenPokemon.id], 100); + chosenPokemon.updateInfo(); }) .build()) - .withOption(new MysteryEncounterOptionBuilder() - .withSceneMoneyRequirement(0, 5) // Wave scaling multiplier of 2 for cost + .withSceneMoneyRequirement(0, 5) // Wave scaling money multiplier of 5 + .withPreOptionPhase(async (scene: BattleScene): Promise => { + const encounter = scene.currentBattle.mysteryEncounter; + const onPokemonSelected = (pokemon: PlayerPokemon) => { + // Update money + updatePlayerMoney(scene, -(encounter.options[1].requirements[0] as MoneyRequirement).requiredMoney); + // Calculate modifiers and dialogue tokens + const modifiers = [ + generateModifierTypeOption(scene, modifierTypes.BASE_STAT_BOOSTER).type, + generateModifierTypeOption(scene, modifierTypes.BASE_STAT_BOOSTER).type + ]; + encounter.setDialogueToken("boost1", modifiers[0].name); + encounter.setDialogueToken("boost2", modifiers[1].name); + encounter.misc = { + chosenPokemon: pokemon, + modifiers: modifiers + }; + }; + + // Only Pokemon that can gain benefits are above 1/3rd HP with no status + const selectableFilter = (pokemon: Pokemon) => { + // If pokemon meets primary pokemon reqs, it can be selected + const meetsReqs = encounter.pokemonMeetsPrimaryRequirements(scene, pokemon); + if (!meetsReqs) { + return i18next.t("mysteryEncounter:shady_vitamin_dealer_invalid_selection"); + } + + return null; + }; + + return selectPokemonForOption(scene, onPokemonSelected, null, selectableFilter); + }) .withOptionPhase(async (scene: BattleScene) => { // Choose Expensive Option - const modifiers = []; - let i = 0; - while (i < 3) { - // 2/1 weight on base stat booster vs PP Up - const roll = randSeedInt(3); - if (roll === 0) { - modifiers.push(modifierTypes.PP_UP); - } else { + const encounter = scene.currentBattle.mysteryEncounter; + const chosenPokemon = encounter.misc.chosenPokemon; + const modifiers = encounter.misc.modifiers; - } - i++; + for (const modType of modifiers) { + const modifier = modType.newModifier(chosenPokemon); + await scene.addModifier(modifier, true, false, false, true); } + scene.updateModifiers(true); - setEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false }); leaveEncounterWithoutBattle(scene); }) - .build() - ) + .withPostOptionPhase(async (scene: BattleScene) => { + // Status applied after dealer leaves (to make thematic sense) + const encounter = scene.currentBattle.mysteryEncounter; + const chosenPokemon = encounter.misc.chosenPokemon; + + // Roll for poison (20%) + if (randSeedInt(10) < 2) { + if (chosenPokemon.trySetStatus(StatusEffect.POISON)) { + // Poison applied + queueEncounterMessage(scene, "mysteryEncounter:shady_vitamin_dealer_poison"); + } else { + // Pokemon immune or something else prevents status + queueEncounterMessage(scene, "mysteryEncounter:shady_vitamin_dealer_no_bad_effects"); + } + } else { + queueEncounterMessage(scene, "mysteryEncounter:shady_vitamin_dealer_no_bad_effects"); + } + + setEncounterExp(scene, [chosenPokemon.id], 100); + + chosenPokemon.updateInfo(); + }) + .build()) .withOptionPhase(async (scene: BattleScene) => { // Leave encounter with no rewards or exp leaveEncounterWithoutBattle(scene, true); diff --git a/src/data/mystery-encounters/encounters/sleeping-snorlax.ts b/src/data/mystery-encounters/encounters/sleeping-snorlax.ts index 1a011bb05a0..cb2029272ef 100644 --- a/src/data/mystery-encounters/encounters/sleeping-snorlax.ts +++ b/src/data/mystery-encounters/encounters/sleeping-snorlax.ts @@ -1,5 +1,4 @@ import { - ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type"; import { BerryType } from "#enums/berry-type"; @@ -15,9 +14,9 @@ import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; import { MoveRequirement } from "../mystery-encounter-requirements"; import { EnemyPartyConfig, - EnemyPokemonConfig, generateModifierType, + EnemyPokemonConfig, generateModifierTypeOption, initBattleWithEnemyConfig, - leaveEncounterWithoutBattle, queueEncounterMessage, + leaveEncounterWithoutBattle, queueEncounterMessage, setEncounterExp, setEncounterRewards } from "../mystery-encounter-utils"; @@ -30,7 +29,9 @@ export const SleepingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuilde fileRoot: "pokemon", hasShadow: true, tint: 0.25, - repeat: true + scale: 1.5, + repeat: true, + y: 5 } ]) .withSceneWaveRangeRequirement(10, 180) // waves 10 to 180 @@ -66,16 +67,19 @@ export const SleepingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuilde scene.executeWithSeedOffset(() => { roll = Utils.randSeedInt(16, 0); }, scene.currentBattle.waveIndex); - console.log(roll); + + // Half Snorlax exp to entire party + setEncounterExp(scene, scene.getParty().map(p => p.id), 98); + if (roll > 4) { // Fall asleep and get a sitrus berry (75%) const p = instance.primaryPokemon; p.status = new Status(StatusEffect.SLEEP, 0, 3); p.updateInfo(true); // const sitrus = (modifierTypes.BERRY?.() as ModifierTypeGenerator).generateType(scene.getParty(), [BerryType.SITRUS]); - const sitrus = generateModifierType(scene, modifierTypes.BERRY, [BerryType.SITRUS]); + const sitrus = generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.SITRUS]); - setEncounterRewards(scene, { guaranteedModifierTypeOptions: [new ModifierTypeOption(sitrus, 0)], fillRemaining: false }); + setEncounterRewards(scene, { guaranteedModifierTypeOptions: [sitrus], fillRemaining: false }); queueEncounterMessage(scene, "mysteryEncounter:sleeping_snorlax_option_2_bad_result"); leaveEncounterWithoutBattle(scene); } else { @@ -96,9 +100,12 @@ export const SleepingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuilde .withOption(new MysteryEncounterOptionBuilder() .withPrimaryPokemonRequirement(new MoveRequirement([Moves.PLUCK, Moves.COVET, Moves.KNOCK_OFF, Moves.THIEF, Moves.TRICK, Moves.SWITCHEROO])) .withOptionPhase(async (scene: BattleScene) => { - // Leave encounter with no rewards or exp + // Steal the Snorlax's Leftovers + const instance = scene.currentBattle.mysteryEncounter; setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.LEFTOVERS], fillRemaining: false }); queueEncounterMessage(scene, "mysteryEncounter:sleeping_snorlax_option_3_good_result"); + // Snorlax exp to Pokemon that did the stealing + setEncounterExp(scene, [instance.primaryPokemon.id], 189); leaveEncounterWithoutBattle(scene); }) .build() diff --git a/src/data/mystery-encounters/mystery-encounter-dialogue.ts b/src/data/mystery-encounters/mystery-encounter-dialogue.ts index 1e2dfe85045..5129f3bf23e 100644 --- a/src/data/mystery-encounters/mystery-encounter-dialogue.ts +++ b/src/data/mystery-encounters/mystery-encounter-dialogue.ts @@ -8,6 +8,7 @@ import { SleepingSnorlaxDialogue } from "./dialogue/sleeping-snorlax-dialogue"; import { DepartmentStoreSaleDialogue } from "#app/data/mystery-encounters/dialogue/department-store-sale-dialogue"; import { ShadyVitaminDealerDialogue } from "#app/data/mystery-encounters/dialogue/shady-vitamin-dealer"; import { TextStyle } from "#app/ui/text"; +import { FieldTripDialogue } from "#app/data/mystery-encounters/dialogue/field-trip-dialogue"; export class TextDisplay { speaker?: TemplateStringsArray | `mysteryEncounter:${string}`; @@ -92,4 +93,5 @@ export function initMysteryEncounterDialogue() { allMysteryEncounterDialogue[MysteryEncounterType.SLEEPING_SNORLAX] = SleepingSnorlaxDialogue; allMysteryEncounterDialogue[MysteryEncounterType.DEPARTMENT_STORE_SALE] = DepartmentStoreSaleDialogue; allMysteryEncounterDialogue[MysteryEncounterType.SHADY_VITAMIN_DEALER] = ShadyVitaminDealerDialogue; + allMysteryEncounterDialogue[MysteryEncounterType.FIELD_TRIP] = FieldTripDialogue; } diff --git a/src/data/mystery-encounters/mystery-encounter-option.ts b/src/data/mystery-encounters/mystery-encounter-option.ts index f1316ff64f1..7731468a977 100644 --- a/src/data/mystery-encounters/mystery-encounter-option.ts +++ b/src/data/mystery-encounters/mystery-encounter-option.ts @@ -3,6 +3,7 @@ import { PlayerPokemon } from "#app/field/pokemon"; import BattleScene from "../../battle-scene"; import * as Utils from "../../utils"; import { EncounterPokemonRequirement, EncounterSceneRequirement, MoneyRequirement } from "./mystery-encounter-requirements"; +import { isNullOrUndefined } from "../../utils"; export type OptionPhaseCallback = (scene: BattleScene) => Promise; @@ -14,6 +15,12 @@ export default interface MysteryEncounterOption { primaryPokemon?: PlayerPokemon; secondaryPokemon?: PlayerPokemon[]; excludePrimaryFromSecondaryRequirements?: boolean; + /** + * There are two modes of option requirements: + * 1 (DEFAULT): Option is completely disabled if requirements are not met (unselectable and greyed out) + * 2: Option is *NOT* disabled if requirements are not met + */ + isDisabledOnRequirementsNotMet?: boolean; /** * Dialogue object containing all the dialogue, messages, tooltips, etc. for this option @@ -33,12 +40,19 @@ export default class MysteryEncounterOption implements MysteryEncounterOption { constructor(option: MysteryEncounterOption) { Object.assign(this, option); this.requirements = this.requirements ? this.requirements : []; + this.primaryPokemonRequirements = this.primaryPokemonRequirements ? this.primaryPokemonRequirements : []; + this.secondaryPokemonRequirements = this.secondaryPokemonRequirements ? this.secondaryPokemonRequirements : []; + this.isDisabledOnRequirementsNotMet = isNullOrUndefined(this.isDisabledOnRequirementsNotMet) ? true : this.isDisabledOnRequirementsNotMet; + } + + hasRequirements?() { + return this.requirements.length > 0 || this.primaryPokemonRequirements.length > 0 || this.secondaryPokemonRequirements.length > 0; } meetsRequirements?(scene: BattleScene) { return !this.requirements.some(requirement => !requirement.meetsRequirement(scene)) && - this.meetsPrimaryRequirementAndPrimaryPokemonSelected(scene) && - this.meetsSupportingRequirementAndSupportingPokemonSelected(scene); + this.meetsSupportingRequirementAndSupportingPokemonSelected(scene) && + this.meetsPrimaryRequirementAndPrimaryPokemonSelected(scene); } meetsPrimaryRequirementAndPrimaryPokemonSelected?(scene: BattleScene) { @@ -124,6 +138,7 @@ export class MysteryEncounterOptionBuilder implements Partial> { + this.isDisabledOnRequirementsNotMet = disabled; + return Object.assign(this, { isDisabledOnRequirementsNotMet: this.isDisabledOnRequirementsNotMet }); + } } diff --git a/src/data/mystery-encounters/mystery-encounter-utils.ts b/src/data/mystery-encounters/mystery-encounter-utils.ts index 2d8d7bfbf66..d2e1a5eba2f 100644 --- a/src/data/mystery-encounters/mystery-encounter-utils.ts +++ b/src/data/mystery-encounters/mystery-encounter-utils.ts @@ -8,7 +8,7 @@ import { TrainerConfig, trainerConfigs, TrainerSlot } from "../trainer-config"; import Pokemon, { FieldPosition, PlayerPokemon } from "#app/field/pokemon"; import Trainer, { TrainerVariant } from "../../field/trainer"; import { ExpBalanceModifier, ExpShareModifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier } from "#app/modifier/modifier"; -import { CustomModifierSettings, getModifierPoolForType, ModifierPoolType, ModifierType, ModifierTypeFunc, ModifierTypeGenerator, modifierTypes, PokemonHeldItemModifierType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; +import { CustomModifierSettings, getModifierPoolForType, ModifierPoolType, ModifierType, ModifierTypeFunc, ModifierTypeGenerator, ModifierTypeOption, modifierTypes, PokemonHeldItemModifierType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import { BattleEndPhase, EggLapsePhase, ExpPhase, ModifierRewardPhase, SelectModifierPhase, ShowPartyExpBarPhase, TrainerVictoryPhase } from "#app/phases"; import { MysteryEncounterBattlePhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phase"; import * as Utils from "../../utils"; @@ -443,7 +443,7 @@ export function updatePlayerMoney(scene: BattleScene, changeValue: number, playS * @param modifier * @param pregenArgs - can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc. */ -export function generateModifierType(scene: BattleScene, modifier: () => ModifierType, pregenArgs?: any[]): ModifierType { +export function generateModifierTypeOption(scene: BattleScene, modifier: () => ModifierType, pregenArgs?: any[]): ModifierTypeOption { const modifierId = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifier); let result: ModifierType = modifierTypes[modifierId]?.(); @@ -463,7 +463,7 @@ export function generateModifierType(scene: BattleScene, modifier: () => Modifie }); result = result instanceof ModifierTypeGenerator ? result.generateType(scene.getParty(), pregenArgs) : result; - return result; + return new ModifierTypeOption(result, 0); } /** @@ -593,6 +593,7 @@ export function setEncounterRewards(scene: BattleScene, customShopRewards?: Cust * 290 - trio legendaries * 340 - box legendaries * 608 - Blissey (highest in game) + * https://bulbapedia.bulbagarden.net/wiki/List_of_Pok%C3%A9mon_by_effort_value_yield_(Generation_IX) * @param useWaveIndex - set to false when directly passing the the full exp value instead of baseExpValue */ export function setEncounterExp(scene: BattleScene, participantIds: integer[], baseExpValue: number, useWaveIndex: boolean = true) { diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index 87b80aba321..0d656988009 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -120,6 +120,7 @@ export default interface MysteryEncounter { /** * Generic property to set any custom data required for the encounter + * Extremely useful for carrying state/data between onPreOptionPhase/onOptionPhase/onPostOptionPhase */ misc?: any; } @@ -276,6 +277,7 @@ export default class MysteryEncounter implements MysteryEncounter { * For multiple support pokemon in the dialogue token, it will have to be overridden. */ populateDialogueTokensFromRequirements?(scene: BattleScene) { + this.meetsRequirements(scene); if (this.requirements?.length > 0) { for (const req of this.requirements) { const dialogueToken = req.getDialogueToken(scene); @@ -304,6 +306,7 @@ export default class MysteryEncounter implements MysteryEncounter { // Dialogue tokens for options for (let i = 0; i < this.options.length; i++) { const opt = this.options[i]; + opt.meetsRequirements(scene); const j = i + 1; if (opt.requirements?.length > 0) { for (const req of opt.requirements) { @@ -316,7 +319,7 @@ export default class MysteryEncounter implements MysteryEncounter { for (const req of opt.primaryPokemonRequirements) { if (!req.invertQuery) { const value = req.getDialogueToken(scene, opt.primaryPokemon); - this.setDialogueToken("option" + j + "Primary", value[1]); + this.setDialogueToken("option" + j + "Primary" + this.capitalizeFirstLetter(value[0]), value[1]); } } } @@ -325,7 +328,7 @@ export default class MysteryEncounter implements MysteryEncounter { for (const req of opt.secondaryPokemonRequirements) { if (!req.invertQuery) { const value = req.getDialogueToken(scene, opt.secondaryPokemon[0]); - this.setDialogueToken("option" + j + "Secondary", value[1]); + this.setDialogueToken("option" + j + "Secondary" + this.capitalizeFirstLetter(value[0]), value[1]); } } } diff --git a/src/data/mystery-encounters/mystery-encounters.ts b/src/data/mystery-encounters/mystery-encounters.ts index e4a748d3a23..a1f8a73192e 100644 --- a/src/data/mystery-encounters/mystery-encounters.ts +++ b/src/data/mystery-encounters/mystery-encounters.ts @@ -9,6 +9,7 @@ import { SleepingSnorlaxEncounter } from "./encounters/sleeping-snorlax"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { DepartmentStoreSaleEncounter } from "#app/data/mystery-encounters/encounters/department-store-sale"; import { ShadyVitaminDealerEncounter } from "#app/data/mystery-encounters/encounters/shady-vitamin-dealer"; +import { FieldTripEncounter } from "#app/data/mystery-encounters/encounters/field-trip-encounter"; // Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * ) / 256 export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1; @@ -95,6 +96,7 @@ export function initMysteryEncounters() { allMysteryEncounters[MysteryEncounterType.SLEEPING_SNORLAX] = SleepingSnorlaxEncounter; allMysteryEncounters[MysteryEncounterType.DEPARTMENT_STORE_SALE] = DepartmentStoreSaleEncounter; allMysteryEncounters[MysteryEncounterType.SHADY_VITAMIN_DEALER] = ShadyVitaminDealerEncounter; + allMysteryEncounters[MysteryEncounterType.FIELD_TRIP] = FieldTripEncounter; // Append encounters that can occur in any biome to biome map const anyBiomeEncounters: MysteryEncounterType[] = Object.keys(MysteryEncounterType).filter(e => !isNaN(Number(e))).map(k => Number(k) as MysteryEncounterType); diff --git a/src/enums/mystery-encounter-type.ts b/src/enums/mystery-encounter-type.ts index 6e2815babca..b5b0144c3be 100644 --- a/src/enums/mystery-encounter-type.ts +++ b/src/enums/mystery-encounter-type.ts @@ -6,5 +6,6 @@ export enum MysteryEncounterType { SLEEPING_SNORLAX, TRAINING_SESSION, DEPARTMENT_STORE_SALE, - SHADY_VITAMIN_DEALER + SHADY_VITAMIN_DEALER, + FIELD_TRIP } diff --git a/src/locales/en/mystery-encounter.ts b/src/locales/en/mystery-encounter.ts index ac04eed2688..59fcf6940c2 100644 --- a/src/locales/en/mystery-encounter.ts +++ b/src/locales/en/mystery-encounter.ts @@ -17,6 +17,7 @@ export const mysteryEncounter: SimpleTranslationEntries = { "unit_test_dialogue": "@ec{test}@ec{test} @ec{test@ec{test}} @ec{test1} @ec{test\} @ec{test\\} @ec{test\\\} {test}", // Mystery Encounters -- Common Tier + "mysterious_chest_intro_message": "You found...@d{32} a chest?", "mysterious_chest_title": "The Mysterious Chest", "mysterious_chest_description": "A beautifully ornamented chest stands on the ground. There must be something good inside... right?", @@ -82,6 +83,7 @@ export const mysteryEncounter: SimpleTranslationEntries = { "shady_vitamin_dealer_title": "The Vitamin Dealer", "shady_vitamin_dealer_description": "The man opens his jacket to reveal some Pokémon vitamins. The numbers he quotes seem like a really good deal. Almost too good...\nHe offers two package deals to choose from.", "shady_vitamin_dealer_query": "Which deal will choose?", + "shady_vitamin_dealer_invalid_selection": "Pokémon must be healthy enough.", "shady_vitamin_dealer_option_1_label": "The Cheap Deal", "shady_vitamin_dealer_option_1_tooltip": "(-) Pay @ec{option1Money}\n(-) Side Effects?\n(+) Chosen Pokémon Gains 2 Random Vitamins", "shady_vitamin_dealer_option_2_label": "The Pricey Deal", @@ -94,9 +96,34 @@ export const mysteryEncounter: SimpleTranslationEntries = { $Your @ec{selectedPokemon} takes some damage\nand becomes badly poisoned...`, "shady_vitamin_dealer_poison": `But the medicine had some side effects! $Your @ec{selectedPokemon} becomes poisoned...`, + "shady_vitamin_dealer_no_bad_effects": "Looks like there were no side-effects this time.", "shady_vitamin_dealer_option_3_label": "Leave", "shady_vitamin_dealer_option_3_tooltip": "(-) No Rewards", - "shady_vitamin_dealer_outro_good": "Looks like there were no side-effects this time.", + + "field_trip_intro_message": "It's a teacher and some school children!", + "field_trip_speaker": "Teacher", + "field_trip_intro_dialogue": `Hello, there! Would you be able to\nspare a minute for my students? + $I'm teaching them about Pokémon moves\nand would love to show them a demonstration. + $Would you mind showing us one of\nthe moves your Pokémon can use?`, + "field_trip_title": "Field Trip", + "field_trip_description": "A teacher is requesting a move demonstration from a Pokémon. Depending on the move you choose, she might have something useful for you in exchange.", + "field_trip_query": "Which move category will you show off?", + // "field_trip_invalid_selection": "Pokémon doesn't know that type of move.", + "field_trip_option_1_label": "A Physical Move", + "field_trip_option_1_tooltip": "(+) Physical Item Rewards", + "field_trip_option_2_label": "A Special Move", + "field_trip_option_2_tooltip": "(+) Special Item Rewards", + "field_trip_option_3_label": "A Status Move", + "field_trip_option_3_tooltip": "(+) Status Item Rewards", + "field_trip_second_option_prompt": "Choose a move for your Pokémon to use.", + "field_trip_option_selected": "@ec{pokeName} shows off an awesome display of @ec{move}!", + "field_trip_option_incorrect": `... + $That isn't a @ec{moveCategory} move! + $I'm sorry, but I can't give you anything.`, + "field_trip_lesson_learned": `Looks like you learned a valuable lesson? + $Your Pokémon also gained some knowledge.`, + "field_trip_outro_good": "Thank you so much for your kindness!\nI hope the items I had were helpful!", + "field_trip_outro_bad": "Come along children, we'll\nfind a better demonstration elsewhere.", // Mystery Encounters -- Uncommon Tier @@ -162,14 +189,14 @@ export const mysteryEncounter: SimpleTranslationEntries = { "sleeping_snorlax_intro_message": `As you walk down a narrow pathway, you see a towering silhouette blocking your path. $You get closer to see a Snorlax sleeping peacefully.\nIt seems like there's no way around it.`, "sleeping_snorlax_title": "Sleeping Snorlax", - "sleeping_snorlax_description": "You could attack it to try and get it to move, or simply wait for it to wake up.", + "sleeping_snorlax_description": "You could attack it to try and get it to move, or simply wait for it to wake up. Who knows how long that could take, though...", "sleeping_snorlax_query": "What will you do?", "sleeping_snorlax_option_1_label": "Fight it", "sleeping_snorlax_option_1_tooltip": "(-) Fight Sleeping Snorlax", "sleeping_snorlax_option_2_label": "Wait for it to move", "sleeping_snorlax_option_2_tooltip": "@[SUMMARY_BLUE]{(75%) Wait a short time}\n@[SUMMARY_BLUE]{(25%) Wait a long time}", - "sleeping_snorlax_option_3_label": "Steal", - "sleeping_snorlax_option_3_tooltip": "(+) Leftovers", + "sleeping_snorlax_option_3_label": "Steal its item", + "sleeping_snorlax_option_3_tooltip": "(+) @ec{option3PrimaryName} uses @ec{option3PrimaryMove}\n(+) Leftovers", "sleeping_snorlax_option_3_disabled_tooltip": "Your Pokémon need to know certain moves to choose this", "sleeping_snorlax_option_1_selected_message": "You approach the\nPokémon without fear.", "sleeping_snorlax_option_2_selected_message": `.@d{32}.@d{32}.@d{32} diff --git a/src/overrides.ts b/src/overrides.ts index ce706173691..e19a5bf20dd 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -8,18 +8,17 @@ import { PokeballCounts } from "./battle-scene"; import { PokeballType } from "./data/pokeball"; import { Gender } from "./data/gender"; import { StatusEffect } from "./data/status-effect"; -import { SpeciesStatBoosterItem, modifierTypes } from "./modifier/modifier-type"; +import { modifierTypes, SpeciesStatBoosterItem } from "./modifier/modifier-type"; import { VariantTier } from "./enums/variant-tiers"; import { EggTier } from "#enums/egg-type"; -import { allSpecies } from "./data/pokemon-species"; // eslint-disable-line @typescript-eslint/no-unused-vars import { Abilities } from "#enums/abilities"; import { BerryType } from "#enums/berry-type"; import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { TimeOfDay } from "#enums/time-of-day"; -import {MysteryEncounterType} from "#enums/mystery-encounter-type"; // eslint-disable-line @typescript-eslint/no-unused-vars -import {MysteryEncounterTier} from "#app/data/mystery-encounters/mystery-encounter"; // eslint-disable-line @typescript-eslint/no-unused-vars +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; // eslint-disable-line @typescript-eslint/no-unused-vars +import { MysteryEncounterTier } from "#app/data/mystery-encounters/mystery-encounter"; // eslint-disable-line @typescript-eslint/no-unused-vars /** * Overrides for testing different in game situations diff --git a/src/phases.ts b/src/phases.ts index 091629b31d5..6c64cc9454f 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -5363,6 +5363,7 @@ export class SelectModifierPhase extends BattlePhase { this.scene.ui.revertMode(); this.scene.ui.setMode(Mode.MESSAGE); super.end(); + break; } modifierType = typeOptions[cursor].type; break; diff --git a/src/ui/mystery-encounter-ui-handler.ts b/src/ui/mystery-encounter-ui-handler.ts index 163d79784f8..8be862b965d 100644 --- a/src/ui/mystery-encounter-ui-handler.ts +++ b/src/ui/mystery-encounter-ui-handler.ts @@ -1,16 +1,16 @@ import BattleScene from "../battle-scene"; -import {addBBCodeTextObject, getBBCodeFrag, TextStyle} from "./text"; -import {Mode} from "./ui"; +import { addBBCodeTextObject, getBBCodeFrag, TextStyle } from "./text"; +import { Mode } from "./ui"; import UiHandler from "./ui-handler"; -import {Button} from "#enums/buttons"; -import {addWindow, WindowVariant} from "./ui-theme"; -import {MysteryEncounterPhase} from "../phases/mystery-encounter-phase"; -import {PartyUiMode} from "./party-ui-handler"; +import { Button } from "#enums/buttons"; +import { addWindow, WindowVariant } from "./ui-theme"; +import { MysteryEncounterPhase } from "../phases/mystery-encounter-phase"; +import { PartyUiMode } from "./party-ui-handler"; import MysteryEncounterOption from "../data/mystery-encounters/mystery-encounter-option"; import * as Utils from "../utils"; -import {isNullOrUndefined} from "../utils"; -import {getPokeballAtlasKey} from "../data/pokeball"; -import {getEncounterText} from "#app/data/mystery-encounters/mystery-encounter-utils"; +import { isNullOrUndefined } from "../utils"; +import { getPokeballAtlasKey } from "../data/pokeball"; +import { getEncounterText } from "#app/data/mystery-encounters/mystery-encounter-utils"; export default class MysteryEncounterUiHandler extends UiHandler { private cursorContainer: Phaser.GameObjects.Container; @@ -319,13 +319,22 @@ export default class MysteryEncounterUiHandler extends UiHandler { optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { wordWrap: { width: 558 }, fontSize: "80px", lineSpacing: -8 }); break; } - const option = mysteryEncounter.dialogue.encounterOptionsDialogue.options[i]; - const text = getEncounterText(this.scene, option.buttonLabel, option.style ? option.style : TextStyle.WINDOW); + this.optionsMeetsReqs.push(this.filteredEncounterOptions[i].meetsRequirements(this.scene)); + + const optionDialogue = mysteryEncounter.dialogue.encounterOptionsDialogue.options[i]; + let text; + if (this.filteredEncounterOptions[i].hasRequirements() && this.optionsMeetsReqs[i]) { + // Options with special requirements that are met are automatically colored green + text = getEncounterText(this.scene, optionDialogue.buttonLabel, TextStyle.SUMMARY_GREEN); + } else { + text = getEncounterText(this.scene, optionDialogue.buttonLabel, optionDialogue.style ? optionDialogue.style : TextStyle.WINDOW); + } + if (text) { optionText.setText(text); } - this.optionsMeetsReqs.push(this.filteredEncounterOptions[i].meetsRequirements(this.scene)); + if (!this.optionsMeetsReqs[i]) { optionText.setAlpha(0.5); From f128b47db5dcd279ce76143c6c2d008f5f9fa04e Mon Sep 17 00:00:00 2001 From: ImperialSympathizer Date: Thu, 11 Jul 2024 14:37:29 -0400 Subject: [PATCH 2/5] update encounter biome mapping and option coloring/disablement with requirements --- .../mystery-encounters/mad_scientist_m.json | 10 +- .../mystery-encounters/mad_scientist_m.png | Bin 13992 -> 920 bytes public/images/mystery-encounters/teacher.json | 2 +- src/battle-scene.ts | 2 +- .../encounters/fight-or-flight.ts | 96 ++++---- .../encounters/sleeping-snorlax.ts | 4 +- .../mystery-encounters/mystery-encounters.ts | 218 ++++++++++++++---- .../requirements/requirement-groups.ts | 42 ++++ src/locales/en/mystery-encounter.ts | 4 +- src/ui/mystery-encounter-ui-handler.ts | 10 +- 10 files changed, 276 insertions(+), 112 deletions(-) create mode 100644 src/data/mystery-encounters/requirements/requirement-groups.ts diff --git a/public/images/mystery-encounters/mad_scientist_m.json b/public/images/mystery-encounters/mad_scientist_m.json index 0cb3f904cb0..10aa3d6f42a 100644 --- a/public/images/mystery-encounters/mad_scientist_m.json +++ b/public/images/mystery-encounters/mad_scientist_m.json @@ -4,8 +4,8 @@ "image": "mad_scientist_m.png", "format": "RGBA8888", "size": { - "w": 44, - "h": 74 + "w": 46, + "h": 76 }, "scale": 1, "frames": [ @@ -24,8 +24,8 @@ "h": 74 }, "frame": { - "x": 0, - "y": 0, + "x": 1, + "y": 1, "w": 44, "h": 74 } @@ -36,6 +36,6 @@ "meta": { "app": "https://www.codeandweb.com/texturepacker", "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:895f0a79b89fa0fb44167f4584fd9a22:357b46953b7e17c6b2f43a62d52855d8:cc1ed0e4f90aaa9dcf1b39a0af1283b0$" + "smartupdate": "$TexturePacker:SmartUpdate:a7f8ff2bbb362868f51125c254eb6681:cf76e61ddd31a8f46af67ced168c44a2:4fc09abe16c0608828269e5da81d0744$" } } diff --git a/public/images/mystery-encounters/mad_scientist_m.png b/public/images/mystery-encounters/mad_scientist_m.png index 84349b46c8a9ab6a3dcf29de3b67f42631c38547..453cb767ec14c4032d52ba665cb23381b8ebf280 100644 GIT binary patch literal 920 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 0) { - // Use first valid pokemon to execute the theivery - const pokemon = validPokemon[0]; - encounter.setDialogueToken("thiefPokemon", pokemon.name); - encounter.setDialogueToken(...moveRequirement.getDialogueToken(scene, pokemon)); + encounter.options[1].meetsRequirements(scene); + const primaryPokemon = encounter.options[1].primaryPokemon; + if (primaryPokemon) { + // Use primaryPokemon to execute the thievery encounter.dialogue.encounterOptionsDialogue.options[1].buttonTooltip = "mysteryEncounter:fight_or_flight_option_2_steal_tooltip"; encounter.dialogue.encounterOptionsDialogue.options[1].style = TextStyle.SUMMARY_GREEN; } else { @@ -104,44 +92,44 @@ export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder setEncounterRewards(scene, { guaranteedModifierTypeOptions: [item], fillRemaining: false }); await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0]); }) - .withOptionPhase(async (scene: BattleScene) => { - // Pick steal - const encounter = scene.currentBattle.mysteryEncounter; - const item = scene.currentBattle.mysteryEncounter.misc as ModifierTypeOption; - setEncounterRewards(scene, { guaranteedModifierTypeOptions: [item], fillRemaining: false }); + .withOption(new MysteryEncounterOptionBuilder() + .withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically + .withDisabledOnRequirementsNotMet(false) + .withOptionPhase(async (scene: BattleScene) => { + // Pick steal + const encounter = scene.currentBattle.mysteryEncounter; + const item = scene.currentBattle.mysteryEncounter.misc as ModifierTypeOption; + setEncounterRewards(scene, { guaranteedModifierTypeOptions: [item], fillRemaining: false }); - // If player has a stealing move, they succeed automatically - const moveRequirement = new MoveRequirement(validMovesForSteal); - const validPokemon = moveRequirement.queryParty(scene.getParty()); - if (validPokemon?.length > 0) { - // Use first valid pokemon to execute the theivery - const pokemon = validPokemon[0]; - encounter.setDialogueToken("thiefPokemon", pokemon.name); - encounter.setDialogueToken(...moveRequirement.getDialogueToken(scene, pokemon)); - await showEncounterText(scene, "mysteryEncounter:fight_or_flight_option_2_steal_result"); - leaveEncounterWithoutBattle(scene); - return; - } + // If player has a stealing move, they succeed automatically + const primaryPokemon = encounter.options[1].primaryPokemon; + if (primaryPokemon) { + // Use primaryPokemon to execute the thievery + await showEncounterText(scene, "mysteryEncounter:fight_or_flight_option_2_steal_result"); + leaveEncounterWithoutBattle(scene); + return; + } - const roll = randSeedInt(16); - if (roll > 6) { - // Noticed and attacked by boss, gets +1 to all stats at start of fight (62.5%) - const config = scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0]; - config.pokemonConfigs[0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON]; - config.pokemonConfigs[0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => { - pokemon.scene.currentBattle.mysteryEncounter.setDialogueToken("enemyPokemon", pokemon.name); - queueEncounterMessage(pokemon.scene, "mysteryEncounter:fight_or_flight_boss_enraged"); - pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD], 1)); - }; - await showEncounterText(scene, "mysteryEncounter:fight_or_flight_option_2_bad_result"); - await initBattleWithEnemyConfig(scene, config); - } else { - // Steal item (37.5%) - // Display result message then proceed to rewards - await showEncounterText(scene, "mysteryEncounter:fight_or_flight_option_2_good_result"); - leaveEncounterWithoutBattle(scene); - } - }) + const roll = randSeedInt(16); + if (roll > 6) { + // Noticed and attacked by boss, gets +1 to all stats at start of fight (62.5%) + const config = scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0]; + config.pokemonConfigs[0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON]; + config.pokemonConfigs[0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => { + pokemon.scene.currentBattle.mysteryEncounter.setDialogueToken("enemyPokemon", pokemon.name); + queueEncounterMessage(pokemon.scene, "mysteryEncounter:fight_or_flight_boss_enraged"); + pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD], 1)); + }; + await showEncounterText(scene, "mysteryEncounter:fight_or_flight_option_2_bad_result"); + await initBattleWithEnemyConfig(scene, config); + } else { + // Steal item (37.5%) + // Display result message then proceed to rewards + await showEncounterText(scene, "mysteryEncounter:fight_or_flight_option_2_good_result"); + leaveEncounterWithoutBattle(scene); + } + }) + .build()) .withOptionPhase(async (scene: BattleScene) => { // Leave encounter with no rewards or exp leaveEncounterWithoutBattle(scene, true); diff --git a/src/data/mystery-encounters/encounters/sleeping-snorlax.ts b/src/data/mystery-encounters/encounters/sleeping-snorlax.ts index cb2029272ef..444ce5a6581 100644 --- a/src/data/mystery-encounters/encounters/sleeping-snorlax.ts +++ b/src/data/mystery-encounters/encounters/sleeping-snorlax.ts @@ -2,7 +2,6 @@ import { modifierTypes } from "#app/modifier/modifier-type"; import { BerryType } from "#enums/berry-type"; -import { Moves } from "#enums/moves"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; import BattleScene from "../../../battle-scene"; @@ -19,6 +18,7 @@ import { leaveEncounterWithoutBattle, queueEncounterMessage, setEncounterExp, setEncounterRewards } from "../mystery-encounter-utils"; +import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; export const SleepingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuilder .withEncounterType(MysteryEncounterType.SLEEPING_SNORLAX) @@ -98,7 +98,7 @@ export const SleepingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuilde } }) .withOption(new MysteryEncounterOptionBuilder() - .withPrimaryPokemonRequirement(new MoveRequirement([Moves.PLUCK, Moves.COVET, Moves.KNOCK_OFF, Moves.THIEF, Moves.TRICK, Moves.SWITCHEROO])) + .withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES)) .withOptionPhase(async (scene: BattleScene) => { // Steal the Snorlax's Leftovers const instance = scene.currentBattle.mysteryEncounter; diff --git a/src/data/mystery-encounters/mystery-encounters.ts b/src/data/mystery-encounters/mystery-encounters.ts index a1f8a73192e..0244281be26 100644 --- a/src/data/mystery-encounters/mystery-encounters.ts +++ b/src/data/mystery-encounters/mystery-encounters.ts @@ -16,36 +16,148 @@ export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1; export const WIGHT_INCREMENT_ON_SPAWN_MISS = 5; export const AVERAGE_ENCOUNTERS_PER_RUN_TARGET = 15; +export const EXTREME_ENCOUNTER_BIOMES = [ + Biome.SEA, + Biome.SEABED, + Biome.BADLANDS, + Biome.DESERT, + Biome.ICE_CAVE, + Biome.VOLCANO, + Biome.WASTELAND, + Biome.ABYSS, + Biome.SPACE, + Biome.END +]; + +export const NON_EXTREME_ENCOUNTER_BIOMES = [ + Biome.TOWN, + Biome.PLAINS, + Biome.GRASS, + Biome.TALL_GRASS, + Biome.METROPOLIS, + Biome.FOREST, + Biome.SWAMP, + Biome.BEACH, + Biome.LAKE, + Biome.MOUNTAIN, + Biome.CAVE, + Biome.MEADOW, + Biome.POWER_PLANT, + Biome.GRAVEYARD, + Biome.DOJO, + Biome.FACTORY, + Biome.RUINS, + Biome.CONSTRUCTION_SITE, + Biome.JUNGLE, + Biome.FAIRY_CAVE, + Biome.TEMPLE, + Biome.SLUM, + Biome.SNOWY_FOREST, + Biome.ISLAND, + Biome.LABORATORY +]; + +/** + * Places where you could very reasonably expect to encounter a single human + * + * Diff from NON_EXTREME_ENCOUNTER_BIOMES: + * + BADLANDS + * + DESERT + * + ICE_CAVE + */ +export const HUMAN_TRANSITABLE_BIOMES = [ + Biome.TOWN, + Biome.PLAINS, + Biome.GRASS, + Biome.TALL_GRASS, + Biome.METROPOLIS, + Biome.FOREST, + Biome.SWAMP, + Biome.BEACH, + Biome.LAKE, + Biome.MOUNTAIN, + Biome.BADLANDS, + Biome.CAVE, + Biome.DESERT, + Biome.ICE_CAVE, + Biome.MEADOW, + Biome.POWER_PLANT, + Biome.GRAVEYARD, + Biome.DOJO, + Biome.FACTORY, + Biome.RUINS, + Biome.CONSTRUCTION_SITE, + Biome.JUNGLE, + Biome.FAIRY_CAVE, + Biome.TEMPLE, + Biome.SLUM, + Biome.SNOWY_FOREST, + Biome.ISLAND, + Biome.LABORATORY +]; + +/** + * Places where you could expect a town or city, some form of large civilization + */ +export const CIVILIZATION_ENCOUNTER_BIOMES = [ + Biome.TOWN, + Biome.PLAINS, + Biome.GRASS, + Biome.TALL_GRASS, + Biome.METROPOLIS, + Biome.BEACH, + Biome.LAKE, + Biome.MEADOW, + Biome.POWER_PLANT, + Biome.GRAVEYARD, + Biome.DOJO, + Biome.FACTORY, + Biome.CONSTRUCTION_SITE, + Biome.SLUM, + Biome.ISLAND +]; + export const allMysteryEncounters: { [encounterType: number]: MysteryEncounter } = {}; +// TO ENABLE AN ENCOUNTER IN ALL BIOMES, DO NOT SPECIFY IN ANY OF THESE LISTS/MAPS + +const extremeBiomeEncounters: MysteryEncounterType[] = []; + +const nonExtremeBiomeEncounters: MysteryEncounterType[] = [ + MysteryEncounterType.FIELD_TRIP +]; + +const humanTransitableBiomeEncounters: MysteryEncounterType[] = [ + MysteryEncounterType.MYSTERIOUS_CHALLENGERS, + MysteryEncounterType.SHADY_VITAMIN_DEALER +]; + +const civilizationBiomeEncounters: MysteryEncounterType[] = [ + MysteryEncounterType.DEPARTMENT_STORE_SALE +]; + +const anyBiomeEncounters: MysteryEncounterType[] = [ + MysteryEncounterType.FIGHT_OR_FLIGHT, + MysteryEncounterType.DARK_DEAL, + MysteryEncounterType.MYSTERIOUS_CHEST, + MysteryEncounterType.TRAINING_SESSION +]; + // Add MysteryEncounterType to biomes to enable it exclusively for those biomes -// To enable an encounter in all biomes, do not add to this map export const mysteryEncountersByBiome = new Map([ - [Biome.TOWN, [ - MysteryEncounterType.DEPARTMENT_STORE_SALE - ]], - [Biome.PLAINS, [ - MysteryEncounterType.DEPARTMENT_STORE_SALE - ]], + [Biome.TOWN, []], + [Biome.PLAINS, []], [Biome.GRASS, [ MysteryEncounterType.SLEEPING_SNORLAX, - MysteryEncounterType.DEPARTMENT_STORE_SALE - ]], - [Biome.TALL_GRASS, [ - MysteryEncounterType.DEPARTMENT_STORE_SALE - ]], - [Biome.METROPOLIS, [ - MysteryEncounterType.DEPARTMENT_STORE_SALE ]], + [Biome.TALL_GRASS, []], + [Biome.METROPOLIS, []], [Biome.FOREST, [ MysteryEncounterType.SLEEPING_SNORLAX ]], - [Biome.SEA, []], [Biome.SWAMP, []], - [Biome.BEACH, [ - MysteryEncounterType.DEPARTMENT_STORE_SALE - ]], + [Biome.BEACH, []], [Biome.LAKE, []], [Biome.SEABED, []], [Biome.MOUNTAIN, [ @@ -57,31 +169,21 @@ export const mysteryEncountersByBiome = new Map([ ]], [Biome.DESERT, []], [Biome.ICE_CAVE, []], - [Biome.MEADOW, [ - MysteryEncounterType.DEPARTMENT_STORE_SALE - ]], - [Biome.POWER_PLANT, [ - MysteryEncounterType.DEPARTMENT_STORE_SALE - ]], + [Biome.MEADOW, []], + [Biome.POWER_PLANT, []], [Biome.VOLCANO, []], [Biome.GRAVEYARD, []], [Biome.DOJO, []], - [Biome.FACTORY, [ - MysteryEncounterType.DEPARTMENT_STORE_SALE - ]], + [Biome.FACTORY, []], [Biome.RUINS, []], [Biome.WASTELAND, []], [Biome.ABYSS, []], [Biome.SPACE, []], - [Biome.CONSTRUCTION_SITE, [ - MysteryEncounterType.DEPARTMENT_STORE_SALE - ]], + [Biome.CONSTRUCTION_SITE, []], [Biome.JUNGLE, []], [Biome.FAIRY_CAVE, []], [Biome.TEMPLE, []], - [Biome.SLUM, [ - MysteryEncounterType.DEPARTMENT_STORE_SALE - ]], + [Biome.SLUM, []], [Biome.SNOWY_FOREST, []], [Biome.ISLAND, []], [Biome.LABORATORY, []] @@ -90,7 +192,7 @@ export const mysteryEncountersByBiome = new Map([ export function initMysteryEncounters() { allMysteryEncounters[MysteryEncounterType.MYSTERIOUS_CHALLENGERS] = MysteriousChallengersEncounter; allMysteryEncounters[MysteryEncounterType.MYSTERIOUS_CHEST] = MysteriousChestEncounter; - allMysteryEncounters[MysteryEncounterType.DARK_DEAL] = DarkDealEncounter; + allMysteryEncounters[MysteryEncounterType.DARK_DEAL] = DarkDealEncounter; // TODO: move to HUMAN if we add an ANY biome ROGUE tier encounter allMysteryEncounters[MysteryEncounterType.FIGHT_OR_FLIGHT] = FightOrFlightEncounter; allMysteryEncounters[MysteryEncounterType.TRAINING_SESSION] = TrainingSessionEncounter; allMysteryEncounters[MysteryEncounterType.SLEEPING_SNORLAX] = SleepingSnorlaxEncounter; @@ -98,15 +200,49 @@ export function initMysteryEncounters() { allMysteryEncounters[MysteryEncounterType.SHADY_VITAMIN_DEALER] = ShadyVitaminDealerEncounter; allMysteryEncounters[MysteryEncounterType.FIELD_TRIP] = FieldTripEncounter; - // Append encounters that can occur in any biome to biome map - const anyBiomeEncounters: MysteryEncounterType[] = Object.keys(MysteryEncounterType).filter(e => !isNaN(Number(e))).map(k => Number(k) as MysteryEncounterType); - mysteryEncountersByBiome.forEach(biomeEncounters => { - biomeEncounters.forEach(e => { - if (anyBiomeEncounters.includes(e)) { - anyBiomeEncounters.splice(anyBiomeEncounters.indexOf(e), 1); + // Add extreme encounters to biome map + extremeBiomeEncounters.forEach(encounter => { + EXTREME_ENCOUNTER_BIOMES.forEach(biome => { + const encountersForBiome = mysteryEncountersByBiome.get(biome); + if (!encountersForBiome.includes(encounter)) { + encountersForBiome.push(encounter); + } + }); + }); + // Add non-extreme encounters to biome map + nonExtremeBiomeEncounters.forEach(encounter => { + NON_EXTREME_ENCOUNTER_BIOMES.forEach(biome => { + const encountersForBiome = mysteryEncountersByBiome.get(biome); + if (!encountersForBiome.includes(encounter)) { + encountersForBiome.push(encounter); + } + }); + }); + // Add human encounters to biome map + humanTransitableBiomeEncounters.forEach(encounter => { + HUMAN_TRANSITABLE_BIOMES.forEach(biome => { + const encountersForBiome = mysteryEncountersByBiome.get(biome); + if (!encountersForBiome.includes(encounter)) { + encountersForBiome.push(encounter); + } + }); + }); + // Add civilization encounters to biome map + civilizationBiomeEncounters.forEach(encounter => { + CIVILIZATION_ENCOUNTER_BIOMES.forEach(biome => { + const encountersForBiome = mysteryEncountersByBiome.get(biome); + if (!encountersForBiome.includes(encounter)) { + encountersForBiome.push(encounter); } }); }); - mysteryEncountersByBiome.forEach(biomeEncounters => biomeEncounters.push(...anyBiomeEncounters)); + // Add ANY biome encounters to biome map + mysteryEncountersByBiome.forEach(biomeEncounters => { + anyBiomeEncounters.forEach(encounter => { + if (!biomeEncounters.includes(encounter)) { + biomeEncounters.push(encounter); + } + }); + }); } diff --git a/src/data/mystery-encounters/requirements/requirement-groups.ts b/src/data/mystery-encounters/requirements/requirement-groups.ts new file mode 100644 index 00000000000..3281129e63e --- /dev/null +++ b/src/data/mystery-encounters/requirements/requirement-groups.ts @@ -0,0 +1,42 @@ +import { Moves } from "#enums/moves"; + +export const STEALING_MOVES = [ + Moves.PLUCK, + Moves.COVET, + Moves.KNOCK_OFF, + Moves.FAKE_OUT, + Moves.THIEF, + Moves.TRICK, + Moves.SWITCHEROO +]; + +export const DISTRACTION_MOVES = [ + Moves.FAKE_OUT, + Moves.FOLLOW_ME, + Moves.TAUNT, + Moves.ROAR, + Moves.TELEPORT, + Moves.CHARM, + Moves.FAKE_TEARS, + Moves.TICKLE, + Moves.CAPTIVATE, + Moves.RAGE_POWDER, + Moves.SUBSTITUTE, + Moves.SHED_TAIL +]; + +export const PROTECTING_MOVES = [ + Moves.PROTECT, + Moves.WIDE_GUARD, + Moves.MAX_GUARD, + Moves.SAFEGUARD, + Moves.REFLECT, + Moves.BARRIER, + Moves.QUICK_GUARD, + Moves.FLOWER_SHIELD, + Moves.KINGS_SHIELD, + Moves.CRAFTY_SHIELD, + Moves.SPIKY_SHIELD, + Moves.OBSTRUCT, + Moves.DETECT +]; diff --git a/src/locales/en/mystery-encounter.ts b/src/locales/en/mystery-encounter.ts index 59fcf6940c2..b07e22f7a9a 100644 --- a/src/locales/en/mystery-encounter.ts +++ b/src/locales/en/mystery-encounter.ts @@ -43,14 +43,14 @@ export const mysteryEncounter: SimpleTranslationEntries = { "fight_or_flight_option_1_tooltip": "(-) Hard Battle\n(+) New Item", "fight_or_flight_option_2_label": "Steal the item", "fight_or_flight_option_2_tooltip": "@[SUMMARY_GREEN]{(35%) Steal Item}\n@[SUMMARY_BLUE]{(65%) Harder Battle}", - "fight_or_flight_option_2_steal_tooltip": "@[SUMMARY_GREEN]{(?) Use a Pokémon Move}", + "fight_or_flight_option_2_steal_tooltip": "(+) @ec{option2PrimaryName} uses @ec{option2PrimaryMove}", "fight_or_flight_option_3_label": "Leave", "fight_or_flight_option_3_tooltip": "(-) No Rewards", "fight_or_flight_option_1_selected_message": "You approach the\nPokémon without fear.", "fight_or_flight_option_2_good_result": `.@d{32}.@d{32}.@d{32} $You manage to sneak your way\npast and grab the item!`, "fight_or_flight_option_2_steal_result": `.@d{32}.@d{32}.@d{32} - $Your @ec{thiefPokemon} helps you out and uses @ec{move}! + $Your @ec{option2PrimaryName} helps you out and uses @ec{option2PrimaryMove}! $ You nabbed the item!`, "fight_or_flight_option_2_bad_result": `.@d{32}.@d{32}.@d{32} $The Pokémon catches you\nas you try to sneak around!`, diff --git a/src/ui/mystery-encounter-ui-handler.ts b/src/ui/mystery-encounter-ui-handler.ts index 8be862b965d..306fb01c575 100644 --- a/src/ui/mystery-encounter-ui-handler.ts +++ b/src/ui/mystery-encounter-ui-handler.ts @@ -110,7 +110,7 @@ export default class MysteryEncounterUiHandler extends UiHandler { this.unblockInput(); }, 300); }); - } else if (this.blockInput || !this.optionsMeetsReqs[cursor]) { + } else if (this.blockInput || (!this.optionsMeetsReqs[cursor] && this.filteredEncounterOptions[cursor].isDisabledOnRequirementsNotMet)) { success = false; } else { const selected = this.filteredEncounterOptions[cursor]; @@ -253,7 +253,7 @@ export default class MysteryEncounterUiHandler extends UiHandler { if (this.blockInput) { this.blockInput = false; for (let i = 0; i < this.optionsContainer.length - 1; i++) { - if (!this.optionsMeetsReqs[i]) { + if (!this.optionsMeetsReqs[i] && this.filteredEncounterOptions[i].isDisabledOnRequirementsNotMet) { continue; } (this.optionsContainer.getAt(i) as Phaser.GameObjects.Text).setAlpha(1); @@ -334,9 +334,7 @@ export default class MysteryEncounterUiHandler extends UiHandler { optionText.setText(text); } - - - if (!this.optionsMeetsReqs[i]) { + if (!this.optionsMeetsReqs[i] && this.filteredEncounterOptions[i].isDisabledOnRequirementsNotMet) { optionText.setAlpha(0.5); } if (this.blockInput) { @@ -424,7 +422,7 @@ export default class MysteryEncounterUiHandler extends UiHandler { const mysteryEncounter = this.scene.currentBattle.mysteryEncounter; let text; const option = mysteryEncounter.dialogue.encounterOptionsDialogue.options[cursor]; - if (!this.optionsMeetsReqs[cursor] && option.disabledTooltip) { + if (!this.optionsMeetsReqs[cursor] && this.filteredEncounterOptions[cursor].isDisabledOnRequirementsNotMet && option.disabledTooltip) { text = getEncounterText(this.scene, option.disabledTooltip, TextStyle.TOOLTIP_CONTENT); } else { text = getEncounterText(this.scene, option.buttonTooltip, TextStyle.TOOLTIP_CONTENT); From b6ef21058490430e9646bfaf94cbf41bc479b441 Mon Sep 17 00:00:00 2001 From: ImperialSympathizer Date: Thu, 11 Jul 2024 14:43:57 -0400 Subject: [PATCH 3/5] remove unnecessary styling override on fight or flight option --- src/data/mystery-encounters/encounters/fight-or-flight.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/data/mystery-encounters/encounters/fight-or-flight.ts b/src/data/mystery-encounters/encounters/fight-or-flight.ts index 4392468c7bc..8a82439772c 100644 --- a/src/data/mystery-encounters/encounters/fight-or-flight.ts +++ b/src/data/mystery-encounters/encounters/fight-or-flight.ts @@ -16,7 +16,6 @@ import { regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import { StatChangePhase } from "#app/phases"; -import { TextStyle } from "#app/ui/text"; import { randSeedInt } from "#app/utils"; import { BattlerTagType } from "#enums/battler-tag-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; @@ -78,10 +77,8 @@ export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder if (primaryPokemon) { // Use primaryPokemon to execute the thievery encounter.dialogue.encounterOptionsDialogue.options[1].buttonTooltip = "mysteryEncounter:fight_or_flight_option_2_steal_tooltip"; - encounter.dialogue.encounterOptionsDialogue.options[1].style = TextStyle.SUMMARY_GREEN; } else { encounter.dialogue.encounterOptionsDialogue.options[1].buttonTooltip = "mysteryEncounter:fight_or_flight_option_2_tooltip"; - encounter.dialogue.encounterOptionsDialogue.options[1].style = null; } return true; From c72bed0ee76b7f50b9be7400703da9fb3b9e2b53 Mon Sep 17 00:00:00 2001 From: ImperialSympathizer Date: Thu, 11 Jul 2024 14:58:44 -0400 Subject: [PATCH 4/5] small cleanup of changes --- src/data/mystery-encounters/mystery-encounters.ts | 15 ++++++++++++--- src/locales/en/mystery-encounter.ts | 7 +++---- src/ui/mystery-encounter-ui-handler.ts | 1 + 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/data/mystery-encounters/mystery-encounters.ts b/src/data/mystery-encounters/mystery-encounters.ts index 0244281be26..1651430e59d 100644 --- a/src/data/mystery-encounters/mystery-encounters.ts +++ b/src/data/mystery-encounters/mystery-encounters.ts @@ -119,7 +119,6 @@ export const CIVILIZATION_ENCOUNTER_BIOMES = [ export const allMysteryEncounters: { [encounterType: number]: MysteryEncounter } = {}; -// TO ENABLE AN ENCOUNTER IN ALL BIOMES, DO NOT SPECIFY IN ANY OF THESE LISTS/MAPS const extremeBiomeEncounters: MysteryEncounterType[] = []; @@ -136,6 +135,10 @@ const civilizationBiomeEncounters: MysteryEncounterType[] = [ MysteryEncounterType.DEPARTMENT_STORE_SALE ]; + +/** + * To add an encounter to every biome possible, use this array + */ const anyBiomeEncounters: MysteryEncounterType[] = [ MysteryEncounterType.FIGHT_OR_FLIGHT, MysteryEncounterType.DARK_DEAL, @@ -143,7 +146,13 @@ const anyBiomeEncounters: MysteryEncounterType[] = [ MysteryEncounterType.TRAINING_SESSION ]; -// Add MysteryEncounterType to biomes to enable it exclusively for those biomes +/** + * ENCOUNTER BIOME MAPPING + * To add an Encounter to a biome group, instead of cluttering the map, use the biome group arrays above + * + * Adding specific Encounters to the mysteryEncountersByBiome map is for specific cases and special circumstances + * that biome groups do not cover + */ export const mysteryEncountersByBiome = new Map([ [Biome.TOWN, []], [Biome.PLAINS, []], @@ -192,7 +201,7 @@ export const mysteryEncountersByBiome = new Map([ export function initMysteryEncounters() { allMysteryEncounters[MysteryEncounterType.MYSTERIOUS_CHALLENGERS] = MysteriousChallengersEncounter; allMysteryEncounters[MysteryEncounterType.MYSTERIOUS_CHEST] = MysteriousChestEncounter; - allMysteryEncounters[MysteryEncounterType.DARK_DEAL] = DarkDealEncounter; // TODO: move to HUMAN if we add an ANY biome ROGUE tier encounter + allMysteryEncounters[MysteryEncounterType.DARK_DEAL] = DarkDealEncounter; allMysteryEncounters[MysteryEncounterType.FIGHT_OR_FLIGHT] = FightOrFlightEncounter; allMysteryEncounters[MysteryEncounterType.TRAINING_SESSION] = TrainingSessionEncounter; allMysteryEncounters[MysteryEncounterType.SLEEPING_SNORLAX] = SleepingSnorlaxEncounter; diff --git a/src/locales/en/mystery-encounter.ts b/src/locales/en/mystery-encounter.ts index b07e22f7a9a..f944615452a 100644 --- a/src/locales/en/mystery-encounter.ts +++ b/src/locales/en/mystery-encounter.ts @@ -108,7 +108,6 @@ export const mysteryEncounter: SimpleTranslationEntries = { "field_trip_title": "Field Trip", "field_trip_description": "A teacher is requesting a move demonstration from a Pokémon. Depending on the move you choose, she might have something useful for you in exchange.", "field_trip_query": "Which move category will you show off?", - // "field_trip_invalid_selection": "Pokémon doesn't know that type of move.", "field_trip_option_1_label": "A Physical Move", "field_trip_option_1_tooltip": "(+) Physical Item Rewards", "field_trip_option_2_label": "A Special Move", @@ -141,6 +140,7 @@ export const mysteryEncounter: SimpleTranslationEntries = { "mysterious_challengers_outro_win": "The mysterious challenger was defeated!", // Mystery Encounters -- Rare Tier + "training_session_intro_message": "You've come across some\ntraining tools and supplies.", "training_session_title": "Training Session", "training_session_description": "These supplies look like they could be used to train a member of your party! There are a few ways you could train your Pokémon, by battling against it with the rest of your team.", @@ -173,8 +173,8 @@ export const mysteryEncounter: SimpleTranslationEntries = { "dark_deal_title": "Dark Deal", "dark_deal_description": "The disturbing fellow holds up some Pokéballs.\n\"I'll make it worth your while! You can have these strong Pokéballs as payment, All I need is a Pokémon from your team! Hehe...\"", "dark_deal_query": "What will you do?", - "dark_deal_option_1_label": "Accept", // Give player 10 rogue balls. Remove a random Pokémon from player's party. Fight a legendary Pokémon as a boss - "dark_deal_option_1_tooltip": "(+) 5 Rogue Balls\n(?) Enhance a Random Pokémon", // Give player 10 rogue balls. Remove a random Pokémon from player's party. Fight a legendary Pokémon as a boss + "dark_deal_option_1_label": "Accept", + "dark_deal_option_1_tooltip": "(+) 5 Rogue Balls\n(?) Enhance a Random Pokémon", "dark_deal_option_2_label": "Refuse", "dark_deal_option_2_tooltip": "(-) No Rewards", "dark_deal_option_1_selected": `Let's see, that @ec{pokeName} will do nicely! @@ -206,6 +206,5 @@ export const mysteryEncounter: SimpleTranslationEntries = { $But on the bright side, the Snorlax left something behind... $@s{item_fanfare}You gained a Berry!`, "sleeping_snorlax_option_3_good_result": "Your @ec{option3PrimaryName} uses @ec{option3PrimaryMove}! @s{item_fanfare}It steals Leftovers off the sleeping Snorlax and you make out like bandits!", - // "sleeping_snorlax_outro_win": "The mysterious challengers were defeated!", } as const; diff --git a/src/ui/mystery-encounter-ui-handler.ts b/src/ui/mystery-encounter-ui-handler.ts index 306fb01c575..cad678acf87 100644 --- a/src/ui/mystery-encounter-ui-handler.ts +++ b/src/ui/mystery-encounter-ui-handler.ts @@ -325,6 +325,7 @@ export default class MysteryEncounterUiHandler extends UiHandler { let text; if (this.filteredEncounterOptions[i].hasRequirements() && this.optionsMeetsReqs[i]) { // Options with special requirements that are met are automatically colored green + // In cases where isDisabledOnRequirementsNotMet = false and requirements are not met, option will not be auto-colored text = getEncounterText(this.scene, optionDialogue.buttonLabel, TextStyle.SUMMARY_GREEN); } else { text = getEncounterText(this.scene, optionDialogue.buttonLabel, optionDialogue.style ? optionDialogue.style : TextStyle.WINDOW); From 390b2e93f92d8aae1a93e6c3c8ff7aa1c60e7dc2 Mon Sep 17 00:00:00 2001 From: ImperialSympathizer Date: Thu, 11 Jul 2024 15:03:57 -0400 Subject: [PATCH 5/5] remove fake out from stealing move group --- src/data/mystery-encounters/requirements/requirement-groups.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/data/mystery-encounters/requirements/requirement-groups.ts b/src/data/mystery-encounters/requirements/requirement-groups.ts index 3281129e63e..c4d1c592df4 100644 --- a/src/data/mystery-encounters/requirements/requirement-groups.ts +++ b/src/data/mystery-encounters/requirements/requirement-groups.ts @@ -4,7 +4,6 @@ export const STEALING_MOVES = [ Moves.PLUCK, Moves.COVET, Moves.KNOCK_OFF, - Moves.FAKE_OUT, Moves.THIEF, Moves.TRICK, Moves.SWITCHEROO