Moved some files around, added many held item ids

This commit is contained in:
Wlowscha 2025-05-29 23:26:50 +02:00
parent d769bd7f11
commit ba01c505d9
No known key found for this signature in database
GPG Key ID: 3C8F1AD330565D04
10 changed files with 125 additions and 444 deletions

76
src/enums/held-items.ts Normal file
View File

@ -0,0 +1,76 @@
export const HeldItems = {
NONE: 0x0000,
// Berries
SITRUS_BERRY: 0x0101,
LUM_BERRY: 0x0102,
ENIGMA_BERRY: 0x0103,
LIECHI_BERRY: 0x0104,
GANLON_BERRY: 0x0105,
PETAYA_BERRY: 0x0106,
APICOT_BERRY: 0x0107,
SALAC_BERRY: 0x0108,
LANSAT_BERRY: 0x0109,
STARF_BERRY: 0x010A,
LEPPA_BERRY: 0x010B,
// Other items that are consumed
REVIVER_SEED: 0x0201,
WHITE_HERB: 0x0202,
// Type Boosters
SILK_SCARF: 0x0301,
BLACK_BELT: 0x0302,
SHARP_BEAK: 0x0303,
POISON_BARB: 0x0304,
SOFT_SAND: 0x0305,
HARD_STONE: 0x0306,
SILVER_POWDER: 0x0307,
SPELL_TAG: 0x0308,
METAL_COAT: 0x0309,
CHARCOAL: 0x030A,
MYSTIC_WATER: 0x030B,
MIRACLE_SEED: 0x030C,
MAGNET: 0x030D,
TWISTED_SPOON: 0x030E,
NEVER_MELT_ICE: 0x030F,
DRAGON_FANG: 0x0310,
BLACK_GLASSES: 0x0311,
FAIRY_FEATHER: 0x0312,
// Stat Boosters
EVIOLITE: 0x0401,
LIGHT_BALL: 0x0402,
THICK_CLUB: 0x0403,
METAL_POWDER: 0x0404,
QUICK_POWDER: 0x0405,
DEEP_SEA_SCALE: 0x0406,
DEEP_SEA_TOOTH: 0x0407,
// Crit Boosters
SCOPE_LENS: 0x0501,
LEEK: 0x0502,
// Items increasing gains
LUCKY_EGG: 0x0601,
GOLDEN_EGG: 0x0602,
SOOTHE_BELL: 0x0603,
// Unique items
FOCUS_BAND: 0x0701,
QUICK_CLAW: 0x0702,
KINGS_ROCK: 0x0703,
LEFTOVERS: 0x0704,
SHELL_BELL: 0x0705,
MYSTICAL_ROCK: 0x0706,
WIDE_LENS: 0x0707,
MULTI_LENS: 0x0708,
GOLDEN_PUNCH: 0x0709,
GRIP_CLAW: 0x070A,
TOXIC_ORB: 0x070B,
FLAME_ORB: 0x070C,
SOUL_DEW: 0x070D,
BATON: 0x070E,
};
export type HeldItems = (typeof HeldItems)[keyof typeof HeldItems];

View File

@ -0,0 +1,44 @@
import { allHeldItems } from "#app/modifier/all-held-items";
import type { HeldItems } from "#app/enums/held-items";
interface HeldItemProperties {
stack: number;
disabled: boolean;
cooldown?: number;
}
type HeldItemPropertyMap = {
[key in HeldItems]: HeldItemProperties;
};
export class PokemonItemManager {
private heldItems: HeldItemPropertyMap;
constructor() {
this.heldItems = {};
}
getHeldItems(): HeldItemPropertyMap {
return this.heldItems;
}
hasItem(itemType: HeldItems): boolean {
return itemType in this.getHeldItems();
}
getItem(itemType: HeldItems): HeldItemProperties {
// TODO: Not very safe
return this.heldItems[itemType];
}
addHeldItem(itemType: HeldItems, addStack = 1) {
const maxStack = allHeldItems[itemType].getMaxStackCount();
if (this.hasItem(itemType)) {
// TODO: We may want an error message of some kind instead
this.heldItems[itemType].stack = Math.min(this.heldItems[itemType].stack + addStack, maxStack);
} else {
this.heldItems[itemType] = { stack: Math.min(addStack, maxStack), disabled: false };
}
}
}

View File

@ -1,26 +0,0 @@
import { allHeldItems } from "#app/modifier/held-items";
import type { HeldItems } from "#app/modifier/held-items";
export class PokemonItemManager {
private heldItems: [HeldItems, number][];
constructor() {
this.heldItems = [];
}
getHeldItems(): [HeldItems, number][] {
return this.heldItems;
}
addHeldItem(itemType: HeldItems, stack: number) {
const maxStack = allHeldItems[itemType].getMaxStackCount();
const existing = this.heldItems.find(([type]) => type === itemType);
if (existing) {
existing[1] = Math.min(existing[1] + stack, maxStack);
} else {
this.heldItems.push([itemType, Math.min(stack, maxStack)]);
}
}
}

View File

@ -255,7 +255,7 @@ import { MoveFlags } from "#enums/MoveFlags";
import { timedEventManager } from "#app/global-event-manager"; import { timedEventManager } from "#app/global-event-manager";
import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader"; import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader";
import { ResetStatusPhase } from "#app/phases/reset-status-phase"; import { ResetStatusPhase } from "#app/phases/reset-status-phase";
import { PokemonItemManager } from "./pokemon-item-manager"; import { PokemonItemManager } from "./pokemon-held-item-manager";
export enum LearnMoveSituation { export enum LearnMoveSituation {
MISC, MISC,

View File

@ -21,7 +21,7 @@ import { initVouchers } from "#app/system/voucher";
import { Biome } from "#enums/biome"; import { Biome } from "#enums/biome";
import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters"; import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters";
import { timedEventManager } from "./global-event-manager"; import { timedEventManager } from "./global-event-manager";
import { initHeldItems } from "./modifier/held-items"; import { initHeldItems } from "./modifier/all-held-items";
export class LoadingScene extends SceneBase { export class LoadingScene extends SceneBase {
public static readonly KEY = "loading"; public static readonly KEY = "loading";

View File

@ -2,58 +2,10 @@ import type Pokemon from "#app/field/pokemon";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { Localizable } from "#app/interfaces/locales"; import type { Localizable } from "#app/interfaces/locales";
import type { NumberHolder } from "#app/utils/common"; import type { NumberHolder } from "#app/utils/common";
import { HeldItems } from "#enums/held-items";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import i18next from "i18next"; import i18next from "i18next";
export const HeldItems = {
NONE: 0x0000,
SITRUS_BERRY: 0x0101,
LEPPA_BERRY: 0x0102,
SILK_SCARF: 0x0201,
BLACK_BELT: 0x0202,
SHARP_BEAK: 0x0203,
POISON_BARB: 0x0204,
SOFT_SAND: 0x0205,
HARD_STONE: 0x0206,
SILVER_POWDER: 0x0207,
SPELL_TAG: 0x0208,
METAL_COAT: 0x0209,
CHARCOAL: 0x020a,
MYSTIC_WATER: 0x020b,
MIRACLE_SEED: 0x020c,
MAGNET: 0x020d,
TWISTED_SPOON: 0x020e,
NEVER_MELT_ICE: 0x020f,
DRAGON_FANG: 0x0210,
BLACK_GLASSES: 0x0211,
FAIRY_FEATHER: 0x0212,
REVIVER_SEED: 0x0301,
SOOTHE_BELL: 0x0302,
SOUL_DEW: 0x0303,
GOLDEN_PUNCH: 0x0304,
GRIP_CLAW: 0x0305,
BATON: 0x0306,
FOCUS_BAND: 0x0307,
QUICK_CLAW: 0x0308,
KINGS_ROCK: 0x0309,
LEFTOVERS: 0x030a,
SHELL_BELL: 0x030b,
};
export type HeldItems = (typeof HeldItems)[keyof typeof HeldItems];
export const HeldItemCategories = {
NONE: 0x0000,
BERRY: 0x0100,
ATTACK_TYPE_BOOSTER: 0x0200,
BASE_STAT_BOOSTER: 0x0400,
};
export type HeldItemCategories = (typeof HeldItemCategories)[keyof typeof HeldItemCategories];
export class HeldItem implements Localizable { export class HeldItem implements Localizable {
// public pokemonId: number; // public pokemonId: number;
public type: HeldItems; public type: HeldItems;
@ -207,9 +159,9 @@ export class AttackTypeBoosterHeldItem extends HeldItem {
export function applyAttackTypeBoosterHeldItem(pokemon: Pokemon, moveType: PokemonType, movePower: NumberHolder) { export function applyAttackTypeBoosterHeldItem(pokemon: Pokemon, moveType: PokemonType, movePower: NumberHolder) {
if (pokemon) { if (pokemon) {
for (const [item, stackCount] of pokemon.heldItemManager.getHeldItems()) { for (const [item, props] of Object.entries(pokemon.heldItemManager.getHeldItems())) {
if (allHeldItems[item] instanceof AttackTypeBoosterHeldItem) { if (allHeldItems[item] instanceof AttackTypeBoosterHeldItem) {
allHeldItems[item].apply(stackCount, moveType, movePower); allHeldItems[item].apply(props.stack, moveType, movePower);
} }
} }
} }

View File

@ -1,120 +0,0 @@
import { AttackMove } from "#app/data/moves/move";
import type Pokemon from "#app/field/pokemon";
import { PokemonType } from "#enums/pokemon-type";
import { attackTypeToHeldItem } from "./held-items";
import { HeldItemReward, type Reward } from "./reward";
function getRandomWeightedSelection<T>(weights: Map<T, number>): T | null {
const totalWeight = Array.from(weights.values()).reduce((sum, weight) => sum + weight, 0);
if (totalWeight === 0) {
return null;
}
const randInt = Math.floor(Math.random() * totalWeight);
let accumulatedWeight = 0;
for (const [item, weight] of weights.entries()) {
accumulatedWeight += weight;
if (randInt < accumulatedWeight) {
return item;
}
}
return null;
}
export class RewardGenerator<T extends number> {
options: T[];
tempWeights: Map<T, number>;
constructor(options: T[]) {
this.options = options;
this.tempWeights = new Map(this.options.map(option => [option, 1]));
}
generate(party: Pokemon[], overrideWeightFunction?: Function) {
const weights = overrideWeightFunction ? overrideWeightFunction(party) : this.weightFunction(party);
for (const [option, tempWeight] of this.tempWeights.entries()) {
if (tempWeight === 0 && weights.has(option)) {
weights.set(option, 0);
}
}
const value: T | null = getRandomWeightedSelection(weights);
if (value) {
this.tempWeights.set(value, 0);
return this.generateReward(value);
}
return null;
}
weightFunction(_party: Pokemon[]): Map<T, number> {
const defaultWeightMap = new Map<T, number>();
this.options.forEach(option => {
defaultWeightMap.set(option, 1);
});
return defaultWeightMap;
}
generateReward(_value: T): Reward | null {
return null;
}
}
export class AttackTypeBoosterHeldItemRewardGenerator extends RewardGenerator<PokemonType> {
constructor() {
//TODO: we can also construct this, but then have to handle options being null
const options = [
PokemonType.NORMAL,
PokemonType.FIGHTING,
PokemonType.FLYING,
PokemonType.POISON,
PokemonType.GROUND,
PokemonType.ROCK,
PokemonType.BUG,
PokemonType.GHOST,
PokemonType.STEEL,
PokemonType.FIRE,
PokemonType.WATER,
PokemonType.GRASS,
PokemonType.ELECTRIC,
PokemonType.PSYCHIC,
PokemonType.ICE,
PokemonType.DRAGON,
PokemonType.DARK,
PokemonType.FAIRY,
];
super(options);
}
weightFunction(party: Pokemon[]): Map<PokemonType, number> {
const attackMoveTypes = party.flatMap(p =>
p
.getMoveset()
.map(m => m.getMove())
.filter(m => m instanceof AttackMove)
.map(m => m.type),
);
const attackMoveTypeWeights = new Map<PokemonType, number>();
for (const type of attackMoveTypes) {
const currentWeight = attackMoveTypeWeights.get(type) ?? 0;
if (currentWeight < 3) {
attackMoveTypeWeights.set(type, currentWeight + 1);
}
}
return attackMoveTypeWeights;
}
generateReward(value: PokemonType) {
return new HeldItemReward(attackTypeToHeldItem[value]);
}
}

View File

@ -1,94 +0,0 @@
/**
import { globalScene } from "#app/global-scene";
import { isNullOrUndefined, NumberHolder } from "#app/utils/common";
import type { RewardGenerator } from "./reward-generator";
import type { RewardTier } from "./reward-tier";
import Overrides from "#app/overrides";
interface RewardPool {
[rewardTier: number]: RewardGenerator[];
}
export interface CustomRewardSettings {
guaranteedModifierTiers?: RewardTier[];
guaranteedModifierTypeOptions?: ModifierTypeOption[];
guaranteedModifierTypeFuncs?: ModifierTypeFunc[];
fillRemaining?: boolean;
//Set to negative value to disable rerolls completely in shop
rerollMultiplier?: number;
allowLuckUpgrades?: boolean;
}
export class RewardPoolManager {
public rerollCount: number;
private rewardPool: RewardPool;
private customRewardSettings?: CustomRewardSettings; //TODO: have a better scheme than just this
constructor(rewardPool: RewardPool) {
this.rewardPool = rewardPool;
}
getRerollCost(lockRarities: boolean): number {
let baseValue = 0;
if (Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
return baseValue;
}
if (lockRarities) {
const tierValues = [50, 125, 300, 750, 2000];
for (const opt of this.typeOptions) {
baseValue += tierValues[opt.type.tier ?? 0];
}
} else {
baseValue = 250;
}
let multiplier = 1;
if (!isNullOrUndefined(this.customRewardSettings?.rerollMultiplier)) {
if (this.customRewardSettings.rerollMultiplier < 0) {
// Completely overrides reroll cost to -1 and early exits
return -1;
}
// Otherwise, continue with custom multiplier
multiplier = this.customRewardSettings.rerollMultiplier;
}
const baseMultiplier = Math.min(
Math.ceil(globalScene.currentBattle.waveIndex / 10) * baseValue * 2 ** this.rerollCount * multiplier,
Number.MAX_SAFE_INTEGER,
);
// Apply Black Sludge to reroll cost
const modifiedRerollCost = new NumberHolder(baseMultiplier);
globalScene.applyModifier(HealShopCostModifier, true, modifiedRerollCost);
return modifiedRerollCost.value;
}
getRewardCount(): NumberHolder {
const modifierCount = new NumberHolder(3);
// TODO: This code is used by golden and silver pokéball to increase the number of item slots
// They will become a trainer item, so there will be no .applyModifiers
globalScene.applyModifiers(ExtraModifierModifier, true, modifierCount);
globalScene.applyModifiers(TempExtraModifierModifier, true, modifierCount);
// If custom rewards are specified, overrides default item count
// TODO: Figure out exactly how and when that would happen
// Presumably in MEs, but possibly also after rerolls? And at specific waves...
if (this.customRewardSettings) {
const newItemCount =
(this.customRewardSettings.guaranteedModifierTiers?.length || 0) +
(this.customRewardSettings.guaranteedModifierTypeOptions?.length || 0) +
(this.customRewardSettings.guaranteedModifierTypeFuncs?.length || 0);
if (this.customRewardSettings.fillRemaining) {
const originalCount = modifierCount.value;
modifierCount.value = originalCount > newItemCount ? originalCount : newItemCount;
} else {
modifierCount.value = newItemCount;
}
}
return modifierCount;
}
}
*/

View File

@ -1,8 +0,0 @@
export enum RewardTier {
COMMON,
GREAT,
ULTRA,
ROGUE,
MASTER,
LUXURY,
}

View File

@ -1,143 +0,0 @@
/**
import { globalScene } from "#app/global-scene";
import type { PokeballType } from "#enums/pokeball";
import i18next from "i18next";
import { allHeldItems, type HeldItems } from "./held-items";
import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball";
import type Pokemon from "#app/field/pokemon";
export class Reward {
getName(): string {
return "";
}
getDescription(): string {
return "";
}
getIcon(): string {
return "";
}
createIcon(): Phaser.GameObjects.Container {
const container = globalScene.add.container(0, 0);
const item = globalScene.add.sprite(0, 12, "items");
item.setFrame(this.getIcon());
item.setOrigin(0, 0.5);
container.add(item);
return container;
}
}
export class PokeballReward extends Reward {
private pokeballType: PokeballType;
private count: number;
constructor(pokeballType: PokeballType, count: number) {
super();
this.pokeballType = pokeballType;
this.count = count;
}
getName(): string {
return i18next.t("modifierType:ModifierType.AddPokeballModifierType.name", {
modifierCount: this.count,
pokeballName: getPokeballName(this.pokeballType),
});
}
getDescription(): string {
return i18next.t("modifierType:ModifierType.AddPokeballModifierType.description", {
modifierCount: this.count,
pokeballName: getPokeballName(this.pokeballType),
catchRate:
getPokeballCatchMultiplier(this.pokeballType) > -1
? `${getPokeballCatchMultiplier(this.pokeballType)}x`
: "100%",
pokeballAmount: `${globalScene.pokeballCounts[this.pokeballType]}`,
});
}
apply(): boolean {
const pokeballCounts = globalScene.pokeballCounts;
pokeballCounts[this.pokeballType] = Math.min(
pokeballCounts[this.pokeballType] + this.count,
MAX_PER_TYPE_POKEBALLS,
);
return true;
}
}
export class PartySelectReward extends Reward {
apply(): {
}
}
export class HeldItemReward extends PartySelectReward {
private itemId: HeldItems;
constructor(itemId: HeldItems) {
super();
this.itemId = itemId;
}
getName(): string {
return allHeldItems[this.itemId].getName();
}
getDescription(): string {
return allHeldItems[this.itemId].getDescription();
}
getIcon(): string {
return allHeldItems[this.itemId].getIcon();
}
apply(): {
}
}
export interface RewardInfo {
options: number[];
rewardType: ;
condition?: (party: Pokemon[], option: number) => void;
optionWeight?: (party: Pokemon[], option: number) => void;
}
interface RewardPool {
[rewardTier: number]: RewardGenerator[];
}
export class RewardManager {
private rewardPool: RewardPool;
constructor(rewardPool: RewardPool) {
this.rewardPool = rewardPool;
}
}
*/