lost-at-sea encounter - further progress

This commit is contained in:
Felix Staud 2024-07-12 14:07:33 -07:00
parent 7164b0afe4
commit c9cbdd7d44
5 changed files with 134 additions and 184 deletions

View File

@ -1,21 +1,12 @@
import { Type } from "#app/data/type.js";
import { Moves } from "#app/enums/moves.js";
import { Species } from "#app/enums/species.js";
import { PlayerPokemon } from "#app/field/pokemon.js";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "../../../battle-scene";
import MysteryEncounter, {
MysteryEncounterBuilder,
MysteryEncounterTier,
} from "../mystery-encounter";
import {
EncounterOptionMode,
MysteryEncounterOptionBuilder,
} from "../mystery-encounter-option";
import {
applyDamageToPokemon,
leaveEncounterWithoutBattle,
setEncounterExp,
} from "../mystery-encounter-utils";
import MysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier } from "../mystery-encounter";
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { applyDamageToPokemon, leaveEncounterWithoutBattle, setEncounterExp } from "../mystery-encounter-utils";
/**
* Damage percentage taken when wandering aimlessly.
@ -26,7 +17,7 @@ const DAMAGE_PERCENTAGE: number = 30; // 0 - 100
/** The i18n namespace for the encounter */
const namepsace = "mysteryEncounter:lostAtSea";
let waterPkm: PlayerPokemon;
let surfablePkm: PlayerPokemon;
let flyingPkm: PlayerPokemon;
/**
@ -34,137 +25,108 @@ let flyingPkm: PlayerPokemon;
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/9 | GitHub Issue #9}
* @see For biome requirements check [mysteryEncountersByBiome](../mystery-encounters.ts)
*/
export const LostAtSeaEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.LOST_AT_SEA)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(11, 179)
.withIntroSpriteConfigs([
{
fileRoot: "pokemon",
spriteKey: `${Species.GYARADOS}`,
hasShadow: false,
scale: 4,
y: 100,
x: 130,
tint: 0.75,
alpha: 0.25,
},
])
.withIntroDialogue([
{
text: `${namepsace}:intro`,
},
])
.withOnInit((scene: BattleScene) => {
const allowedPokemon = scene
.getParty()
.filter((p) => p.isAllowedInBattle());
const { mysteryEncounter } = scene.currentBattle;
export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.LOST_AT_SEA)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(11, 179)
.withIntroSpriteConfigs([
{
fileRoot: "pokemon",
spriteKey: `${Species.GYARADOS}`,
hasShadow: false,
scale: 4,
y: 100,
x: 130,
tint: 0.75,
alpha: 0.25,
},
])
.withIntroDialogue([{ text: `${namepsace}:intro` }])
.withOnInit((scene: BattleScene) => {
// const allowedPokemon = scene.getParty().filter((p) => p.isAllowedInBattle());
const { mysteryEncounter } = scene.currentBattle;
mysteryEncounter.setDialogueToken(
"damagePercentage",
String(DAMAGE_PERCENTAGE)
);
mysteryEncounter.setDialogueToken("damagePercentage", String(DAMAGE_PERCENTAGE));
// check for water pokemon
waterPkm = findPokemonByType(allowedPokemon, Type.WATER);
mysteryEncounter.setDialogueToken("waterPkm", waterPkm?.name ?? "<NONE>");
// check for water pokemon
// surfablePkm = findPokemonThatCanLearnMove(allowedPokemon, Type.WATER);
// mysteryEncounter.setDialogueToken("waterPkm", surfablePkm?.name ?? "");
// check for flying pokemon
flyingPkm = findPokemonByType(allowedPokemon, Type.FLYING);
mysteryEncounter.setDialogueToken(
"flyingPkm",
flyingPkm?.name ?? "<NONE>"
);
// check for flying pokemon
// flyingPkm = findPokemonThatCanLearnMove(allowedPokemon, Type.FLYING);
// mysteryEncounter.setDialogueToken("flyingPkm", flyingPkm?.name ?? "");
return true;
})
.withTitle(`${namepsace}:title`)
.withDescription(`${namepsace}:description`)
.withQuery(`${namepsace}:query`)
.withOption(
/**
* Option 1: Use a (non fainted) water pokemon to guide you back.
* Receives EXP similar to defeating a Lapras
*/
new MysteryEncounterOptionBuilder()
.withPokemonTypeRequirement(Type.WATER, true, 1)
.withOptionMode(EncounterOptionMode.DISABLED_OR_DEFAULT)
.withDialogue({
buttonLabel: `${namepsace}:option:1:label`,
buttonTooltip: `${namepsace}:option:1:tooltip`,
selected: [
{
text: `${namepsace}:option:1:selected`,
},
],
})
.withOptionPhase(async (scene: BattleScene) =>
handleGuidingOptionPhase(scene, waterPkm)
)
.build()
)
.withOption(
/**
* Option 2: Use a (non fainted) flying pokemon to guide you back.
* Receives EXP similar to defeating a Lapras
*/
new MysteryEncounterOptionBuilder()
.withPokemonTypeRequirement(Type.FLYING, true, 1)
.withOptionMode(EncounterOptionMode.DISABLED_OR_DEFAULT)
.withDialogue({
buttonLabel: `${namepsace}:option:2:label`,
buttonTooltip: `${namepsace}:option:2:tooltip`,
selected: [
{
text: `${namepsace}:option:2:selected`,
},
],
})
.withOptionPhase(async (scene: BattleScene) =>
handleGuidingOptionPhase(scene, flyingPkm)
)
.build()
)
.withSimpleOption(
/**
* Option 3: Wander aimlessly. All pokemons lose {@linkcode DAMAGE_PERCENTAGE}}% of their HP (or KO on 0 HP).
*/
{
buttonLabel: `${namepsace}:option:3:label`,
buttonTooltip: `${namepsace}:option:3:tooltip`,
return true;
})
.withTitle(`${namepsace}:title`)
.withDescription(`${namepsace}:description`)
.withQuery(`${namepsace}:query`)
.withOption(
/**
* Option 1: Use a (non fainted) water pokemon to guide you back.
* Receives EXP similar to defeating a Lapras
*/
new MysteryEncounterOptionBuilder()
.withPokemonCanLearnMoveRequirement(Moves.SURF)
.withOptionMode(EncounterOptionMode.DISABLED_OR_DEFAULT)
.withDialogue({
buttonLabel: `${namepsace}:option:1:label`,
buttonTooltip: `${namepsace}:option:1:tooltip`,
selected: [
{
text: `${namepsace}:option:3:selected`,
text: `${namepsace}:option:1:selected`,
},
],
},
async (scene: BattleScene) => {
const allowedPokemon = scene
.getParty()
.filter((p) => p.isAllowedInBattle());
})
.withOptionPhase(async (scene: BattleScene) => handleGuidingOptionPhase(scene, surfablePkm))
.build()
)
.withOption(
/**
* Option 2: Use a (non fainted) flying pokemon to guide you back.
* Receives EXP similar to defeating a Lapras
*/
new MysteryEncounterOptionBuilder()
.withPokemonTypeRequirement(Type.FLYING, true, 1)
.withOptionMode(EncounterOptionMode.DISABLED_OR_DEFAULT)
.withDialogue({
buttonLabel: `${namepsace}:option:2:label`,
buttonTooltip: `${namepsace}:option:2:tooltip`,
selected: [
{
text: `${namepsace}:option:2:selected`,
},
],
})
.withOptionPhase(async (scene: BattleScene) => handleGuidingOptionPhase(scene, flyingPkm))
.build()
)
.withSimpleOption(
/**
* Option 3: Wander aimlessly. All pokemons lose {@linkcode DAMAGE_PERCENTAGE}}% of their HP (or KO on 0 HP).
*/
{
buttonLabel: `${namepsace}:option:3:label`,
buttonTooltip: `${namepsace}:option:3:tooltip`,
selected: [
{
text: `${namepsace}:option:3:selected`,
},
],
},
async (scene: BattleScene) => {
const allowedPokemon = scene.getParty().filter((p) => p.isAllowedInBattle());
allowedPokemon.forEach((pkm) => {
const percentage = DAMAGE_PERCENTAGE / 100;
const damage = Math.floor(pkm.getMaxHp() * percentage);
return applyDamageToPokemon(pkm, damage);
});
leaveEncounterWithoutBattle(scene);
return true;
}
)
.build();
allowedPokemon.forEach((pkm) => {
const percentage = DAMAGE_PERCENTAGE / 100;
const damage = Math.floor(pkm.getMaxHp() * percentage);
return applyDamageToPokemon(pkm, damage);
});
leaveEncounterWithoutBattle(scene);
/**
* Find a pokemon inside the given party by a given type
*
* @param party player pokemon party
* @param type type to search for
* @returns
*/
function findPokemonByType(party: PlayerPokemon[], type: Type) {
return party.find((p) => p.getTypes(true).includes(type));
}
return true;
}
)
.build();
/**
* Generic handler for using a guiding pokemon to guide you back.
@ -172,19 +134,14 @@ function findPokemonByType(party: PlayerPokemon[], type: Type) {
* @param scene Battle scene
* @param guidePokemon pokemon choosen as a guide
*/
function handleGuidingOptionPhase(
scene: BattleScene,
guidePokemon: PlayerPokemon
) {
function handleGuidingOptionPhase(scene: BattleScene, guidePokemon: PlayerPokemon) {
/** Base EXP value for guiding pokemon. Currently Lapras base-value */
const baseExpValue: number = 187;
if (guidePokemon) {
setEncounterExp(scene, guidePokemon.id, baseExpValue, true);
} else {
console.warn(
"Lost at sea: No guide pokemon found but pokemon guides player. huh!?"
);
console.warn("Lost at sea: No guide pokemon found but pokemon guides player. huh!?");
}
leaveEncounterWithoutBattle(scene);

View File

@ -1,9 +1,11 @@
import { OptionTextDisplay } from "#app/data/mystery-encounters/mystery-encounter-dialogue";
import { Moves } from "#app/enums/moves.js";
import { PlayerPokemon } from "#app/field/pokemon";
import BattleScene from "../../battle-scene";
import * as Utils from "../../utils";
import { Type } from "../type";
import { EncounterPokemonRequirement, EncounterSceneRequirement, MoneyRequirement, TypeRequirement } from "./mystery-encounter-requirements";
import { CanLearnMoveRequirement, CanlearnMoveRequirementOptions } from "./requirements/can-learn-move-requirement";
export enum EncounterOptionMode {
/** Default style */
@ -194,8 +196,18 @@ export class MysteryEncounterOptionBuilder implements Partial<MysteryEncounterOp
* @returns
*/
withPokemonTypeRequirement(type: Type | Type[], excludeFainted?: boolean, minNumberOfPokemon?: number, invertQuery?: boolean) {
const types = Array.isArray(type) ? type : [type];
return this.withPrimaryPokemonRequirement(new TypeRequirement(types, excludeFainted, minNumberOfPokemon, invertQuery));
return this.withPrimaryPokemonRequirement(new TypeRequirement(type, excludeFainted, minNumberOfPokemon, invertQuery));
}
/**
* Player is required to have a pokemon that can learn a certain move/moveset
*
* @param move the required move/moves
* @param options see {@linkcode CanlearnMoveRequirementOptions}
* @returns
*/
withPokemonCanLearnMoveRequirement(move: Moves | Moves[], options?: CanlearnMoveRequirementOptions) {
return this.withPrimaryPokemonRequirement(new CanLearnMoveRequirement(move, options));
}
withSecondaryPokemonRequirement(requirement: EncounterPokemonRequirement, excludePrimaryFromSecondaryRequirements?: boolean): this & Required<Pick<MysteryEncounterOption, "secondaryPokemonRequirements">> {

View File

@ -1,15 +1,16 @@
import IMysteryEncounter from "./mystery-encounter";
import { Biome } from "#enums/biome";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { DarkDealEncounter } from "./encounters/dark-deal-encounter";
import { DepartmentStoreSaleEncounter } from "./encounters/department-store-sale-encounter";
import { FieldTripEncounter } from "./encounters/field-trip-encounter";
import { FightOrFlightEncounter } from "./encounters/fight-or-flight-encounter";
import { LostAtSeaEncounter } from "./encounters/lost-at-sea-encounter";
import { MysteriousChallengersEncounter } from "./encounters/mysterious-challengers-encounter";
import { MysteriousChestEncounter } from "./encounters/mysterious-chest-encounter";
import { FightOrFlightEncounter } from "./encounters/fight-or-flight-encounter";
import { TrainingSessionEncounter } from "./encounters/training-session-encounter";
import { Biome } from "#enums/biome";
import { SleepingSnorlaxEncounter } from "./encounters/sleeping-snorlax-encounter";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { DepartmentStoreSaleEncounter } from "./encounters/department-store-sale-encounter";
import { ShadyVitaminDealerEncounter } from "./encounters/shady-vitamin-dealer-encounter";
import { LostAtSeaEncounter } from "./encounters/lost-at-sea-encounter";
import { SleepingSnorlaxEncounter } from "./encounters/sleeping-snorlax-encounter";
import { TrainingSessionEncounter } from "./encounters/training-session-encounter";
import IMysteryEncounter from "./mystery-encounter";
// Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / 256
export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1;

View File

@ -7,7 +7,7 @@ import { EncounterPokemonRequirement } from "../mystery-encounter-requirements";
/**
* {@linkcode CanLearnMoveRequirement} options
*/
interface Options {
export interface CanlearnMoveRequirementOptions {
excludeLevelMoves?: boolean;
excludeTmMoves?: boolean;
excludeEggMoves?: boolean;
@ -26,20 +26,11 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement {
private readonly excludeEggMoves?: boolean;
private readonly includeFainted?: boolean;
constructor(requiredMoves: Moves | Moves[], options: Options = {}) {
constructor(requiredMoves: Moves | Moves[], options: CanlearnMoveRequirementOptions = {}) {
super();
this.requiredMoves = Array.isArray(requiredMoves)
? requiredMoves
: [requiredMoves];
this.requiredMoves = Array.isArray(requiredMoves) ? requiredMoves : [requiredMoves];
const {
excludeLevelMoves,
excludeTmMoves,
excludeEggMoves,
includeFainted,
minNumberOfPokemon,
invertQuery,
} = options;
const { excludeLevelMoves, excludeTmMoves, excludeEggMoves, includeFainted, minNumberOfPokemon, invertQuery } = options;
this.excludeLevelMoves = excludeLevelMoves ?? false;
this.excludeTmMoves = excludeTmMoves ?? false;
@ -50,11 +41,7 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement {
}
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) {
return false;
@ -67,26 +54,19 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) =>
// every required move should be included
this.requiredMoves.every((requiredMove) =>
this.getAllPokemonMoves(pokemon).includes(requiredMove)
)
this.requiredMoves.every((requiredMove) => this.getAllPokemonMoves(pokemon).includes(requiredMove))
);
} else {
return partyPokemon.filter(
(pokemon) =>
// none of the "required" moves should be included
!this.requiredMoves.some((requiredMove) =>
this.getAllPokemonMoves(pokemon).includes(requiredMove)
)
!this.requiredMoves.some((requiredMove) => this.getAllPokemonMoves(pokemon).includes(requiredMove))
);
}
}
override getDialogueToken(
_scene: BattleScene,
_pokemon?: PlayerPokemon
): [string, string] {
return ["requiredMoves", this.requiredMoves.join(", ")];
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
return pokemon ? ["pokemonCanLearnMove", pokemon.name] : null;
}
private getPokemonLevelMoves(pkm: PlayerPokemon): Moves[] {

View File

@ -6,10 +6,10 @@ export const lostAtSea = {
query: "What will you do?",
option: {
1: {
label: "Use @ec{waterPkm}", // pkm has to be of type water
label: "Use @ec{pokemonCanLearnMove}", // pkm has to be of type water
tooltip:
"Use @ec{waterPkm} to guide you back. @ec{waterPkm} earns EXP as if having defeated a Lapras.",
selected: "@ec{waterPkm} guides you back and earns EXP.",
"Use @ec{pokemonCanLearnMove} to guide you back. @ec{pokemonCanLearnMove} earns EXP as if having defeated a Lapras.",
selected: "@ec{pokemonCanLearnMove} guides you back and earns EXP.",
},
2: {
label: "Use @ec{flyingPkm}", // pkm has to be of type flying