Merge pull request #4135 from ben-lear/mystery-encounters-feedback

update tests for MEs
This commit is contained in:
ImperialSympathizer 2024-09-09 11:58:05 -04:00 committed by GitHub
commit e5f598d55f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 52 additions and 40 deletions

View File

@ -897,6 +897,12 @@ export default class BattleScene extends SceneBase {
return pokemon;
}
/**
* Removes a 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
* @param pokemon
* @param destroy - Default true. If true, will destroy the Pokemon object after removing
*/
removePokemonFromPlayerParty(pokemon: PlayerPokemon, destroy: boolean = true) {
if (!pokemon) {
return;
@ -1113,7 +1119,7 @@ export default class BattleScene extends SceneBase {
}
}
newBattle(waveIndex?: integer, battleType?: BattleType, trainerData?: TrainerData, double?: boolean, mysteryEncounter?: MysteryEncounter): Battle | null {
newBattle(waveIndex?: integer, battleType?: BattleType, trainerData?: TrainerData, double?: boolean, mysteryEncounterType?: MysteryEncounterType): Battle | null {
const _startingWave = Overrides.STARTING_WAVE_OVERRIDE || startingWave;
const newWaveIndex = waveIndex || ((this.currentBattle?.waveIndex || (_startingWave - 1)) + 1);
let newDouble: boolean | undefined;
@ -1236,7 +1242,7 @@ export default class BattleScene extends SceneBase {
// Disable double battle on mystery encounters (it may be re-enabled as part of encounter)
this.currentBattle.double = false;
this.executeWithSeedOffset(() => {
this.currentBattle.mysteryEncounter = this.getMysteryEncounter(mysteryEncounter);
this.currentBattle.mysteryEncounter = this.getMysteryEncounter(mysteryEncounterType);
}, this.currentBattle.waveIndex << 4);
}
@ -3016,16 +3022,16 @@ export default class BattleScene extends SceneBase {
/**
* Loads or generates a mystery encounter
* @param override - used to load session encounter when restarting game, etc.
* @param encounterType - used to load session encounter when restarting game, etc.
* @returns
*/
getMysteryEncounter(override: MysteryEncounter | undefined): MysteryEncounter {
getMysteryEncounter(encounterType?: MysteryEncounterType): MysteryEncounter {
// Loading override or session encounter
let encounter: MysteryEncounter | null;
if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE!)) {
encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE!];
} else {
encounter = override?.encounterType && override.encounterType >= 0 ? allMysteryEncounters[override.encounterType] : null;
encounter = !isNullOrUndefined(encounterType) ? allMysteryEncounters[encounterType!] : null;
}
// Check for queued encounters first

View File

@ -2,15 +2,15 @@ import { Abilities } from "#enums/abilities";
import { Type } from "#app/data/type";
export class MysteryEncounterPokemonData {
public spriteScale: number | undefined;
public ability: Abilities | undefined;
public passive: Abilities | undefined;
public spriteScale: number;
public ability: Abilities | -1;
public passive: Abilities | -1;
public types: Type[];
constructor(spriteScale?: number, ability?: Abilities, passive?: Abilities, types?: Type[]) {
this.spriteScale = spriteScale;
this.ability = ability;
this.passive = passive;
this.spriteScale = spriteScale ?? -1;
this.ability = ability ?? -1;
this.passive = passive ?? -1;
this.types = types ?? [];
}
}

View File

@ -5,7 +5,7 @@ import { variantData } from "#app/data/variant";
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "../ui/battle-info";
import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr } from "../data/move";
import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species";
import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils";
import { Constructor, randSeedInt } from "#app/utils";
import * as Utils from "../utils";
import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type";
import { getLevelTotalExp } from "../data/exp";
@ -223,6 +223,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.variant = this.shiny ? this.generateVariant() : 0;
}
this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData();
if (nature !== undefined) {
this.setNature(nature);
} else {
@ -250,7 +252,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
this.luck = (this.shiny ? this.variant + 1 : 0) + (this.fusionShiny ? this.fusionVariant + 1 : 0);
this.fusionLuck = this.luck;
this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData();
}
this.generateName();
@ -578,8 +579,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const formKey = this.getFormKey();
if (formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1 || formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1) {
return 1.5;
} else if (!isNullOrUndefined(this.mysteryEncounterPokemonData.spriteScale) && this.mysteryEncounterPokemonData.spriteScale !== 0) {
return this.mysteryEncounterPokemonData.spriteScale!;
} else if (this.mysteryEncounterPokemonData.spriteScale > 0) {
return this.mysteryEncounterPokemonData.spriteScale;
}
return 1;
}
@ -1150,7 +1151,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (Overrides.OPP_ABILITY_OVERRIDE && !this.isPlayer()) {
return allAbilities[Overrides.OPP_ABILITY_OVERRIDE];
}
if (this.mysteryEncounterPokemonData?.ability) {
if (this.mysteryEncounterPokemonData.ability !== -1) {
return allAbilities[this.mysteryEncounterPokemonData.ability];
}
if (this.isFusion()) {
@ -1177,7 +1178,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE && !this.isPlayer()) {
return allAbilities[Overrides.OPP_PASSIVE_ABILITY_OVERRIDE];
}
if (this.mysteryEncounterPokemonData?.passive) {
if (this.mysteryEncounterPokemonData.passive !== -1) {
return allAbilities[this.mysteryEncounterPokemonData.passive];
}

View File

@ -238,7 +238,7 @@ export class GameOverPhase extends BattlePhase {
gameVersion: this.scene.game.config.gameVersion,
timestamp: new Date().getTime(),
challenges: this.scene.gameMode.challenges.map(c => new ChallengeData(c)),
mysteryEncounter: this.scene.currentBattle.mysteryEncounter,
mysteryEncounterType: this.scene.currentBattle.mysteryEncounter?.encounterType,
mysteryEncounterSaveData: this.scene.mysteryEncounterSaveData
} as SessionSaveData;
}

View File

@ -47,7 +47,7 @@ import { ReloadSessionPhase } from "#app/phases/reload-session-phase";
import { RUN_HISTORY_LIMIT } from "#app/ui/run-history-ui-handler";
import { applySessionDataPatches, applySettingsDataPatches, applySystemDataPatches } from "./version-converter";
import { MysteryEncounterSaveData } from "../data/mystery-encounters/mystery-encounter-save-data";
import MysteryEncounter from "../data/mystery-encounters/mystery-encounter";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
export const defaultStarterSpecies: Species[] = [
Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE,
@ -132,7 +132,7 @@ export interface SessionSaveData {
gameVersion: string;
timestamp: integer;
challenges: ChallengeData[];
mysteryEncounter: MysteryEncounter;
mysteryEncounterType: MysteryEncounterType | -1; // Only defined when current wave is ME,
mysteryEncounterSaveData: MysteryEncounterSaveData;
}
@ -952,7 +952,7 @@ export class GameData {
gameVersion: scene.game.config.gameVersion,
timestamp: new Date().getTime(),
challenges: scene.gameMode.challenges.map(c => new ChallengeData(c)),
mysteryEncounter: scene.currentBattle.mysteryEncounter,
mysteryEncounterType: scene.currentBattle.mysteryEncounter?.encounterType,
mysteryEncounterSaveData: scene.mysteryEncounterSaveData
} as SessionSaveData;
}
@ -1050,8 +1050,8 @@ export class GameData {
const battleType = sessionData.battleType || 0;
const trainerConfig = sessionData.trainer ? trainerConfigs[sessionData.trainer.trainerType] : null;
const mysteryEncounterConfig = sessionData?.mysteryEncounter;
const battle = scene.newBattle(sessionData.waveIndex, battleType, sessionData.trainer, battleType === BattleType.TRAINER ? trainerConfig?.doubleOnly || sessionData.trainer?.variant === TrainerVariant.DOUBLE : sessionData.enemyParty.length > 1, mysteryEncounterConfig)!; // TODO: is this bang correct?
const mysteryEncounterType = sessionData.mysteryEncounterType !== -1 ? sessionData.mysteryEncounterType : undefined;
const battle = scene.newBattle(sessionData.waveIndex, battleType, sessionData.trainer, battleType === BattleType.TRAINER ? trainerConfig?.doubleOnly || sessionData.trainer?.variant === TrainerVariant.DOUBLE : sessionData.enemyParty.length > 1, mysteryEncounterType)!; // TODO: is this bang correct?
battle.enemyLevels = sessionData.enemyParty.map(p => p.level);
scene.arena.init();
@ -1263,8 +1263,8 @@ export class GameData {
return ret;
}
if (k === "mysteryEncounter") {
return new MysteryEncounter(v);
if (k === "mysteryEncounterType") {
return v as MysteryEncounterType;
}
if (k === "mysteryEncounterSaveData") {

View File

@ -185,7 +185,7 @@ describe("Absolute Avarice - Mystery Encounter", () => {
});
});
it("Should return 3 (2/5ths floored) berries if 8 were stolen", async () => {
it("Should return 3 (2/5ths floored) berries if 8 were stolen", {retry: 5}, async () => {
game.override.startingHeldItems([{name: "BERRY", count: 2, type: BerryType.SITRUS}, {name: "BERRY", count: 3, type: BerryType.GANLON}, {name: "BERRY", count: 3, type: BerryType.APICOT}]);
await game.runToMysteryEncounter(MysteryEncounterType.ABSOLUTE_AVARICE, defaultParty);
@ -201,7 +201,7 @@ describe("Absolute Avarice - Mystery Encounter", () => {
expect(berryCountAfter).toBe(3);
});
it("Should return 2 (2/5ths floored) berries if 7 were stolen", async () => {
it("Should return 2 (2/5ths floored) berries if 7 were stolen", {retry: 5}, async () => {
game.override.startingHeldItems([{name: "BERRY", count: 2, type: BerryType.SITRUS}, {name: "BERRY", count: 3, type: BerryType.GANLON}, {name: "BERRY", count: 2, type: BerryType.APICOT}]);
await game.runToMysteryEncounter(MysteryEncounterType.ABSOLUTE_AVARICE, defaultParty);

View File

@ -16,6 +16,7 @@ import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Moves } from "#enums/moves";
import { ShinyRateBoosterModifier } from "#app/modifier/modifier";
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
const namespace = "mysteryEncounter:offerYouCantRefuse";
/** Gyarados for Indimidate */
@ -203,6 +204,7 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => {
const expBefore = gyarados.exp;
await runMysteryEncounterToEnd(game, 2);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(gyarados.exp).toBe(expBefore + Math.floor(getPokemonSpecies(Species.LIEPARD).baseExp * defaultWave / 5 + 1));
});
@ -215,6 +217,7 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => {
const expBefore = abra.exp;
await runMysteryEncounterToEnd(game, 2);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(abra.exp).toBe(expBefore + Math.floor(getPokemonSpecies(Species.LIEPARD).baseExp * defaultWave / 5 + 1));
});

View File

@ -103,8 +103,7 @@ describe("Field Trip - Mystery Encounter", () => {
it("Should give no reward on incorrect option", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.FIELD_TRIP, defaultParty);
await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1, optionNo: 2 });
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
@ -114,8 +113,7 @@ describe("Field Trip - Mystery Encounter", () => {
it("Should give proper rewards on correct Physical move option", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.FIELD_TRIP, defaultParty);
await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1, optionNo: 1 });
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
@ -151,8 +149,7 @@ describe("Field Trip - Mystery Encounter", () => {
it("Should give no reward on incorrect option", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.FIELD_TRIP, defaultParty);
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1 });
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
@ -162,8 +159,7 @@ describe("Field Trip - Mystery Encounter", () => {
it("Should give proper rewards on correct Special move option", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.FIELD_TRIP, defaultParty);
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 2 });
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
@ -199,8 +195,7 @@ describe("Field Trip - Mystery Encounter", () => {
it("Should give no reward on incorrect option", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.FIELD_TRIP, defaultParty);
await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 });
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
@ -210,8 +205,7 @@ describe("Field Trip - Mystery Encounter", () => {
it("Should give proper rewards on correct Special move option", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.FIELD_TRIP, defaultParty);
await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 3 });
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;

View File

@ -14,6 +14,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
import BattleScene from "#app/battle-scene";
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
import { PartyExpPhase } from "#app/phases/party-exp-phase";
const namespace = "mysteryEncounter:lostAtSea";
/** Blastoise for surf. Pidgeot for fly. Abra for none. */
@ -133,6 +134,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
const expBefore = blastoise!.exp;
await runMysteryEncounterToEnd(game, 1);
await game.phaseInterceptor.to(PartyExpPhase);
expect(blastoise?.exp).toBe(expBefore + Math.floor(laprasSpecies.baseExp * defaultWave / 5 + 1));
});
@ -197,6 +199,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
const expBefore = pidgeot!.exp;
await runMysteryEncounterToEnd(game, 2);
await game.phaseInterceptor.to(PartyExpPhase);
expect(pidgeot!.exp).toBe(expBefore + Math.floor(laprasBaseExp * defaultWave / 5 + 1));
});

View File

@ -19,6 +19,8 @@ import { MovePhase } from "#app/phases/move-phase";
import { speciesEggMoves } from "#app/data/egg-moves";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { BerryType } from "#enums/berry-type";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat";
const namespace = "mysteryEncounter:uncommonBreed";
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
@ -119,6 +121,7 @@ describe("Uncommon Breed - Mystery Encounter", () => {
it("should start a fight against the boss", async () => {
const phaseSpy = vi.spyOn(scene, "pushPhase");
const unshiftPhaseSpy = vi.spyOn(scene, "unshiftPhase");
await game.runToMysteryEncounter(MysteryEncounterType.UNCOMMON_BREED, defaultParty);
const config = game.scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0];
@ -130,7 +133,9 @@ describe("Uncommon Breed - Mystery Encounter", () => {
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
expect(enemyField.length).toBe(1);
expect(enemyField[0].species.speciesId).toBe(speciesToSpawn);
expect(enemyField[0].summonData.statStages).toEqual([1, 1, 1, 1, 1, 0, 0]);
const statStagePhases = unshiftPhaseSpy.mock.calls.filter(p => p[0] instanceof StatStageChangePhase)[0][0] as any;
expect(statStagePhases.stats).toEqual([Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD]);
// Should have used its egg move pre-battle
const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof MovePhase).map(p => p[0]);