[ME] update CombinationRequirements to allow AND or OR combinations

* refactor: CombinationPokemonRequirement `.Some()` and `Every()`

* chore: rename `orRequirements` to `requirements`

* fix: returns of `Some()` and `Any()`

* apply `Some()` / `Any()` pattern to `CombinationSceneRequirement` too
This commit is contained in:
flx-sta 2024-10-21 13:16:36 -07:00 committed by GitHub
parent 96d8a2127a
commit a3fb7bc829
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 125 additions and 57 deletions

View File

@ -135,9 +135,11 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new CombinationPokemonRequirement(
.withPrimaryPokemonRequirement(
CombinationPokemonRequirement.Some(
new MoveRequirement(EXTORTION_MOVES, true),
new AbilityRequirement(EXTORTION_ABILITIES, true))
new AbilityRequirement(EXTORTION_ABILITIES, true)
)
)
.withDialogue({
buttonLabel: `${namespace}:option.2.label`,

View File

@ -193,12 +193,14 @@ const WAVE_LEVEL_BREAKPOINTS = [ 30, 50, 70, 100, 120, 140, 160 ];
export const BugTypeSuperfanEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.BUG_TYPE_SUPERFAN)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withPrimaryPokemonRequirement(new CombinationPokemonRequirement(
.withPrimaryPokemonRequirement(
CombinationPokemonRequirement.Some(
// Must have at least 1 Bug type on team, OR have a bug item somewhere on the team
new HeldItemRequirement([ "BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier" ], 1),
new AttackTypeBoosterHeldItemTypeRequirement(Type.BUG, 1),
new TypeRequirement(Type.BUG, false, 1)
))
)
)
.withMaxAllowedEncounters(1)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withIntroSpriteConfigs([]) // These are set in onInit()
@ -405,11 +407,13 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
.build())
.withOption(MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withPrimaryPokemonRequirement(new CombinationPokemonRequirement(
.withPrimaryPokemonRequirement(
CombinationPokemonRequirement.Some(
// Meets one or both of the below reqs
new HeldItemRequirement([ "BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier" ], 1),
new AttackTypeBoosterHeldItemTypeRequirement(Type.BUG, 1)
))
)
)
.withDialogue({
buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`,

View File

@ -45,10 +45,13 @@ export const DelibirdyEncounter: MysteryEncounter =
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new MoneyRequirement(0, DELIBIRDY_MONEY_PRICE_MULTIPLIER)) // Must have enough money for it to spawn at the very least
.withPrimaryPokemonRequirement(new CombinationPokemonRequirement( // Must also have either option 2 or 3 available to spawn
.withPrimaryPokemonRequirement(
CombinationPokemonRequirement.Some(
// Must also have either option 2 or 3 available to spawn
new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS),
new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true)
))
)
)
.withIntroSpriteConfigs([
{
spriteKey: "",

View File

@ -215,10 +215,12 @@ export const FieryFalloutEncounter: MysteryEncounter =
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new CombinationPokemonRequirement(
.withPrimaryPokemonRequirement(
CombinationPokemonRequirement.Some(
new TypeRequirement(Type.FIRE, true, 1),
new AbilityRequirement(FIRE_RESISTANT_ABILITIES, true)
)) // Will set option3PrimaryName dialogue token automatically
)
) // Will set option3PrimaryName dialogue token automatically
.withDialogue({
buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`,

View File

@ -37,31 +37,58 @@ export abstract class EncounterSceneRequirement implements EncounterRequirement
abstract getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string];
}
/**
* Combination of multiple {@linkcode EncounterSceneRequirement | EncounterSceneRequirements} (OR/AND possible. See {@linkcode isAnd})
*/
export class CombinationSceneRequirement extends EncounterSceneRequirement {
orRequirements: EncounterSceneRequirement[];
/** If `true`, all requirements must be met (AND). If `false`, any requirement must be met (OR) */
private isAnd: boolean;
requirements: EncounterSceneRequirement[];
constructor(... orRequirements: EncounterSceneRequirement[]) {
public static Some(...requirements: EncounterSceneRequirement[]): CombinationSceneRequirement {
return new CombinationSceneRequirement(false, ...requirements);
}
public static Every(...requirements: EncounterSceneRequirement[]): CombinationSceneRequirement {
return new CombinationSceneRequirement(true, ...requirements);
}
private constructor(isAnd: boolean, ...requirements: EncounterSceneRequirement[]) {
super();
this.orRequirements = orRequirements;
this.isAnd = isAnd;
this.requirements = requirements;
}
/**
* Checks if all/any requirements are met (depends on {@linkcode isAnd})
* @param scene The {@linkcode BattleScene} to check against
* @returns true if all/any requirements are met (depends on {@linkcode isAnd})
*/
override meetsRequirement(scene: BattleScene): boolean {
for (const req of this.orRequirements) {
if (req.meetsRequirement(scene)) {
return true;
}
}
return false;
return this.isAnd
? this.requirements.every(req => req.meetsRequirement(scene))
: this.requirements.some(req => req.meetsRequirement(scene));
}
/**
* Retrieves a dialogue token key/value pair for the given {@linkcode EncounterSceneRequirement | requirements}.
* @param scene The {@linkcode BattleScene} to check against
* @param pokemon The {@linkcode PlayerPokemon} to check against
* @returns A dialogue token key/value pair
* @throws An {@linkcode Error} if {@linkcode isAnd} is `true` (not supported)
*/
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
for (const req of this.orRequirements) {
if (this.isAnd) {
throw new Error("Not implemented (Sorry)");
} else {
for (const req of this.requirements) {
if (req.meetsRequirement(scene)) {
return req.getDialogueToken(scene, pokemon);
}
}
return this.orRequirements[0].getDialogueToken(scene, pokemon);
return this.requirements[0].getDialogueToken(scene, pokemon);
}
}
}
@ -90,44 +117,74 @@ export abstract class EncounterPokemonRequirement implements EncounterRequiremen
abstract getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string];
}
/**
* Combination of multiple {@linkcode EncounterPokemonRequirement | EncounterPokemonRequirements} (OR/AND possible. See {@linkcode isAnd})
*/
export class CombinationPokemonRequirement extends EncounterPokemonRequirement {
orRequirements: EncounterPokemonRequirement[];
/** If `true`, all requirements must be met (AND). If `false`, any requirement must be met (OR) */
private isAnd: boolean;
private requirements: EncounterPokemonRequirement[];
constructor(...orRequirements: EncounterPokemonRequirement[]) {
public static Some(...requirements: EncounterPokemonRequirement[]): CombinationPokemonRequirement {
return new CombinationPokemonRequirement(false, ...requirements);
}
public static Every(...requirements: EncounterPokemonRequirement[]): CombinationPokemonRequirement {
return new CombinationPokemonRequirement(true, ...requirements);
}
private constructor(isAnd: boolean, ...requirements: EncounterPokemonRequirement[]) {
super();
this.isAnd = isAnd;
this.invertQuery = false;
this.minNumberOfPokemon = 1;
this.orRequirements = orRequirements;
this.requirements = requirements;
}
/**
* Checks if all/any requirements are met (depends on {@linkcode isAnd})
* @param scene The {@linkcode BattleScene} to check against
* @returns true if all/any requirements are met (depends on {@linkcode isAnd})
*/
override meetsRequirement(scene: BattleScene): boolean {
for (const req of this.orRequirements) {
if (req.meetsRequirement(scene)) {
return true;
}
}
return false;
return this.isAnd
? this.requirements.every(req => req.meetsRequirement(scene))
: this.requirements.some(req => req.meetsRequirement(scene));
}
/**
* Queries the players party for all party members that are compatible with all/any requirements (depends on {@linkcode isAnd})
* @param partyPokemon The party of {@linkcode PlayerPokemon}
* @returns All party members that are compatible with all/any requirements (depends on {@linkcode isAnd})
*/
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
for (const req of this.orRequirements) {
const result = req.queryParty(partyPokemon);
if (result?.length > 0) {
return result;
if (this.isAnd) {
return this.requirements.reduce((relevantPokemon, req) => req.queryParty(relevantPokemon), partyPokemon);
} else {
const matchingRequirement = this.requirements.find(req => req.queryParty(partyPokemon).length > 0);
return matchingRequirement ? matchingRequirement.queryParty(partyPokemon) : [];
}
}
return [];
}
/**
* Retrieves a dialogue token key/value pair for the given {@linkcode EncounterPokemonRequirement | requirements}.
* @param scene The {@linkcode BattleScene} to check against
* @param pokemon The {@linkcode PlayerPokemon} to check against
* @returns A dialogue token key/value pair
* @throws An {@linkcode Error} if {@linkcode isAnd} is `true` (not supported)
*/
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
for (const req of this.orRequirements) {
if (this.isAnd) {
throw new Error("Not implemented (Sorry)");
} else {
for (const req of this.requirements) {
if (req.meetsRequirement(scene)) {
return req.getDialogueToken(scene, pokemon);
}
}
return this.orRequirements[0].getDialogueToken(scene, pokemon);
return this.requirements[0].getDialogueToken(scene, pokemon);
}
}
}