Challenge types for shop removals and reward table changes

This commit is contained in:
AJ Fontaine 2025-03-24 14:21:59 -04:00
parent 8ba733d0c9
commit 8d29c1fb31
2 changed files with 121 additions and 65 deletions

View File

@ -21,6 +21,7 @@ import { TypeColor, TypeShadow } from "#enums/color";
import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
import { pokemonFormChanges } from "#app/data/pokemon-forms";
import { ModifierTier } from "#app/modifier/modifier-tier";
import type { ModifierTypeKeys, RewardTableModification } from "#app/modifier/modifier-type";
/** A constant for the default max cost of the starting party before a run */
const DEFAULT_PARTY_MAX_COST = 10;
@ -105,6 +106,14 @@ export enum ChallengeType {
* Negates PP Usage
*/
NO_PP_USE,
/**
* Modifies reward table
*/
REWARD_TABLE_MODIFY,
/**
* Removes items from the shop
*/
SHOP_REMOVAL,
}
/**
@ -457,6 +466,14 @@ export abstract class Challenge {
applyNoPPUsage(_valid: Utils.BooleanHolder) {
return false;
}
applyRewardTableModify(_modifications: RewardTableModification[]) {
return false;
}
applyShopRemovals(_removals: ModifierTypeKeys[]) {
return false;
}
}
type ChallengeCondition = (data: GameData) => boolean;
@ -910,6 +927,14 @@ export class FreshStartChallenge extends Challenge {
return true;
}
override applyRewardTableModify(modifications: RewardTableModification[]): boolean {
modifications.push(
{ type: "EVIOLITE", tier: ModifierTier.ULTRA, maxWeight: 0 }, // No Eviolite
{ type: "MINI_BLACK_HOLE", tier: ModifierTier.MASTER, maxWeight: 0 }, // No MBH
);
return true;
}
override getDifficulty(): number {
return 0;
}
@ -1014,6 +1039,27 @@ export class MetronomeChallenge extends Challenge {
return true;
}
override applyRewardTableModify(modifications: RewardTableModification[]): boolean {
modifications.push(
{ type: "TM_COMMON", tier: ModifierTier.COMMON, maxWeight: 0 }, // Remove TMs
{ type: "ETHER", tier: ModifierTier.COMMON, maxWeight: 0 }, // Remove PP Restores
{ type: "MAX_ETHER", tier: ModifierTier.GREAT, maxWeight: 0 }, // Remove PP Restores
{ type: "ELIXIR", tier: ModifierTier.GREAT, maxWeight: 0 }, // Remove PP Restores
{ type: "MAX_ELIXIR", tier: ModifierTier.GREAT, maxWeight: 0 }, // Remove PP Restores
{ type: "PP_UP", tier: ModifierTier.GREAT, maxWeight: 0 }, // Remove PP Upgrades
{ type: "MEMORY_MUSHROOM", tier: ModifierTier.GREAT, maxWeight: 0 }, // Remove Mushrooms
{ type: "TM_GREAT", tier: ModifierTier.GREAT, maxWeight: 0 }, // Remove TMs
{ type: "TM_ULTRA", tier: ModifierTier.ULTRA, maxWeight: 0 }, // Remove TMs
{ type: "PP_MAX", tier: ModifierTier.ULTRA, maxWeight: 0 }, // Remove PP Upgrades
);
return true;
}
override applyShopRemovals(removals: ModifierTypeKeys[]): boolean {
removals.push("ETHER", "MAX_ETHER", "ELIXIR", "MAX_ELIXIR", "MEMORY_MUSHROOM");
return true;
}
static loadChallenge(source: MetronomeChallenge | any): MetronomeChallenge {
const newChallenge = new MetronomeChallenge();
newChallenge.value = source.value;
@ -1298,6 +1344,18 @@ export function applyChallenges(
valid: Utils.BooleanHolder,
): boolean;
export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.REWARD_TABLE_MODIFY,
modifications: RewardTableModification[],
): boolean;
export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.SHOP_REMOVAL,
removals: ModifierTypeKeys[],
): boolean;
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType, ...args: any[]): boolean {
let ret = false;
gameMode.challenges.forEach(c => {
@ -1354,6 +1412,12 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
case ChallengeType.NO_PP_USE:
ret ||= c.applyNoPPUsage(args[0]);
break;
case ChallengeType.REWARD_TABLE_MODIFY:
ret ||= c.applyRewardTableModify(args[0]);
break;
case ChallengeType.SHOP_REMOVAL:
ret ||= c.applyShopRemovals(args[0]);
break;
}
}
});

View File

@ -2468,30 +2468,14 @@ const modifierPool: ModifierPool = {
new WeightedModifierType(modifierTypes.LURE, lureWeightFunc(10, 2)),
new WeightedModifierType(modifierTypes.TEMP_STAT_STAGE_BOOSTER, 4),
new WeightedModifierType(modifierTypes.BERRY, 2),
new WeightedModifierType(
modifierTypes.TM_COMMON,
() => {
const noMoveLearning = new BooleanHolder(false);
applyChallenges(globalScene.gameMode, ChallengeType.NO_MOVE_LEARNING, noMoveLearning);
return noMoveLearning.value ? 0 : 2;
},
2,
),
new WeightedModifierType(modifierTypes.TM_COMMON, 2),
].map(m => {
m.setTier(ModifierTier.COMMON);
return m;
}),
[ModifierTier.GREAT]: [
new WeightedModifierType(modifierTypes.GREAT_BALL, () => (hasMaximumBalls(PokeballType.GREAT_BALL) ? 0 : 6), 6),
new WeightedModifierType(
modifierTypes.PP_UP,
() => {
const noMoveLearning = new BooleanHolder(false);
applyChallenges(globalScene.gameMode, ChallengeType.NO_PP_USE, noMoveLearning);
return noMoveLearning.value ? 0 : 2;
},
2,
),
new WeightedModifierType(modifierTypes.PP_UP, 2),
new WeightedModifierType(
modifierTypes.FULL_HEAL,
(party: Pokemon[]) => {
@ -2587,11 +2571,6 @@ const modifierPool: ModifierPool = {
new WeightedModifierType(
modifierTypes.ELIXIR,
(party: Pokemon[]) => {
const noMoveLearning = new BooleanHolder(false);
applyChallenges(globalScene.gameMode, ChallengeType.NO_PP_USE, noMoveLearning);
if (noMoveLearning.value) {
return 0;
}
const thresholdPartyMemberCount = Math.min(
party.filter(
p =>
@ -2611,11 +2590,6 @@ const modifierPool: ModifierPool = {
new WeightedModifierType(
modifierTypes.MAX_ELIXIR,
(party: Pokemon[]) => {
const noMoveLearning = new BooleanHolder(false);
applyChallenges(globalScene.gameMode, ChallengeType.NO_PP_USE, noMoveLearning);
if (noMoveLearning.value) {
return 0;
}
const thresholdPartyMemberCount = Math.min(
party.filter(
p =>
@ -2648,21 +2622,11 @@ const modifierPool: ModifierPool = {
2,
),
new WeightedModifierType(modifierTypes.SOOTHE_BELL, 2),
new WeightedModifierType(
modifierTypes.TM_GREAT,
() => {
const noMoveLearning = new BooleanHolder(false);
applyChallenges(globalScene.gameMode, ChallengeType.NO_MOVE_LEARNING, noMoveLearning);
return noMoveLearning.value ? 0 : 3;
},
3,
),
new WeightedModifierType(modifierTypes.TM_GREAT, 3),
new WeightedModifierType(
modifierTypes.MEMORY_MUSHROOM,
(party: Pokemon[]) => {
const noMoveLearning = new BooleanHolder(false);
applyChallenges(globalScene.gameMode, ChallengeType.NO_MOVE_LEARNING, noMoveLearning); // Yeah this is kind of dumb
if (noMoveLearning.value || !party.find(p => p.getLearnableLevelMoves().length)) {
if (!party.find(p => p.getLearnableLevelMoves().length)) {
return 0;
}
const highestPartyLevel = party
@ -2708,15 +2672,7 @@ const modifierPool: ModifierPool = {
new WeightedModifierType(modifierTypes.ULTRA_BALL, () => (hasMaximumBalls(PokeballType.ULTRA_BALL) ? 0 : 15), 15),
new WeightedModifierType(modifierTypes.MAX_LURE, lureWeightFunc(30, 4)),
new WeightedModifierType(modifierTypes.BIG_NUGGET, skipInLastClassicWaveOrDefault(12)),
new WeightedModifierType(
modifierTypes.PP_MAX,
() => {
const noMoveLearning = new BooleanHolder(false);
applyChallenges(globalScene.gameMode, ChallengeType.NO_PP_USE, noMoveLearning); // Yeah this is kind of dumb
return noMoveLearning.value ? 0 : 3;
},
3,
),
new WeightedModifierType(modifierTypes.PP_MAX, 3),
new WeightedModifierType(modifierTypes.MINT, 4),
new WeightedModifierType(
modifierTypes.RARE_EVOLUTION_ITEM,
@ -2731,7 +2687,7 @@ const modifierPool: ModifierPool = {
new WeightedModifierType(modifierTypes.AMULET_COIN, skipInLastClassicWaveOrDefault(3)),
new WeightedModifierType(modifierTypes.EVIOLITE, (party: Pokemon[]) => {
const { gameMode, gameData } = globalScene;
if (gameMode.isDaily || (!gameMode.isFreshStartChallenge() && gameData.isUnlocked(Unlockables.EVIOLITE))) {
if (gameMode.isDaily || gameData.isUnlocked(Unlockables.EVIOLITE)) {
return party.some(p => {
// Check if Pokemon's species (or fusion species, if applicable) can evolve or if they're G-Max'd
if (
@ -2861,15 +2817,7 @@ const modifierPool: ModifierPool = {
new WeightedModifierType(modifierTypes.REVIVER_SEED, 4),
new WeightedModifierType(modifierTypes.CANDY_JAR, skipInLastClassicWaveOrDefault(5)),
new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 9),
new WeightedModifierType(
modifierTypes.TM_ULTRA,
() => {
const noMoveLearning = new BooleanHolder(false);
applyChallenges(globalScene.gameMode, ChallengeType.NO_MOVE_LEARNING, noMoveLearning); // Yeah this is kind of dumb
return noMoveLearning.value ? 0 : 11;
},
11,
),
new WeightedModifierType(modifierTypes.TM_ULTRA, 11),
new WeightedModifierType(modifierTypes.RARER_CANDY, 4),
new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, skipInLastClassicWaveOrDefault(2)),
new WeightedModifierType(modifierTypes.IV_SCANNER, skipInLastClassicWaveOrDefault(4)),
@ -2955,11 +2903,7 @@ const modifierPool: ModifierPool = {
),
new WeightedModifierType(
modifierTypes.MINI_BLACK_HOLE,
() =>
globalScene.gameMode.isDaily ||
(!globalScene.gameMode.isFreshStartChallenge() && globalScene.gameData.isUnlocked(Unlockables.MINI_BLACK_HOLE))
? 1
: 0,
() => (globalScene.gameMode.isDaily || globalScene.gameData.isUnlocked(Unlockables.MINI_BLACK_HOLE) ? 1 : 0),
1,
),
].map(m => {
@ -3170,8 +3114,51 @@ const tierWeights = [768 / 1024, 195 / 1024, 48 / 1024, 12 / 1024, 1 / 1024];
*/
export const itemPoolChecks: Map<ModifierTypeKeys, boolean | undefined> = new Map();
export interface RewardTableModification {
type: ModifierTypeKeys;
tier: ModifierTier;
maxWeight: number;
}
export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: ModifierPoolType, rerollCount = 0) {
const modifications: RewardTableModification[] = [];
applyChallenges(globalScene.gameMode, ChallengeType.REWARD_TABLE_MODIFY, modifications);
const pool = getModifierPoolForType(poolType);
modifications.map(mod => {
let t = mod.tier;
let dindex = pool[mod.tier].findIndex(wm => wm.modifierType.id === mod.type);
if (mod.maxWeight === 0) {
// Remove the modifier from the specified tier
pool[t].splice(dindex, 1);
} else if (dindex < 0) {
// Add the modifier to specified tier
for (t = ModifierTier.COMMON; t <= ModifierTier.MASTER && dindex < 0; t++) {
if (t === mod.tier) {
// We know it's not in that tier
continue;
}
dindex = pool[t].findIndex(wm => wm.modifierType.id === mod.type);
}
if (dindex >= 0) {
// Move the existing WMT to the specified tier with same func and specified max weight
const wmt = pool[t].splice(dindex, 1)[0];
wmt.maxWeight = mod.maxWeight;
wmt.setTier(mod.tier);
pool[mod.tier].push(wmt);
} else {
// Item isn't anywhere on the table, make a new WMT and push it
const newWMT = new WeightedModifierType(getModifierTypeFuncById(mod.type), mod.maxWeight);
newWMT.setTier(mod.tier);
pool[mod.tier].push(newWMT);
}
} else {
pool[t].map(wmt => {
if (wmt.modifierType.id === mod.type) {
wmt.maxWeight = mod.maxWeight;
}
});
}
});
itemPoolChecks.forEach((_v, k) => {
itemPoolChecks.set(k, false);
});
@ -3459,7 +3446,12 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: number, baseC
[new ModifierTypeOption(modifierTypes.FULL_RESTORE(), 0, baseCost * 2.25)],
[new ModifierTypeOption(modifierTypes.SACRED_ASH(), 0, baseCost * 10)],
];
return options.slice(0, Math.ceil(Math.max(waveIndex + 10, 0) / 30)).flat();
const removeShop: ModifierTypeKeys[] = [];
applyChallenges(globalScene.gameMode, ChallengeType.SHOP_REMOVAL, removeShop);
return options
.slice(0, Math.ceil(Math.max(waveIndex + 10, 0) / 30))
.flat()
.filter(s => !removeShop.includes(s.type.id as ModifierTypeKeys));
}
export function getEnemyBuffModifierForWave(