finish unit tests for fiery fallout

This commit is contained in:
ImperialSympathizer 2024-07-19 14:51:38 -04:00
parent 37419eb1d3
commit 03f999e169
12 changed files with 80 additions and 139 deletions

View File

@ -2650,7 +2650,7 @@ export default class BattleScene extends SceneBase {
return encounter;
}
// Common / Uncommon / Rare / Super Rare
// Common / Great / Ultra / Rogue
const tierWeights = [64, 40, 21, 3];
// Adjust tier weights by previously encountered events to lower odds of only common/uncommons in run

View File

@ -528,10 +528,10 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
/**
* Fetches animation configs to be used in a Mystery Encounter
* @param scene
* @param anims - one or more animations to fetch
* @param encounterAnim - one or more animations to fetch
*/
export async function initEncounterAnims(scene: BattleScene, anims: EncounterAnim | EncounterAnim[]): Promise<void> {
anims = anims instanceof Array ? anims : [anims];
export async function initEncounterAnims(scene: BattleScene, encounterAnim: EncounterAnim | EncounterAnim[]): Promise<void> {
const anims = Array.isArray(encounterAnim) ? encounterAnim : [encounterAnim];
const encounterAnimNames = Utils.getEnumKeys(EncounterAnim);
const encounterAnimIds = Utils.getEnumValues(EncounterAnim);
const encounterAnimFetches = [];

View File

@ -175,7 +175,7 @@ export const FieryFalloutEncounter: IMysteryEncounter =
async (scene: BattleScene) => {
// Damage non-fire types and burn 1 random non-fire type member
const encounter = scene.currentBattle.mysteryEncounter;
const nonFireTypes = scene.getParty().filter((p) => !p.getTypes().includes(Type.FIRE));
const nonFireTypes = scene.getParty().filter((p) => p.isAllowedInBattle() && !p.getTypes().includes(Type.FIRE));
for (const pkm of nonFireTypes) {
const percentage = DAMAGE_PERCENTAGE / 100;
@ -201,8 +201,8 @@ export const FieryFalloutEncounter: IMysteryEncounter =
.withOption(
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new TypeRequirement(Type.STEEL, true,1)) // Will set option3PrimaryName dialogue token automatically
.withSecondaryPokemonRequirement(new TypeRequirement(Type.STEEL, true,1)) // Will set option3SecondaryName dialogue token automatically
.withPrimaryPokemonRequirement(new TypeRequirement(Type.FIRE, true,1)) // Will set option3PrimaryName dialogue token automatically
.withSecondaryPokemonRequirement(new TypeRequirement(Type.FIRE, true,1)) // Will set option3SecondaryName dialogue token automatically
.withDialogue({
buttonLabel: `${namespace}:option:3:label`,
buttonTooltip: `${namespace}:option:3:tooltip`,

View File

@ -103,12 +103,7 @@ export class TimeOfDayRequirement extends EncounterSceneRequirement {
constructor(timeOfDay: TimeOfDay | TimeOfDay[]) {
super();
if (timeOfDay instanceof Array) {
this.requiredTimeOfDay = timeOfDay;
} else {
this.requiredTimeOfDay = [];
this.requiredTimeOfDay.push(timeOfDay);
}
this.requiredTimeOfDay = Array.isArray(timeOfDay) ? timeOfDay : [timeOfDay];
}
meetsRequirement(scene: BattleScene): boolean {
@ -130,12 +125,7 @@ export class WeatherRequirement extends EncounterSceneRequirement {
constructor(weather: WeatherType | WeatherType[]) {
super();
if (weather instanceof Array) {
this.requiredWeather = weather;
} else {
this.requiredWeather = [];
this.requiredWeather.push(weather);
}
this.requiredWeather = Array.isArray(weather) ? weather : [weather];
}
meetsRequirement(scene: BattleScene): boolean {
@ -185,12 +175,7 @@ export class PersistentModifierRequirement extends EncounterSceneRequirement {
requiredItems?: ModifierType[]; // TODO: not implemented
constructor(item: ModifierType | ModifierType[]) {
super();
if (item instanceof Array) {
this.requiredItems = item;
} else {
this.requiredItems = [];
this.requiredItems.push(item);
}
this.requiredItems = Array.isArray(item) ? item : [item];
}
meetsRequirement(scene: BattleScene): boolean {
@ -251,12 +236,7 @@ export class SpeciesRequirement extends EncounterPokemonRequirement {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
if (species instanceof Array) {
this.requiredSpecies = species;
} else {
this.requiredSpecies = [];
this.requiredSpecies.push(species);
}
this.requiredSpecies = Array.isArray(species) ? species : [species];
}
meetsRequirement(scene: BattleScene): boolean {
@ -294,12 +274,7 @@ export class NatureRequirement extends EncounterPokemonRequirement {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
if (nature instanceof Array) {
this.requiredNature = nature;
} else {
this.requiredNature = [];
this.requiredNature.push(nature);
}
this.requiredNature = Array.isArray(nature) ? nature : [nature];
}
meetsRequirement(scene: BattleScene): boolean {
@ -338,12 +313,7 @@ export class TypeRequirement extends EncounterPokemonRequirement {
this.excludeFainted = excludeFainted;
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
if (type instanceof Array) {
this.requiredType = type;
} else {
this.requiredType = [];
this.requiredType.push(type);
}
this.requiredType = Array.isArray(type) ? type : [type];
}
meetsRequirement(scene: BattleScene): boolean {
@ -388,12 +358,7 @@ export class MoveRequirement extends EncounterPokemonRequirement {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
if (moves instanceof Array) {
this.requiredMoves = moves;
} else {
this.requiredMoves = [];
this.requiredMoves.push(moves);
}
this.requiredMoves = Array.isArray(moves) ? moves : [moves];
}
meetsRequirement(scene: BattleScene): boolean {
@ -437,12 +402,7 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
if (learnableMove instanceof Array) {
this.requiredMoves = learnableMove;
} else {
this.requiredMoves = [];
this.requiredMoves.push(learnableMove);
}
this.requiredMoves = Array.isArray(learnableMove) ? learnableMove : [learnableMove];
}
meetsRequirement(scene: BattleScene): boolean {
@ -482,12 +442,7 @@ export class EvolutionTargetSpeciesRequirement extends EncounterPokemonRequireme
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
if (evolutionTargetSpecies instanceof Array) {
this.requiredEvolutionTargetSpecies = evolutionTargetSpecies;
} else {
this.requiredEvolutionTargetSpecies = [];
this.requiredEvolutionTargetSpecies.push(evolutionTargetSpecies);
}
this.requiredEvolutionTargetSpecies = Array.isArray(evolutionTargetSpecies) ? evolutionTargetSpecies : [evolutionTargetSpecies];
}
meetsRequirement(scene: BattleScene): boolean {
@ -526,12 +481,7 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
if (abilities instanceof Array) {
this.requiredAbilities = abilities;
} else {
this.requiredAbilities = [];
this.requiredAbilities.push(abilities);
}
this.requiredAbilities = Array.isArray(abilities) ? abilities : [abilities];
}
meetsRequirement(scene: BattleScene): boolean {
@ -571,12 +521,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
if (statusEffect instanceof Array) {
this.requiredStatusEffect = statusEffect;
} else {
this.requiredStatusEffect = [];
this.requiredStatusEffect.push(statusEffect);
}
this.requiredStatusEffect = Array.isArray(statusEffect) ? statusEffect : [statusEffect];
}
meetsRequirement(scene: BattleScene): boolean {
@ -646,12 +591,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
if (formChangeItem instanceof Array) {
this.requiredFormChangeItem = formChangeItem;
} else {
this.requiredFormChangeItem = [];
this.requiredFormChangeItem.push(formChangeItem);
}
this.requiredFormChangeItem = Array.isArray(formChangeItem) ? formChangeItem : [formChangeItem];
}
meetsRequirement(scene: BattleScene): boolean {
@ -703,12 +643,7 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
if (evolutionItems instanceof Array) {
this.requiredEvolutionItem = evolutionItems;
} else {
this.requiredEvolutionItem = [];
this.requiredEvolutionItem.push(evolutionItems);
}
this.requiredEvolutionItem = Array.isArray(evolutionItems) ? evolutionItems : [evolutionItems];
}
meetsRequirement(scene: BattleScene): boolean {
@ -757,12 +692,7 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
if (heldItem instanceof Array) {
this.requiredHeldItemModifier = heldItem;
} else {
this.requiredHeldItemModifier = [];
this.requiredHeldItemModifier.push(heldItem);
}
this.requiredHeldItemModifier = Array.isArray(heldItem) ? heldItem : [heldItem];
}
meetsRequirement(scene: BattleScene): boolean {

View File

@ -489,8 +489,8 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @returns
*/
withAnimations(...encounterAnimations: EncounterAnim[]): this & Required<Pick<IMysteryEncounter, "encounterAnimations">> {
encounterAnimations = encounterAnimations instanceof Array ? encounterAnimations : [encounterAnimations];
return Object.assign(this, { encounterAnimations: encounterAnimations });
const animations = Array.isArray(encounterAnimations) ? encounterAnimations : [encounterAnimations];
return Object.assign(this, { encounterAnimations: animations });
}
/**

View File

@ -207,10 +207,11 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
}
// Set Status
if (partyConfig.pokemonConfigs[e].status) {
const statusEffects = partyConfig.pokemonConfigs[e].status;
if (statusEffects) {
// Default to cureturn 3 for sleep
const status = partyConfig.pokemonConfigs[e].status instanceof Array ? partyConfig.pokemonConfigs[e].status[0] : partyConfig.pokemonConfigs[e].status;
const cureTurn = partyConfig.pokemonConfigs[e].status instanceof Array ? partyConfig.pokemonConfigs[e].status[1] : partyConfig.pokemonConfigs[e].status === StatusEffect.SLEEP ? 3 : null;
const status = Array.isArray(statusEffects) ? statusEffects[0] : statusEffects;
const cureTurn = Array.isArray(statusEffects) ? statusEffects[1] : statusEffects === StatusEffect.SLEEP ? 3 : null;
enemyPokemon.status = new Status(status, 0, cureTurn);
}
@ -281,7 +282,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
* @param moves
*/
export function initCustomMovesForEncounter(scene: BattleScene, moves: Moves | Moves[]) {
moves = moves instanceof Array ? moves : [moves];
moves = Array.isArray(moves) ? moves : [moves];
return Promise.all(moves.map(move => initMoveAnim(scene, move)))
.then(() => loadMoveAnimAssets(scene, moves));
}

View File

@ -98,8 +98,8 @@ export function getLowestLevelPlayerPokemon(scene: BattleScene, unfainted: boole
* @returns
*/
export function getRandomSpeciesByStarterTier(starterTiers: number | [number, number], excludedSpecies?: Species[], types?: Type[]): Species {
let min = starterTiers instanceof Array ? starterTiers[0] : starterTiers;
let max = starterTiers instanceof Array ? starterTiers[1] : starterTiers;
let min = Array.isArray(starterTiers) ? starterTiers[0] : starterTiers;
let max = Array.isArray(starterTiers) ? starterTiers[1] : starterTiers;
let filteredSpecies: [PokemonSpecies, number][] = Object.keys(speciesStarters)
.map(s => [parseInt(s) as Species, speciesStarters[s] as number])

View File

@ -89,8 +89,7 @@ export async function skipBattleRunMysteryEncounterRewardsPhase(game: GameManage
p.status = new Status(StatusEffect.FAINT);
game.scene.field.remove(p);
});
game.scene.unshiftPhase(new VictoryPhase(game.scene, 0));
game.endPhase();
game.scene.pushPhase(new VictoryPhase(game.scene, 0));
game.phaseInterceptor.superEndPhase();
await game.phaseInterceptor.to(MysteryEncounterRewardsPhase, true);
}

View File

@ -9,16 +9,19 @@ import Battle from "#app/battle";
import { Gender } from "#app/data/gender";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import * as BattleAnims from "#app/data/battle-anims";
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { EncounterOptionMode } from "#app/data/mystery-encounters/mystery-encounter-option";
import { runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounterTestUtils";
import { CommandPhase, MovePhase, SelectModifierPhase } from "#app/phases";
import { Moves } from "#enums/moves";
import BattleScene from "#app/battle-scene";
import { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { Type } from "#app/data/type";
import { Status, StatusEffect } from "#app/data/status-effect";
const namespace = "mysteryEncounter:fieryFallout";
/** Arcanine and Ninetails for 2 Fire types. Lapras for burnable mon. */
const defaultParty = [Species.ARCANINE, Species.NINETALES, Species.LAPRAS];
/** Arcanine and Ninetails for 2 Fire types. Lapras, Gengar, Abra for burnable mon. */
const defaultParty = [Species.ARCANINE, Species.NINETALES, Species.LAPRAS, Species.GENGAR, Species.ABRA];
const defaultBiome = Biome.VOLCANO;
const defaultWave = 45;
@ -37,6 +40,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.trainerWave(false);
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
new Map<Biome, MysteryEncounterType[]>([
@ -168,12 +172,12 @@ describe("Fiery Fallout - Mystery Encounter", () => {
&& (m as PokemonHeldItemModifier).pokemonId === leadPokemonId, true) as PokemonHeldItemModifier[];
const charcoal = leadPokemonItems.find(i => i.type.name === "Charcoal");
expect(charcoal).toBeDefined;
});
}, 100000000);
});
describe("Option 2 - Suffer the weather", () => {
it("should have the correct properties", () => {
const option1 = FieryFalloutEncounter.options[0];
const option1 = FieryFalloutEncounter.options[1];
expect(option1.optionMode).toBe(EncounterOptionMode.DEFAULT);
expect(option1.dialogue).toBeDefined();
expect(option1.dialogue).toStrictEqual({
@ -187,32 +191,32 @@ describe("Fiery Fallout - Mystery Encounter", () => {
});
});
it("should damage all (allowed in battle) party PKM by 25%", async () => {
game.override.startingWave(33);
it("should damage all non-fire party PKM by 20% and randomly burn 1", async () => {
await game.runToMysteryEncounter(defaultParty);
const party = scene.getParty();
const lapras = party.find((pkm) => pkm.species.speciesId === Species.LAPRAS);
lapras.status = new Status(StatusEffect.POISON);
const abra = party.find((pkm) => pkm.species.speciesId === Species.ABRA);
vi.spyOn(abra, "isAllowedInBattle").mockReturnValue(false);
await runSelectMysteryEncounterOption(game, 3);
await runSelectMysteryEncounterOption(game, 2);
const allowedPkm = party.filter((pkm) => pkm.isAllowedInBattle());
const notAllowedPkm = party.filter((pkm) => !pkm.isAllowedInBattle());
allowedPkm.forEach((pkm) =>
expect(pkm.hp, `${pkm.name} should have receivd 25% damage: ${pkm.hp} / ${pkm.getMaxHp()} HP`).toBe(pkm.getMaxHp() - Math.floor(pkm.getMaxHp() * 0.25))
);
notAllowedPkm.forEach((pkm) => expect(pkm.hp, `${pkm.name} should be full hp: ${pkm.hp} / ${pkm.getMaxHp()} HP`).toBe(pkm.getMaxHp()));
const burnablePokemon = party.filter((pkm) => pkm.isAllowedInBattle() && !pkm.getTypes().includes(Type.FIRE));
const notBurnablePokemon = party.filter((pkm) => !pkm.isAllowedInBattle() || pkm.getTypes().includes(Type.FIRE));
expect(scene.currentBattle.mysteryEncounter.dialogueTokens["burnedPokemon"]).toBe("Gengar");
burnablePokemon.forEach((pkm) => {
expect(pkm.hp, `${pkm.name} should have received 20% damage: ${pkm.hp} / ${pkm.getMaxHp()} HP`).toBe(pkm.getMaxHp() - Math.floor(pkm.getMaxHp() * 0.2));
});
expect(burnablePokemon.some(pkm => pkm?.status?.effect === StatusEffect.BURN)).toBeTruthy();
notBurnablePokemon.forEach((pkm) => expect(pkm.hp, `${pkm.name} should be full hp: ${pkm.hp} / ${pkm.getMaxHp()} HP`).toBe(pkm.getMaxHp()));
});
it("should leave encounter without battle", async () => {
game.override.startingWave(33);
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(defaultParty);
await runSelectMysteryEncounterOption(game, 3);
await runSelectMysteryEncounterOption(game, 2);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
@ -220,42 +224,36 @@ describe("Fiery Fallout - Mystery Encounter", () => {
describe("Option 3 - use FIRE types", () => {
it("should have the correct properties", () => {
const option1 = FieryFalloutEncounter.options[0];
expect(option1.optionMode).toBe(EncounterOptionMode.DEFAULT);
const option1 = FieryFalloutEncounter.options[2];
expect(option1.optionMode).toBe(EncounterOptionMode.DISABLED_OR_SPECIAL);
expect(option1.dialogue).toBeDefined();
expect(option1.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:1:label`,
buttonTooltip: `${namespace}:option:1:tooltip`,
buttonLabel: `${namespace}:option:3:label`,
buttonTooltip: `${namespace}:option:3:tooltip`,
disabledButtonTooltip: `${namespace}:option:3:disabled_tooltip`,
selected: [
{
text: `${namespace}:option:1:selected`,
text: `${namespace}:option:3:selected`,
},
],
});
});
it("should damage all (allowed in battle) party PKM by 25%", async () => {
game.override.startingWave(33);
it("should give charcoal to lead pokemon", async () => {
await game.runToMysteryEncounter(defaultParty);
const party = scene.getParty();
const abra = party.find((pkm) => pkm.species.speciesId === Species.ABRA);
vi.spyOn(abra, "isAllowedInBattle").mockReturnValue(false);
await runSelectMysteryEncounterOption(game, 3);
// await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
const allowedPkm = party.filter((pkm) => pkm.isAllowedInBattle());
const notAllowedPkm = party.filter((pkm) => !pkm.isAllowedInBattle());
allowedPkm.forEach((pkm) =>
expect(pkm.hp, `${pkm.name} should have receivd 25% damage: ${pkm.hp} / ${pkm.getMaxHp()} HP`).toBe(pkm.getMaxHp() - Math.floor(pkm.getMaxHp() * 0.25))
);
notAllowedPkm.forEach((pkm) => expect(pkm.hp, `${pkm.name} should be full hp: ${pkm.hp} / ${pkm.getMaxHp()} HP`).toBe(pkm.getMaxHp()));
const leadPokemonId = scene.getParty()?.[0].id;
const leadPokemonItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& (m as PokemonHeldItemModifier).pokemonId === leadPokemonId, true) as PokemonHeldItemModifier[];
const charcoal = leadPokemonItems.find(i => i.type.name === "Charcoal");
expect(charcoal).toBeDefined;
});
it("should leave encounter without battle", async () => {
game.override.startingWave(33);
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(defaultParty);

View File

@ -31,6 +31,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.trainerWave(false);
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
new Map<Biome, MysteryEncounterType[]>([

View File

@ -24,6 +24,7 @@ describe("Mystery Encounters", () => {
game.override.startingWave(11);
game.override.mysteryEncounterChance(100);
game.override.mysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS);
game.override.trainerWave(false);
});
it("Spawns a mystery encounter", async () => {

View File

@ -60,6 +60,17 @@ export class OverridesHelper {
return spy;
}
/**
* Override each wave to have or not have standard trainer battles
* @returns spy instance
* @param isTrainer
*/
trainerWave(isTrainer: boolean): MockInstance {
const spy = vi.spyOn(this.game.scene.gameMode, "isWaveTrainer").mockReturnValue(isTrainer);
this.log(`${isTrainer? "forcing" : "ignoring"} trainer waves!`);
return spy;
}
/**
* Override the weather (type)
* @param type weather type to set