From e1b0e0f0aebc8f91d22bb981370468d8b637f605 Mon Sep 17 00:00:00 2001 From: Fabi <192151969+fabske0@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:54:12 +0100 Subject: [PATCH] [Balance] Allow candy gain for uncaught pokemon (#6791) * allow candy gain for uncaught mons * carry over friendship * apply suggestions Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Add friendship/candy related tests * Refactor friendship cap tests * Fix typo * Apply suggestions from code review Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> * Fix test * Update test/field/pokemon.test.ts Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> * Replace `.startBattle` with `.runToSummon` --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> --- src/field/pokemon.ts | 7 ++-- src/system/game-data.ts | 9 +---- test/field/pokemon.test.ts | 83 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 10 deletions(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 5c59706b517..781c5508409 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -5956,9 +5956,10 @@ export class PlayerPokemon extends Pokemon { // Add to candy progress for this mon's starter species and its fused species (if it has one) starterData.forEach(([sd, id]: [StarterDataEntry, SpeciesId]) => { sd.friendship = (sd.friendship || 0) + candyFriendshipAmount; - if (sd.friendship >= getStarterValueFriendshipCap(speciesStarterCosts[id])) { - globalScene.gameData.addStarterCandy(getPokemonSpecies(id), 1); - sd.friendship = 0; + const friendshipCap = getStarterValueFriendshipCap(speciesStarterCosts[id]); + if (sd.friendship >= friendshipCap) { + globalScene.gameData.addStarterCandy(getPokemonSpecies(id), Math.floor(sd.friendship / friendshipCap)); + sd.friendship %= friendshipCap; } }); } diff --git a/src/system/game-data.ts b/src/system/game-data.ts index e2ccbc8abd0..86679a2d956 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -1803,17 +1803,12 @@ export class GameData { /** * Adds a candy to the player's game data for a given {@linkcode PokemonSpecies}. - * Will do nothing if the player does not have the Pokemon owned in their system save data. * @param species * @param count */ addStarterCandy(species: PokemonSpecies, count: number): void { - // Only gain candies if the Pokemon has already been marked as caught in dex (ignore "rental" pokemon) - const speciesRootForm = species.getRootSpeciesId(); - if (globalScene.gameData.dexData[speciesRootForm].caughtAttr) { - globalScene.candyBar.showStarterSpeciesCandy(species.speciesId, count); - this.starterData[species.speciesId].candyCount += count; - } + globalScene.candyBar.showStarterSpeciesCandy(species.speciesId, count); + this.starterData[species.speciesId].candyCount += count; } /** diff --git a/test/field/pokemon.test.ts b/test/field/pokemon.test.ts index 94b3a2316ea..075ae36672d 100644 --- a/test/field/pokemon.test.ts +++ b/test/field/pokemon.test.ts @@ -1,4 +1,7 @@ import type { BattleScene } from "#app/battle-scene"; +import { RARE_CANDY_FRIENDSHIP_CAP } from "#app/constants"; +import { globalScene } from "#app/global-scene"; +import { getStarterValueFriendshipCap, speciesStarterCosts } from "#balance/starters"; import { CustomPokemonData } from "#data/pokemon-data"; import { MoveId } from "#enums/move-id"; import { PokeballType } from "#enums/pokeball"; @@ -226,4 +229,84 @@ describe("Spec - Pokemon", () => { expect(pokemon.friendship).toBe(friendship); } }); + + describe("Friendship", () => { + it("should cap friendship at 255", async () => { + await game.classicMode.runToSummon([SpeciesId.FEEBAS]); + + const feebas = game.field.getPlayerPokemon(); + feebas.addFriendship(999); + + expect(feebas.friendship).toBe(255); + }); + + it("should not go below 0 friendship", async () => { + await game.classicMode.runToSummon([SpeciesId.FEEBAS]); + + const feebas = game.field.getPlayerPokemon(); + feebas.addFriendship(-999); + + expect(feebas.friendship).toBe(0); + }); + + it("should respect Rare Candy friendship gain cap", async () => { + await game.classicMode.runToSummon([SpeciesId.FEEBAS]); + + const feebas = game.field.getPlayerPokemon(); + feebas.addFriendship(999, true); + + expect(feebas.friendship).toBe(RARE_CANDY_FRIENDSHIP_CAP); + }); + + it("should get 3x candy friendship in classic mode", async () => { + await game.classicMode.runToSummon([SpeciesId.FEEBAS]); + + const feebas = game.field.getPlayerPokemon(); + const pokemonData = globalScene.gameData.starterData[SpeciesId.FEEBAS]; + feebas.friendship = 0; + pokemonData.friendship = 0; + + feebas.addFriendship(10); + + expect(feebas.friendship).toBe(10); + expect(pokemonData.friendship).toBe(30); + }); + + it("should carry over excess friendship into next candy, even if capped", async () => { + await game.classicMode.runToSummon([SpeciesId.FEEBAS]); + + const feebas = game.field.getPlayerPokemon(); + const pokemonData = globalScene.gameData.starterData[SpeciesId.FEEBAS]; + feebas.friendship = 0; + pokemonData.friendship = 15; + pokemonData.candyCount = 0; + + const cap = getStarterValueFriendshipCap(speciesStarterCosts[SpeciesId.FEEBAS]); + expect(cap).toBeLessThan(2015); + + feebas.addFriendship(2000, true); + + // Friendship gain was capped, but candy friendship overflowed several times over + expect(feebas.friendship).toBe(RARE_CANDY_FRIENDSHIP_CAP); + expect(pokemonData.friendship).toBe(6015 % cap); + expect(pokemonData.candyCount).toBe(Math.floor(6015 / cap)); + }); + }); + + it("should allow gaining candy for uncaught Pokémon", async () => { + await game.classicMode.runToSummon([SpeciesId.FEEBAS]); + + const feebas = game.field.getPlayerPokemon(); + const pokemonData = globalScene.gameData.starterData[SpeciesId.FEEBAS]; + feebas.friendship = 0; + pokemonData.candyCount = 0; + // mark feebas as uncaught + const dexEntry = globalScene.gameData.dexData[SpeciesId.FEEBAS]; + dexEntry.caughtAttr = 0n; + + feebas.addFriendship(2000); + + expect(dexEntry.caughtAttr).toBe(0n); + expect(pokemonData.candyCount).toBeGreaterThan(0); + }); });