clean up challenge requirements for disabling certain MEs

This commit is contained in:
ImperialSympathizer 2024-09-21 23:00:48 -04:00
parent cfafbb3492
commit 3a37b7850a
14 changed files with 102 additions and 87 deletions

View File

@ -3165,11 +3165,17 @@ export default class BattleScene extends SceneBase {
if (encounterCandidate.encounterTier !== tier) { // Encounter is in tier
return false;
}
const disabledModes = encounterCandidate.disabledGameModes;
if (disabledModes && disabledModes.length > 0
&& disabledModes.includes(this.gameMode.modeId)) { // Encounter is enabled for game mode
const disallowedGameModes = encounterCandidate.disallowedGameModes;
if (disallowedGameModes && disallowedGameModes.length > 0
&& disallowedGameModes.includes(this.gameMode.modeId)) { // Encounter is enabled for game mode
return false;
}
if (this.gameMode.modeId === GameModes.CHALLENGE) { // Encounter is enabled for challenges
const disallowedChallenges = encounterCandidate.disallowedChallenges;
if (disallowedChallenges && disallowedChallenges.length > 0 && this.gameMode.challenges.some(challenge => disallowedChallenges.includes(challenge.id))) {
return false;
}
}
if (!encounterCandidate.meetsRequirements(this)) { // Meets encounter requirements
return false;
}

View File

@ -338,7 +338,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
.withOptionPhase(async (scene: BattleScene) => {
// Let it have the food
// Greedent joins the team, level equal to 2 below highest party member
const level = getHighestLevelPlayerPokemon(scene).level - 2;
const level = getHighestLevelPlayerPokemon(scene, false, true).level - 2;
const greedent = new EnemyPokemon(scene, getPokemonSpecies(Species.GREEDENT), level, TrainerSlot.NONE, false);
greedent.moveset = [new PokemonMove(Moves.THRASH), new PokemonMove(Moves.BODY_PRESS), new PokemonMove(Moves.STUFF_CHEEKS), new PokemonMove(Moves.SLACK_OFF)];
greedent.passive = true;

View File

@ -60,7 +60,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
.withQuery(`${namespace}.query`)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
const pokemon = getHighestStatTotalPlayerPokemon(scene, false);
const pokemon = getHighestStatTotalPlayerPokemon(scene, true, true);
const price = scene.getWaveMoneyAmount(10);
encounter.setDialogueToken("strongestPokemon", pokemon.getNameToRender());

View File

@ -102,7 +102,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
];
// Get fastest party pokemon for option 2
const fastestPokemon = getHighestStatPlayerPokemon(scene, PERMANENT_STATS[Stat.SPD], true);
const fastestPokemon = getHighestStatPlayerPokemon(scene, PERMANENT_STATS[Stat.SPD], true, false);
encounter.misc.fastestPokemon = fastestPokemon;
encounter.misc.enemySpeed = bossPokemon.getStat(Stat.SPD);
encounter.setDialogueToken("fastestPokemon", fastestPokemon.getNameToRender());

View File

@ -29,8 +29,9 @@ import { Moves } from "#enums/moves";
import { EncounterBattleAnim } from "#app/data/battle-anims";
import { MoveCategory } from "#app/data/move";
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES, GameModes } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { EncounterAnim } from "#enums/encounter-anims";
import { Challenges } from "#enums/challenges";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:clowningAround";
@ -61,7 +62,7 @@ const RANDOM_ABILITY_POOL = [
export const ClowningAroundEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.CLOWNING_AROUND)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withDisabledGameModes(GameModes.CHALLENGE)
.withDisallowedChallenges(Challenges.SINGLE_TYPE)
.withSceneWaveRangeRequirement(80, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
.withAnimations(EncounterAnim.SMOKESCREEN)
.withAutoHideIntroVisuals(false)

View File

@ -269,10 +269,10 @@ export const DancingLessonsEncounter: MysteryEncounter =
});
};
// Only Pokemon that have a Dancing move can be selected
// Only challenge legal/unfainted Pokemon that have a Dancing move can be selected
const selectableFilter = (pokemon: Pokemon) => {
// If pokemon meets primary pokemon reqs, it can be selected
if (!pokemon.isAllowed()) {
if (!pokemon.isAllowedInBattle()) {
return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: pokemon.getNameToRender() }) ?? null;
}
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(scene, pokemon);

View File

@ -139,7 +139,7 @@ export const DarkDealEncounter: MysteryEncounter =
.withPreOptionPhase(async (scene: BattleScene) => {
// Removes random pokemon (including fainted) from party and adds name to dialogue data tokens
// Will never return last battle able mon and instead pick fainted/unable to battle
const removedPokemon = getRandomPlayerPokemon(scene, false, true);
const removedPokemon = getRandomPlayerPokemon(scene, true, false, true);
// Get all the pokemon's held items
const modifiers = removedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier));
scene.removePokemonFromPlayerParty(removedPokemon);

View File

@ -163,7 +163,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
leaveEncounterWithoutBattle(scene);
} else {
// Your highest level unfainted Pokemon gets OHKO. Start battle against a Gimmighoul (35%)
const highestLevelPokemon = getHighestLevelPlayerPokemon(scene, true);
const highestLevelPokemon = getHighestLevelPlayerPokemon(scene, true, false);
koPlayerPokemon(scene, highestLevelPokemon);
encounter.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender());

View File

@ -50,7 +50,7 @@ export const UncommonBreedEncounter: MysteryEncounter =
// Calculate boss mon
// Level equal to 2 below highest party member
const level = getHighestLevelPlayerPokemon(scene).level - 2;
const level = getHighestLevelPlayerPokemon(scene, false, true).level - 2;
const species = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getParty()), true);
const pokemon = new EnemyPokemon(scene, species, level, TrainerSlot.NONE, true);
const speciesRootForm = pokemon.species.getRootSpeciesId();

View File

@ -20,7 +20,8 @@ import i18next from "#app/plugins/i18n";
import { doPokemonTransformationSequence, TransformationScreenPosition } from "#app/data/mystery-encounters/utils/encounter-transformation-sequence";
import { getLevelTotalExp } from "#app/data/exp";
import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES, GameModes } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { Challenges } from "#enums/challenges";
/** i18n namespace for encounter */
const namespace = "mysteryEncounter:weirdDream";
@ -104,7 +105,7 @@ const STANDARD_BST_TRANSFORM_BASE_VALUES: [number, number] = [40, 50];
export const WeirdDreamEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.WEIRD_DREAM)
.withEncounterTier(MysteryEncounterTier.ROGUE)
.withDisabledGameModes(GameModes.CHALLENGE)
.withDisallowedChallenges(Challenges.SINGLE_TYPE)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withIntroSpriteConfigs([
{

View File

@ -15,6 +15,7 @@ import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { GameModes } from "#app/game-mode";
import { EncounterAnim } from "#enums/encounter-anims";
import { Challenges } from "#enums/challenges";
export interface EncounterStartOfBattleEffect {
sourcePokemon?: Pokemon;
@ -40,7 +41,8 @@ export interface IMysteryEncounter {
spriteConfigs: MysteryEncounterSpriteConfig[];
encounterTier: MysteryEncounterTier;
encounterAnimations?: EncounterAnim[];
disabledGameModes?: GameModes[];
disallowedGameModes?: GameModes[];
disallowedChallenges?: Challenges[];
hideBattleIntroMessage: boolean;
autoHideIntroVisuals: boolean;
enterIntroVisualsFromRight: boolean;
@ -93,7 +95,11 @@ export default class MysteryEncounter implements IMysteryEncounter {
/**
* If specified, defines any game modes where the {@linkcode MysteryEncounter} should *NOT* spawn
*/
disabledGameModes?: GameModes[];
disallowedGameModes?: GameModes[];
/**
* If specified, defines any challenges (from Challenge game mode) where the {@linkcode MysteryEncounter} should *NOT* spawn
*/
disallowedChallenges?: Challenges[];
/**
* If true, hides "A Wild X Appeared" etc. messages
* Default true
@ -661,11 +667,21 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
/**
* Defines any game modes where the Mystery Encounter should *NOT* spawn
* @returns
* @param disabledGameModes
* @param disallowedGameModes
*/
withDisabledGameModes(...disabledGameModes: GameModes[]): this & Required<Pick<IMysteryEncounter, "disabledGameModes">> {
const gameModes = Array.isArray(disabledGameModes) ? disabledGameModes : [disabledGameModes];
return Object.assign(this, { disabledGameModes: gameModes });
withDisallowedGameModes(...disallowedGameModes: GameModes[]): this & Required<Pick<IMysteryEncounter, "disallowedGameModes">> {
const gameModes = Array.isArray(disallowedGameModes) ? disallowedGameModes : [disallowedGameModes];
return Object.assign(this, { disallowedGameModes: gameModes });
}
/**
* Defines any challenges (from Challenge game mode) where the Mystery Encounter should *NOT* spawn
* @returns
* @param disallowedChallenges
*/
withDisallowedChallenges(...disallowedChallenges: Challenges[]): this & Required<Pick<IMysteryEncounter, "disallowedChallenges">> {
const challenges = Array.isArray(disallowedChallenges) ? disallowedChallenges : [disallowedChallenges];
return Object.assign(this, { disallowedChallenges: challenges });
}
/**

View File

@ -50,28 +50,39 @@ export function getSpriteKeysFromPokemon(pokemon: Pokemon): { spriteKey: string,
}
/**
*
* 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
* @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 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)
* @param isAllowed Default false. If true, only picks from legal mons. If no legal mons are found (or there is 1, with `doNotReturnLastAllowedMon = true), will return a mon that is not allowed.
* @param isFainted Default false. If true, includes fainted mons.
* @param doNotReturnLastAllowedMon 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
*/
export function getRandomPlayerPokemon(scene: BattleScene, isAllowedInBattle: boolean = false, doNotReturnLastAbleMon: boolean = false): PlayerPokemon {
export function getRandomPlayerPokemon(scene: BattleScene, isAllowed: boolean = false, isFainted: boolean = false, doNotReturnLastAllowedMon: boolean = false): PlayerPokemon {
const party = scene.getParty();
let chosenIndex: number;
let chosenPokemon: PlayerPokemon;
const unfaintedMons = party.filter(p => p.isAllowedInBattle());
const faintedMons = party.filter(p => !p.isAllowedInBattle());
let chosenPokemon: PlayerPokemon | null = null;
const fullyLegalMons = party.filter(p => (!isAllowed || p.isAllowed()) && (isFainted || !p.isFainted()));
const allowedOnlyMons = party.filter(p => p.isAllowed());
if (doNotReturnLastAbleMon && unfaintedMons.length === 1) {
chosenIndex = randSeedInt(faintedMons.length);
chosenPokemon = faintedMons[chosenIndex];
} else if (isAllowedInBattle) {
chosenIndex = randSeedInt(unfaintedMons.length);
chosenPokemon = unfaintedMons[chosenIndex];
} else {
if (doNotReturnLastAllowedMon && fullyLegalMons.length === 1) {
// If there is only 1 legal/unfainted mon left, select from fainted legal mons
const faintedLegalMons = party.filter(p => (!isAllowed || p.isAllowed()) && p.isFainted());
if (faintedLegalMons.length > 0) {
chosenIndex = randSeedInt(faintedLegalMons.length);
chosenPokemon = faintedLegalMons[chosenIndex];
}
}
if (!chosenPokemon && fullyLegalMons.length > 0) {
chosenIndex = randSeedInt(fullyLegalMons.length);
chosenPokemon = fullyLegalMons[chosenIndex];
}
if (!chosenPokemon && isAllowed && allowedOnlyMons.length > 0) {
chosenIndex = randSeedInt(allowedOnlyMons.length);
chosenPokemon = allowedOnlyMons[chosenIndex];
}
if (!chosenPokemon) {
// If no other options worked, returns fully random
chosenIndex = randSeedInt(party.length);
chosenPokemon = party[chosenIndex];
}
@ -82,15 +93,19 @@ export function getRandomPlayerPokemon(scene: BattleScene, isAllowedInBattle: bo
/**
* Ties are broken by whatever mon is closer to the front of the party
* @param scene
* @param unfainted Default false. If true, only picks from unfainted mons.
* @param isAllowed Default false. If true, only picks from legal mons.
* @param isFainted Default false. If true, includes fainted mons.
* @returns
*/
export function getHighestLevelPlayerPokemon(scene: BattleScene, unfainted: boolean = false): PlayerPokemon {
export function getHighestLevelPlayerPokemon(scene: BattleScene, isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon {
const party = scene.getParty();
let pokemon: PlayerPokemon | null = null;
for (const p of party) {
if (unfainted && p.isFainted()) {
if (isAllowed && !p.isAllowed()) {
continue;
}
if (!isFainted && p.isFainted()) {
continue;
}
@ -104,15 +119,19 @@ export function getHighestLevelPlayerPokemon(scene: BattleScene, unfainted: bool
* Ties are broken by whatever mon is closer to the front of the party
* @param scene
* @param stat Stat to search for
* @param unfainted Default false. If true, only picks from unfainted mons.
* @param isAllowed Default false. If true, only picks from legal mons.
* @param isFainted Default false. If true, includes fainted mons.
* @returns
*/
export function getHighestStatPlayerPokemon(scene: BattleScene, stat: PermanentStat, unfainted: boolean = false): PlayerPokemon {
export function getHighestStatPlayerPokemon(scene: BattleScene, stat: PermanentStat, isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon {
const party = scene.getParty();
let pokemon: PlayerPokemon | null = null;
for (const p of party) {
if (unfainted && p.isFainted()) {
if (isAllowed && !p.isAllowed()) {
continue;
}
if (!isFainted && p.isFainted()) {
continue;
}
@ -125,15 +144,19 @@ export function getHighestStatPlayerPokemon(scene: BattleScene, stat: PermanentS
/**
* Ties are broken by whatever mon is closer to the front of the party
* @param scene
* @param unfainted - default false. If true, only picks from unfainted mons.
* @param isAllowed Default false. If true, only picks from legal mons.
* @param isFainted Default false. If true, includes fainted mons.
* @returns
*/
export function getLowestLevelPlayerPokemon(scene: BattleScene, unfainted: boolean = false): PlayerPokemon {
export function getLowestLevelPlayerPokemon(scene: BattleScene, isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon {
const party = scene.getParty();
let pokemon: PlayerPokemon | null = null;
for (const p of party) {
if (unfainted && p.isFainted()) {
if (isAllowed && !p.isAllowed()) {
continue;
}
if (!isFainted && p.isFainted()) {
continue;
}
@ -146,15 +169,19 @@ export function getLowestLevelPlayerPokemon(scene: BattleScene, unfainted: boole
/**
* Ties are broken by whatever mon is closer to the front of the party
* @param scene
* @param unfainted - default false. If true, only picks from unfainted mons.
* @param isAllowed Default false. If true, only picks from legal mons.
* @param isFainted Default false. If true, includes fainted mons.
* @returns
*/
export function getHighestStatTotalPlayerPokemon(scene: BattleScene, unfainted: boolean = false): PlayerPokemon {
export function getHighestStatTotalPlayerPokemon(scene: BattleScene, isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon {
const party = scene.getParty();
let pokemon: PlayerPokemon | null = null;
for (const p of party) {
if (unfainted && p.isFainted()) {
if (isAllowed && !p.isAllowed()) {
continue;
}
if (!isFainted && p.isFainted()) {
continue;
}

View File

@ -345,7 +345,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
isAllowed(): boolean {
const challengeAllowed = new Utils.BooleanHolder(true);
applyChallenges(this.scene.gameMode, ChallengeType.POKEMON_IN_BATTLE, this, challengeAllowed);
return !this.isFainted() && challengeAllowed.value;
return challengeAllowed.value;
}
isActive(onField?: boolean): boolean {

View File

@ -67,7 +67,7 @@ describe("Mystery Encounter Utils", () => {
expect(result.species.speciesId).toBe(Species.ARCEUS);
});
it("gets an unfainted pokemon from player party if isAllowedInBattle is true", () => {
it("gets an unfainted legal pokemon from player party if isAllowed is true and isFainted is false", () => {
// Only faint 1st pokemon
const party = scene.getParty();
party[0].hp = 0;
@ -115,12 +115,12 @@ describe("Mystery Encounter Utils", () => {
// Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal)
game.override.seed("random");
let result = getRandomPlayerPokemon(scene, true, true);
let result = getRandomPlayerPokemon(scene, true, false, true);
expect(result.species.speciesId).toBe(Species.ARCEUS);
game.override.seed("random2");
result = getRandomPlayerPokemon(scene, true, true);
result = getRandomPlayerPokemon(scene, true, false, true);
expect(result.species.speciesId).toBe(Species.ARCEUS);
});
});
@ -301,41 +301,5 @@ describe("Mystery Encounter Utils", () => {
expect(spy).toHaveBeenCalledWith("valuevalue {{testvalue}} {{test1}} {{test}} {{test\\}} {{test\\}} {test}}", "valuevalue {{testvalue}} {{test1}} {{test}} {{test\\}} {{test\\}} {test}}", null, expect.any(Function), 0);
});
});
describe("initBattleWithEnemyConfig", () => {
it("", () => {
});
});
describe("setCustomEncounterRewards", () => {
it("", () => {
});
});
describe("selectPokemonForOption", () => {
it("", () => {
});
});
describe("setEncounterExp", () => {
it("", () => {
});
});
describe("leaveEncounterWithoutBattle", () => {
it("", () => {
});
});
describe("handleMysteryEncounterVictory", () => {
it("", () => {
});
});
});