From a2360fe4796dd4861a1c75d86431a5f104cc6454 Mon Sep 17 00:00:00 2001 From: Frederico Santos Date: Sat, 26 Oct 2024 16:36:19 +0100 Subject: [PATCH] [Feature] Add event-based friendship multiplier and update TrainerConfig for event modifiers --- src/data/trainer-config.ts | 31 +++++++++++++++++++++++++++++ src/field/pokemon.ts | 6 +++++- src/modifier/modifier-type.ts | 14 +++++++------ src/phases/trainer-victory-phase.ts | 6 ++++++ src/timed-event-manager.ts | 24 ++++++++++++++++++++++ 5 files changed, 74 insertions(+), 7 deletions(-) diff --git a/src/data/trainer-config.ts b/src/data/trainer-config.ts index bc69b611075..f63644ba07e 100644 --- a/src/data/trainer-config.ts +++ b/src/data/trainer-config.ts @@ -203,6 +203,7 @@ export class TrainerConfig { public modifierRewardFuncs: ModifierTypeFunc[] = []; public partyTemplates: TrainerPartyTemplate[]; public partyTemplateFunc: PartyTemplateFunc; + public eventRewardFuncs: ModifierTypeFunc[] = []; public partyMemberFuncs: PartyMemberFuncs = {}; public speciesPools: TrainerTierPools; public speciesFilter: PokemonSpeciesFilter; @@ -546,6 +547,17 @@ export class TrainerConfig { return this; } + setEventModifierRewardFuncs(...modifierTypeFuncs: (() => ModifierTypeFunc)[]): TrainerConfig { + this.eventRewardFuncs = modifierTypeFuncs.map(func => () => { + const modifierTypeFunc = func(); + const modifierType = modifierTypeFunc(); + modifierType.withIdFromFunc(modifierTypeFunc); + return modifierType; + }); + return this; + } + + setModifierRewardFuncs(...modifierTypeFuncs: (() => ModifierTypeFunc)[]): TrainerConfig { this.modifierRewardFuncs = modifierTypeFuncs.map(func => () => { const modifierTypeFunc = func(); @@ -1091,6 +1103,8 @@ export class TrainerConfig { }); } + clone = this.eventTemplateFunc ? clone.setEventTemplateFunc(this.eventTemplateFunc) : clone; + clone = this.speciesPools ? clone.setSpeciesPools(this.speciesPools) : clone; clone = this.speciesFilter ? clone.setSpeciesFilter(this.speciesFilter) : clone; if (this.specialtyTypes) { @@ -1147,6 +1161,21 @@ function getGymLeaderPartyTemplate(scene: BattleScene) { return getWavePartyTemplate(scene, trainerPartyTemplates.GYM_LEADER_1, trainerPartyTemplates.GYM_LEADER_2, trainerPartyTemplates.GYM_LEADER_3, trainerPartyTemplates.GYM_LEADER_4, trainerPartyTemplates.GYM_LEADER_5); } +function setEventModifierReward(scene: BattleScene, ...modifierTypeFuncs: (() => ModifierTypeFunc)[]) { + if (scene.eventManager.isEventActive()) { + const rewardFuncs = modifierTypeFuncs.map(func => () => { + const modifierTypeFunc = func(); + const modifierType = modifierTypeFunc(); + modifierType.withIdFromFunc(modifierTypeFunc); + return modifierType; + }); + + this.modifierRewardFuncs.push(...rewardFuncs); + } + + return this; +} + /** * Randomly selects one of the `Species` from `speciesPool`, determines its evolution, level, and strength. * Then adds Pokemon to scene. @@ -1828,10 +1857,12 @@ export const trainerConfigs: TrainerConfigs = { [TrainerType.RIVAL]: new TrainerConfig((t = TrainerType.RIVAL)).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setStaticParty().setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival").setMixedBattleBgm("battle_rival").setPartyTemplates(trainerPartyTemplates.RIVAL) .setModifierRewardFuncs(() => modifierTypes.SUPER_EXP_CHARM, () => modifierTypes.EXP_SHARE) + .setEventModifierRewardFuncs(() => modifierTypes.SHINY_CHARM, () => modifierTypes.ABILITY_CHARM) .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE, Species.CHIKORITA, Species.CYNDAQUIL, Species.TOTODILE, Species.TREECKO, Species.TORCHIC, Species.MUDKIP, Species.TURTWIG, Species.CHIMCHAR, Species.PIPLUP, Species.SNIVY, Species.TEPIG, Species.OSHAWOTT, Species.CHESPIN, Species.FENNEKIN, Species.FROAKIE, Species.ROWLET, Species.LITTEN, Species.POPPLIO, Species.GROOKEY, Species.SCORBUNNY, Species.SOBBLE, Species.SPRIGATITO, Species.FUECOCO, Species.QUAXLY ], TrainerSlot.TRAINER, true)) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEY, Species.HOOTHOOT, Species.TAILLOW, Species.STARLY, Species.PIDOVE, Species.FLETCHLING, Species.PIKIPEK, Species.ROOKIDEE, Species.WATTREL ], TrainerSlot.TRAINER, true)), [TrainerType.RIVAL_2]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setStaticParty().setMoneyMultiplier(1.25).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival").setMixedBattleBgm("battle_rival").setPartyTemplates(trainerPartyTemplates.RIVAL_2) .setModifierRewardFuncs(() => modifierTypes.EXP_SHARE) + .setEventModifierRewardFuncs(scene => setEventModifierReward(scene, () => modifierTypes.SHINY_CHARM)) .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.IVYSAUR, Species.CHARMELEON, Species.WARTORTLE, Species.BAYLEEF, Species.QUILAVA, Species.CROCONAW, Species.GROVYLE, Species.COMBUSKEN, Species.MARSHTOMP, Species.GROTLE, Species.MONFERNO, Species.PRINPLUP, Species.SERVINE, Species.PIGNITE, Species.DEWOTT, Species.QUILLADIN, Species.BRAIXEN, Species.FROGADIER, Species.DARTRIX, Species.TORRACAT, Species.BRIONNE, Species.THWACKEY, Species.RABOOT, Species.DRIZZILE, Species.FLORAGATO, Species.CROCALOR, Species.QUAXWELL ], TrainerSlot.TRAINER, true)) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOTTO, Species.HOOTHOOT, Species.TAILLOW, Species.STARAVIA, Species.TRANQUILL, Species.FLETCHINDER, Species.TRUMBEAK, Species.CORVISQUIRE, Species.WATTREL ], TrainerSlot.TRAINER, true)) .setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450)), diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index a708b2067a7..4cb8390d4f4 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -4114,7 +4114,11 @@ export class PlayerPokemon extends Pokemon { fusionStarterSpeciesId ? this.scene.gameData.starterData[fusionStarterSpeciesId] : null ].filter(d => !!d); const amount = new Utils.IntegerHolder(friendship); - const starterAmount = new Utils.IntegerHolder(Math.floor(friendship * (this.scene.gameMode.isClassic && friendship > 0 ? CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER : 1) / (fusionStarterSpeciesId ? 2 : 1))); + let candyFriendshipMultiplier = CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER; + if (this.scene.eventManager.isEventActive()) { + candyFriendshipMultiplier *= this.scene.eventManager.getFriendshipMultiplier(); + } + const starterAmount = new Utils.IntegerHolder(Math.floor(friendship * (this.scene.gameMode.isClassic && friendship > 0 ? candyFriendshipMultiplier : 1) / (fusionStarterSpeciesId ? 2 : 1))); if (amount.value > 0) { this.scene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount); this.scene.applyModifier(PokemonFriendshipBoosterModifier, true, this, starterAmount); diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index f4b59b9d882..cf2d74483f9 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1301,13 +1301,12 @@ function lureWeightFunc(maxBattles: number, weight: number): WeightedModifierTyp return !(party[0].scene.gameMode.isClassic && party[0].scene.currentBattle.waveIndex === 199) && (lures.length === 0 || lures.filter(m => m.getMaxBattles() === maxBattles && m.getBattleCount() >= maxBattles * 0.6).length === 0) ? weight : 0; }; } - class WeightedModifierType { public modifierType: ModifierType; public weight: integer | WeightedModifierTypeWeightFunc; - public maxWeight: integer; + public maxWeight: integer | WeightedModifierTypeWeightFunc; - constructor(modifierTypeFunc: ModifierTypeFunc, weight: integer | WeightedModifierTypeWeightFunc, maxWeight?: integer) { + constructor(modifierTypeFunc: ModifierTypeFunc, weight: integer | WeightedModifierTypeWeightFunc, maxWeight?: integer | WeightedModifierTypeWeightFunc) { this.modifierType = modifierTypeFunc(); this.modifierType.id = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifierTypeFunc)!; // TODO: is this bang correct? this.weight = weight; @@ -1694,7 +1693,10 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.EVOLUTION_ITEM, (party: Pokemon[]) => { return Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15), 8); }, 8), - new WeightedModifierType(modifierTypes.MAP, (party: Pokemon[]) => party[0].scene.gameMode.isClassic && party[0].scene.currentBattle.waveIndex < 180 ? 1 : 0, 1), + new WeightedModifierType(modifierTypes.MAP, + (party: Pokemon[]) => party[0].scene.gameMode.isClassic && party[0].scene.currentBattle.waveIndex < 180 ? party[0].scene.eventManager.isEventActive() ? 2 : 1 : 0, + (party: Pokemon[]) => party[0].scene.eventManager.isEventActive() ? 2 : 1), + new WeightedModifierType(modifierTypes.SOOTHE_BELL, (party: Pokemon[]) => party[0].scene.eventManager.isEventActive() ? 3 : 0), new WeightedModifierType(modifierTypes.TM_GREAT, 3), new WeightedModifierType(modifierTypes.MEMORY_MUSHROOM, (party: Pokemon[]) => { if (!party.find(p => p.getLearnableLevelMoves().length)) { @@ -1762,7 +1764,7 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.CANDY_JAR, skipInLastClassicWaveOrDefault(5)), new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 9), new WeightedModifierType(modifierTypes.TM_ULTRA, 11), - new WeightedModifierType(modifierTypes.RARER_CANDY, 4), + new WeightedModifierType(modifierTypes.RARER_CANDY, (party: Pokemon[]) => party[0].scene.eventManager.isEventActive() ? 6 : 4), new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, skipInLastClassicWaveOrDefault(2)), new WeightedModifierType(modifierTypes.IV_SCANNER, skipInLastClassicWaveOrDefault(4)), new WeightedModifierType(modifierTypes.EXP_CHARM, skipInLastClassicWaveOrDefault(8)), @@ -1785,7 +1787,7 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.BATON, 2), new WeightedModifierType(modifierTypes.SOUL_DEW, 7), //new WeightedModifierType(modifierTypes.OVAL_CHARM, 6), - new WeightedModifierType(modifierTypes.SOOTHE_BELL, 4), + new WeightedModifierType(modifierTypes.SOOTHE_BELL, (party: Pokemon[]) => party[0].scene.eventManager.isEventActive() ? 0 : 4), new WeightedModifierType(modifierTypes.ABILITY_CHARM, skipInClassicAfterWave(189, 6)), new WeightedModifierType(modifierTypes.FOCUS_BAND, 5), new WeightedModifierType(modifierTypes.KINGS_ROCK, 3), diff --git a/src/phases/trainer-victory-phase.ts b/src/phases/trainer-victory-phase.ts index fd48b91b1a1..dc1b962f47e 100644 --- a/src/phases/trainer-victory-phase.ts +++ b/src/phases/trainer-victory-phase.ts @@ -27,6 +27,12 @@ export class TrainerVictoryPhase extends BattlePhase { this.scene.unshiftPhase(new ModifierRewardPhase(this.scene, modifierRewardFunc)); } + if (this.scene.eventManager.isEventActive()) { + for (const rewardFunc of this.scene.currentBattle.trainer?.config.eventRewardFuncs!) { + this.scene.unshiftPhase(new ModifierRewardPhase(this.scene, rewardFunc)); + } + } + const trainerType = this.scene.currentBattle.trainer?.config.trainerType!; // TODO: is this bang correct? if (vouchers.hasOwnProperty(TrainerType[trainerType])) { if (!this.scene.validateVoucher(vouchers[TrainerType[trainerType]]) && this.scene.currentBattle.trainer?.config.isBoss) { diff --git a/src/timed-event-manager.ts b/src/timed-event-manager.ts index 513d21bd2f3..90f1ba0106f 100644 --- a/src/timed-event-manager.ts +++ b/src/timed-event-manager.ts @@ -20,6 +20,7 @@ interface TimedEvent extends EventBanner { name: string; eventType: EventType; shinyMultiplier?: number; + friendshipMultiplier?: number; startDate: Date; endDate: Date; } @@ -35,6 +36,19 @@ const timedEvents: TimedEvent[] = [ yPosition: 120, scale: 0.21, availableLangs: [ "en", "de", "it", "fr", "ja", "ko", "es", "pt-BR", "zh-CN" ] + }, + { + name: "Halloween Update", + eventType: EventType.SHINY, + shinyMultiplier: 2, + friendshipMultiplier: 2, + startDate: new Date(Date.UTC(2024, 9, 25, 0)), + endDate: new Date(Date.UTC(2024, 10, 4, 0)), + bannerKey: "halloween-banner", + xPosition: 19, + yPosition: 120, + scale: 0.21, + availableLangs: [ "en", "de", "it", "fr", "ja", "ko", "es", "pt-BR", "zh-CN" ] } ]; @@ -61,6 +75,16 @@ export class TimedEventManager { return activeEvents.length > 0; } + getFriendshipMultiplier(): number { + let multiplier = 1; + const friendshipEvents = timedEvents.filter((te) => this.isActive(te)); + friendshipEvents.forEach((fe) => { + multiplier *= fe.friendshipMultiplier ?? 1; + }); + + return multiplier; + } + getShinyMultiplier(): number { let multiplier = 1; const shinyEvents = timedEvents.filter((te) => te.eventType === EventType.SHINY && this.isActive(te));