add encounter exp utility and small cleanups/refactors

This commit is contained in:
ImperialSympathizer 2024-07-09 21:26:24 -04:00
parent cf43589260
commit deb1e95e34
19 changed files with 297 additions and 164 deletions

View File

@ -2681,7 +2681,7 @@ export default class BattleScene extends SceneBase {
const tier = val[1];
if (tier === MysteryEncounterTier.COMMON) {
tierWeights[0] = tierWeights[0] - 6;
} else if (tier === MysteryEncounterTier.UNCOMMON) {
} else if (tier === MysteryEncounterTier.GREAT) {
tierWeights[1] = tierWeights[1] - 4;
}
});
@ -2691,7 +2691,7 @@ export default class BattleScene extends SceneBase {
const commonThreshold = totalWeight - tierWeights[0];
const uncommonThreshold = totalWeight - tierWeights[0] - tierWeights[1];
const rareThreshold = totalWeight - tierWeights[0] - tierWeights[1] - tierWeights[2];
let tier = tierValue > commonThreshold ? MysteryEncounterTier.COMMON : tierValue > uncommonThreshold ? MysteryEncounterTier.UNCOMMON : tierValue > rareThreshold ? MysteryEncounterTier.RARE : MysteryEncounterTier.SUPER_RARE;
let tier = tierValue > commonThreshold ? MysteryEncounterTier.COMMON : tierValue > uncommonThreshold ? MysteryEncounterTier.GREAT : tierValue > rareThreshold ? MysteryEncounterTier.ULTRA : MysteryEncounterTier.ROGUE;
if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE)) {
tier = Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE;

View File

@ -239,7 +239,7 @@ export class MoneyRequirement extends EncounterSceneRequirement {
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
const value = this?.scalingMultiplier > 0 ? scene.getWaveMoneyAmount(this.scalingMultiplier).toString() : this.requiredMoney.toString();
// Colors money text
return ["money", "@ecCol[MONEY]{₽" + value + "}"];
return ["money", "@[MONEY]{₽" + value + "}"];
}
}

View File

@ -24,10 +24,10 @@ export enum MysteryEncounterVariant {
export enum MysteryEncounterTier {
COMMON,
UNCOMMON,
RARE,
SUPER_RARE,
ULTRA_RARE // Not currently used
GREAT,
ULTRA,
ROGUE,
MASTER // Not currently used
}
export default interface MysteryEncounter {
@ -44,6 +44,7 @@ export default interface MysteryEncounter {
hideBattleIntroMessage?: boolean;
hideIntroVisuals?: boolean;
catchAllowed?: boolean;
doEncounterExp?: (scene: BattleScene) => boolean;
doEncounterRewards?: (scene: BattleScene) => boolean;
onInit?: (scene: BattleScene) => boolean;
@ -347,6 +348,7 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
secondaryPokemonRequirements ?: EncounterPokemonRequirement[] = [];
excludePrimaryFromSupportRequirements?: boolean;
dialogueTokens?: Map<string, [RegExp, string]>;
doEncounterExp?: (scene: BattleScene) => boolean;
doEncounterRewards?: (scene: BattleScene) => boolean;
onInit?: (scene: BattleScene) => boolean;
hideBattleIntroMessage?: boolean;
@ -448,8 +450,7 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
*
* NOTE: If rewards are dependent on options selected, runtime data, etc.,
* It may be better to programmatically set doEncounterRewards elsewhere.
* For instance, doEncounterRewards could instead be set inside the onOptionPhase() callback function for a MysteryEncounterOption
* Check other existing mystery encounters for examples on how to use this
* There is a helper function in mystery-encounter utils, setEncounterRewards(), which can be called programmatically to set rewards
* @param doEncounterRewards - synchronous callback function to perform during rewards phase of the encounter
* @returns
*/
@ -457,6 +458,20 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
return Object.assign(this, { doEncounterRewards: doEncounterRewards });
}
/**
* Can set custom encounter exp via this callback function
* If exp always deterministic for an encounter, this is a good way to set them
*
* NOTE: If rewards are dependent on options selected, runtime data, etc.,
* It may be better to programmatically set doEncounterExp elsewhere.
* There is a helper function in mystery-encounter utils, setEncounterExp(), which can be called programmatically to set rewards
* @param doEncounterExp - synchronous callback function to perform during rewards phase of the encounter
* @returns
*/
withExp(doEncounterExp: (scene: BattleScene) => boolean): this & Required<Pick<MysteryEncounter, "doEncounterExp">> {
return Object.assign(this, { doEncounterExp: doEncounterExp });
}
/**
* Can be used to perform init logic before intro visuals are shown and before the MysteryEncounterPhase begins
* Useful for performing things like procedural generation of intro sprites, etc.

View File

@ -69,7 +69,7 @@ const excludedBosses = [
export const DarkDealEncounter: MysteryEncounter = new MysteryEncounterBuilder()
.withEncounterType(MysteryEncounterType.DARK_DEAL)
.withEncounterTier(MysteryEncounterTier.ULTRA_RARE)
.withEncounterTier(MysteryEncounterTier.ROGUE)
.withIntroSpriteConfigs([
{
spriteKey: "mad_scientist_m",

View File

@ -1,7 +1,7 @@
import BattleScene from "../../battle-scene";
import {
leaveEncounterWithoutBattle,
setCustomEncounterRewards,
leaveEncounterWithoutBattle, setEncounterExp,
setEncounterRewards,
} from "#app/data/mystery-encounters/mystery-encounter-utils";
import MysteryEncounter, {MysteryEncounterBuilder, MysteryEncounterTier} from "../mystery-encounter";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
@ -49,7 +49,8 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = new MysteryEncount
i++;
}
setCustomEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false});
setEncounterExp(scene, scene.getParty().map(p => p.id), 300);
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false});
leaveEncounterWithoutBattle(scene);
})
.build())
@ -69,7 +70,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = new MysteryEncount
i++;
}
setCustomEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false});
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false});
leaveEncounterWithoutBattle(scene);
})
.build())
@ -89,7 +90,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = new MysteryEncount
i++;
}
setCustomEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false});
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false});
leaveEncounterWithoutBattle(scene);
})
.build())
@ -113,7 +114,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = new MysteryEncount
i++;
}
setCustomEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false});
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false});
leaveEncounterWithoutBattle(scene);
})
.build())

View File

@ -4,7 +4,7 @@ import {
EnemyPartyConfig,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle, queueEncounterMessage,
setCustomEncounterRewards,
setEncounterRewards,
showEncounterText
} from "#app/data/mystery-encounters/mystery-encounter-utils";
import MysteryEncounter, {MysteryEncounterBuilder, MysteryEncounterTier} from "../mystery-encounter";
@ -103,7 +103,7 @@ export const FightOrFlightEncounter: MysteryEncounter = new MysteryEncounterBuil
.withOptionPhase(async (scene: BattleScene) => {
// Pick battle
const item = scene.currentBattle.mysteryEncounter.misc as ModifierTypeOption;
setCustomEncounterRewards(scene, { guaranteedModifierTypeOptions: [item], fillRemaining: false});
setEncounterRewards(scene, { guaranteedModifierTypeOptions: [item], fillRemaining: false});
await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0]);
})
.build())
@ -112,7 +112,7 @@ export const FightOrFlightEncounter: MysteryEncounter = new MysteryEncounterBuil
// Pick steal
const encounter = scene.currentBattle.mysteryEncounter;
const item = scene.currentBattle.mysteryEncounter.misc as ModifierTypeOption;
setCustomEncounterRewards(scene, { guaranteedModifierTypeOptions: [item], fillRemaining: false});
setEncounterRewards(scene, { guaranteedModifierTypeOptions: [item], fillRemaining: false});
// If player has a stealing move, they succeed automatically
const moveRequirement = new MoveRequirement(validMovesForSteal);

View File

@ -1,7 +1,7 @@
import BattleScene from "../../battle-scene";
import { ModifierTier } from "#app/modifier/modifier-tier";
import {modifierTypes} from "#app/modifier/modifier-type";
import { EnemyPartyConfig, initBattleWithEnemyConfig, setCustomEncounterRewards } from "#app/data/mystery-encounters/mystery-encounter-utils";
import { EnemyPartyConfig, initBattleWithEnemyConfig, setEncounterRewards } from "#app/data/mystery-encounters/mystery-encounter-utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import MysteryEncounter, {MysteryEncounterBuilder, MysteryEncounterTier} from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
@ -17,7 +17,7 @@ import {PartyMemberStrength} from "#enums/party-member-strength";
export const MysteriousChallengersEncounter: MysteryEncounter = new MysteryEncounterBuilder()
.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHALLENGERS)
.withEncounterTier(MysteryEncounterTier.UNCOMMON)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withIntroSpriteConfigs([]) // These are set in onInit()
.withSceneRequirement(new WaveCountRequirement([10, 180])) // waves 10 to 180
.withOnInit((scene: BattleScene) => {
@ -103,7 +103,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = new MysteryEncou
// Spawn standard trainer battle with memory mushroom reward
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
setCustomEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.TM_COMMON, modifierTypes.TM_GREAT, modifierTypes.MEMORY_MUSHROOM], fillRemaining: true });
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.TM_COMMON, modifierTypes.TM_GREAT, modifierTypes.MEMORY_MUSHROOM], fillRemaining: true });
// Seed offsets to remove possibility of different trainers having exact same teams
let ret;
@ -119,7 +119,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = new MysteryEncou
// Spawn hard fight with ULTRA/GREAT reward (can improve with luck)
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[1];
setCustomEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT], fillRemaining: true });
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT], fillRemaining: true });
// Seed offsets to remove possibility of different trainers having exact same teams
let ret;
@ -138,7 +138,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = new MysteryEncou
// To avoid player level snowballing from picking this option
encounter.expMultiplier = 0.9;
setCustomEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT], fillRemaining: true });
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT], fillRemaining: true });
// Seed offsets to remove possibility of different trainers having exact same teams
let ret;

View File

@ -5,7 +5,7 @@ import {
koPlayerPokemon,
leaveEncounterWithoutBattle,
queueEncounterMessage,
setCustomEncounterRewards,
setEncounterRewards,
showEncounterText
} from "#app/data/mystery-encounters/mystery-encounter-utils";
import MysteryEncounter, {MysteryEncounterBuilder, MysteryEncounterTier} from "../mystery-encounter";
@ -42,25 +42,25 @@ export const MysteriousChestEncounter: MysteryEncounter = new MysteryEncounterBu
const roll = randSeedInt(100);
if (roll > 60) {
// Choose between 2 COMMON / 2 GREAT tier items (40%)
setCustomEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.COMMON, ModifierTier.COMMON, ModifierTier.GREAT, ModifierTier.GREAT]});
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.COMMON, ModifierTier.COMMON, ModifierTier.GREAT, ModifierTier.GREAT]});
// Display result message then proceed to rewards
queueEncounterMessage(scene, "mysteryEncounter:mysterious_chest_option_1_normal_result");
leaveEncounterWithoutBattle(scene);
} else if (roll > 40) {
// Choose between 3 ULTRA tier items (20%)
setCustomEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA]});
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA]});
// Display result message then proceed to rewards
queueEncounterMessage(scene, "mysteryEncounter:mysterious_chest_option_1_good_result");
leaveEncounterWithoutBattle(scene);
} else if (roll > 36) {
// Choose between 2 ROGUE tier items (4%)
setCustomEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE]});
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE]});
// Display result message then proceed to rewards
queueEncounterMessage(scene, "mysteryEncounter:mysterious_chest_option_1_great_result");
leaveEncounterWithoutBattle(scene);
} else if (roll > 35) {
// Choose 1 MASTER tier item (1%)
setCustomEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.MASTER]});
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.MASTER]});
// Display result message then proceed to rewards
queueEncounterMessage(scene, "mysteryEncounter:mysterious_chest_option_1_amazing_result");
leaveEncounterWithoutBattle(scene);

View File

@ -7,7 +7,12 @@ import {Status, StatusEffect} from "../status-effect";
import {TrainerConfig, trainerConfigs, TrainerSlot} from "../trainer-config";
import Pokemon, {FieldPosition, PlayerPokemon} from "#app/field/pokemon";
import Trainer, {TrainerVariant} from "../../field/trainer";
import {PokemonExpBoosterModifier} from "#app/modifier/modifier";
import {
ExpBalanceModifier,
ExpShareModifier,
MultipleParticipantExpBonusModifier,
PokemonExpBoosterModifier
} from "#app/modifier/modifier";
import {
CustomModifierSettings,
getModifierPoolForType,
@ -19,7 +24,14 @@ import {
PokemonHeldItemModifierType,
regenerateModifierPoolThresholds
} from "#app/modifier/modifier-type";
import {BattleEndPhase, EggLapsePhase, ModifierRewardPhase, TrainerVictoryPhase} from "#app/phases";
import {
BattleEndPhase,
EggLapsePhase,
ExpPhase,
ModifierRewardPhase,
ShowPartyExpBarPhase,
TrainerVictoryPhase
} from "#app/phases";
import {MysteryEncounterBattlePhase, MysteryEncounterRewardsPhase} from "#app/phases/mystery-encounter-phase";
import * as Utils from "../../utils";
import {isNullOrUndefined} from "#app/utils";
@ -35,7 +47,9 @@ import {Mode} from "#app/ui/ui";
import {PartyOption, PartyUiMode} from "#app/ui/party-ui-handler";
import {OptionSelectConfig, OptionSelectItem} from "#app/ui/abstact-option-select-ui-handler";
import {WIGHT_INCREMENT_ON_SPAWN_MISS} from "#app/data/mystery-encounters/mystery-encounters";
import {getBBCodeFrag, TextStyle} from "#app/ui/text";
import {getTextWithColors, TextStyle} from "#app/ui/text";
import * as Overrides from "#app/overrides";
import {UiTheme} from "#enums/ui-theme";
/**
*
@ -167,17 +181,29 @@ export function koPlayerPokemon(pokemon: PlayerPokemon) {
pokemon.updateInfo();
}
export function getTextWithEncounterDialogueTokensAndColor(scene: BattleScene, textKey: TemplateStringsArray | `mysteryEncounter:${string}`, primaryStyle: TextStyle = TextStyle.MESSAGE): string {
export function getEncounterText(scene: BattleScene, textKey: TemplateStringsArray | `mysteryEncounter:${string}`, primaryStyle?: TextStyle, uiTheme: UiTheme = UiTheme.DEFAULT): string {
if (isNullOrUndefined(textKey)) {
return null;
}
let textString: string = getTextWithDialogueTokens(scene, textKey);
// Can only color the text if a Primary Style is defined
// primaryStyle is applied to all text that does not have its own specified style
if (primaryStyle) {
textString = getTextWithColors(textString, primaryStyle, uiTheme);
}
return textString;
}
function getTextWithDialogueTokens(scene: BattleScene, textKey: TemplateStringsArray | `mysteryEncounter:${string}`): string {
if (isNullOrUndefined(textKey)) {
return null;
}
let textString: string = i18next.t(textKey);
// Apply primary styling before anything else, if it exists
textString = getBBCodeFrag(textString, primaryStyle) + "[/color][/shadow]";
const primaryStyleString = [...textString.match(new RegExp(/\[color=[^\[]*\]\[shadow=[^\[]*\]/i))][0];
// Apply dialogue tokens
const dialogueTokens = scene.currentBattle?.mysteryEncounter?.dialogueTokens;
if (dialogueTokens) {
@ -186,16 +212,6 @@ export function getTextWithEncounterDialogueTokensAndColor(scene: BattleScene, t
});
}
// Set custom colors
// Looks for any pattern like this: @ecCol[SUMMARY_BLUE]{my text to color}
// Resulting in: "my text to color" string with TextStyle.SUMMARY_BLUE
textString = textString.replace(/@ecCol\[([^{]*)\]{([^}]*)}/gi, (substring, textStyle: string, textToColor: string) => {
return "[/color][/shadow]" + getBBCodeFrag(textToColor, TextStyle[textStyle]) + "[/color][/shadow]" + primaryStyleString;
});
// Remove extra style block at the end
textString = textString.replace(/\[color=[^\[]*\]\[shadow=[^\[]*\]\[\/color\]\[\/shadow\]/gi, "");
return textString;
}
@ -205,7 +221,7 @@ export function getTextWithEncounterDialogueTokensAndColor(scene: BattleScene, t
* @param contentKey
*/
export function queueEncounterMessage(scene: BattleScene, contentKey: TemplateStringsArray | `mysteryEncounter:${string}`): void {
const text: string = getTextWithEncounterDialogueTokensAndColor(scene, contentKey, TextStyle.MESSAGE);
const text: string = getEncounterText(scene, contentKey);
scene.queueMessage(text, null, true);
}
@ -216,7 +232,7 @@ export function queueEncounterMessage(scene: BattleScene, contentKey: TemplateSt
*/
export function showEncounterText(scene: BattleScene, contentKey: TemplateStringsArray | `mysteryEncounter:${string}`): Promise<void> {
return new Promise<void>(resolve => {
const text: string = getTextWithEncounterDialogueTokensAndColor(scene, contentKey, TextStyle.MESSAGE);
const text: string = getEncounterText(scene, contentKey);
scene.ui.showText(text, null, () => resolve(), 0, true);
});
}
@ -229,8 +245,8 @@ export function showEncounterText(scene: BattleScene, contentKey: TemplateString
* @param callback
*/
export function showEncounterDialogue(scene: BattleScene, textContentKey: TemplateStringsArray | `mysteryEncounter:${string}`, speakerContentKey: TemplateStringsArray | `mysteryEncounter:${string}`, callback?: Function) {
const text: string = getTextWithEncounterDialogueTokensAndColor(scene, textContentKey, TextStyle.MESSAGE);
const speaker: string = getTextWithEncounterDialogueTokensAndColor(scene, speakerContentKey);
const text: string = getEncounterText(scene, textContentKey);
const speaker: string = getEncounterText(scene, speakerContentKey);
scene.ui.showDialogue(text, speaker, null, callback, 0, 0);
}
@ -246,6 +262,7 @@ export class EnemyPokemonConfig {
tags?: BattlerTagType[];
mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void;
status?: StatusEffect;
passive?: boolean;
}
export class EnemyPartyConfig {
@ -341,6 +358,11 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
const enemyPokemon = scene.getEnemyParty()[e];
// Make sure basic data is clean
enemyPokemon.hp = enemyPokemon.getMaxHp();
enemyPokemon.status = null;
enemyPokemon.passive = false;
if (e < (doubleBattle ? 2 : 1)) {
enemyPokemon.setX(-66 + enemyPokemon.getFieldPositionOffset()[0]);
enemyPokemon.resetSummonData();
@ -353,7 +375,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
if (e < partyConfig?.pokemonConfigs?.length) {
const config = partyConfig?.pokemonConfigs?.[e];
// Generate new id in case using data source
// Generate new id, reset status and HP in case using data source
if (config.dataSource) {
enemyPokemon.id = Utils.randSeedInt(4294967296);
}
@ -372,6 +394,11 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
enemyPokemon.setBoss(true, segments);
}
// Set Passive
if (partyConfig.pokemonConfigs[e].passive) {
enemyPokemon.passive = true;
}
// Set Status
if (partyConfig.pokemonConfigs[e].status) {
// Default to cureturn 3 for sleep
@ -462,40 +489,6 @@ export function generateModifierType(scene: BattleScene, modifier: () => Modifie
return result;
}
/**
* Will initialize reward phases to follow the mystery encounter
* Can have shop displayed or skipped
* @param scene - Battle Scene
* @param customShopRewards - adds a shop phase with the specified rewards / reward tiers
* @param nonShopRewards - will add a non-shop reward phase for each specified item/modifier (can happen in addition to a shop)
* @param preRewardsCallback - can execute an arbitrary callback before the new phases if necessary (useful for updating items/party/injecting new phases before MysteryEncounterRewardsPhase)
*/
export function setCustomEncounterRewards(scene: BattleScene, customShopRewards?: CustomModifierSettings, nonShopRewards?: ModifierTypeFunc[], preRewardsCallback?: Function) {
scene.currentBattle.mysteryEncounter.doEncounterRewards = (scene: BattleScene) => {
if (preRewardsCallback) {
preRewardsCallback();
}
if (customShopRewards) {
scene.unshiftPhase(new SelectModifierPhase(scene, 0, null, customShopRewards));
} else {
scene.tryRemovePhase(p => p instanceof SelectModifierPhase);
}
if (nonShopRewards?.length > 0) {
nonShopRewards.forEach((reward) => {
scene.unshiftPhase(new ModifierRewardPhase(scene, reward));
});
} else {
while (!isNullOrUndefined(scene.findPhase(p => p instanceof ModifierRewardPhase))) {
scene.tryRemovePhase(p => p instanceof ModifierRewardPhase);
}
}
return true;
};
}
/**
*
* @param scene
@ -557,7 +550,7 @@ export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (p
if (!textPromptKey) {
displayOptions();
} else {
const secondOptionSelectPrompt = getTextWithEncounterDialogueTokensAndColor(scene, textPromptKey, TextStyle.MESSAGE);
const secondOptionSelectPrompt = getEncounterText(scene, textPromptKey, TextStyle.MESSAGE);
scene.ui.showText(secondOptionSelectPrompt, null, displayOptions, null, true);
}
});
@ -575,49 +568,140 @@ export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (p
}
/**
* Will initialize exp phases to follow the mystery encounter (in addition to any combat or other exp earned)
* Exp earned will be a simple function that linearly scales with wave index, that can be increased or decreased by the expMultiplier
* Exp Share will have no effect (so no accounting for what mon is "on the field")
* Exp Balance will still function as normal
* Will initialize reward phases to follow the mystery encounter
* Can have shop displayed or skipped
* @param scene - Battle Scene
* @param expMultiplier - default is 100, can be increased or decreased as desired
* @param customShopRewards - adds a shop phase with the specified rewards / reward tiers
* @param nonShopRewards - will add a non-shop reward phase for each specified item/modifier (can happen in addition to a shop)
* @param preRewardsCallback - can execute an arbitrary callback before the new phases if necessary (useful for updating items/party/injecting new phases before MysteryEncounterRewardsPhase)
*/
export function setEncounterExp(scene: BattleScene, expMultiplier: number = 100) {
//const expBalanceModifier = scene.findModifier(m => m instanceof ExpBalanceModifier) as ExpBalanceModifier;
const expVal = scene.currentBattle.waveIndex * expMultiplier;
const pokemonExp = new Utils.NumberHolder(expVal);
const partyMemberExp = [];
export function setEncounterRewards(scene: BattleScene, customShopRewards?: CustomModifierSettings, nonShopRewards?: ModifierTypeFunc[], preRewardsCallback?: Function) {
scene.currentBattle.mysteryEncounter.doEncounterRewards = (scene: BattleScene) => {
if (preRewardsCallback) {
preRewardsCallback();
}
const party = scene.getParty();
party.forEach(pokemon => {
scene.applyModifiers(PokemonExpBoosterModifier, true, pokemon, pokemonExp);
partyMemberExp.push(Math.floor(pokemonExp.value));
});
if (customShopRewards) {
scene.unshiftPhase(new SelectModifierPhase(scene, 0, null, customShopRewards));
} else {
scene.tryRemovePhase(p => p instanceof SelectModifierPhase);
}
// TODO
//if (expBalanceModifier) {
// let totalLevel = 0;
// let totalExp = 0;
// expPartyMembers.forEach((expPartyMember, epm) => {
// totalExp += partyMemberExp[epm];
// totalLevel += expPartyMember.level;
// });
if (nonShopRewards?.length > 0) {
nonShopRewards.forEach((reward) => {
scene.unshiftPhase(new ModifierRewardPhase(scene, reward));
});
} else {
while (!isNullOrUndefined(scene.findPhase(p => p instanceof ModifierRewardPhase))) {
scene.tryRemovePhase(p => p instanceof ModifierRewardPhase);
}
}
// const medianLevel = Math.floor(totalLevel / expPartyMembers.length);
return true;
};
}
// const recipientExpPartyMemberIndexes = [];
// expPartyMembers.forEach((expPartyMember, epm) => {
// if (expPartyMember.level <= medianLevel) {
// recipientExpPartyMemberIndexes.push(epm);
// }
// });
/**
* Will initialize exp phases into the phase queue (these are in addition to any combat or other exp earned)
* Exp Share and Exp Balance will still function as normal
* @param scene - Battle Scene
* @param participantIds - ids of party pokemon that get full exp value. Other party members will receive Exp Share amounts
* @param baseExpValue - gives exp equivalent to a pokemon of the wave index's level.
* Guidelines:
* 36 - Sunkern (lowest in game)
* 62-64 - regional starter base evos
* 100 - Scyther
* 170 - Spiritomb
* 250 - Gengar
* 290 - trio legendaries
* 340 - box legendaries
* 608 - Blissey (highest in game)
* @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) {
scene.currentBattle.mysteryEncounter.doEncounterExp = (scene: BattleScene) => {
const party = scene.getParty();
const expShareModifier = scene.findModifier(m => m instanceof ExpShareModifier) as ExpShareModifier;
const expBalanceModifier = scene.findModifier(m => m instanceof ExpBalanceModifier) as ExpBalanceModifier;
const multipleParticipantExpBonusModifier = scene.findModifier(m => m instanceof MultipleParticipantExpBonusModifier) as MultipleParticipantExpBonusModifier;
const nonFaintedPartyMembers = party.filter(p => p.hp);
const expPartyMembers = nonFaintedPartyMembers.filter(p => p.level < scene.getMaxExpLevel());
const partyMemberExp = [];
let expValue = baseExpValue * (useWaveIndex ? scene.currentBattle.waveIndex : 1);
// const splitExp = Math.floor(totalExp / recipientExpPartyMemberIndexes.length);
if (participantIds?.length > 0) {
if (scene.currentBattle.mysteryEncounter.encounterVariant === MysteryEncounterVariant.TRAINER_BATTLE) {
expValue = Math.floor(expValue * 1.5);
}
for (const partyMember of nonFaintedPartyMembers) {
const pId = partyMember.id;
const participated = participantIds.includes(pId);
if (participated) {
partyMember.addFriendship(2);
}
if (!expPartyMembers.includes(partyMember)) {
continue;
}
if (!participated && !expShareModifier) {
partyMemberExp.push(0);
continue;
}
let expMultiplier = 0;
if (participated) {
expMultiplier += (1 / participantIds.length);
if (participantIds.length > 1 && multipleParticipantExpBonusModifier) {
expMultiplier += multipleParticipantExpBonusModifier.getStackCount() * 0.2;
}
} else if (expShareModifier) {
expMultiplier += (expShareModifier.getStackCount() * 0.2) / participantIds.length;
}
if (partyMember.pokerus) {
expMultiplier *= 1.5;
}
if (Overrides.XP_MULTIPLIER_OVERRIDE !== null) {
expMultiplier = Overrides.XP_MULTIPLIER_OVERRIDE;
}
const pokemonExp = new Utils.NumberHolder(expValue * expMultiplier);
scene.applyModifiers(PokemonExpBoosterModifier, true, partyMember, pokemonExp);
partyMemberExp.push(Math.floor(pokemonExp.value));
}
// expPartyMembers.forEach((_partyMember, pm) => {
// partyMemberExp[pm] = Phaser.Math.Linear(partyMemberExp[pm], recipientExpPartyMemberIndexes.indexOf(pm) > -1 ? splitExp : 0, 0.2 * expBalanceModifier.getStackCount());
// });
//}
if (expBalanceModifier) {
let totalLevel = 0;
let totalExp = 0;
expPartyMembers.forEach((expPartyMember, epm) => {
totalExp += partyMemberExp[epm];
totalLevel += expPartyMember.level;
});
const medianLevel = Math.floor(totalLevel / expPartyMembers.length);
const recipientExpPartyMemberIndexes = [];
expPartyMembers.forEach((expPartyMember, epm) => {
if (expPartyMember.level <= medianLevel) {
recipientExpPartyMemberIndexes.push(epm);
}
});
const splitExp = Math.floor(totalExp / recipientExpPartyMemberIndexes.length);
expPartyMembers.forEach((_partyMember, pm) => {
partyMemberExp[pm] = Phaser.Math.Linear(partyMemberExp[pm], recipientExpPartyMemberIndexes.indexOf(pm) > -1 ? splitExp : 0, 0.2 * expBalanceModifier.getStackCount());
});
}
for (let pm = 0; pm < expPartyMembers.length; pm++) {
const exp = partyMemberExp[pm];
if (exp) {
const partyMemberIndex = party.indexOf(expPartyMembers[pm]);
scene.unshiftPhase(expPartyMembers[pm].isOnField() ? new ExpPhase(scene, partyMemberIndex, exp) : new ShowPartyExpBarPhase(scene, partyMemberIndex, exp));
}
}
}
return true;
};
}
/**

View File

@ -4,7 +4,7 @@ import {
leaveEncounterWithoutBattle,
queueEncounterMessage,
selectPokemonForOption,
setCustomEncounterRewards,
setEncounterRewards,
updatePlayerMoney,
} from "#app/data/mystery-encounters/mystery-encounter-utils";
import MysteryEncounter, {MysteryEncounterBuilder, MysteryEncounterTier} from "../mystery-encounter";
@ -134,7 +134,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = new MysteryEncounte
i++;
}
setCustomEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false});
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false});
leaveEncounterWithoutBattle(scene);
})
.build())

View File

@ -4,7 +4,7 @@ import {
EnemyPokemonConfig, generateModifierType,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle, queueEncounterMessage,
setCustomEncounterRewards
setEncounterRewards
} from "./mystery-encounter-utils";
import MysteryEncounter, {MysteryEncounterBuilder, MysteryEncounterTier} from "../mystery-encounter";
import * as Utils from "../../utils";
@ -23,7 +23,7 @@ import { BerryType } from "#enums/berry-type";
export const SleepingSnorlaxEncounter: MysteryEncounter = new MysteryEncounterBuilder()
.withEncounterType(MysteryEncounterType.SLEEPING_SNORLAX)
.withEncounterTier(MysteryEncounterTier.RARE)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withIntroSpriteConfigs([
{
spriteKey: Species.SNORLAX.toString(),
@ -78,7 +78,7 @@ export const SleepingSnorlaxEncounter: MysteryEncounter = new MysteryEncounterBu
// const sitrus = (modifierTypes.BERRY?.() as ModifierTypeGenerator).generateType(scene.getParty(), [BerryType.SITRUS]);
const sitrus = generateModifierType(scene, modifierTypes.BERRY, [BerryType.SITRUS]);
setCustomEncounterRewards(scene, { guaranteedModifierTypeOptions: [new ModifierTypeOption(sitrus, 0)], fillRemaining: false});
setEncounterRewards(scene, { guaranteedModifierTypeOptions: [new ModifierTypeOption(sitrus, 0)], fillRemaining: false});
queueEncounterMessage(scene, "mysteryEncounter:sleeping_snorlax_option_2_bad_result");
leaveEncounterWithoutBattle(scene);
} else {
@ -101,7 +101,7 @@ export const SleepingSnorlaxEncounter: MysteryEncounter = new MysteryEncounterBu
.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
setCustomEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.LEFTOVERS], fillRemaining: false});
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.LEFTOVERS], fillRemaining: false});
queueEncounterMessage(scene, "mysteryEncounter:sleeping_snorlax_option_3_good_result");
leaveEncounterWithoutBattle(scene);
})

View File

@ -1,10 +1,10 @@
import BattleScene from "../../battle-scene";
import {
EnemyPartyConfig,
getTextWithEncounterDialogueTokensAndColor,
getEncounterText,
initBattleWithEnemyConfig,
selectPokemonForOption,
setCustomEncounterRewards
setEncounterRewards
} from "#app/data/mystery-encounters/mystery-encounter-utils";
import {MysteryEncounterType} from "#enums/mystery-encounter-type";
import MysteryEncounter, {MysteryEncounterBuilder, MysteryEncounterTier} from "../mystery-encounter";
@ -26,7 +26,7 @@ import {pokemonInfo} from "#app/locales/en/pokemon-info";
export const TrainingSessionEncounter: MysteryEncounter = new MysteryEncounterBuilder()
.withEncounterType(MysteryEncounterType.TRAINING_SESSION)
.withEncounterTier(MysteryEncounterTier.RARE)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withIntroSpriteConfigs([
{
spriteKey: "training_gear",
@ -128,10 +128,10 @@ export const TrainingSessionEncounter: MysteryEncounter = new MysteryEncounterBu
scene.addModifier(mod, true, false, false, true);
}
scene.updateModifiers(true);
scene.queueMessage(getTextWithEncounterDialogueTokensAndColor(scene, "mysteryEncounter:training_session_battle_finished_1"), null, true);
scene.queueMessage(getEncounterText(scene, "mysteryEncounter:training_session_battle_finished_1"), null, true);
};
setCustomEncounterRewards(scene, { fillRemaining: true }, null, onBeforeRewardsPhase);
setEncounterRewards(scene, { fillRemaining: true }, null, onBeforeRewardsPhase);
return initBattleWithEnemyConfig(scene, config);
})
@ -174,7 +174,7 @@ export const TrainingSessionEncounter: MysteryEncounter = new MysteryEncounterBu
scene.removePokemonFromPlayerParty(playerPokemon, false);
const onBeforeRewardsPhase = () => {
scene.queueMessage(getTextWithEncounterDialogueTokensAndColor(scene, "mysteryEncounter:training_session_battle_finished_2"), null, true);
scene.queueMessage(getEncounterText(scene, "mysteryEncounter:training_session_battle_finished_2"), null, true);
// Add the pokemon back to party with Nature change
playerPokemon.setNature(encounter.misc.chosenNature);
scene.gameData.setPokemonCaught(playerPokemon, false);
@ -187,7 +187,7 @@ export const TrainingSessionEncounter: MysteryEncounter = new MysteryEncounterBu
scene.updateModifiers(true);
};
setCustomEncounterRewards(scene, { fillRemaining: true }, null, onBeforeRewardsPhase);
setEncounterRewards(scene, { fillRemaining: true }, null, onBeforeRewardsPhase);
return initBattleWithEnemyConfig(scene, config);
})
@ -237,7 +237,7 @@ export const TrainingSessionEncounter: MysteryEncounter = new MysteryEncounterBu
scene.removePokemonFromPlayerParty(playerPokemon, false);
const onBeforeRewardsPhase = () => {
scene.queueMessage(getTextWithEncounterDialogueTokensAndColor(scene, "mysteryEncounter:training_session_battle_finished_3"), null, true);
scene.queueMessage(getEncounterText(scene, "mysteryEncounter:training_session_battle_finished_3"), null, true);
// Add the pokemon back to party with ability change
const abilityIndex = encounter.misc.abilityIndex;
if (!!playerPokemon.getFusionSpeciesForm()) {
@ -268,7 +268,7 @@ export const TrainingSessionEncounter: MysteryEncounter = new MysteryEncounterBu
scene.updateModifiers(true);
};
setCustomEncounterRewards(scene, { fillRemaining: true }, null, onBeforeRewardsPhase);
setEncounterRewards(scene, { fillRemaining: true }, null, onBeforeRewardsPhase);
return initBattleWithEnemyConfig(scene, config);
})

View File

@ -7,10 +7,10 @@ import {SimpleTranslationEntries} from "#app/interfaces/locales";
*
* '@ec{<token>}' will auto-inject the matching token value for the specified Encounter
*
* '@ecCol[<TextStyle>]{<text>}' will auto-color the given text to a specified TextStyle (e.g. TextStyle.SUMMARY_GREEN)
* '@[<TextStyle>]{<text>}' will auto-color the given text to a specified TextStyle (e.g. TextStyle.SUMMARY_GREEN)
*
* Any '(+)' or '(-)' type of tooltip will auto-color to green/blue respectively. THIS ONLY OCCURS FOR OPTION TOOLTIPS, NOWHERE ELSE
* Other types of '(...)' tooltips will have to specify the text color manually by using '@ecCol[SUMMARY_GREEN]{<text>}' pattern
* Other types of '(...)' tooltips will have to specify the text color manually by using '@[SUMMARY_GREEN]{<text>}' pattern
*/
export const mysteryEncounter: SimpleTranslationEntries = {
// DO NOT REMOVE
@ -22,7 +22,7 @@ export const mysteryEncounter: SimpleTranslationEntries = {
"mysterious_chest_description": "A beautifully ornamented chest stands on the ground. There must be something good inside... right?",
"mysterious_chest_query": "Will you open it?",
"mysterious_chest_option_1_label": "Open it",
"mysterious_chest_option_1_tooltip": "@ecCol[SUMMARY_BLUE]{(35%) Something terrible}\n@ecCol[SUMMARY_GREEN]{(40%) Okay Rewards}\n@ecCol[SUMMARY_GREEN]{(20%) Good Rewards}\n@ecCol[SUMMARY_GREEN]{(4%) Great Rewards}\n@ecCol[SUMMARY_GREEN]{(1%) Amazing Rewards}",
"mysterious_chest_option_1_tooltip": "@[SUMMARY_BLUE]{(35%) Something terrible}\n@[SUMMARY_GREEN]{(40%) Okay Rewards}\n@[SUMMARY_GREEN]{(20%) Good Rewards}\n@[SUMMARY_GREEN]{(4%) Great Rewards}\n@[SUMMARY_GREEN]{(1%) Amazing Rewards}",
"mysterious_chest_option_2_label": "It's too risky, leave",
"mysterious_chest_option_2_tooltip": "(-) No Rewards",
"mysterious_chest_option_1_selected_message": "You open the chest to find...",
@ -41,8 +41,8 @@ export const mysteryEncounter: SimpleTranslationEntries = {
"fight_or_flight_option_1_label": "Battle the Pokémon",
"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": "@ecCol[SUMMARY_GREEN]{(35%) Steal Item}\n@ecCol[SUMMARY_BLUE]{(65%) Harder Battle}",
"fight_or_flight_option_2_steal_tooltip": "@ecCol[SUMMARY_GREEN]{(?) Use a Pokémon Move}",
"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_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.",
@ -167,7 +167,7 @@ export const mysteryEncounter: SimpleTranslationEntries = {
"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": "@ecCol[SUMMARY_BLUE]{(75%) Wait a short time}\n@ecCol[SUMMARY_BLUE]{(25%) Wait a long time}",
"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_disabled_tooltip": "Your Pokémon need to know certain moves to choose this",

View File

@ -67,7 +67,7 @@ import { TrainerType } from "#enums/trainer-type";
import { BattlePhase } from "#app/phases/battle-phase";
import { MysteryEncounterVariant } from "#app/data/mystery-encounter";
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phase";
import { getTextWithEncounterDialogueTokensAndColor, handleMysteryEncounterVictory } from "#app/data/mystery-encounters/mystery-encounter-utils";
import { getEncounterText, handleMysteryEncounterVictory } from "#app/data/mystery-encounters/mystery-encounter-utils";
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
const { t } = i18next;
@ -1081,8 +1081,8 @@ export class EncounterPhase extends BattlePhase {
const showNextDialogue = () => {
const nextAction = i === introDialogue.length - 1 ? doShowEncounterOptions : showNextDialogue;
const dialogue = introDialogue[i];
const title = getTextWithEncounterDialogueTokensAndColor(this.scene, dialogue.speaker);
const text = getTextWithEncounterDialogueTokensAndColor(this.scene, dialogue.text);
const title = getEncounterText(this.scene, dialogue.speaker);
const text = getEncounterText(this.scene, dialogue.text);
if (title) {
this.scene.ui.showDialogue(text, title, null, nextAction, 0, i === 0 ? 750 : 0);
} else {

View File

@ -3,7 +3,7 @@ import BattleScene from "../battle-scene";
import { Phase } from "../phase";
import { Mode } from "../ui/ui";
import {
getTextWithEncounterDialogueTokensAndColor
getEncounterText
} from "../data/mystery-encounters/mystery-encounter-utils";
import { CheckSwitchPhase, NewBattlePhase, PostSummonPhase, ReturnPhase, ScanIvsPhase, SummonPhase, ToggleDoublePositionPhase } from "../phases";
import MysteryEncounterOption from "../data/mystery-encounter-option";
@ -89,9 +89,9 @@ export class MysteryEncounterPhase extends Phase {
const nextAction = i === selectedDialogue.length - 1 ? endDialogueAndContinueEncounter : showNextDialogue;
const dialogue = selectedDialogue[i];
let title: string = null;
const text: string = getTextWithEncounterDialogueTokensAndColor(this.scene, dialogue.text);
const text: string = getEncounterText(this.scene, dialogue.text);
if (dialogue.speaker) {
title = getTextWithEncounterDialogueTokensAndColor(this.scene, dialogue.speaker);
title = getEncounterText(this.scene, dialogue.speaker);
}
if (title) {
@ -377,6 +377,7 @@ export class MysteryEncounterBattlePhase extends Phase {
/**
* Will handle (in order):
* - Any encounter reward logic that is set within MysteryEncounter doEncounterExp
* - Any encounter reward logic that is set within MysteryEncounter doEncounterRewards
* - Otherwise, can add a no-reward-item shop with only Potions, etc. if addHealPhase is true
* - Queuing of the PostMysteryEncounterPhase
@ -393,6 +394,10 @@ export class MysteryEncounterRewardsPhase extends Phase {
super.start();
this.scene.executeWithSeedOffset(() => {
if (this.scene.currentBattle.mysteryEncounter.doEncounterExp) {
this.scene.currentBattle.mysteryEncounter.doEncounterExp(this.scene);
}
if (this.scene.currentBattle.mysteryEncounter.doEncounterRewards) {
this.scene.currentBattle.mysteryEncounter.doEncounterRewards(this.scene);
} else if (this.addHealPhase) {
@ -451,9 +456,9 @@ export class PostMysteryEncounterPhase extends Phase {
const nextAction = i === outroDialogue.length - 1 ? endPhase : showNextDialogue;
const dialogue = outroDialogue[i];
let title: string = null;
const text: string = getTextWithEncounterDialogueTokensAndColor(this.scene, dialogue.text);
const text: string = getEncounterText(this.scene, dialogue.text);
if (dialogue.speaker) {
title = getTextWithEncounterDialogueTokensAndColor(this.scene, dialogue.speaker);
title = getEncounterText(this.scene, dialogue.speaker);
}
this.scene.ui.setMode(Mode.MESSAGE);

View File

@ -3,7 +3,7 @@ import GameManager from "#app/test/utils/gameManager";
import Phaser from "phaser";
import {
getHighestLevelPlayerPokemon, getLowestLevelPlayerPokemon,
getRandomPlayerPokemon, getRandomSpeciesByStarterTier, getTextWithEncounterDialogueTokensAndColor,
getRandomPlayerPokemon, getRandomSpeciesByStarterTier, getEncounterText,
koPlayerPokemon, queueEncounterMessage, showEncounterDialogue, showEncounterText,
} from "#app/data/mystery-encounters/mystery-encounter-utils";
import {initSceneWithoutEncounterPhase} from "#test/utils/gameManagerUtils";
@ -276,7 +276,7 @@ describe("Mystery Encounter Utils", () => {
scene.currentBattle.mysteryEncounter = new MysteryEncounter(null);
scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value");
const result = getTextWithEncounterDialogueTokensAndColor(scene, "mysteryEncounter:unit_test_dialogue");
const result = getEncounterText(scene, "mysteryEncounter:unit_test_dialogue");
expect(result).toEqual("[color=#f8f8f8][shadow=#6b5a73]valuevalue @ec{testvalue} @ec{test1} value @ec{test\\} @ec{test\\} {test}[/color][/shadow]");
});
@ -285,7 +285,7 @@ describe("Mystery Encounter Utils", () => {
scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value");
scene.currentBattle.mysteryEncounter.setDialogueToken("testvalue", "new");
const result = getTextWithEncounterDialogueTokensAndColor(scene, "mysteryEncounter:unit_test_dialogue");
const result = getEncounterText(scene, "mysteryEncounter:unit_test_dialogue");
expect(result).toEqual("[color=#f8f8f8][shadow=#6b5a73]valuevalue new @ec{test1} value @ec{test\\} @ec{test\\} {test}[/color][/shadow]");
});
});

View File

@ -63,7 +63,7 @@ describe("Mystery Encounter Phases", () => {
expect(game.scene.mysteryEncounterData.encounteredEvents.length).toBeGreaterThan(0);
expect(game.scene.mysteryEncounterData.encounteredEvents[0][0]).toEqual(MysteryEncounterType.MYSTERIOUS_CHALLENGERS);
expect(game.scene.mysteryEncounterData.encounteredEvents[0][1]).toEqual(MysteryEncounterTier.UNCOMMON);
expect(game.scene.mysteryEncounterData.encounteredEvents[0][1]).toEqual(MysteryEncounterTier.GREAT);
expect(game.scene.ui.getMode()).toBe(Mode.MYSTERY_ENCOUNTER);
});

View File

@ -10,7 +10,7 @@ import MysteryEncounterOption from "../data/mystery-encounter-option";
import * as Utils from "../utils";
import {isNullOrUndefined} from "../utils";
import {getPokeballAtlasKey} from "../data/pokeball";
import {getTextWithEncounterDialogueTokensAndColor} from "#app/data/mystery-encounters/mystery-encounter-utils";
import {getEncounterText} from "#app/data/mystery-encounters/mystery-encounter-utils";
export default class MysteryEncounterUiHandler extends UiHandler {
private cursorContainer: Phaser.GameObjects.Container;
@ -298,9 +298,9 @@ export default class MysteryEncounterUiHandler extends UiHandler {
this.filteredEncounterOptions = mysteryEncounter.options;
this.optionsMeetsReqs = [];
const titleText: string = getTextWithEncounterDialogueTokensAndColor(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue.title, TextStyle.TOOLTIP_TITLE);
const descriptionText: string = getTextWithEncounterDialogueTokensAndColor(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue.description, TextStyle.TOOLTIP_CONTENT);
const queryText: string = getTextWithEncounterDialogueTokensAndColor(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue.query, TextStyle.TOOLTIP_CONTENT);
const titleText: string = getEncounterText(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue.title, TextStyle.TOOLTIP_TITLE);
const descriptionText: string = getEncounterText(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue.description, TextStyle.TOOLTIP_CONTENT);
const queryText: string = getEncounterText(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue.query, TextStyle.TOOLTIP_CONTENT);
// Clear options container (except cursor)
this.optionsContainer.removeAll();
@ -320,7 +320,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
break;
}
const option = mysteryEncounter.dialogue.encounterOptionsDialogue.options[i];
const text = getTextWithEncounterDialogueTokensAndColor(this.scene, option.buttonLabel, option.style ? option.style : TextStyle.WINDOW);
const text = getEncounterText(this.scene, option.buttonLabel, option.style ? option.style : TextStyle.WINDOW);
if (text) {
optionText.setText(text);
}
@ -416,15 +416,15 @@ export default class MysteryEncounterUiHandler extends UiHandler {
let text;
const option = mysteryEncounter.dialogue.encounterOptionsDialogue.options[cursor];
if (!this.optionsMeetsReqs[cursor] && option.disabledTooltip) {
text = getTextWithEncounterDialogueTokensAndColor(this.scene, option.disabledTooltip, TextStyle.TOOLTIP_CONTENT);
text = getEncounterText(this.scene, option.disabledTooltip, TextStyle.TOOLTIP_CONTENT);
} else {
text = getTextWithEncounterDialogueTokensAndColor(this.scene, option.buttonTooltip, TextStyle.TOOLTIP_CONTENT);
text = getEncounterText(this.scene, option.buttonTooltip, TextStyle.TOOLTIP_CONTENT);
}
// Auto-color options green/blue for good/bad by looking for (+)/(-)
const primaryStyleString = [...text.match(new RegExp(/\[color=[^\[]*\]\[shadow=[^\[]*\]/i))][0];
text = text.replace(/(\([^\(]*\+\)[^\(\[]*)/gi, substring => "[/color][/shadow]" + getBBCodeFrag(substring, TextStyle.SUMMARY_GREEN) + "[/color][/shadow]" + primaryStyleString);
text = text.replace(/(\([^\(]*\-\)[^\(\[]*)/gi, substring => "[/color][/shadow]" + getBBCodeFrag(substring, TextStyle.SUMMARY_BLUE) + "[/color][/shadow]" + primaryStyleString);
text = text.replace(/(\(\+\)[^\(\[]*)/gi, substring => "[/color][/shadow]" + getBBCodeFrag(substring, TextStyle.SUMMARY_GREEN) + "[/color][/shadow]" + primaryStyleString);
text = text.replace(/(\(\-\)[^\(\[]*)/gi, substring => "[/color][/shadow]" + getBBCodeFrag(substring, TextStyle.SUMMARY_BLUE) + "[/color][/shadow]" + primaryStyleString);
if (text) {
const tooltipTextObject = addBBCodeTextObject(this.scene, 6, 7, text, TextStyle.TOOLTIP_CONTENT, { wordWrap: { width: 600 }, fontSize: "72px" });

View File

@ -171,6 +171,34 @@ export function getBBCodeFrag(content: string, textStyle: TextStyle, uiTheme: Ui
return `[color=${getTextColor(textStyle, false, uiTheme)}][shadow=${getTextColor(textStyle, true, uiTheme)}]${content}`;
}
/**
* Should only be used with BBCodeText (see addBBCodeTextObject())
* This does NOT work with UI showText() or showDialogue() methods.
* Method will do pattern match/replace and apply BBCode color/shadow styling to substrings within the content:
* @[<TextStyle>]{<text to color>}
*
* Example: passing a content string of "@[SUMMARY_BLUE]{blue text} primaryStyle text @[SUMMARY_RED]{red text}" will result in:
* - "blue text" with TextStyle.SUMMARY_BLUE applied
* - " primaryStyle text " with primaryStyle TextStyle applied
* - "red text" with TextStyle.SUMMARY_RED applied
* @param content - string with styling that need to be applied for BBCodeTextObject
* @param primaryStyle - primary style is required in order to escape BBCode styling properly.
* @param uiTheme
*/
export function getTextWithColors(content: string, primaryStyle: TextStyle, uiTheme: UiTheme = UiTheme.DEFAULT): string {
// Apply primary styling before anything else
let text = getBBCodeFrag(content, primaryStyle, uiTheme) + "[/color][/shadow]";
const primaryStyleString = [...text.match(new RegExp(/\[color=[^\[]*\]\[shadow=[^\[]*\]/i))][0];
// Set custom colors
text = text.replace(/@\[([^{]*)\]{([^}]*)}/gi, (substring, textStyle: string, textToColor: string) => {
return "[/color][/shadow]" + getBBCodeFrag(textToColor, TextStyle[textStyle], uiTheme) + "[/color][/shadow]" + primaryStyleString;
});
// Remove extra style block at the end
return text.replace(/\[color=[^\[]*\]\[shadow=[^\[]*\]\[\/color\]\[\/shadow\]/gi, "");
}
export function getTextColor(textStyle: TextStyle, shadow?: boolean, uiTheme: UiTheme = UiTheme.DEFAULT): string {
switch (textStyle) {
case TextStyle.MESSAGE: