add jsdocs and more cleanup
This commit is contained in:
parent
f62af4ab68
commit
e795a62629
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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 () => {
|
||||||
|
|
|
@ -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 = [];
|
||||||
|
|
Loading…
Reference in New Issue