mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2024-11-26 00:36:25 +00:00
Merge pull request #4210 from ben-lear/mystery-encounters
PR feedback from NightKev
This commit is contained in:
commit
1870926f3b
@ -904,10 +904,10 @@ export default class BattleScene extends SceneBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a PlayerPokemon from the party, and clears modifiers for that Pokemon's id
|
* Removes a {@linkcode PlayerPokemon} from the party, and clears modifiers for that Pokemon's id
|
||||||
* Useful for MEs/Challenges that remove Pokemon from the player party temporarily or permanently
|
* Useful for MEs/Challenges that remove Pokemon from the player party temporarily or permanently
|
||||||
* @param pokemon
|
* @param pokemon
|
||||||
* @param destroy - Default true. If true, will destroy the Pokemon object after removing
|
* @param destroy Default true. If true, will destroy the {@linkcode PlayerPokemon} after removing
|
||||||
*/
|
*/
|
||||||
removePokemonFromPlayerParty(pokemon: PlayerPokemon, destroy: boolean = true) {
|
removePokemonFromPlayerParty(pokemon: PlayerPokemon, destroy: boolean = true) {
|
||||||
if (!pokemon) {
|
if (!pokemon) {
|
||||||
@ -2939,10 +2939,10 @@ export default class BattleScene extends SceneBase {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates Exp and level values for Player's party, adding new level up phases as required
|
* Updates Exp and level values for Player's party, adding new level up phases as required
|
||||||
* @param expValue - raw value of exp to split among participants, OR the base multiplier to use with waveIndex
|
* @param expValue raw value of exp to split among participants, OR the base multiplier to use with waveIndex
|
||||||
* @param pokemonDefeated - If true, will increment Macho Brace stacks and give the party Pokemon friendship increases
|
* @param pokemonDefeated If true, will increment Macho Brace stacks and give the party Pokemon friendship increases
|
||||||
* @param useWaveIndexMultiplier - Default false. If true, will multiply expValue by a scaling waveIndex multiplier. Not needed if expValue is already scaled by level/wave
|
* @param useWaveIndexMultiplier Default false. If true, will multiply expValue by a scaling waveIndex multiplier. Not needed if expValue is already scaled by level/wave
|
||||||
* @param pokemonParticipantIds - Participants. If none are defined, no exp will be given. To spread evenly among the party, should pass all ids of party members.
|
* @param pokemonParticipantIds Participants. If none are defined, no exp will be given. To spread evenly among the party, should pass all ids of party members.
|
||||||
*/
|
*/
|
||||||
applyPartyExp(expValue: number, pokemonDefeated: boolean, useWaveIndexMultiplier?: boolean, pokemonParticipantIds?: Set<number>): void {
|
applyPartyExp(expValue: number, pokemonDefeated: boolean, useWaveIndexMultiplier?: boolean, pokemonParticipantIds?: Set<number>): void {
|
||||||
const participantIds = pokemonParticipantIds ?? this.currentBattle.playerParticipantIds;
|
const participantIds = pokemonParticipantIds ?? this.currentBattle.playerParticipantIds;
|
||||||
@ -3040,7 +3040,7 @@ export default class BattleScene extends SceneBase {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads or generates a mystery encounter
|
* Loads or generates a mystery encounter
|
||||||
* @param encounterType - used to load session encounter when restarting game, etc.
|
* @param encounterType used to load session encounter when restarting game, etc.
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
getMysteryEncounter(encounterType?: MysteryEncounterType): MysteryEncounter {
|
getMysteryEncounter(encounterType?: MysteryEncounterType): MysteryEncounter {
|
||||||
@ -3111,10 +3111,11 @@ export default class BattleScene extends SceneBase {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const disabledModes = encounterCandidate.disabledGameModes;
|
const disabledModes = encounterCandidate.disabledGameModes;
|
||||||
if (disabledModes && disabledModes.length > 0 && disabledModes.includes(this.gameMode.modeId)) { // Encounter is enabled for game mode
|
if (disabledModes && disabledModes.length > 0
|
||||||
|
&& disabledModes.includes(this.gameMode.modeId)) { // Encounter is enabled for game mode
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!encounterCandidate.meetsRequirements!(this)) { // Meets encounter requirements
|
if (!encounterCandidate.meetsRequirements(this)) { // Meets encounter requirements
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (previousEncounter !== null && encounterType === previousEncounter) { // Previous encounter was not this one
|
if (previousEncounter !== null && encounterType === previousEncounter) { // Previous encounter was not this one
|
||||||
@ -3148,7 +3149,7 @@ export default class BattleScene extends SceneBase {
|
|||||||
encounter = availableEncounters[Utils.randSeedInt(availableEncounters.length)];
|
encounter = availableEncounters[Utils.randSeedInt(availableEncounters.length)];
|
||||||
// New encounter object to not dirty flags
|
// New encounter object to not dirty flags
|
||||||
encounter = new MysteryEncounter(encounter);
|
encounter = new MysteryEncounter(encounter);
|
||||||
encounter.populateDialogueTokensFromRequirements!(this);
|
encounter.populateDialogueTokensFromRequirements(this);
|
||||||
return encounter;
|
return encounter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -535,7 +535,7 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
|
|||||||
/**
|
/**
|
||||||
* Fetches animation configs to be used in a Mystery Encounter
|
* Fetches animation configs to be used in a Mystery Encounter
|
||||||
* @param scene
|
* @param scene
|
||||||
* @param encounterAnim - one or more animations to fetch
|
* @param encounterAnim one or more animations to fetch
|
||||||
*/
|
*/
|
||||||
export async function initEncounterAnims(scene: BattleScene, encounterAnim: EncounterAnim | EncounterAnim[]): Promise<void> {
|
export async function initEncounterAnims(scene: BattleScene, encounterAnim: EncounterAnim | EncounterAnim[]): Promise<void> {
|
||||||
const anims = Array.isArray(encounterAnim) ? encounterAnim : [encounterAnim];
|
const anims = Array.isArray(encounterAnim) ? encounterAnim : [encounterAnim];
|
||||||
@ -608,7 +608,7 @@ export function loadCommonAnimAssets(scene: BattleScene, startLoad?: boolean): P
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads encounter animation assets to scene
|
* Loads encounter animation assets to scene
|
||||||
* MUST be called after [initEncounterAnims()](./battle-anims.ts) to load all required animations properly
|
* MUST be called after {@linkcode initEncounterAnims()} to load all required animations properly
|
||||||
* @param scene
|
* @param scene
|
||||||
* @param startLoad
|
* @param startLoad
|
||||||
*/
|
*/
|
||||||
@ -1079,7 +1079,6 @@ export abstract class BattleAnim {
|
|||||||
[AnimFrameTarget.USER]: [],
|
[AnimFrameTarget.USER]: [],
|
||||||
[AnimFrameTarget.TARGET]: []
|
[AnimFrameTarget.TARGET]: []
|
||||||
};
|
};
|
||||||
const spritePriorities: integer[] = [];
|
|
||||||
|
|
||||||
const cleanUpAndComplete = () => {
|
const cleanUpAndComplete = () => {
|
||||||
for (const ms of Object.values(spriteCache).flat()) {
|
for (const ms of Object.values(spriteCache).flat()) {
|
||||||
@ -1104,8 +1103,8 @@ export abstract class BattleAnim {
|
|||||||
this.srcLine = [ userFocusX, userFocusY, targetFocusX, targetFocusY ];
|
this.srcLine = [ userFocusX, userFocusY, targetFocusX, targetFocusY ];
|
||||||
this.dstLine = [ 150, 75, targetInitialX, targetInitialY ];
|
this.dstLine = [ 150, 75, targetInitialX, targetInitialY ];
|
||||||
|
|
||||||
let r = anim!.frames.length;
|
let totalFrames = anim!.frames.length;
|
||||||
let f = 0;
|
let frameCount = 0;
|
||||||
|
|
||||||
let existingFieldSprites = scene.field.getAll().slice(0);
|
let existingFieldSprites = scene.field.getAll().slice(0);
|
||||||
|
|
||||||
@ -1114,11 +1113,9 @@ export abstract class BattleAnim {
|
|||||||
repeat: anim!.frames.length,
|
repeat: anim!.frames.length,
|
||||||
onRepeat: () => {
|
onRepeat: () => {
|
||||||
existingFieldSprites = scene.field.getAll().slice(0);
|
existingFieldSprites = scene.field.getAll().slice(0);
|
||||||
const spriteFrames = anim!.frames[f];
|
const spriteFrames = anim!.frames[frameCount];
|
||||||
const frameData = this.getGraphicFrameDataWithoutTarget(anim!.frames[f], targetInitialX, targetInitialY);
|
const frameData = this.getGraphicFrameDataWithoutTarget(anim!.frames[frameCount], targetInitialX, targetInitialY);
|
||||||
const u = 0;
|
let graphicFrameCount = 0;
|
||||||
const t = 0;
|
|
||||||
let g = 0;
|
|
||||||
for (const frame of spriteFrames) {
|
for (const frame of spriteFrames) {
|
||||||
if (frame.target !== AnimFrameTarget.GRAPHIC) {
|
if (frame.target !== AnimFrameTarget.GRAPHIC) {
|
||||||
console.log("Encounter animations do not support targets");
|
console.log("Encounter animations do not support targets");
|
||||||
@ -1126,16 +1123,14 @@ export abstract class BattleAnim {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sprites = spriteCache[AnimFrameTarget.GRAPHIC];
|
const sprites = spriteCache[AnimFrameTarget.GRAPHIC];
|
||||||
if (g === sprites.length) {
|
if (graphicFrameCount === sprites.length) {
|
||||||
const newSprite: Phaser.GameObjects.Sprite = scene.addFieldSprite(0, 0, anim!.graphic, 1);
|
const newSprite: Phaser.GameObjects.Sprite = scene.addFieldSprite(0, 0, anim!.graphic, 1);
|
||||||
sprites.push(newSprite);
|
sprites.push(newSprite);
|
||||||
scene.field.add(newSprite);
|
scene.field.add(newSprite);
|
||||||
spritePriorities.push(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const graphicIndex = g++;
|
const graphicIndex = graphicFrameCount++;
|
||||||
const moveSprite = sprites[graphicIndex];
|
const moveSprite = sprites[graphicIndex];
|
||||||
spritePriorities[graphicIndex] = frame.priority;
|
|
||||||
if (!isNullOrUndefined(frame.priority)) {
|
if (!isNullOrUndefined(frame.priority)) {
|
||||||
const setSpritePriority = (priority: integer) => {
|
const setSpritePriority = (priority: integer) => {
|
||||||
if (existingFieldSprites.length > priority) {
|
if (existingFieldSprites.length > priority) {
|
||||||
@ -1162,40 +1157,37 @@ export abstract class BattleAnim {
|
|||||||
moveSprite.setBlendMode(frame.blendType === AnimBlendType.NORMAL ? Phaser.BlendModes.NORMAL : frame.blendType === AnimBlendType.ADD ? Phaser.BlendModes.ADD : Phaser.BlendModes.DIFFERENCE);
|
moveSprite.setBlendMode(frame.blendType === AnimBlendType.NORMAL ? Phaser.BlendModes.NORMAL : frame.blendType === AnimBlendType.ADD ? Phaser.BlendModes.ADD : Phaser.BlendModes.DIFFERENCE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (anim?.frameTimedEvents.get(f)) {
|
if (anim?.frameTimedEvents.get(frameCount)) {
|
||||||
for (const event of anim.frameTimedEvents.get(f)!) {
|
for (const event of anim.frameTimedEvents.get(frameCount)!) {
|
||||||
r = Math.max((anim.frames.length - f) + event.execute(scene, this, frameTimedEventPriority), r);
|
totalFrames = Math.max((anim.frames.length - frameCount) + event.execute(scene, this, frameTimedEventPriority), totalFrames);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const targets = Utils.getEnumValues(AnimFrameTarget);
|
const targets = Utils.getEnumValues(AnimFrameTarget);
|
||||||
for (const i of targets) {
|
for (const i of targets) {
|
||||||
const count = i === AnimFrameTarget.GRAPHIC ? g : i === AnimFrameTarget.USER ? u : t;
|
const count = graphicFrameCount;
|
||||||
if (count < spriteCache[i].length) {
|
if (count < spriteCache[i].length) {
|
||||||
const spritesToRemove = spriteCache[i].slice(count, spriteCache[i].length);
|
const spritesToRemove = spriteCache[i].slice(count, spriteCache[i].length);
|
||||||
for (const rs of spritesToRemove) {
|
for (const sprite of spritesToRemove) {
|
||||||
if (!rs.getData("locked") as boolean) {
|
if (!sprite.getData("locked") as boolean) {
|
||||||
const spriteCacheIndex = spriteCache[i].indexOf(rs);
|
const spriteCacheIndex = spriteCache[i].indexOf(sprite);
|
||||||
spriteCache[i].splice(spriteCacheIndex, 1);
|
spriteCache[i].splice(spriteCacheIndex, 1);
|
||||||
if (i === AnimFrameTarget.GRAPHIC) {
|
sprite.destroy();
|
||||||
spritePriorities.splice(spriteCacheIndex, 1);
|
|
||||||
}
|
|
||||||
rs.destroy();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
f++;
|
frameCount++;
|
||||||
r--;
|
totalFrames--;
|
||||||
},
|
},
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
for (const ms of Object.values(spriteCache).flat()) {
|
for (const sprite of Object.values(spriteCache).flat()) {
|
||||||
if (ms && !ms.getData("locked")) {
|
if (sprite && !sprite.getData("locked")) {
|
||||||
ms.destroy();
|
sprite.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (r) {
|
if (totalFrames) {
|
||||||
scene.tweens.addCounter({
|
scene.tweens.addCounter({
|
||||||
duration: Utils.getFrameMs(r),
|
duration: Utils.getFrameMs(totalFrames),
|
||||||
onComplete: () => cleanUpAndComplete()
|
onComplete: () => cleanUpAndComplete()
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -1277,7 +1269,7 @@ export class EncounterBattleAnim extends BattleAnim {
|
|||||||
public oppAnim: boolean;
|
public oppAnim: boolean;
|
||||||
|
|
||||||
constructor(encounterAnim: EncounterAnim, user: Pokemon, target?: Pokemon, oppAnim?: boolean) {
|
constructor(encounterAnim: EncounterAnim, user: Pokemon, target?: Pokemon, oppAnim?: boolean) {
|
||||||
super(user, target || user, true);
|
super(user, target ?? user, true);
|
||||||
|
|
||||||
this.encounterAnim = encounterAnim;
|
this.encounterAnim = encounterAnim;
|
||||||
this.oppAnim = oppAnim ?? false;
|
this.oppAnim = oppAnim ?? false;
|
||||||
|
@ -2212,12 +2212,14 @@ export class TarShotTag extends BattlerTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tag that adds extra post-summon effects to a battle for a specific Pokemon
|
* Tag that adds extra post-summon effects to a battle for a specific Pokemon.
|
||||||
* Currently used only in MysteryEncounters to provide start of fight stat buffs
|
* These post-summon effects are performed through {@linkcode Pokemon.mysteryEncounterBattleEffects},
|
||||||
|
* and can be used to unshift special phases, etc.
|
||||||
|
* Currently used only in MysteryEncounters to provide start of fight stat buffs.
|
||||||
*/
|
*/
|
||||||
export class MysteryEncounterPostSummonTag extends BattlerTag {
|
export class MysteryEncounterPostSummonTag extends BattlerTag {
|
||||||
constructor(sourceMove: Moves) {
|
constructor() {
|
||||||
super(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON, BattlerTagLapseType.CUSTOM, 1, sourceMove);
|
super(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON, BattlerTagLapseType.CUSTOM, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
onAdd(pokemon: Pokemon): void {
|
onAdd(pokemon: Pokemon): void {
|
||||||
@ -2228,13 +2230,11 @@ export class MysteryEncounterPostSummonTag extends BattlerTag {
|
|||||||
const ret = super.lapse(pokemon, lapseType);
|
const ret = super.lapse(pokemon, lapseType);
|
||||||
|
|
||||||
if (lapseType === BattlerTagLapseType.CUSTOM) {
|
if (lapseType === BattlerTagLapseType.CUSTOM) {
|
||||||
// Give pokemon +1 stats for battle
|
|
||||||
const cancelled = new Utils.BooleanHolder(false);
|
const cancelled = new Utils.BooleanHolder(false);
|
||||||
applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled);
|
applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled);
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
const mysteryEncounterBattleEffects = pokemon.mysteryEncounterBattleEffects;
|
if (pokemon.mysteryEncounterBattleEffects) {
|
||||||
if (mysteryEncounterBattleEffects) {
|
pokemon.mysteryEncounterBattleEffects(pokemon);
|
||||||
mysteryEncounterBattleEffects(pokemon);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2407,7 +2407,7 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
|
|||||||
case BattlerTagType.GORILLA_TACTICS:
|
case BattlerTagType.GORILLA_TACTICS:
|
||||||
return new GorillaTacticsTag();
|
return new GorillaTacticsTag();
|
||||||
case BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON:
|
case BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON:
|
||||||
return new MysteryEncounterPostSummonTag(sourceMove);
|
return new MysteryEncounterPostSummonTag();
|
||||||
case BattlerTagType.NONE:
|
case BattlerTagType.NONE:
|
||||||
default:
|
default:
|
||||||
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
|
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
|
||||||
|
@ -451,7 +451,7 @@ function doGreedentEatBerries(scene: BattleScene) {
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param scene
|
* @param scene
|
||||||
* @param isEat - default false. Will "create" pile when false, and remove pile when true.
|
* @param isEat Default false. Will "create" pile when false, and remove pile when true.
|
||||||
*/
|
*/
|
||||||
function doBerrySpritePile(scene: BattleScene, isEat: boolean = false) {
|
function doBerrySpritePile(scene: BattleScene, isEat: boolean = false) {
|
||||||
const berryAddDelay = 150;
|
const berryAddDelay = 150;
|
||||||
|
@ -12,6 +12,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
|
|||||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
|
import { GameOverPhase } from "#app/phases/game-over-phase";
|
||||||
|
|
||||||
/** i18n namespace for encounter */
|
/** i18n namespace for encounter */
|
||||||
const namespace = "mysteryEncounter:mysteriousChest";
|
const namespace = "mysteryEncounter:mysteriousChest";
|
||||||
@ -116,8 +117,8 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
|||||||
// Open the chest
|
// Open the chest
|
||||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||||
const roll = encounter.misc.roll;
|
const roll = encounter.misc.roll;
|
||||||
if (roll > 60) {
|
if (roll > 80) {
|
||||||
// Choose between 2 COMMON / 2 GREAT tier items (30%)
|
// Choose between 2 COMMON / 2 GREAT tier items (20%)
|
||||||
setEncounterRewards(scene, {
|
setEncounterRewards(scene, {
|
||||||
guaranteedModifierTiers: [
|
guaranteedModifierTiers: [
|
||||||
ModifierTier.COMMON,
|
ModifierTier.COMMON,
|
||||||
@ -129,8 +130,8 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
|||||||
// Display result message then proceed to rewards
|
// Display result message then proceed to rewards
|
||||||
queueEncounterMessage(scene, `${namespace}.option.1.normal`);
|
queueEncounterMessage(scene, `${namespace}.option.1.normal`);
|
||||||
leaveEncounterWithoutBattle(scene);
|
leaveEncounterWithoutBattle(scene);
|
||||||
} else if (roll > 40) {
|
} else if (roll > 50) {
|
||||||
// Choose between 3 ULTRA tier items (20%)
|
// Choose between 3 ULTRA tier items (30%)
|
||||||
setEncounterRewards(scene, {
|
setEncounterRewards(scene, {
|
||||||
guaranteedModifierTiers: [
|
guaranteedModifierTiers: [
|
||||||
ModifierTier.ULTRA,
|
ModifierTier.ULTRA,
|
||||||
@ -141,35 +142,38 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
|||||||
// Display result message then proceed to rewards
|
// Display result message then proceed to rewards
|
||||||
queueEncounterMessage(scene, `${namespace}.option.1.good`);
|
queueEncounterMessage(scene, `${namespace}.option.1.good`);
|
||||||
leaveEncounterWithoutBattle(scene);
|
leaveEncounterWithoutBattle(scene);
|
||||||
} else if (roll > 36) {
|
} else if (roll > 40) {
|
||||||
// Choose between 2 ROGUE tier items (10%)
|
// Choose between 2 ROGUE tier items (10%)
|
||||||
setEncounterRewards(scene, {
|
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE] });
|
||||||
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE],
|
|
||||||
});
|
|
||||||
// Display result message then proceed to rewards
|
// Display result message then proceed to rewards
|
||||||
queueEncounterMessage(scene, `${namespace}.option.1.great`);
|
queueEncounterMessage(scene, `${namespace}.option.1.great`);
|
||||||
leaveEncounterWithoutBattle(scene);
|
leaveEncounterWithoutBattle(scene);
|
||||||
} else if (roll > 35) {
|
} else if (roll > 35) {
|
||||||
// Choose 1 MASTER tier item (5%)
|
// Choose 1 MASTER tier item (5%)
|
||||||
setEncounterRewards(scene, {
|
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.MASTER] });
|
||||||
guaranteedModifierTiers: [ModifierTier.MASTER],
|
|
||||||
});
|
|
||||||
// Display result message then proceed to rewards
|
// Display result message then proceed to rewards
|
||||||
queueEncounterMessage(scene, `${namespace}.option.1.amazing`);
|
queueEncounterMessage(scene, `${namespace}.option.1.amazing`);
|
||||||
leaveEncounterWithoutBattle(scene);
|
leaveEncounterWithoutBattle(scene);
|
||||||
} else {
|
} else {
|
||||||
// Your highest level unfainted Pokemon gets OHKO. Progress with no rewards (35%)
|
// Your highest level unfainted Pokemon gets OHKO. Start battle against a Gimmighoul (35%)
|
||||||
const highestLevelPokemon = getHighestLevelPlayerPokemon(
|
const highestLevelPokemon = getHighestLevelPlayerPokemon(
|
||||||
scene,
|
scene,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
koPlayerPokemon(scene, highestLevelPokemon);
|
koPlayerPokemon(scene, highestLevelPokemon);
|
||||||
|
// Handle game over edge case
|
||||||
encounter.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender());
|
const allowedPokemon = scene.getParty().filter(p => p.isAllowedInBattle());
|
||||||
// Show which Pokemon was KOed, then start battle against Gimmighoul
|
if (allowedPokemon.length === 0) {
|
||||||
await showEncounterText(scene, `${namespace}.option.1.bad`);
|
// If there are no longer any legal pokemon in the party, game over.
|
||||||
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
|
scene.clearPhaseQueue();
|
||||||
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
|
scene.unshiftPhase(new GameOverPhase(scene));
|
||||||
|
} else {
|
||||||
|
// Show which Pokemon was KOed, then start battle against Gimmighoul
|
||||||
|
encounter.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender());
|
||||||
|
await showEncounterText(scene, `${namespace}.option.1.bad`);
|
||||||
|
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
|
||||||
|
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.build()
|
.build()
|
||||||
|
@ -2,21 +2,20 @@ import { OptionTextDisplay } from "#app/data/mystery-encounters/mystery-encounte
|
|||||||
import { Moves } from "#app/enums/moves";
|
import { Moves } from "#app/enums/moves";
|
||||||
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
|
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
|
||||||
import BattleScene from "#app/battle-scene";
|
import BattleScene from "#app/battle-scene";
|
||||||
import * as Utils from "#app/utils";
|
|
||||||
import { Type } from "../type";
|
import { Type } from "../type";
|
||||||
import { EncounterPokemonRequirement, EncounterSceneRequirement, MoneyRequirement, TypeRequirement } from "./mystery-encounter-requirements";
|
import { EncounterPokemonRequirement, EncounterSceneRequirement, MoneyRequirement, TypeRequirement } from "./mystery-encounter-requirements";
|
||||||
import { CanLearnMoveRequirement, CanLearnMoveRequirementOptions } from "./requirements/can-learn-move-requirement";
|
import { CanLearnMoveRequirement, CanLearnMoveRequirementOptions } from "./requirements/can-learn-move-requirement";
|
||||||
import { isNullOrUndefined } from "#app/utils";
|
import { isNullOrUndefined, randSeedInt } from "#app/utils";
|
||||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
|
|
||||||
|
|
||||||
export type OptionPhaseCallback = (scene: BattleScene) => Promise<void | boolean>;
|
export type OptionPhaseCallback = (scene: BattleScene) => Promise<void | boolean>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used by {@link MysteryEncounterOptionBuilder} class to define required/optional properties on the {@link MysteryEncounterOption} class when building.
|
* Used by {@linkcode MysteryEncounterOptionBuilder} class to define required/optional properties on the {@linkcode MysteryEncounterOption} class when building.
|
||||||
*
|
*
|
||||||
* Should ONLY contain properties that are necessary for {@link MysteryEncounterOption} construction.
|
* Should ONLY contain properties that are necessary for {@linkcode MysteryEncounterOption} construction.
|
||||||
* Post-construct and flag data properties are defined in the {@link MysteryEncounterOption} class itself.
|
* Post-construct and flag data properties are defined in the {@linkcode MysteryEncounterOption} class itself.
|
||||||
*/
|
*/
|
||||||
export interface IMysteryEncounterOption {
|
export interface IMysteryEncounterOption {
|
||||||
optionMode: MysteryEncounterOptionMode;
|
optionMode: MysteryEncounterOptionMode;
|
||||||
@ -66,31 +65,48 @@ export default class MysteryEncounterOption implements IMysteryEncounterOption {
|
|||||||
this.secondaryPokemonRequirements = this.secondaryPokemonRequirements ?? [];
|
this.secondaryPokemonRequirements = this.secondaryPokemonRequirements ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
hasRequirements() {
|
/**
|
||||||
|
* Returns true if option contains any {@linkcode EncounterRequirement}s, false otherwise.
|
||||||
|
*/
|
||||||
|
hasRequirements(): boolean {
|
||||||
return this.requirements.length > 0 || this.primaryPokemonRequirements.length > 0 || this.secondaryPokemonRequirements.length > 0;
|
return this.requirements.length > 0 || this.primaryPokemonRequirements.length > 0 || this.secondaryPokemonRequirements.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
meetsRequirements(scene: BattleScene) {
|
/**
|
||||||
return !this.requirements.some(requirement => !requirement.meetsRequirement(scene)) &&
|
* Returns true if all {@linkcode EncounterRequirement}s for the option are met
|
||||||
this.meetsSupportingRequirementAndSupportingPokemonSelected(scene) &&
|
* @param scene
|
||||||
this.meetsPrimaryRequirementAndPrimaryPokemonSelected(scene);
|
*/
|
||||||
|
meetsRequirements(scene: BattleScene): boolean {
|
||||||
|
return !this.requirements.some(requirement => !requirement.meetsRequirement(scene))
|
||||||
|
&& this.meetsSupportingRequirementAndSupportingPokemonSelected(scene)
|
||||||
|
&& this.meetsPrimaryRequirementAndPrimaryPokemonSelected(scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
pokemonMeetsPrimaryRequirements(scene: BattleScene, pokemon: Pokemon) {
|
/**
|
||||||
|
* Returns true if all PRIMARY {@linkcode EncounterRequirement}s for the option are met
|
||||||
|
* @param scene
|
||||||
|
* @param pokemon
|
||||||
|
*/
|
||||||
|
pokemonMeetsPrimaryRequirements(scene: BattleScene, pokemon: Pokemon): boolean {
|
||||||
return !this.primaryPokemonRequirements.some(req => !req.queryParty(scene.getParty()).map(p => p.id).includes(pokemon.id));
|
return !this.primaryPokemonRequirements.some(req => !req.queryParty(scene.getParty()).map(p => p.id).includes(pokemon.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
meetsPrimaryRequirementAndPrimaryPokemonSelected(scene: BattleScene) {
|
/**
|
||||||
|
* Returns true if all PRIMARY {@linkcode EncounterRequirement}s for the option are met,
|
||||||
|
* AND there is a valid Pokemon assigned to {@linkcode primaryPokemon}.
|
||||||
|
* If both {@linkcode primaryPokemonRequirements} and {@linkcode secondaryPokemonRequirements} are defined,
|
||||||
|
* can cause scenarios where there are not enough Pokemon that are sufficient for all requirements.
|
||||||
|
* @param scene
|
||||||
|
*/
|
||||||
|
meetsPrimaryRequirementAndPrimaryPokemonSelected(scene: BattleScene): boolean {
|
||||||
if (!this.primaryPokemonRequirements || this.primaryPokemonRequirements.length === 0) {
|
if (!this.primaryPokemonRequirements || this.primaryPokemonRequirements.length === 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
let qualified: PlayerPokemon[] = scene.getParty();
|
let qualified: PlayerPokemon[] = scene.getParty();
|
||||||
for (const req of this.primaryPokemonRequirements) {
|
for (const req of this.primaryPokemonRequirements) {
|
||||||
if (req.meetsRequirement(scene)) {
|
if (req.meetsRequirement(scene)) {
|
||||||
if (req instanceof EncounterPokemonRequirement) {
|
const queryParty = req.queryParty(scene.getParty());
|
||||||
const queryParty = req.queryParty(scene.getParty());
|
qualified = qualified.filter(pkmn => queryParty.includes(pkmn));
|
||||||
qualified = qualified.filter(pkmn => queryParty.includes(pkmn));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.primaryPokemon = undefined;
|
this.primaryPokemon = undefined;
|
||||||
return false;
|
return false;
|
||||||
@ -114,13 +130,12 @@ export default class MysteryEncounterOption implements IMysteryEncounterOption {
|
|||||||
}
|
}
|
||||||
if (truePrimaryPool.length > 0) {
|
if (truePrimaryPool.length > 0) {
|
||||||
// always choose from the non-overlapping pokemon first
|
// always choose from the non-overlapping pokemon first
|
||||||
this.primaryPokemon = truePrimaryPool[Utils.randSeedInt(truePrimaryPool.length, 0)];
|
this.primaryPokemon = truePrimaryPool[randSeedInt(truePrimaryPool.length)];
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
// if there are multiple overlapping pokemon, we're okay - just choose one and take it out of the supporting pokemon pool
|
// if there are multiple overlapping pokemon, we're okay - just choose one and take it out of the supporting pokemon pool
|
||||||
if (overlap.length > 1 || (this.secondaryPokemon.length - overlap.length >= 1)) {
|
if (overlap.length > 1 || (this.secondaryPokemon.length - overlap.length >= 1)) {
|
||||||
// is this working?
|
this.primaryPokemon = overlap[randSeedInt(overlap.length)];
|
||||||
this.primaryPokemon = overlap[Utils.randSeedInt(overlap.length, 0)];
|
|
||||||
this.secondaryPokemon = this.secondaryPokemon.filter((supp) => supp !== this.primaryPokemon);
|
this.secondaryPokemon = this.secondaryPokemon.filter((supp) => supp !== this.primaryPokemon);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -134,7 +149,14 @@ export default class MysteryEncounterOption implements IMysteryEncounterOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
meetsSupportingRequirementAndSupportingPokemonSelected(scene: BattleScene) {
|
/**
|
||||||
|
* Returns true if all SECONDARY {@linkcode EncounterRequirement}s for the option are met,
|
||||||
|
* AND there is a valid Pokemon assigned to {@linkcode secondaryPokemon} (if applicable).
|
||||||
|
* If both {@linkcode primaryPokemonRequirements} and {@linkcode secondaryPokemonRequirements} are defined,
|
||||||
|
* can cause scenarios where there are not enough Pokemon that are sufficient for all requirements.
|
||||||
|
* @param scene
|
||||||
|
*/
|
||||||
|
meetsSupportingRequirementAndSupportingPokemonSelected(scene: BattleScene): boolean {
|
||||||
if (!this.secondaryPokemonRequirements || this.secondaryPokemonRequirements.length === 0) {
|
if (!this.secondaryPokemonRequirements || this.secondaryPokemonRequirements.length === 0) {
|
||||||
this.secondaryPokemon = [];
|
this.secondaryPokemon = [];
|
||||||
return true;
|
return true;
|
||||||
@ -143,10 +165,8 @@ export default class MysteryEncounterOption implements IMysteryEncounterOption {
|
|||||||
let qualified: PlayerPokemon[] = scene.getParty();
|
let qualified: PlayerPokemon[] = scene.getParty();
|
||||||
for (const req of this.secondaryPokemonRequirements) {
|
for (const req of this.secondaryPokemonRequirements) {
|
||||||
if (req.meetsRequirement(scene)) {
|
if (req.meetsRequirement(scene)) {
|
||||||
if (req instanceof EncounterPokemonRequirement) {
|
const queryParty = req.queryParty(scene.getParty());
|
||||||
const queryParty = req.queryParty(scene.getParty());
|
qualified = qualified.filter(pkmn => queryParty.includes(pkmn));
|
||||||
qualified = qualified.filter(pkmn => queryParty.includes(pkmn));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.secondaryPokemon = [];
|
this.secondaryPokemon = [];
|
||||||
return false;
|
return false;
|
||||||
@ -175,6 +195,10 @@ export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterO
|
|||||||
return Object.assign(this, { hasDexProgress: hasDexProgress });
|
return Object.assign(this, { hasDexProgress: hasDexProgress });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a {@linkcode EncounterSceneRequirement} to {@linkcode requirements}
|
||||||
|
* @param requirement
|
||||||
|
*/
|
||||||
withSceneRequirement(requirement: EncounterSceneRequirement): this & Required<Pick<IMysteryEncounterOption, "requirements">> {
|
withSceneRequirement(requirement: EncounterSceneRequirement): this & Required<Pick<IMysteryEncounterOption, "requirements">> {
|
||||||
if (requirement instanceof EncounterPokemonRequirement) {
|
if (requirement instanceof EncounterPokemonRequirement) {
|
||||||
Error("Incorrectly added pokemon requirement as scene requirement.");
|
Error("Incorrectly added pokemon requirement as scene requirement.");
|
||||||
@ -188,10 +212,20 @@ export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterO
|
|||||||
return this.withSceneRequirement(new MoneyRequirement(requiredMoney, scalingMultiplier));
|
return this.withSceneRequirement(new MoneyRequirement(requiredMoney, scalingMultiplier));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines logic that runs immediately when an option is selected, but before the Encounter continues.
|
||||||
|
* Can determine whether or not the Encounter *should* continue.
|
||||||
|
* If there are scenarios where the Encounter should NOT continue, should return boolean instead of void.
|
||||||
|
* @param onPreOptionPhase
|
||||||
|
*/
|
||||||
withPreOptionPhase(onPreOptionPhase: OptionPhaseCallback): this & Required<Pick<IMysteryEncounterOption, "onPreOptionPhase">> {
|
withPreOptionPhase(onPreOptionPhase: OptionPhaseCallback): this & Required<Pick<IMysteryEncounterOption, "onPreOptionPhase">> {
|
||||||
return Object.assign(this, { onPreOptionPhase: onPreOptionPhase });
|
return Object.assign(this, { onPreOptionPhase: onPreOptionPhase });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MUST be defined by every {@linkcode MysteryEncounterOption}
|
||||||
|
* @param onOptionPhase
|
||||||
|
*/
|
||||||
withOptionPhase(onOptionPhase: OptionPhaseCallback): this & Required<Pick<IMysteryEncounterOption, "onOptionPhase">> {
|
withOptionPhase(onOptionPhase: OptionPhaseCallback): this & Required<Pick<IMysteryEncounterOption, "onOptionPhase">> {
|
||||||
return Object.assign(this, { onOptionPhase: onOptionPhase });
|
return Object.assign(this, { onOptionPhase: onOptionPhase });
|
||||||
}
|
}
|
||||||
@ -200,6 +234,10 @@ export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterO
|
|||||||
return Object.assign(this, { onPostOptionPhase: onPostOptionPhase });
|
return Object.assign(this, { onPostOptionPhase: onPostOptionPhase });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a {@linkcode EncounterPokemonRequirement} to {@linkcode primaryPokemonRequirements}
|
||||||
|
* @param requirement
|
||||||
|
*/
|
||||||
withPrimaryPokemonRequirement(requirement: EncounterPokemonRequirement): this & Required<Pick<IMysteryEncounterOption, "primaryPokemonRequirements">> {
|
withPrimaryPokemonRequirement(requirement: EncounterPokemonRequirement): this & Required<Pick<IMysteryEncounterOption, "primaryPokemonRequirements">> {
|
||||||
if (requirement instanceof EncounterSceneRequirement) {
|
if (requirement instanceof EncounterSceneRequirement) {
|
||||||
Error("Incorrectly added scene requirement as pokemon requirement.");
|
Error("Incorrectly added scene requirement as pokemon requirement.");
|
||||||
@ -233,6 +271,11 @@ export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterO
|
|||||||
return this.withPrimaryPokemonRequirement(new CanLearnMoveRequirement(move, options));
|
return this.withPrimaryPokemonRequirement(new CanLearnMoveRequirement(move, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a {@linkcode EncounterPokemonRequirement} to {@linkcode secondaryPokemonRequirements}
|
||||||
|
* @param requirement
|
||||||
|
* @param excludePrimaryFromSecondaryRequirements
|
||||||
|
*/
|
||||||
withSecondaryPokemonRequirement(requirement: EncounterPokemonRequirement, excludePrimaryFromSecondaryRequirements: boolean = true): this & Required<Pick<IMysteryEncounterOption, "secondaryPokemonRequirements">> {
|
withSecondaryPokemonRequirement(requirement: EncounterPokemonRequirement, excludePrimaryFromSecondaryRequirements: boolean = true): this & Required<Pick<IMysteryEncounterOption, "secondaryPokemonRequirements">> {
|
||||||
if (requirement instanceof EncounterSceneRequirement) {
|
if (requirement instanceof EncounterSceneRequirement) {
|
||||||
Error("Incorrectly added scene requirement as pokemon requirement.");
|
Error("Incorrectly added scene requirement as pokemon requirement.");
|
||||||
@ -244,7 +287,7 @@ export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterO
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Se the full dialogue object to the option. Will override anything already set
|
* Set the full dialogue object to the option. Will override anything already set
|
||||||
*
|
*
|
||||||
* @param dialogue see {@linkcode OptionTextDisplay}
|
* @param dialogue see {@linkcode OptionTextDisplay}
|
||||||
* @returns
|
* @returns
|
||||||
|
@ -157,7 +157,7 @@ export class WaveRangeRequirement extends EncounterSceneRequirement {
|
|||||||
/**
|
/**
|
||||||
* Used for specifying a unique wave or wave range requirement
|
* Used for specifying a unique wave or wave range requirement
|
||||||
* If minWaveIndex and maxWaveIndex are equivalent, will check for exact wave number
|
* If minWaveIndex and maxWaveIndex are equivalent, will check for exact wave number
|
||||||
* @param waveRange - [min, max]
|
* @param waveRange [min, max]
|
||||||
*/
|
*/
|
||||||
constructor(waveRange: [number, number]) {
|
constructor(waveRange: [number, number]) {
|
||||||
super();
|
super();
|
||||||
@ -165,9 +165,9 @@ export class WaveRangeRequirement extends EncounterSceneRequirement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
meetsRequirement(scene: BattleScene): boolean {
|
meetsRequirement(scene: BattleScene): boolean {
|
||||||
if (!isNullOrUndefined(this?.waveRange) && this.waveRange?.[0] <= this.waveRange?.[1]) {
|
if (!isNullOrUndefined(this.waveRange) && this.waveRange?.[0] <= this.waveRange?.[1]) {
|
||||||
const waveIndex = scene.currentBattle.waveIndex;
|
const waveIndex = scene.currentBattle.waveIndex;
|
||||||
if (waveIndex >= 0 && (this?.waveRange?.[0] >= 0 && this.waveRange?.[0] > waveIndex) || (this?.waveRange?.[1] >= 0 && this.waveRange?.[1] < waveIndex)) {
|
if (waveIndex >= 0 && (this.waveRange?.[0] >= 0 && this.waveRange?.[0] > waveIndex) || (this.waveRange?.[1] >= 0 && this.waveRange?.[1] < waveIndex)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -186,8 +186,8 @@ export class WaveModulusRequirement extends EncounterSceneRequirement {
|
|||||||
/**
|
/**
|
||||||
* Used for specifying a modulus requirement on the wave index
|
* Used for specifying a modulus requirement on the wave index
|
||||||
* For example, can be used to require the wave index to end with 1, 2, or 3
|
* For example, can be used to require the wave index to end with 1, 2, or 3
|
||||||
* @param waveModuli - number[], the allowed modulus results
|
* @param waveModuli The allowed modulus results
|
||||||
* @param modulusValue - number, the modulus calculation value
|
* @param modulusValue The modulus calculation value
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
* new WaveModulusRequirement([1, 2, 3], 10) will check for 1st/2nd/3rd waves that are immediately after a multiple of 10 wave
|
* new WaveModulusRequirement([1, 2, 3], 10) will check for 1st/2nd/3rd waves that are immediately after a multiple of 10 wave
|
||||||
@ -218,7 +218,7 @@ export class TimeOfDayRequirement extends EncounterSceneRequirement {
|
|||||||
|
|
||||||
meetsRequirement(scene: BattleScene): boolean {
|
meetsRequirement(scene: BattleScene): boolean {
|
||||||
const timeOfDay = scene.arena?.getTimeOfDay();
|
const timeOfDay = scene.arena?.getTimeOfDay();
|
||||||
if (!isNullOrUndefined(timeOfDay) && this?.requiredTimeOfDay?.length > 0 && !this.requiredTimeOfDay.includes(timeOfDay)) {
|
if (!isNullOrUndefined(timeOfDay) && this.requiredTimeOfDay?.length > 0 && !this.requiredTimeOfDay.includes(timeOfDay)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,7 +240,7 @@ export class WeatherRequirement extends EncounterSceneRequirement {
|
|||||||
|
|
||||||
meetsRequirement(scene: BattleScene): boolean {
|
meetsRequirement(scene: BattleScene): boolean {
|
||||||
const currentWeather = scene.arena.weather?.weatherType;
|
const currentWeather = scene.arena.weather?.weatherType;
|
||||||
if (!isNullOrUndefined(currentWeather) && this?.requiredWeather?.length > 0 && !this.requiredWeather.includes(currentWeather!)) {
|
if (!isNullOrUndefined(currentWeather) && this.requiredWeather?.length > 0 && !this.requiredWeather.includes(currentWeather!)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,7 +264,7 @@ export class PartySizeRequirement extends EncounterSceneRequirement {
|
|||||||
/**
|
/**
|
||||||
* Used for specifying a party size requirement
|
* Used for specifying a party size requirement
|
||||||
* If min and max are equivalent, will check for exact size
|
* If min and max are equivalent, will check for exact size
|
||||||
* @param partySizeRange - [min, max]
|
* @param partySizeRange
|
||||||
* @param excludeFainted
|
* @param excludeFainted
|
||||||
*/
|
*/
|
||||||
constructor(partySizeRange: [number, number], excludeFainted: boolean) {
|
constructor(partySizeRange: [number, number], excludeFainted: boolean) {
|
||||||
@ -274,9 +274,9 @@ export class PartySizeRequirement extends EncounterSceneRequirement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
meetsRequirement(scene: BattleScene): boolean {
|
meetsRequirement(scene: BattleScene): boolean {
|
||||||
if (!isNullOrUndefined(this?.partySizeRange) && this.partySizeRange?.[0] <= this.partySizeRange?.[1]) {
|
if (!isNullOrUndefined(this.partySizeRange) && this.partySizeRange?.[0] <= this.partySizeRange?.[1]) {
|
||||||
const partySize = this.excludeFainted ? scene.getParty().filter(p => p.isAllowedInBattle()).length : scene.getParty().length;
|
const partySize = this.excludeFainted ? scene.getParty().filter(p => p.isAllowedInBattle()).length : scene.getParty().length;
|
||||||
if (partySize >= 0 && (this?.partySizeRange?.[0] >= 0 && this.partySizeRange?.[0] > partySize) || (this?.partySizeRange?.[1] >= 0 && this.partySizeRange?.[1] < partySize)) {
|
if (partySize >= 0 && (this.partySizeRange?.[0] >= 0 && this.partySizeRange?.[0] > partySize) || (this.partySizeRange?.[1] >= 0 && this.partySizeRange?.[1] < partySize)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -301,7 +301,7 @@ export class PersistentModifierRequirement extends EncounterSceneRequirement {
|
|||||||
|
|
||||||
meetsRequirement(scene: BattleScene): boolean {
|
meetsRequirement(scene: BattleScene): boolean {
|
||||||
const partyPokemon = scene.getParty();
|
const partyPokemon = scene.getParty();
|
||||||
if (isNullOrUndefined(partyPokemon) || this?.requiredHeldItemModifiers?.length < 0) {
|
if (isNullOrUndefined(partyPokemon) || this.requiredHeldItemModifiers?.length < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let modifierCount = 0;
|
let modifierCount = 0;
|
||||||
@ -364,7 +364,7 @@ export class SpeciesRequirement extends EncounterPokemonRequirement {
|
|||||||
|
|
||||||
meetsRequirement(scene: BattleScene): boolean {
|
meetsRequirement(scene: BattleScene): boolean {
|
||||||
const partyPokemon = scene.getParty();
|
const partyPokemon = scene.getParty();
|
||||||
if (isNullOrUndefined(partyPokemon) || this?.requiredSpecies?.length < 0) {
|
if (isNullOrUndefined(partyPokemon) || this.requiredSpecies?.length < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
||||||
@ -402,7 +402,7 @@ export class NatureRequirement extends EncounterPokemonRequirement {
|
|||||||
|
|
||||||
meetsRequirement(scene: BattleScene): boolean {
|
meetsRequirement(scene: BattleScene): boolean {
|
||||||
const partyPokemon = scene.getParty();
|
const partyPokemon = scene.getParty();
|
||||||
if (isNullOrUndefined(partyPokemon) || this?.requiredNature?.length < 0) {
|
if (isNullOrUndefined(partyPokemon) || this.requiredNature?.length < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
||||||
@ -486,7 +486,7 @@ export class MoveRequirement extends EncounterPokemonRequirement {
|
|||||||
|
|
||||||
meetsRequirement(scene: BattleScene): boolean {
|
meetsRequirement(scene: BattleScene): boolean {
|
||||||
const partyPokemon = scene.getParty();
|
const partyPokemon = scene.getParty();
|
||||||
if (isNullOrUndefined(partyPokemon) || this?.requiredMoves?.length < 0) {
|
if (isNullOrUndefined(partyPokemon) || this.requiredMoves?.length < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
||||||
@ -530,7 +530,7 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement {
|
|||||||
|
|
||||||
meetsRequirement(scene: BattleScene): boolean {
|
meetsRequirement(scene: BattleScene): boolean {
|
||||||
const partyPokemon = scene.getParty();
|
const partyPokemon = scene.getParty();
|
||||||
if (isNullOrUndefined(partyPokemon) || this?.requiredMoves?.length < 0) {
|
if (isNullOrUndefined(partyPokemon) || this.requiredMoves?.length < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
||||||
@ -570,7 +570,7 @@ export class EvolutionTargetSpeciesRequirement extends EncounterPokemonRequireme
|
|||||||
|
|
||||||
meetsRequirement(scene: BattleScene): boolean {
|
meetsRequirement(scene: BattleScene): boolean {
|
||||||
const partyPokemon = scene.getParty();
|
const partyPokemon = scene.getParty();
|
||||||
if (isNullOrUndefined(partyPokemon) || this?.requiredEvolutionTargetSpecies?.length < 0) {
|
if (isNullOrUndefined(partyPokemon) || this.requiredEvolutionTargetSpecies?.length < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
||||||
@ -609,7 +609,7 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
|
|||||||
|
|
||||||
meetsRequirement(scene: BattleScene): boolean {
|
meetsRequirement(scene: BattleScene): boolean {
|
||||||
const partyPokemon = scene.getParty();
|
const partyPokemon = scene.getParty();
|
||||||
if (isNullOrUndefined(partyPokemon) || this?.requiredAbilities?.length < 0) {
|
if (isNullOrUndefined(partyPokemon) || this.requiredAbilities?.length < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
||||||
@ -646,7 +646,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
|
|||||||
|
|
||||||
meetsRequirement(scene: BattleScene): boolean {
|
meetsRequirement(scene: BattleScene): boolean {
|
||||||
const partyPokemon = scene.getParty();
|
const partyPokemon = scene.getParty();
|
||||||
if (isNullOrUndefined(partyPokemon) || this?.requiredStatusEffect?.length < 0) {
|
if (isNullOrUndefined(partyPokemon) || this.requiredStatusEffect?.length < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const x = this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
const x = this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
||||||
@ -716,7 +716,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
|
|||||||
|
|
||||||
meetsRequirement(scene: BattleScene): boolean {
|
meetsRequirement(scene: BattleScene): boolean {
|
||||||
const partyPokemon = scene.getParty();
|
const partyPokemon = scene.getParty();
|
||||||
if (isNullOrUndefined(partyPokemon) || this?.requiredFormChangeItem?.length < 0) {
|
if (isNullOrUndefined(partyPokemon) || this.requiredFormChangeItem?.length < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
||||||
@ -768,7 +768,7 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
|
|||||||
|
|
||||||
meetsRequirement(scene: BattleScene): boolean {
|
meetsRequirement(scene: BattleScene): boolean {
|
||||||
const partyPokemon = scene.getParty();
|
const partyPokemon = scene.getParty();
|
||||||
if (isNullOrUndefined(partyPokemon) || this?.requiredEvolutionItem?.length < 0) {
|
if (isNullOrUndefined(partyPokemon) || this.requiredEvolutionItem?.length < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
||||||
|
@ -26,10 +26,10 @@ export interface EncounterStartOfBattleEffect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used by {@link MysteryEncounterBuilder} class to define required/optional properties on the {@link MysteryEncounter} class when building.
|
* Used by {@linkcode MysteryEncounterBuilder} class to define required/optional properties on the {@linkcode MysteryEncounter} class when building.
|
||||||
*
|
*
|
||||||
* Should ONLY contain properties that are necessary for {@link MysteryEncounter} construction.
|
* Should ONLY contain properties that are necessary for {@linkcode MysteryEncounter} construction.
|
||||||
* Post-construct and flag data properties are defined in the {@link MysteryEncounter} class itself.
|
* Post-construct and flag data properties are defined in the {@linkcode MysteryEncounter} class itself.
|
||||||
*/
|
*/
|
||||||
export interface IMysteryEncounter {
|
export interface IMysteryEncounter {
|
||||||
encounterType: MysteryEncounterType;
|
encounterType: MysteryEncounterType;
|
||||||
@ -124,12 +124,12 @@ export default class MysteryEncounter implements IMysteryEncounter {
|
|||||||
maxAllowedEncounters: number;
|
maxAllowedEncounters: number;
|
||||||
/**
|
/**
|
||||||
* If true, encounter will not animate the target Pokemon as part of battle animations
|
* If true, encounter will not animate the target Pokemon as part of battle animations
|
||||||
* Used for encounters where it is not a "real" battle, but still uses battle animations and commands (see {@link FunAndGamesEncounter} for an example)
|
* Used for encounters where it is not a "real" battle, but still uses battle animations and commands (see {@linkcode FunAndGamesEncounter} for an example)
|
||||||
*/
|
*/
|
||||||
hasBattleAnimationsWithoutTargets: boolean;
|
hasBattleAnimationsWithoutTargets: boolean;
|
||||||
/**
|
/**
|
||||||
* If true, will skip enemy pokemon turns during battle for the encounter
|
* If true, will skip enemy pokemon turns during battle for the encounter
|
||||||
* Used for encounters where it is not a "real" battle, but still uses battle animations and commands (see {@link FunAndGamesEncounter} for an example)
|
* Used for encounters where it is not a "real" battle, but still uses battle animations and commands (see {@linkcode FunAndGamesEncounter} for an example)
|
||||||
*/
|
*/
|
||||||
skipEnemyBattleTurns: boolean;
|
skipEnemyBattleTurns: boolean;
|
||||||
/**
|
/**
|
||||||
@ -144,9 +144,9 @@ export default class MysteryEncounter implements IMysteryEncounter {
|
|||||||
onInit?: (scene: BattleScene) => boolean;
|
onInit?: (scene: BattleScene) => boolean;
|
||||||
/** Event when battlefield visuals have finished sliding in and the encounter dialogue begins */
|
/** Event when battlefield visuals have finished sliding in and the encounter dialogue begins */
|
||||||
onVisualsStart?: (scene: BattleScene) => boolean;
|
onVisualsStart?: (scene: BattleScene) => boolean;
|
||||||
/** Event triggered prior to {@link CommandPhase}, during {@link TurnInitPhase} */
|
/** Event triggered prior to {@linkcode CommandPhase}, during {@linkcode TurnInitPhase} */
|
||||||
onTurnStart?: (scene: BattleScene) => boolean;
|
onTurnStart?: (scene: BattleScene) => boolean;
|
||||||
/** Event prior to any rewards logic in {@link MysteryEncounterRewardsPhase} */
|
/** Event prior to any rewards logic in {@linkcode MysteryEncounterRewardsPhase} */
|
||||||
onRewards?: (scene: BattleScene) => Promise<void>;
|
onRewards?: (scene: BattleScene) => Promise<void>;
|
||||||
/** Will provide the player party EXP before rewards are displayed for that wave */
|
/** Will provide the player party EXP before rewards are displayed for that wave */
|
||||||
doEncounterExp?: (scene: BattleScene) => boolean;
|
doEncounterExp?: (scene: BattleScene) => boolean;
|
||||||
@ -279,7 +279,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
|
|||||||
* @param scene
|
* @param scene
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
meetsRequirements(scene: BattleScene) {
|
meetsRequirements(scene: BattleScene): boolean {
|
||||||
const sceneReq = !this.requirements.some(requirement => !requirement.meetsRequirement(scene));
|
const sceneReq = !this.requirements.some(requirement => !requirement.meetsRequirement(scene));
|
||||||
const secReqs = this.meetsSecondaryRequirementAndSecondaryPokemonSelected(scene); // secondary is checked first to handle cases of primary overlapping with secondary
|
const secReqs = this.meetsSecondaryRequirementAndSecondaryPokemonSelected(scene); // secondary is checked first to handle cases of primary overlapping with secondary
|
||||||
const priReqs = this.meetsPrimaryRequirementAndPrimaryPokemonSelected(scene);
|
const priReqs = this.meetsPrimaryRequirementAndPrimaryPokemonSelected(scene);
|
||||||
@ -293,10 +293,17 @@ export default class MysteryEncounter implements IMysteryEncounter {
|
|||||||
* @param scene
|
* @param scene
|
||||||
* @param pokemon
|
* @param pokemon
|
||||||
*/
|
*/
|
||||||
pokemonMeetsPrimaryRequirements(scene: BattleScene, pokemon: Pokemon) {
|
pokemonMeetsPrimaryRequirements(scene: BattleScene, pokemon: Pokemon): boolean {
|
||||||
return !this.primaryPokemonRequirements.some(req => !req.queryParty(scene.getParty()).map(p => p.id).includes(pokemon.id));
|
return !this.primaryPokemonRequirements.some(req => !req.queryParty(scene.getParty()).map(p => p.id).includes(pokemon.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if all PRIMARY {@linkcode EncounterRequirement}s for the option are met,
|
||||||
|
* AND there is a valid Pokemon assigned to {@linkcode primaryPokemon}.
|
||||||
|
* If both {@linkcode primaryPokemonRequirements} and {@linkcode secondaryPokemonRequirements} are defined,
|
||||||
|
* can cause scenarios where there are not enough Pokemon that are sufficient for all requirements.
|
||||||
|
* @param scene
|
||||||
|
*/
|
||||||
private meetsPrimaryRequirementAndPrimaryPokemonSelected(scene: BattleScene): boolean {
|
private meetsPrimaryRequirementAndPrimaryPokemonSelected(scene: BattleScene): boolean {
|
||||||
if (!this.primaryPokemonRequirements || this.primaryPokemonRequirements.length === 0) {
|
if (!this.primaryPokemonRequirements || this.primaryPokemonRequirements.length === 0) {
|
||||||
const activeMon = scene.getParty().filter(p => p.isActive(true));
|
const activeMon = scene.getParty().filter(p => p.isActive(true));
|
||||||
@ -354,6 +361,13 @@ export default class MysteryEncounter implements IMysteryEncounter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if all SECONDARY {@linkcode EncounterRequirement}s for the option are met,
|
||||||
|
* AND there is a valid Pokemon assigned to {@linkcode secondaryPokemon} (if applicable).
|
||||||
|
* If both {@linkcode primaryPokemonRequirements} and {@linkcode secondaryPokemonRequirements} are defined,
|
||||||
|
* can cause scenarios where there are not enough Pokemon that are sufficient for all requirements.
|
||||||
|
* @param scene
|
||||||
|
*/
|
||||||
private meetsSecondaryRequirementAndSecondaryPokemonSelected(scene: BattleScene): boolean {
|
private meetsSecondaryRequirementAndSecondaryPokemonSelected(scene: BattleScene): boolean {
|
||||||
if (!this.secondaryPokemonRequirements || this.secondaryPokemonRequirements.length === 0) {
|
if (!this.secondaryPokemonRequirements || this.secondaryPokemonRequirements.length === 0) {
|
||||||
this.secondaryPokemon = [];
|
this.secondaryPokemon = [];
|
||||||
@ -377,7 +391,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
|
|||||||
* Initializes encounter intro sprites based on the sprite configs defined in spriteConfigs
|
* Initializes encounter intro sprites based on the sprite configs defined in spriteConfigs
|
||||||
* @param scene
|
* @param scene
|
||||||
*/
|
*/
|
||||||
initIntroVisuals(scene: BattleScene) {
|
initIntroVisuals(scene: BattleScene): void {
|
||||||
this.introVisuals = new MysteryEncounterIntroVisuals(scene, this);
|
this.introVisuals = new MysteryEncounterIntroVisuals(scene, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -386,7 +400,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
|
|||||||
* Will use the first support pokemon in list
|
* Will use the first support pokemon in list
|
||||||
* For multiple support pokemon in the dialogue token, it will have to be overridden.
|
* For multiple support pokemon in the dialogue token, it will have to be overridden.
|
||||||
*/
|
*/
|
||||||
populateDialogueTokensFromRequirements(scene: BattleScene) {
|
populateDialogueTokensFromRequirements(scene: BattleScene): void {
|
||||||
this.meetsRequirements(scene);
|
this.meetsRequirements(scene);
|
||||||
if (this.requirements?.length > 0) {
|
if (this.requirements?.length > 0) {
|
||||||
for (const req of this.requirements) {
|
for (const req of this.requirements) {
|
||||||
@ -461,7 +475,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
|
|||||||
/**
|
/**
|
||||||
* Used to cache a dialogue token for the encounter.
|
* Used to cache a dialogue token for the encounter.
|
||||||
* Tokens will be auto-injected via the `{{key}}` pattern with `value`,
|
* Tokens will be auto-injected via the `{{key}}` pattern with `value`,
|
||||||
* when using the {@link showEncounterText} and {@link showEncounterDialogue} helper functions.
|
* when using the {@linkcode showEncounterText} and {@linkcode showEncounterDialogue} helper functions.
|
||||||
*
|
*
|
||||||
* @param key
|
* @param key
|
||||||
* @param value
|
* @param value
|
||||||
@ -471,10 +485,10 @@ export default class MysteryEncounter implements IMysteryEncounter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If an encounter uses {@link MysteryEncounterMode.continuousEncounter},
|
* If an encounter uses {@linkcode MysteryEncounterMode.continuousEncounter},
|
||||||
* should rely on this value for seed offset instead of wave index.
|
* should rely on this value for seed offset instead of wave index.
|
||||||
*
|
*
|
||||||
* This offset is incremented for each new {@link MysteryEncounterPhase} that occurs,
|
* This offset is incremented for each new {@linkcode MysteryEncounterPhase} that occurs,
|
||||||
* so multi-encounter RNG will be consistent on resets and not be affected by number of turns, move RNG, etc.
|
* so multi-encounter RNG will be consistent on resets and not be affected by number of turns, move RNG, etc.
|
||||||
*/
|
*/
|
||||||
getSeedOffset() {
|
getSeedOffset() {
|
||||||
@ -539,7 +553,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||||||
* Use for complex options.
|
* Use for complex options.
|
||||||
* There should be at least 2 options defined and no more than 4.
|
* There should be at least 2 options defined and no more than 4.
|
||||||
*
|
*
|
||||||
* @param option - MysteryEncounterOption to add, can use MysteryEncounterOptionBuilder to create instance
|
* @param option MysteryEncounterOption to add, can use MysteryEncounterOptionBuilder to create instance
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
withOption(option: MysteryEncounterOption): this & Pick<IMysteryEncounter, "options"> {
|
withOption(option: MysteryEncounterOption): this & Pick<IMysteryEncounter, "options"> {
|
||||||
@ -558,9 +572,8 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||||||
* There should be at least 2 options defined and no more than 4.
|
* There should be at least 2 options defined and no more than 4.
|
||||||
* If complex use {@linkcode MysteryEncounterBuilder.withOption}
|
* If complex use {@linkcode MysteryEncounterBuilder.withOption}
|
||||||
*
|
*
|
||||||
* @param hasDexProgress -
|
* @param dialogue {@linkcode OptionTextDisplay}
|
||||||
* @param dialogue - {@linkcode OptionTextDisplay}
|
* @param callback {@linkcode OptionPhaseCallback}
|
||||||
* @param callback - {@linkcode OptionPhaseCallback}
|
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
withSimpleOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<IMysteryEncounter, "options"> {
|
withSimpleOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<IMysteryEncounter, "options"> {
|
||||||
@ -658,7 +671,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* If true, encounter will not animate the target Pokemon as part of battle animations
|
* If true, encounter will not animate the target Pokemon as part of battle animations
|
||||||
* Used for encounters where it is not a "real" battle, but still uses battle animations and commands (see {@link FunAndGamesEncounter} for an example)
|
* Used for encounters where it is not a "real" battle, but still uses battle animations and commands (see {@linkcode FunAndGamesEncounter} for an example)
|
||||||
* Default false
|
* Default false
|
||||||
* @param hasBattleAnimationsWithoutTargets
|
* @param hasBattleAnimationsWithoutTargets
|
||||||
*/
|
*/
|
||||||
@ -668,7 +681,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* If true, encounter will not animate the target Pokemon as part of battle animations
|
* If true, encounter will not animate the target Pokemon as part of battle animations
|
||||||
* Used for encounters where it is not a "real" battle, but still uses battle animations and commands (see {@link FunAndGamesEncounter} for an example)
|
* Used for encounters where it is not a "real" battle, but still uses battle animations and commands (see {@linkcode FunAndGamesEncounter} for an example)
|
||||||
* Default false
|
* Default false
|
||||||
* @param skipEnemyBattleTurns
|
* @param skipEnemyBattleTurns
|
||||||
*/
|
*/
|
||||||
|
@ -30,20 +30,18 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement {
|
|||||||
super();
|
super();
|
||||||
this.requiredMoves = Array.isArray(requiredMoves) ? requiredMoves : [requiredMoves];
|
this.requiredMoves = Array.isArray(requiredMoves) ? requiredMoves : [requiredMoves];
|
||||||
|
|
||||||
const { excludeLevelMoves, excludeTmMoves, excludeEggMoves, includeFainted, minNumberOfPokemon, invertQuery } = options;
|
this.excludeLevelMoves = options.excludeLevelMoves ?? false;
|
||||||
|
this.excludeTmMoves = options.excludeTmMoves ?? false;
|
||||||
this.excludeLevelMoves = excludeLevelMoves ?? false;
|
this.excludeEggMoves = options.excludeEggMoves ?? false;
|
||||||
this.excludeTmMoves = excludeTmMoves ?? false;
|
this.includeFainted = options.includeFainted ?? false;
|
||||||
this.excludeEggMoves = excludeEggMoves ?? false;
|
this.minNumberOfPokemon = options.minNumberOfPokemon ?? 1;
|
||||||
this.includeFainted = includeFainted ?? false;
|
this.invertQuery = options.invertQuery ?? false;
|
||||||
this.minNumberOfPokemon = minNumberOfPokemon ?? 1;
|
|
||||||
this.invertQuery = invertQuery ?? false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override meetsRequirement(scene: BattleScene): boolean {
|
override meetsRequirement(scene: BattleScene): boolean {
|
||||||
const partyPokemon = scene.getParty().filter((pkm) => (this.includeFainted ? pkm.isAllowed() : pkm.isAllowedInBattle()));
|
const partyPokemon = scene.getParty().filter((pkm) => (this.includeFainted ? pkm.isAllowed() : pkm.isAllowedInBattle()));
|
||||||
|
|
||||||
if (isNullOrUndefined(partyPokemon) || this?.requiredMoves?.length < 0) {
|
if (isNullOrUndefined(partyPokemon) || this.requiredMoves?.length < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,11 +5,11 @@ import { isNullOrUndefined } from "#app/utils";
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will inject all relevant dialogue tokens that exist in the {@link BattleScene.currentBattle.mysteryEncounter.dialogueTokens}, into i18n text.
|
* Will inject all relevant dialogue tokens that exist in the {@linkcode BattleScene.currentBattle.mysteryEncounter.dialogueTokens}, into i18n text.
|
||||||
* Also adds BBCodeText fragments for colored text, if applicable
|
* Also adds BBCodeText fragments for colored text, if applicable
|
||||||
* @param scene
|
* @param scene
|
||||||
* @param keyOrString
|
* @param keyOrString
|
||||||
* @param primaryStyle - can define a text style to be applied to the entire string. Must be defined for BBCodeText styles to be applied correctly
|
* @param primaryStyle Can define a text style to be applied to the entire string. Must be defined for BBCodeText styles to be applied correctly
|
||||||
* @param uiTheme
|
* @param uiTheme
|
||||||
*/
|
*/
|
||||||
export function getEncounterText(scene: BattleScene, keyOrString?: string, primaryStyle?: TextStyle, uiTheme: UiTheme = UiTheme.DEFAULT): string | null {
|
export function getEncounterText(scene: BattleScene, keyOrString?: string, primaryStyle?: TextStyle, uiTheme: UiTheme = UiTheme.DEFAULT): string | null {
|
||||||
@ -17,7 +17,7 @@ export function getEncounterText(scene: BattleScene, keyOrString?: string, prima
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let textString: string | null = getTextWithDialogueTokens(scene, keyOrString);
|
let textString: string | null = getTextWithDialogueTokens(scene, keyOrString!);
|
||||||
|
|
||||||
// Can only color the text if a Primary Style is defined
|
// 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
|
// primaryStyle is applied to all text that does not have its own specified style
|
||||||
@ -29,22 +29,15 @@ export function getEncounterText(scene: BattleScene, keyOrString?: string, prima
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to inject {@link BattleScene.currentBattle.mysteryEncounter.dialogueTokens} into a given content string
|
* Helper function to inject {@linkcode BattleScene.currentBattle.mysteryEncounter.dialogueTokens} into a given content string
|
||||||
* @param scene
|
* @param scene
|
||||||
* @param keyOrString
|
* @param keyOrString
|
||||||
*/
|
*/
|
||||||
function getTextWithDialogueTokens(scene: BattleScene, keyOrString?: string): string | null {
|
function getTextWithDialogueTokens(scene: BattleScene, keyOrString: string): string | null {
|
||||||
if (isNullOrUndefined(keyOrString)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tokens = scene.currentBattle?.mysteryEncounter?.dialogueTokens;
|
const tokens = scene.currentBattle?.mysteryEncounter?.dialogueTokens;
|
||||||
// @ts-ignore
|
|
||||||
if (i18next.exists(keyOrString, tokens)) {
|
if (i18next.exists(keyOrString, tokens)) {
|
||||||
const stringArray = [`${keyOrString}`] as any;
|
return i18next.t(keyOrString, tokens) as string;
|
||||||
stringArray.raw = [`${keyOrString}`];
|
|
||||||
// @ts-ignore
|
|
||||||
return i18next.t(stringArray, tokens) as string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return keyOrString ?? null;
|
return keyOrString ?? null;
|
||||||
|
@ -101,8 +101,8 @@ export interface EnemyPartyConfig {
|
|||||||
* Generates an enemy party for a mystery encounter battle
|
* Generates an enemy party for a mystery encounter battle
|
||||||
* This will override and replace any standard encounter generation logic
|
* This will override and replace any standard encounter generation logic
|
||||||
* Useful for tailoring specific battles to mystery encounters
|
* Useful for tailoring specific battles to mystery encounters
|
||||||
* @param scene - Battle Scene
|
* @param scene Battle Scene
|
||||||
* @param partyConfig - Can pass various customizable attributes for the enemy party, see EnemyPartyConfig
|
* @param partyConfig Can pass various customizable attributes for the enemy party, see EnemyPartyConfig
|
||||||
*/
|
*/
|
||||||
export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: EnemyPartyConfig): Promise<void> {
|
export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: EnemyPartyConfig): Promise<void> {
|
||||||
const loaded: boolean = false;
|
const loaded: boolean = false;
|
||||||
@ -352,7 +352,7 @@ export function loadCustomMovesForEncounter(scene: BattleScene, moves: Moves | M
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Will update player money, and animate change (sound optional)
|
* Will update player money, and animate change (sound optional)
|
||||||
* @param scene - Battle Scene
|
* @param scene
|
||||||
* @param changeValue
|
* @param changeValue
|
||||||
* @param playSound
|
* @param playSound
|
||||||
* @param showMessage
|
* @param showMessage
|
||||||
@ -375,9 +375,9 @@ export function updatePlayerMoney(scene: BattleScene, changeValue: number, playS
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts modifier bullshit to an actual item
|
* Converts modifier bullshit to an actual item
|
||||||
* @param scene - Battle Scene
|
* @param scene Battle Scene
|
||||||
* @param modifier
|
* @param modifier
|
||||||
* @param pregenArgs - can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc.
|
* @param pregenArgs Can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc.
|
||||||
*/
|
*/
|
||||||
export function generateModifierType(scene: BattleScene, modifier: () => ModifierType, pregenArgs?: any[]): ModifierType | null {
|
export function generateModifierType(scene: BattleScene, modifier: () => ModifierType, pregenArgs?: any[]): ModifierType | null {
|
||||||
const modifierId = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifier);
|
const modifierId = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifier);
|
||||||
@ -805,10 +805,10 @@ export function handleMysteryEncounterBattleStartEffects(scene: BattleScene) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Can queue extra phases or logic during {@link TurnInitPhase}
|
* Can queue extra phases or logic during {@linkcode TurnInitPhase}
|
||||||
* Should mostly just be used for injecting custom phases into the battle system on turn start
|
* Should mostly just be used for injecting custom phases into the battle system on turn start
|
||||||
* @param scene
|
* @param scene
|
||||||
* @return boolean - if true, will skip the remainder of the {@link TurnInitPhase}
|
* @return boolean - if true, will skip the remainder of the {@linkcode TurnInitPhase}
|
||||||
*/
|
*/
|
||||||
export function handleMysteryEncounterTurnStartEffects(scene: BattleScene): boolean {
|
export function handleMysteryEncounterTurnStartEffects(scene: BattleScene): boolean {
|
||||||
const encounter = scene.currentBattle.mysteryEncounter;
|
const encounter = scene.currentBattle.mysteryEncounter;
|
||||||
|
@ -50,8 +50,8 @@ export function getSpriteKeysFromPokemon(pokemon: Pokemon): { spriteKey: string,
|
|||||||
* Will never remove the player's last non-fainted Pokemon (if they only have 1)
|
* Will never remove the player's last non-fainted Pokemon (if they only have 1)
|
||||||
* Otherwise, picks a Pokemon completely at random and removes from the party
|
* Otherwise, picks a Pokemon completely at random and removes from the party
|
||||||
* @param scene
|
* @param scene
|
||||||
* @param isAllowedInBattle - default false. If true, only picks from unfainted mons. If there is only 1 unfainted mon left and doNotReturnLastAbleMon is also true, will return fainted mon
|
* @param isAllowedInBattle Default false. If true, only picks from unfainted mons. If there is only 1 unfainted mon left and doNotReturnLastAbleMon is also true, will return fainted mon
|
||||||
* @param doNotReturnLastAbleMon - If true, will never return the last unfainted pokemon in the party. Useful when this function is being used to determine what Pokemon to remove from the party (Don't want to remove last unfainted)
|
* @param doNotReturnLastAbleMon Default false. If true, will never return the last unfainted pokemon in the party. Useful when this function is being used to determine what Pokemon to remove from the party (Don't want to remove last unfainted)
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function getRandomPlayerPokemon(scene: BattleScene, isAllowedInBattle: boolean = false, doNotReturnLastAbleMon: boolean = false): PlayerPokemon {
|
export function getRandomPlayerPokemon(scene: BattleScene, isAllowedInBattle: boolean = false, doNotReturnLastAbleMon: boolean = false): PlayerPokemon {
|
||||||
@ -78,7 +78,7 @@ export function getRandomPlayerPokemon(scene: BattleScene, isAllowedInBattle: bo
|
|||||||
/**
|
/**
|
||||||
* Ties are broken by whatever mon is closer to the front of the party
|
* Ties are broken by whatever mon is closer to the front of the party
|
||||||
* @param scene
|
* @param scene
|
||||||
* @param unfainted - default false. If true, only picks from unfainted mons.
|
* @param unfainted Default false. If true, only picks from unfainted mons.
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function getHighestLevelPlayerPokemon(scene: BattleScene, unfainted: boolean = false): PlayerPokemon {
|
export function getHighestLevelPlayerPokemon(scene: BattleScene, unfainted: boolean = false): PlayerPokemon {
|
||||||
@ -99,8 +99,8 @@ export function getHighestLevelPlayerPokemon(scene: BattleScene, unfainted: bool
|
|||||||
/**
|
/**
|
||||||
* Ties are broken by whatever mon is closer to the front of the party
|
* Ties are broken by whatever mon is closer to the front of the party
|
||||||
* @param scene
|
* @param scene
|
||||||
* @param stat - stat to search for
|
* @param stat Stat to search for
|
||||||
* @param unfainted - default false. If true, only picks from unfainted mons.
|
* @param unfainted Default false. If true, only picks from unfainted mons.
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function getHighestStatPlayerPokemon(scene: BattleScene, stat: PermanentStat, unfainted: boolean = false): PlayerPokemon {
|
export function getHighestStatPlayerPokemon(scene: BattleScene, stat: PermanentStat, unfainted: boolean = false): PlayerPokemon {
|
||||||
@ -218,7 +218,7 @@ export function koPlayerPokemon(scene: BattleScene, pokemon: PlayerPokemon) {
|
|||||||
/**
|
/**
|
||||||
* Handles applying hp changes to a player pokemon.
|
* Handles applying hp changes to a player pokemon.
|
||||||
* Takes care of not going below `0`, above max-hp, adding `FNT` status correctly and updating the pokemon info.
|
* Takes care of not going below `0`, above max-hp, adding `FNT` status correctly and updating the pokemon info.
|
||||||
* TODO: handle special cases like wonder-guard/ninjask
|
* TODO: should we handle special cases like wonder-guard/shedinja?
|
||||||
* @param scene the battle scene
|
* @param scene the battle scene
|
||||||
* @param pokemon the player pokemon to apply the hp change to
|
* @param pokemon the player pokemon to apply the hp change to
|
||||||
* @param value the hp change amount. Positive for heal. Negative for damage
|
* @param value the hp change amount. Positive for heal. Negative for damage
|
||||||
@ -258,7 +258,7 @@ export function applyDamageToPokemon(scene: BattleScene, pokemon: PlayerPokemon,
|
|||||||
*/
|
*/
|
||||||
export function applyHealToPokemon(scene: BattleScene, pokemon: PlayerPokemon, heal: number) {
|
export function applyHealToPokemon(scene: BattleScene, pokemon: PlayerPokemon, heal: number) {
|
||||||
if (heal <= 0) {
|
if (heal <= 0) {
|
||||||
console.warn("Damaging pokemong with `applyHealToPokemon` is not recommended! Please use `applyDamageToPokemon` instead.");
|
console.warn("Damaging pokemon with `applyHealToPokemon` is not recommended! Please use `applyDamageToPokemon` instead.");
|
||||||
}
|
}
|
||||||
|
|
||||||
applyHpChangeToPokemon(scene, pokemon, heal);
|
applyHpChangeToPokemon(scene, pokemon, heal);
|
||||||
|
@ -263,7 +263,7 @@ function doCircleInward(scene: BattleScene, transformationBaseBg: Phaser.GameObj
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function for {@link doSpiralUpward}, handles a single particle
|
* Helper function for {@linkcode doSpiralUpward}, handles a single particle
|
||||||
* @param scene
|
* @param scene
|
||||||
* @param trigIndex
|
* @param trigIndex
|
||||||
* @param transformationBaseBg
|
* @param transformationBaseBg
|
||||||
@ -308,7 +308,7 @@ function doSpiralUpwardParticle(scene: BattleScene, trigIndex: number, transform
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function for {@link doArcDownward}, handles a single particle
|
* Helper function for {@linkcode doArcDownward}, handles a single particle
|
||||||
* @param scene
|
* @param scene
|
||||||
* @param trigIndex
|
* @param trigIndex
|
||||||
* @param transformationBaseBg
|
* @param transformationBaseBg
|
||||||
|
@ -227,6 +227,7 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase {
|
|||||||
const legalPlayerPartyPokemon = legalPlayerPokemon.filter(p => !p.isActive(true));
|
const legalPlayerPartyPokemon = legalPlayerPokemon.filter(p => !p.isActive(true));
|
||||||
if (!legalPlayerPokemon.length) {
|
if (!legalPlayerPokemon.length) {
|
||||||
this.scene.unshiftPhase(new GameOverPhase(this.scene));
|
this.scene.unshiftPhase(new GameOverPhase(this.scene));
|
||||||
|
return this.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for any KOd player mons and switch
|
// Check for any KOd player mons and switch
|
||||||
|
@ -24,6 +24,8 @@ import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
|
|||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { BerryModifier } from "#app/modifier/modifier";
|
import { BerryModifier } from "#app/modifier/modifier";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||||
|
import * as TextUtils from "#app/ui/text";
|
||||||
|
import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
|
||||||
|
|
||||||
const namespace = "mysteryEncounter:uncommonBreed";
|
const namespace = "mysteryEncounter:uncommonBreed";
|
||||||
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
|
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
|
||||||
@ -167,7 +169,10 @@ describe("Uncommon Breed - Mystery Encounter", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should NOT be selectable if the player doesn't have enough berries", { retry: 5 }, async () => {
|
it("should NOT be selectable if the player doesn't have enough berries", async () => {
|
||||||
|
// For some reason, BBCodeText has issues with this test
|
||||||
|
vi.spyOn(TextUtils, "addBBCodeTextObject").mockImplementation(() => new BBCodeText(scene, 0, 0, "test"));
|
||||||
|
|
||||||
await game.runToMysteryEncounter(MysteryEncounterType.UNCOMMON_BREED, defaultParty);
|
await game.runToMysteryEncounter(MysteryEncounterType.UNCOMMON_BREED, defaultParty);
|
||||||
// Clear out any pesky mods that slipped through test spin-up
|
// Clear out any pesky mods that slipped through test spin-up
|
||||||
scene.modifiers.forEach(mod => {
|
scene.modifiers.forEach(mod => {
|
||||||
|
@ -189,8 +189,8 @@ export default class GameManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs the game to a mystery encounter phase.
|
* Runs the game to a mystery encounter phase.
|
||||||
* @param encounterType - if specified, will expect encounter to have been spawned
|
* @param encounterType if specified, will expect encounter to have been spawned
|
||||||
* @param species - Optional array of species for party.
|
* @param species Optional array of species for party.
|
||||||
* @returns A promise that resolves when the EncounterPhase ends.
|
* @returns A promise that resolves when the EncounterPhase ends.
|
||||||
*/
|
*/
|
||||||
async runToMysteryEncounter(encounterType?: MysteryEncounterType, species?: Species[]) {
|
async runToMysteryEncounter(encounterType?: MysteryEncounterType, species?: Species[]) {
|
||||||
|
@ -17,9 +17,7 @@ export default class MockText implements MockGameObject {
|
|||||||
this.scene = textureManager.scene;
|
this.scene = textureManager.scene;
|
||||||
this.textureManager = textureManager;
|
this.textureManager = textureManager;
|
||||||
this.style = {};
|
this.style = {};
|
||||||
// DO NOT REMOVE: function needs to be stubbed for tests
|
// Phaser.GameObjects.TextStyle.prototype.setStyle = () => this;
|
||||||
// @ts-ignore
|
|
||||||
Phaser.GameObjects.TextStyle.prototype.setStyle = () => this;
|
|
||||||
// Phaser.GameObjects.Text.prototype.updateText = () => null;
|
// Phaser.GameObjects.Text.prototype.updateText = () => null;
|
||||||
// Phaser.Textures.TextureManager.prototype.addCanvas = () => {};
|
// Phaser.Textures.TextureManager.prototype.addCanvas = () => {};
|
||||||
UI.prototype.showText = this.showText;
|
UI.prototype.showText = this.showText;
|
||||||
|
@ -104,7 +104,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
|||||||
this.dexProgressContainer.setVisible(true);
|
this.dexProgressContainer.setVisible(true);
|
||||||
this.displayEncounterOptions(slideInDescription);
|
this.displayEncounterOptions(slideInDescription);
|
||||||
const cursor = this.getCursor();
|
const cursor = this.getCursor();
|
||||||
if (cursor === (this?.optionsContainer?.length || 0) - 1) {
|
if (cursor === (this.optionsContainer?.length || 0) - 1) {
|
||||||
// Always resets cursor on view party button if it was last there
|
// Always resets cursor on view party button if it was last there
|
||||||
this.setCursor(cursor);
|
this.setCursor(cursor);
|
||||||
} else {
|
} else {
|
||||||
|
@ -227,7 +227,7 @@ export function getBBCodeFrag(content: string, textStyle: TextStyle, uiTheme: Ui
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should only be used with BBCodeText (see addBBCodeTextObject())
|
* Should only be used with BBCodeText (see {@linkcode addBBCodeTextObject()})
|
||||||
* This does NOT work with UI showText() or showDialogue() methods.
|
* 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:
|
* Method will do pattern match/replace and apply BBCode color/shadow styling to substrings within the content:
|
||||||
* @[<TextStyle>]{<text to color>}
|
* @[<TextStyle>]{<text to color>}
|
||||||
@ -236,8 +236,8 @@ export function getBBCodeFrag(content: string, textStyle: TextStyle, uiTheme: Ui
|
|||||||
* - "blue text" with TextStyle.SUMMARY_BLUE applied
|
* - "blue text" with TextStyle.SUMMARY_BLUE applied
|
||||||
* - " primaryStyle text " with primaryStyle TextStyle applied
|
* - " primaryStyle text " with primaryStyle TextStyle applied
|
||||||
* - "red text" with TextStyle.SUMMARY_RED applied
|
* - "red text" with TextStyle.SUMMARY_RED applied
|
||||||
* @param content - string with styling that need to be applied for BBCodeTextObject
|
* @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 primaryStyle Primary style is required in order to escape BBCode styling properly.
|
||||||
* @param uiTheme
|
* @param uiTheme
|
||||||
*/
|
*/
|
||||||
export function getTextWithColors(content: string, primaryStyle: TextStyle, uiTheme: UiTheme = UiTheme.DEFAULT): string {
|
export function getTextWithColors(content: string, primaryStyle: TextStyle, uiTheme: UiTheme = UiTheme.DEFAULT): string {
|
||||||
|
Loading…
Reference in New Issue
Block a user