add jsdocs and more cleanup

This commit is contained in:
ImperialSympathizer 2024-07-19 20:06:49 -04:00
parent f62af4ab68
commit e795a62629
10 changed files with 66 additions and 24 deletions

View File

@ -64,7 +64,7 @@ export const SafariZoneEncounter: IMysteryEncounter =
.withOptionPhase(async (scene: BattleScene) => { .withOptionPhase(async (scene: BattleScene) => {
// Start safari encounter // Start safari encounter
const encounter = scene.currentBattle.mysteryEncounter; const encounter = scene.currentBattle.mysteryEncounter;
encounter.encounterVariant = MysteryEncounterVariant.REPEATED_ENCOUNTER; encounter.encounterVariant = MysteryEncounterVariant.CONTINUOUS_ENCOUNTER;
encounter.misc = { encounter.misc = {
safariPokemonRemaining: 3 safariPokemonRemaining: 3
}; };

View File

@ -28,7 +28,7 @@ export enum MysteryEncounterVariant {
BOSS_BATTLE, BOSS_BATTLE,
NO_BATTLE, NO_BATTLE,
/** For spawning new encounter queries instead of continuing to next wave */ /** For spawning new encounter queries instead of continuing to next wave */
REPEATED_ENCOUNTER CONTINUOUS_ENCOUNTER
} }
/** /**
@ -115,11 +115,6 @@ export default interface IMysteryEncounter {
* You probably shouldn't do anything with this unless you have a very specific need * You probably shouldn't do anything with this unless you have a very specific need
*/ */
introVisuals?: MysteryEncounterIntroVisuals; introVisuals?: MysteryEncounterIntroVisuals;
/**
* Used for keeping RNG consistent on session resets, but increments when cycling through multiple "Encounters" on the same wave
* You should never need to modify this
*/
seedOffset?: any;
/** /**
* Flags * Flags
@ -172,6 +167,12 @@ export default interface IMysteryEncounter {
* Unless you know what you're doing, you should use MysteryEncounterBuilder to create an instance for this class * Unless you know what you're doing, you should use MysteryEncounterBuilder to create an instance for this class
*/ */
export default class IMysteryEncounter implements IMysteryEncounter { export default class IMysteryEncounter implements IMysteryEncounter {
/**
* Used for keeping RNG consistent on session resets, but increments when cycling through multiple "Encounters" on the same wave
* You should only need to interact via getter/update methods
*/
private seedOffset?: any;
constructor(encounter: IMysteryEncounter) { constructor(encounter: IMysteryEncounter) {
if (!isNullOrUndefined(encounter)) { if (!isNullOrUndefined(encounter)) {
Object.assign(this, encounter); Object.assign(this, encounter);
@ -371,6 +372,20 @@ export default class IMysteryEncounter implements IMysteryEncounter {
this.dialogueTokens[key] = value; this.dialogueTokens[key] = value;
} }
getSeedOffset?() {
return this.seedOffset;
}
/**
* Maintains seed offset for RNG consistency
* Increments if the same MysteryEncounter has multiple option select cycles
* @param scene
*/
updateSeedOffset?(scene: BattleScene) {
const currentOffset = this.seedOffset ?? scene.currentBattle.waveIndex * 1000;
this.seedOffset = currentOffset + 512;
}
private capitalizeFirstLetter?(str: string) { private capitalizeFirstLetter?(str: string) {
return str.charAt(0).toUpperCase() + str.slice(1); return str.charAt(0).toUpperCase() + str.slice(1);
} }
@ -442,7 +457,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param callback - {@linkcode OptionPhaseCallback} * @param callback - {@linkcode OptionPhaseCallback}
* @returns * @returns
*/ */
withSimpleOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this { withSimpleOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<IMysteryEncounter, "options"> {
return this.withOption(new MysteryEncounterOptionBuilder().withOptionMode(EncounterOptionMode.DEFAULT).withDialogue(dialogue).withOptionPhase(callback).build()); return this.withOption(new MysteryEncounterOptionBuilder().withOptionMode(EncounterOptionMode.DEFAULT).withDialogue(dialogue).withOptionPhase(callback).build());
} }

View File

@ -30,6 +30,10 @@ import { Gender } from "#app/data/gender";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims";
/**
* Animates exclamation sprite over trainer's head at start of encounter
* @param scene
*/
export function doTrainerExclamation(scene: BattleScene) { export function doTrainerExclamation(scene: BattleScene) {
const exclamationSprite = scene.addFieldSprite(0, 0, "exclaim"); const exclamationSprite = scene.addFieldSprite(0, 0, "exclaim");
exclamationSprite.setName("exclamation"); exclamationSprite.setName("exclamation");
@ -476,6 +480,7 @@ export function setEncounterExp(scene: BattleScene, participantId: integer | int
const nonFaintedPartyMembers = party.filter(p => p.hp); const nonFaintedPartyMembers = party.filter(p => p.hp);
const expPartyMembers = nonFaintedPartyMembers.filter(p => p.level < scene.getMaxExpLevel()); const expPartyMembers = nonFaintedPartyMembers.filter(p => p.level < scene.getMaxExpLevel());
const partyMemberExp = []; const partyMemberExp = [];
// EXP value calculation is based off Pokemon.getExpValue
let expValue = Math.floor(baseExpValue * (useWaveIndex ? scene.currentBattle.waveIndex : 1) / 5 + 1); let expValue = Math.floor(baseExpValue * (useWaveIndex ? scene.currentBattle.waveIndex : 1) / 5 + 1);
if (participantIds?.length > 0) { if (participantIds?.length > 0) {
@ -597,7 +602,7 @@ export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase:
// If in repeated encounter variant, do nothing // If in repeated encounter variant, do nothing
// Variant must eventually be swapped in order to handle "true" end of the encounter // Variant must eventually be swapped in order to handle "true" end of the encounter
if (scene.currentBattle.mysteryEncounter.encounterVariant === MysteryEncounterVariant.REPEATED_ENCOUNTER) { if (scene.currentBattle.mysteryEncounter.encounterVariant === MysteryEncounterVariant.CONTINUOUS_ENCOUNTER) {
return; return;
} else if (scene.currentBattle.mysteryEncounter.encounterVariant === MysteryEncounterVariant.NO_BATTLE) { } else if (scene.currentBattle.mysteryEncounter.encounterVariant === MysteryEncounterVariant.NO_BATTLE) {
scene.pushPhase(new EggLapsePhase(scene)); scene.pushPhase(new EggLapsePhase(scene));
@ -657,7 +662,12 @@ export function transitionMysteryEncounterIntroVisuals(scene: BattleScene, hide:
}); });
} }
export function handleEncounterStartOfBattleEffects(scene: BattleScene) { /**
* Will queue moves for any pokemon to use before the first CommandPhase of a battle
* Mostly useful for allowing MysteryEncounter enemies to "cheat" and use moves before the first turn
* @param scene
*/
export function handleMysteryEncounterBattleStartEffects(scene: BattleScene) {
const encounter = scene.currentBattle?.mysteryEncounter; const encounter = scene.currentBattle?.mysteryEncounter;
if (scene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER && encounter.encounterVariant !== MysteryEncounterVariant.NO_BATTLE && !encounter.startOfBattleEffectsComplete) { if (scene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER && encounter.encounterVariant !== MysteryEncounterVariant.NO_BATTLE && !encounter.startOfBattleEffectsComplete) {
const effects = encounter.startOfBattleEffects; const effects = encounter.startOfBattleEffects;

View File

@ -349,6 +349,10 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
}); });
} }
/**
* Sets container and all child sprites to visible
* @param value - true for visible, false for hidden
*/
setVisible(value: boolean): this { setVisible(value: boolean): this {
this.getSprites().forEach(sprite => { this.getSprites().forEach(sprite => {
sprite.setVisible(value); sprite.setVisible(value);

View File

@ -101,6 +101,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public battleSummonData: PokemonBattleSummonData; public battleSummonData: PokemonBattleSummonData;
public turnData: PokemonTurnData; public turnData: PokemonTurnData;
/** Used by Mystery Encounters to execute pokemon-specific logic (such as stat boosts) at start of battle */
public mysteryEncounterBattleEffects: (pokemon: Pokemon) => void = null; public mysteryEncounterBattleEffects: (pokemon: Pokemon) => void = null;
public fieldPosition: FieldPosition; public fieldPosition: FieldPosition;

View File

@ -67,7 +67,7 @@ import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { MysteryEncounterVariant } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterVariant } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
import { doTrainerExclamation, handleEncounterStartOfBattleEffects, handleMysteryEncounterVictory } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { doTrainerExclamation, handleMysteryEncounterBattleStartEffects, handleMysteryEncounterVictory } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler"; import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler";
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
@ -2053,8 +2053,7 @@ export class TurnInitPhase extends FieldPhase {
//this.scene.pushPhase(new MoveAnimTestPhase(this.scene)); //this.scene.pushPhase(new MoveAnimTestPhase(this.scene));
this.scene.eventTarget.dispatchEvent(new TurnInitEvent()); this.scene.eventTarget.dispatchEvent(new TurnInitEvent());
// Start of battle effects for Mystery Encounters handleMysteryEncounterBattleStartEffects(this.scene);
handleEncounterStartOfBattleEffects(this.scene);
this.scene.getField().forEach((pokemon, i) => { this.scene.getField().forEach((pokemon, i) => {
if (pokemon?.isActive()) { if (pokemon?.isActive()) {

View File

@ -28,6 +28,8 @@ import { BattlerTagLapseType } from "#app/data/battler-tags";
export class MysteryEncounterPhase extends Phase { export class MysteryEncounterPhase extends Phase {
optionSelectSettings: OptionSelectSettings; optionSelectSettings: OptionSelectSettings;
private FIRST_DIALOGUE_PROMPT_DELAY = 300;
/** /**
* *
* @param scene * @param scene
@ -46,9 +48,7 @@ export class MysteryEncounterPhase extends Phase {
this.scene.clearPhaseQueue(); this.scene.clearPhaseQueue();
this.scene.clearPhaseQueueSplice(); this.scene.clearPhaseQueueSplice();
// Generates seed offset for RNG consistency, but incremented if the same MysteryEncounter has multiple option select cycles this.scene.currentBattle.mysteryEncounter.updateSeedOffset(this.scene);
const offset = this.scene.currentBattle.mysteryEncounter.seedOffset ?? this.scene.currentBattle.waveIndex * 1000;
this.scene.currentBattle.mysteryEncounter.seedOffset = offset + 512;
if (!this.optionSelectSettings) { if (!this.optionSelectSettings) {
// Sets flag that ME was encountered, only if this is not a followup option select phase // Sets flag that ME was encountered, only if this is not a followup option select phase
@ -79,7 +79,7 @@ export class MysteryEncounterPhase extends Phase {
this.continueEncounter(); this.continueEncounter();
} }
}); });
}, this.scene.currentBattle.mysteryEncounter.seedOffset); }, this.scene.currentBattle.mysteryEncounter.getSeedOffset());
} else { } else {
this.continueEncounter(); this.continueEncounter();
} }
@ -109,9 +109,9 @@ export class MysteryEncounterPhase extends Phase {
} }
if (title) { if (title) {
this.scene.ui.showDialogue(text, title, null, nextAction, 0, i === 0 ? 300 : 0); this.scene.ui.showDialogue(text, title, null, nextAction, 0, i === 0 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0);
} else { } else {
this.scene.ui.showText(text, null, nextAction, i === 0 ? 300 : 0, true); this.scene.ui.showText(text, null, nextAction, i === 0 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0, true);
} }
i++; i++;
}; };
@ -154,14 +154,14 @@ export class MysteryEncounterOptionSelectedPhase extends Phase {
this.onOptionSelect(this.scene).finally(() => { this.onOptionSelect(this.scene).finally(() => {
this.end(); this.end();
}); });
}, this.scene.currentBattle.mysteryEncounter.seedOffset); }, this.scene.currentBattle.mysteryEncounter.getSeedOffset());
}); });
} else { } else {
this.scene.executeWithSeedOffset(() => { this.scene.executeWithSeedOffset(() => {
this.onOptionSelect(this.scene).finally(() => { this.onOptionSelect(this.scene).finally(() => {
this.end(); this.end();
}); });
}, this.scene.currentBattle.mysteryEncounter.seedOffset); }, this.scene.currentBattle.mysteryEncounter.getSeedOffset());
} }
} }
} }
@ -279,7 +279,7 @@ export class MysteryEncounterBattlePhase extends Phase {
} else { } else {
const trainer = this.scene.currentBattle.trainer; const trainer = this.scene.currentBattle.trainer;
let message: string; let message: string;
scene.executeWithSeedOffset(() => message = Utils.randSeedItem(encounterMessages), this.scene.currentBattle.mysteryEncounter.seedOffset); scene.executeWithSeedOffset(() => message = Utils.randSeedItem(encounterMessages), this.scene.currentBattle.mysteryEncounter.getSeedOffset());
const showDialogueAndSummon = () => { const showDialogueAndSummon = () => {
scene.ui.showDialogue(message, trainer.getName(TrainerSlot.NONE, true), null, () => { scene.ui.showDialogue(message, trainer.getName(TrainerSlot.NONE, true), null, () => {
@ -438,7 +438,7 @@ export class PostMysteryEncounterPhase extends Phase {
this.continueEncounter(); this.continueEncounter();
} }
}); });
}, this.scene.currentBattle.mysteryEncounter.seedOffset); }, this.scene.currentBattle.mysteryEncounter.getSeedOffset());
} else { } else {
this.continueEncounter(); this.continueEncounter();
} }

View File

@ -7,6 +7,12 @@ import GameManager from "../utils/gameManager";
import MessageUiHandler from "#app/ui/message-ui-handler"; import MessageUiHandler from "#app/ui/message-ui-handler";
import { Status, StatusEffect } from "#app/data/status-effect"; import { Status, StatusEffect } from "#app/data/status-effect";
/**
* Runs a MysteryEncounter to either the start of a battle, or to the MysteryEncounterRewardsPhase, depending on the option selected
* @param game
* @param optionNo - human number, not index
* @param isBattle - if selecting option should lead to battle, set to true
*/
export async function runSelectMysteryEncounterOption(game: GameManager, optionNo: number, isBattle: boolean = false) { export async function runSelectMysteryEncounterOption(game: GameManager, optionNo: number, isBattle: boolean = false) {
// Handle any eventual queued messages (e.g. weather phase, etc.) // Handle any eventual queued messages (e.g. weather phase, etc.)
game.onNextPrompt("MessagePhase", Mode.MESSAGE, () => { game.onNextPrompt("MessagePhase", Mode.MESSAGE, () => {
@ -81,6 +87,10 @@ export async function runSelectMysteryEncounterOption(game: GameManager, optionN
} }
} }
/**
* For any MysteryEncounter that has a battle, can call this to skip battle and proceed to MysteryEncounterRewardsPhase
* @param game
*/
export async function skipBattleRunMysteryEncounterRewardsPhase(game: GameManager) { export async function skipBattleRunMysteryEncounterRewardsPhase(game: GameManager) {
game.scene.clearPhaseQueue(); game.scene.clearPhaseQueue();
game.scene.clearPhaseQueueSplice(); game.scene.clearPhaseQueueSplice();

View File

@ -74,7 +74,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
game.override.startingBiome(Biome.MOUNTAIN); game.override.startingBiome(Biome.MOUNTAIN);
await game.runToMysteryEncounter(); await game.runToMysteryEncounter();
expect(scene.currentBattle.mysteryEncounter.encounterType).not.toBe(MysteryEncounterType.LOST_AT_SEA); expect(scene.currentBattle.mysteryEncounter.encounterType).not.toBe(MysteryEncounterType.FIERY_FALLOUT);
}); });
it("should not run below wave 41", async () => { it("should not run below wave 41", async () => {

View File

@ -1,3 +1,6 @@
/**
* Class will intercept any text or dialogue message calls and log them for test purposes
*/
export default class TextInterceptor { export default class TextInterceptor {
private scene; private scene;
public logs = []; public logs = [];