new Field Trip encounter and add EXP gains to other encounters

This commit is contained in:
ImperialSympathizer 2024-07-11 11:40:07 -04:00
parent 253447136a
commit 810c50cda2
18 changed files with 495 additions and 64 deletions

View File

@ -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$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 727 B

View File

@ -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"
}
]
}
]
}
};

View File

@ -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"
},
}
]
},
{

View File

@ -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);
})

View File

@ -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<boolean> => {
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<boolean> => {
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<boolean> => {
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();

View File

@ -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<boolean> => {
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<boolean> => {
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);

View File

@ -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()

View File

@ -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;
}

View File

@ -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<void | boolean>;
@ -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<MysteryEncounterOp
primaryPokemonRequirements?: EncounterPokemonRequirement[] = [];
secondaryPokemonRequirements ?: EncounterPokemonRequirement[] = [];
excludePrimaryFromSecondaryRequirements?: boolean;
isDisabledOnRequirementsNotMet?: boolean;
onPreOptionPhase?: OptionPhaseCallback;
onOptionPhase?: OptionPhaseCallback;
onPostOptionPhase?: OptionPhaseCallback;
@ -163,4 +178,9 @@ export class MysteryEncounterOptionBuilder implements Partial<MysteryEncounterOp
this.excludePrimaryFromSecondaryRequirements = excludePrimaryFromSecondaryRequirements;
return Object.assign(this, { secondaryPokemonRequirements: this.secondaryPokemonRequirements });
}
withDisabledOnRequirementsNotMet(disabled: boolean): this & Required<Pick<MysteryEncounterOption, "isDisabledOnRequirementsNotMet">> {
this.isDisabledOnRequirementsNotMet = disabled;
return Object.assign(this, { isDisabledOnRequirementsNotMet: this.isDisabledOnRequirementsNotMet });
}
}

View File

@ -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) {

View File

@ -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]);
}
}
}

View File

@ -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 * <number of missed spawns>) / 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);

View File

@ -6,5 +6,6 @@ export enum MysteryEncounterType {
SLEEPING_SNORLAX,
TRAINING_SESSION,
DEPARTMENT_STORE_SALE,
SHADY_VITAMIN_DEALER
SHADY_VITAMIN_DEALER,
FIELD_TRIP
}

View File

@ -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}

View File

@ -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

View File

@ -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;

View File

@ -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);