[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( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new CombinationPokemonRequirement( .withPrimaryPokemonRequirement(
new MoveRequirement(EXTORTION_MOVES, true), CombinationPokemonRequirement.Some(
new AbilityRequirement(EXTORTION_ABILITIES, true)) new MoveRequirement(EXTORTION_MOVES, true),
new AbilityRequirement(EXTORTION_ABILITIES, true)
)
) )
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.2.label`, 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 = export const BugTypeSuperfanEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.BUG_TYPE_SUPERFAN) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.BUG_TYPE_SUPERFAN)
.withEncounterTier(MysteryEncounterTier.GREAT) .withEncounterTier(MysteryEncounterTier.GREAT)
.withPrimaryPokemonRequirement(new CombinationPokemonRequirement( .withPrimaryPokemonRequirement(
// Must have at least 1 Bug type on team, OR have a bug item somewhere on the team CombinationPokemonRequirement.Some(
new HeldItemRequirement([ "BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier" ], 1), // Must have at least 1 Bug type on team, OR have a bug item somewhere on the team
new AttackTypeBoosterHeldItemTypeRequirement(Type.BUG, 1), new HeldItemRequirement([ "BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier" ], 1),
new TypeRequirement(Type.BUG, false, 1) new AttackTypeBoosterHeldItemTypeRequirement(Type.BUG, 1),
)) new TypeRequirement(Type.BUG, false, 1)
)
)
.withMaxAllowedEncounters(1) .withMaxAllowedEncounters(1)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withIntroSpriteConfigs([]) // These are set in onInit() .withIntroSpriteConfigs([]) // These are set in onInit()
@ -405,11 +407,13 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
.build()) .build())
.withOption(MysteryEncounterOptionBuilder .withOption(MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withPrimaryPokemonRequirement(new CombinationPokemonRequirement( .withPrimaryPokemonRequirement(
// Meets one or both of the below reqs CombinationPokemonRequirement.Some(
new HeldItemRequirement([ "BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier" ], 1), // Meets one or both of the below reqs
new AttackTypeBoosterHeldItemTypeRequirement(Type.BUG, 1) new HeldItemRequirement([ "BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier" ], 1),
)) new AttackTypeBoosterHeldItemTypeRequirement(Type.BUG, 1)
)
)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,

View File

@ -45,10 +45,13 @@ export const DelibirdyEncounter: MysteryEncounter =
.withEncounterTier(MysteryEncounterTier.GREAT) .withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .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 .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(
new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS), CombinationPokemonRequirement.Some(
new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true) // 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([ .withIntroSpriteConfigs([
{ {
spriteKey: "", spriteKey: "",

View File

@ -215,10 +215,12 @@ export const FieryFalloutEncounter: MysteryEncounter =
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new CombinationPokemonRequirement( .withPrimaryPokemonRequirement(
new TypeRequirement(Type.FIRE, true, 1), CombinationPokemonRequirement.Some(
new AbilityRequirement(FIRE_RESISTANT_ABILITIES, true) new TypeRequirement(Type.FIRE, true, 1),
)) // Will set option3PrimaryName dialogue token automatically new AbilityRequirement(FIRE_RESISTANT_ABILITIES, true)
)
) // Will set option3PrimaryName dialogue token automatically
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`, 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]; 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 { 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(); 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 { override meetsRequirement(scene: BattleScene): boolean {
for (const req of this.orRequirements) { return this.isAnd
if (req.meetsRequirement(scene)) { ? this.requirements.every(req => req.meetsRequirement(scene))
return true; : this.requirements.some(req => req.meetsRequirement(scene));
}
}
return false;
} }
/**
* 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] { override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
for (const req of this.orRequirements) { if (this.isAnd) {
if (req.meetsRequirement(scene)) { throw new Error("Not implemented (Sorry)");
return req.getDialogueToken(scene, pokemon); } 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]; 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 { 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(); super();
this.isAnd = isAnd;
this.invertQuery = false; this.invertQuery = false;
this.minNumberOfPokemon = 1; 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 { override meetsRequirement(scene: BattleScene): boolean {
for (const req of this.orRequirements) { return this.isAnd
if (req.meetsRequirement(scene)) { ? this.requirements.every(req => req.meetsRequirement(scene))
return true; : this.requirements.some(req => req.meetsRequirement(scene));
}
}
return false;
} }
/**
* 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[] { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
for (const req of this.orRequirements) { if (this.isAnd) {
const result = req.queryParty(partyPokemon); return this.requirements.reduce((relevantPokemon, req) => req.queryParty(relevantPokemon), partyPokemon);
if (result?.length > 0) { } else {
return result; 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] { override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
for (const req of this.orRequirements) { if (this.isAnd) {
if (req.meetsRequirement(scene)) { throw new Error("Not implemented (Sorry)");
return req.getDialogueToken(scene, pokemon); } 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);
}
} }
} }