WIP: add option to use dialogues in builder

This commit is contained in:
Felix Staud 2024-07-11 12:56:15 -07:00
parent 253447136a
commit 74125ef1cf
8 changed files with 195 additions and 111 deletions

View File

@ -1,44 +1,44 @@
import MysteryEncounterDialogue from "#app/data/mystery-encounters/mystery-encounter-dialogue";
export const DarkDealDialogue: MysteryEncounterDialogue = {
intro: [
{
text: "mysteryEncounter:dark_deal_intro_message"
},
{
speaker: "mysteryEncounter:dark_deal_speaker",
text: "mysteryEncounter:dark_deal_intro_dialogue"
}
],
// intro: [
// {
// text: "mysteryEncounter:dark_deal_intro_message"
// },
// {
// speaker: "mysteryEncounter:dark_deal_speaker",
// text: "mysteryEncounter:dark_deal_intro_dialogue"
// }
// ],
encounterOptionsDialogue: {
title: "mysteryEncounter:dark_deal_title",
description: "mysteryEncounter:dark_deal_description",
query: "mysteryEncounter:dark_deal_query",
options: [
{
buttonLabel: "mysteryEncounter:dark_deal_option_1_label",
buttonTooltip: "mysteryEncounter:dark_deal_option_1_tooltip",
selected: [
{
speaker: "mysteryEncounter:dark_deal_speaker",
text: "mysteryEncounter:dark_deal_option_1_selected"
},
{
text: "mysteryEncounter:dark_deal_option_1_selected_message"
}
]
},
{
buttonLabel: "mysteryEncounter:dark_deal_option_2_label",
buttonTooltip: "mysteryEncounter:dark_deal_option_2_tooltip",
selected: [
{
speaker: "mysteryEncounter:dark_deal_speaker",
text: "mysteryEncounter:dark_deal_option_2_selected"
}
]
}
]
// options: [
// {
// buttonLabel: "mysteryEncounter:dark_deal_option_1_label",
// buttonTooltip: "mysteryEncounter:dark_deal_option_1_tooltip",
// selected: [
// {
// speaker: "mysteryEncounter:dark_deal_speaker",
// text: "mysteryEncounter:dark_deal_option_1_selected"
// },
// {
// text: "mysteryEncounter:dark_deal_option_1_selected_message"
// }
// ]
// },
// {
// buttonLabel: "mysteryEncounter:dark_deal_option_2_label",
// buttonTooltip: "mysteryEncounter:dark_deal_option_2_tooltip",
// selected: [
// {
// speaker: "mysteryEncounter:dark_deal_speaker",
// text: "mysteryEncounter:dark_deal_option_2_selected"
// }
// ]
// }
// ]
},
outro: [
{

View File

@ -7,14 +7,18 @@ import BattleScene from "../../../battle-scene";
import { AddPokeballModifierType } from "../../../modifier/modifier-type";
import { PokeballType } from "../../pokeball";
import { getPokemonSpecies } from "../../pokemon-species";
import MysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier } from "../mystery-encounter";
import MysteryEncounter, {
MysteryEncounterBuilder,
MysteryEncounterTier,
} from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import {
EnemyPartyConfig, EnemyPokemonConfig,
EnemyPartyConfig,
EnemyPokemonConfig,
getRandomPlayerPokemon,
getRandomSpeciesByStarterTier,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle
leaveEncounterWithoutBattle,
} from "../mystery-encounter-utils";
// Exclude Ultra Beasts, Paradox, Necrozma, Eternatus, and egg-locked mythicals
@ -63,71 +67,132 @@ const excludedBosses = [
Species.ARCEUS,
Species.VICTINI,
Species.MELTAN,
Species.PECHARUNT
Species.PECHARUNT,
];
export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder
.withEncounterType(MysteryEncounterType.DARK_DEAL)
.withEncounterTier(MysteryEncounterTier.ROGUE)
.withIntroSpriteConfigs([
{
spriteKey: "mad_scientist_m",
fileRoot: "mystery-encounters",
hasShadow: true
},
{
spriteKey: "dark_deal_porygon",
fileRoot: "mystery-encounters",
hasShadow: true,
repeat: true
}
])
.withSceneWaveRangeRequirement(30, 180) // waves 30 to 180
.withScenePartySizeRequirement(2, 6) // Must have at least 2 pokemon in party
.withCatchAllowed(true)
.withOption(new MysteryEncounterOptionBuilder()
.withPreOptionPhase(async (scene: BattleScene) => {
// Removes random pokemon (including fainted) from party and adds name to dialogue data tokens
// Will never return last battle able mon and instead pick fainted/unable to battle
const removedPokemon = getRandomPlayerPokemon(scene, false, true);
scene.removePokemonFromPlayerParty(removedPokemon);
export const DarkDealEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DARK_DEAL)
.withEncounterTier(MysteryEncounterTier.ROGUE)
.withIntroSpriteConfigs([
{
spriteKey: "mad_scientist_m",
fileRoot: "mystery-encounters",
hasShadow: true,
},
{
spriteKey: "dark_deal_porygon",
fileRoot: "mystery-encounters",
hasShadow: true,
repeat: true,
},
])
.withIntroDialogue([
{
text: "mysteryEncounter:dark_deal_intro_message",
},
{
speaker: "mysteryEncounter:dark_deal_speaker",
text: "mysteryEncounter:dark_deal_intro_dialogue",
},
])
.withSceneWaveRangeRequirement(30, 180) // waves 30 to 180
.withScenePartySizeRequirement(2, 6) // Must have at least 2 pokemon in party
.withCatchAllowed(true)
.withOption(
new MysteryEncounterOptionBuilder()
.withDialogue({
buttonLabel: "mysteryEncounter:dark_deal_option_1_label",
buttonTooltip: "mysteryEncounter:dark_deal_option_1_tooltip",
selected: [
{
speaker: "mysteryEncounter:dark_deal_speaker",
text: "mysteryEncounter:dark_deal_option_1_selected",
},
{
text: "mysteryEncounter:dark_deal_option_1_selected_message",
},
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
// Removes random pokemon (including fainted) from party and adds name to dialogue data tokens
// Will never return last battle able mon and instead pick fainted/unable to battle
const removedPokemon = getRandomPlayerPokemon(scene, false, true);
scene.removePokemonFromPlayerParty(removedPokemon);
scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", removedPokemon.name);
scene.currentBattle.mysteryEncounter.setDialogueToken(
"pokeName",
removedPokemon.name
);
// Store removed pokemon types
scene.currentBattle.mysteryEncounter.misc = [removedPokemon.species.type1];
if (removedPokemon.species.type2) {
scene.currentBattle.mysteryEncounter.misc.push(removedPokemon.species.type2);
}
})
.withOptionPhase(async (scene: BattleScene) => {
// Give the player 5 Rogue Balls
scene.unshiftPhase(new ModifierRewardPhase(scene, () => new AddPokeballModifierType("rb", PokeballType.ROGUE_BALL, 5)));
// Store removed pokemon types
scene.currentBattle.mysteryEncounter.misc = [
removedPokemon.species.type1,
];
if (removedPokemon.species.type2) {
scene.currentBattle.mysteryEncounter.misc.push(
removedPokemon.species.type2
);
}
})
.withOptionPhase(async (scene: BattleScene) => {
// Give the player 5 Rogue Balls
scene.unshiftPhase(
new ModifierRewardPhase(
scene,
() =>
new AddPokeballModifierType("rb", PokeballType.ROGUE_BALL, 5)
)
);
// Start encounter with random legendary (7-10 starter strength) that has level additive
const bossTypes = scene.currentBattle.mysteryEncounter.misc as Type[];
// Starter egg tier, 35/50/10/5 %odds for tiers 6/7/8/9+
const roll = randSeedInt(100);
const starterTier: number | [number, number] = roll > 65 ? 6 : roll > 15 ? 7 : roll > 5 ? 8 : [9, 10];
const bossSpecies = getPokemonSpecies(getRandomSpeciesByStarterTier(starterTier, excludedBosses, bossTypes));
const pokemonConfig: EnemyPokemonConfig = {
species: bossSpecies,
isBoss: true
};
if (!isNullOrUndefined(bossSpecies.forms) && bossSpecies.forms.length > 0) {
pokemonConfig.formIndex = 0;
}
const config: EnemyPartyConfig = {
levelAdditiveMultiplier: 0.75,
pokemonConfigs: [pokemonConfig]
};
return initBattleWithEnemyConfig(scene, config);
})
.build()
)
.withOptionPhase(async (scene: BattleScene) => {
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(scene, true);
return true;
})
.build();
// Start encounter with random legendary (7-10 starter strength) that has level additive
const bossTypes = scene.currentBattle.mysteryEncounter.misc as Type[];
// Starter egg tier, 35/50/10/5 %odds for tiers 6/7/8/9+
const roll = randSeedInt(100);
const starterTier: number | [number, number] =
roll > 65 ? 6 : roll > 15 ? 7 : roll > 5 ? 8 : [9, 10];
const bossSpecies = getPokemonSpecies(
getRandomSpeciesByStarterTier(
starterTier,
excludedBosses,
bossTypes
)
);
const pokemonConfig: EnemyPokemonConfig = {
species: bossSpecies,
isBoss: true,
};
if (
!isNullOrUndefined(bossSpecies.forms) &&
bossSpecies.forms.length > 0
) {
pokemonConfig.formIndex = 0;
}
const config: EnemyPartyConfig = {
levelAdditiveMultiplier: 0.75,
pokemonConfigs: [pokemonConfig],
};
return initBattleWithEnemyConfig(scene, config);
})
.build()
)
.withOption(
new MysteryEncounterOptionBuilder()
.withDialogue({
buttonLabel: "mysteryEncounter:dark_deal_option_2_label",
buttonTooltip: "mysteryEncounter:dark_deal_option_2_tooltip",
selected: [
{
speaker: "mysteryEncounter:dark_deal_speaker",
text: "mysteryEncounter:dark_deal_option_2_selected",
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(scene, true);
return true;
})
.build()
)
.build();

View File

@ -28,7 +28,7 @@ export class EncounterOptionsDialogue {
title: TemplateStringsArray | `mysteryEncounter:${string}`;
description: TemplateStringsArray | `mysteryEncounter:${string}`;
query?: TemplateStringsArray | `mysteryEncounter:${string}`;
options: [OptionTextDisplay, OptionTextDisplay, ...OptionTextDisplay[]]; // Options array with minimum 2 options
options?: [...OptionTextDisplay[]]; // Options array with minimum 2 options
}
export default class MysteryEncounterDialogue {

View File

@ -127,6 +127,7 @@ export class MysteryEncounterOptionBuilder implements Partial<MysteryEncounterOp
onPreOptionPhase?: OptionPhaseCallback;
onOptionPhase?: OptionPhaseCallback;
onPostOptionPhase?: OptionPhaseCallback;
dialogue?: OptionTextDisplay;
withSceneRequirement(requirement: EncounterSceneRequirement): this & Required<Pick<MysteryEncounterOption, "requirements">> {
this.requirements.push(requirement);
@ -163,4 +164,9 @@ export class MysteryEncounterOptionBuilder implements Partial<MysteryEncounterOp
this.excludePrimaryFromSecondaryRequirements = excludePrimaryFromSecondaryRequirements;
return Object.assign(this, { secondaryPokemonRequirements: this.secondaryPokemonRequirements });
}
withDialogue(dialogue: OptionTextDisplay) {
this.dialogue = dialogue;
return this;
}
}

View File

@ -135,15 +135,17 @@ export default class MysteryEncounter implements MysteryEncounter {
Object.assign(this, encounter);
}
this.encounterTier = this.encounterTier ? this.encounterTier : MysteryEncounterTier.COMMON;
this.dialogue = allMysteryEncounterDialogue[this.encounterType];
this.dialogue = Object.assign((this.dialogue ?? {}), allMysteryEncounterDialogue[this.encounterType]);
// this.dialogue = allMysteryEncounterDialogue[this.encounterType];
console.log(`${MysteryEncounterType[encounter.encounterType]} Encounter Dialogue:`, this.dialogue);
this.encounterVariant = MysteryEncounterVariant.DEFAULT;
this.requirements = this.requirements ? this.requirements : [];
this.hideBattleIntroMessage = !isNullOrUndefined(this.hideBattleIntroMessage) ? this.hideBattleIntroMessage : false;
this.hideIntroVisuals = !isNullOrUndefined(this.hideIntroVisuals) ? this.hideIntroVisuals : true;
// Populate options with respective dialogue
if (this.dialogue) {
this.options.forEach((o, i) => o.dialogue = this.dialogue.encounterOptionsDialogue.options[i]);
if (this.dialogue?.encounterOptionsDialogue) {
// this.options.forEach((o, i) => o.dialogue = this.dialogue.encounterOptionsDialogue.options[i]);
}
// Reset any dirty flags or encounter data
@ -408,9 +410,20 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
* @returns
*/
withIntroSpriteConfigs(spriteConfigs: MysteryEncounterSpriteConfig[]): this & Pick<MysteryEncounter, "spriteConfigs"> {
console.debug("with intro sprite configs: ", spriteConfigs);
return Object.assign(this, { spriteConfigs: spriteConfigs });
}
withIntroDialogue(dialogue: MysteryEncounterDialogue["intro"] = []): this {
console.debug("with intro dialogue: ", dialogue);
this.dialogue = {...this.dialogue, intro: dialogue };
return this;
}
withIntro({spriteConfigs, dialogue} : {spriteConfigs: MysteryEncounterSpriteConfig[], dialogue?: MysteryEncounterDialogue["intro"]}) {
return this.withIntroSpriteConfigs(spriteConfigs).withIntroDialogue(dialogue);
}
/**
* OPTIONAL
*/

View File

@ -35,7 +35,7 @@ export const SEED_OVERRIDE: string = "";
export const WEATHER_OVERRIDE: WeatherType = WeatherType.NONE;
export const DOUBLE_BATTLE_OVERRIDE: boolean = false;
export const SINGLE_BATTLE_OVERRIDE: boolean = false;
export const STARTING_WAVE_OVERRIDE: integer = 0;
export const STARTING_WAVE_OVERRIDE: integer = 33;
export const STARTING_BIOME_OVERRIDE: Biome = Biome.TOWN;
export const ARENA_TINT_OVERRIDE: TimeOfDay = null;
// Multiplies XP gained by this value including 0. Set to null to ignore the override
@ -118,9 +118,9 @@ export const EGG_GACHA_PULL_COUNT_OVERRIDE: number = 0;
*/
// 1 to 256, set to null to ignore
export const MYSTERY_ENCOUNTER_RATE_OVERRIDE: number = null;
export const MYSTERY_ENCOUNTER_RATE_OVERRIDE: number = 10000;
export const MYSTERY_ENCOUNTER_TIER_OVERRIDE: MysteryEncounterTier = null;
export const MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = null;
export const MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = MysteryEncounterType.DARK_DEAL;
/**
* MODIFIER / ITEM OVERRIDES

View File

@ -828,7 +828,7 @@ export class EncounterPhase extends BattlePhase {
if (mysteryEncounter.onInit) {
mysteryEncounter.onInit(this.scene);
}
mysteryEncounter.populateDialogueTokensFromRequirements(this.scene);
// mysteryEncounter.populateDialogueTokensFromRequirements(this.scene);
}, this.scene.currentBattle.waveIndex);
// Add intro visuals for mystery encounter

View File

@ -319,13 +319,13 @@ 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);
const option = this.filteredEncounterOptions[i];
const text = getEncounterText(this.scene, option.dialogue?.buttonLabel, option.dialogue?.style ? option.dialogue?.style : TextStyle.WINDOW);
if (text) {
optionText.setText(text);
}
this.optionsMeetsReqs.push(this.filteredEncounterOptions[i].meetsRequirements(this.scene));
this.optionsMeetsReqs.push(option.meetsRequirements(this.scene));
if (!this.optionsMeetsReqs[i]) {
optionText.setAlpha(0.5);