diff --git a/src/data/ability.ts b/src/data/ability.ts index 8ff4cfea59b..66624f03436 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -5953,7 +5953,7 @@ export function initAbilities() { .bypassFaint() .partial(), // Meteor form should protect against status effects and yawn new Ability(Abilities.STAKEOUT, 7) - .attr(MovePowerBoostAbAttr, (user, target, move) => user?.scene.currentBattle.turnCommands[target?.getBattlerIndex() ?? BattlerIndex.ATTACKER]?.command === Command.POKEMON, 2), + .attr(MovePowerBoostAbAttr, (user, target, move) => !!target?.turnData.switchedInThisTurn, 2), new Ability(Abilities.WATER_BUBBLE, 7) .attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 0.5) .attr(MoveTypePowerBoostAbAttr, Type.WATER, 2) diff --git a/src/data/balance/starters.ts b/src/data/balance/starters.ts index d6a1f0c3eaf..a4a3c9d011c 100644 --- a/src/data/balance/starters.ts +++ b/src/data/balance/starters.ts @@ -3,10 +3,10 @@ import { Species } from "#enums/species"; export const POKERUS_STARTER_COUNT = 5; // #region Friendship constants -export const CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER = 2; -export const FRIENDSHIP_GAIN_FROM_BATTLE = 2; -export const FRIENDSHIP_GAIN_FROM_RARE_CANDY = 5; -export const FRIENDSHIP_LOSS_FROM_FAINT = 10; +export const CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER = 3; +export const FRIENDSHIP_GAIN_FROM_BATTLE = 3; +export const FRIENDSHIP_GAIN_FROM_RARE_CANDY = 6; +export const FRIENDSHIP_LOSS_FROM_FAINT = 5; /** * Function to get the cumulative friendship threshold at which a candy is earned @@ -16,19 +16,19 @@ export const FRIENDSHIP_LOSS_FROM_FAINT = 10; export function getStarterValueFriendshipCap(starterCost: number): number { switch (starterCost) { case 1: - return 20; + return 25; case 2: - return 40; + return 50; case 3: - return 60; + return 75; case 4: return 100; case 5: - return 140; + return 150; case 6: return 200; case 7: - return 280; + return 300; case 8: case 9: return 450; diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index ce25b56157c..75b7cddb904 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1085,10 +1085,6 @@ export class OctolockTag extends TrappedTag { super(BattlerTagType.OCTOLOCK, BattlerTagLapseType.TURN_END, 1, Moves.OCTOLOCK, sourceId); } - canAdd(pokemon: Pokemon): boolean { - return !pokemon.getTag(BattlerTagType.OCTOLOCK); - } - lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { const shouldLapse = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); diff --git a/src/data/move.ts b/src/data/move.ts index 64f8e43cb7b..091301990f3 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -7541,6 +7541,8 @@ const failIfLastInPartyCondition: MoveConditionFunc = (user: Pokemon, target: Po return party.some(pokemon => pokemon.isActive() && !pokemon.isOnField()); }; +const failIfGhostTypeCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => !target.isOfType(Type.GHOST); + export type MoveAttrFilter = (attr: MoveAttr) => boolean; function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon | null, target: Pokemon | null, move: Move, args: any[]): Promise { @@ -8287,6 +8289,7 @@ export function initMoves() { new AttackMove(Moves.THIEF, Type.DARK, MoveCategory.PHYSICAL, 60, 100, 25, -1, 0, 2) .attr(StealHeldItemChanceAttr, 0.3), new StatusMove(Moves.SPIDER_WEB, Type.BUG, -1, 10, -1, 0, 2) + .condition(failIfGhostTypeCondition) .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1), new StatusMove(Moves.MIND_READER, Type.NORMAL, -1, 5, -1, 0, 2) .attr(IgnoreAccuracyAttr), @@ -8423,6 +8426,7 @@ export function initMoves() { new AttackMove(Moves.STEEL_WING, Type.STEEL, MoveCategory.PHYSICAL, 70, 90, 25, 10, 0, 2) .attr(StatStageChangeAttr, [ Stat.DEF ], 1, true), new StatusMove(Moves.MEAN_LOOK, Type.NORMAL, -1, 5, -1, 0, 2) + .condition(failIfGhostTypeCondition) .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1), new StatusMove(Moves.ATTRACT, Type.NORMAL, 100, 15, -1, 0, 2) .attr(AddBattlerTagAttr, BattlerTagType.INFATUATED) @@ -8802,6 +8806,7 @@ export function initMoves() { new SelfStatusMove(Moves.IRON_DEFENSE, Type.STEEL, -1, 15, -1, 0, 3) .attr(StatStageChangeAttr, [ Stat.DEF ], 2, true), new StatusMove(Moves.BLOCK, Type.NORMAL, -1, 5, -1, 0, 3) + .condition(failIfGhostTypeCondition) .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1), new StatusMove(Moves.HOWL, Type.NORMAL, -1, 40, -1, 0, 3) .attr(StatStageChangeAttr, [ Stat.ATK ], 1) @@ -10096,6 +10101,7 @@ export function initMoves() { .attr(EatBerryAttr) .target(MoveTarget.ALL), new StatusMove(Moves.OCTOLOCK, Type.FIGHTING, 100, 15, -1, 0, 8) + .condition(failIfGhostTypeCondition) .attr(AddBattlerTagAttr, BattlerTagType.OCTOLOCK, false, true, 1), new AttackMove(Moves.BOLT_BEAK, Type.ELECTRIC, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 8) .attr(FirstAttackDoublePowerAttr), diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index ae6cabd4dae..6bd6856604b 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -276,6 +276,8 @@ export const ClowningAroundEncounter: MysteryEncounter = generateItemsOfTier(scene, mostHeldItemsPokemon, numBerries, "Berries"); // Shuffle Transferable held items in the same tier (only shuffles Ultra and Rogue atm) + // For the purpose of this ME, Soothe Bells and Lucky Eggs are counted as Ultra tier + // And Golden Eggs as Rogue tier let numUltra = 0; let numRogue = 0; items.filter(m => m.isTransferable && !(m instanceof BerryModifier)) @@ -285,7 +287,7 @@ export const ClowningAroundEncounter: MysteryEncounter = if (type.id === "GOLDEN_EGG" || tier === ModifierTier.ROGUE) { numRogue += m.stackCount; scene.removeModifier(m); - } else if (type.id === "LUCKY_EGG" || tier === ModifierTier.ULTRA) { + } else if (type.id === "LUCKY_EGG" || type.id === "SOOTHE_BELL" || tier === ModifierTier.ULTRA) { numUltra += m.stackCount; scene.removeModifier(m); } @@ -456,7 +458,6 @@ function generateItemsOfTier(scene: BattleScene, pokemon: PlayerPokemon, numItem [ modifierTypes.LEFTOVERS, 4 ], [ modifierTypes.SHELL_BELL, 4 ], [ modifierTypes.SOUL_DEW, 10 ], - [ modifierTypes.SOOTHE_BELL, 3 ], [ modifierTypes.SCOPE_LENS, 1 ], [ modifierTypes.BATON, 1 ], [ modifierTypes.FOCUS_BAND, 5 ], diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 4986c1feab1..04776afc624 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1702,7 +1702,8 @@ 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 ? 2 : 0, 2), + new WeightedModifierType(modifierTypes.SOOTHE_BELL, 2), new WeightedModifierType(modifierTypes.TM_GREAT, 3), new WeightedModifierType(modifierTypes.MEMORY_MUSHROOM, (party: Pokemon[]) => { if (!party.find(p => p.getLearnableLevelMoves().length)) { @@ -1730,8 +1731,14 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.EVIOLITE, (party: Pokemon[]) => { const { gameMode, gameData } = party[0].scene; if (gameMode.isDaily || (!gameMode.isFreshStartChallenge() && gameData.isUnlocked(Unlockables.EVIOLITE))) { - return party.some(p => ((p.getSpeciesForm(true).speciesId in pokemonEvolutions) || (p.isFusion() && (p.getFusionSpeciesForm(true).speciesId in pokemonEvolutions))) - && !p.getHeldItems().some(i => i instanceof EvolutionStatBoosterModifier) && !p.isMax()) ? 10 : 0; + return party.some(p => { + // Check if Pokemon's species (or fusion species, if applicable) can evolve or if they're G-Max'd + if (!p.isMax() && ((p.getSpeciesForm(true).speciesId in pokemonEvolutions) || (p.isFusion() && (p.getFusionSpeciesForm(true).speciesId in pokemonEvolutions)))) { + // Check if Pokemon is already holding an Eviolite + return !p.getHeldItems().some(i => i.type.id === "EVIOLITE"); + } + return false; + }) ? 10 : 0; } return 0; }), @@ -1794,7 +1801,6 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.SOUL_DEW, 7), //new WeightedModifierType(modifierTypes.OVAL_CHARM, 6), new WeightedModifierType(modifierTypes.CATCHING_CHARM, (party: Pokemon[]) => !party[0].scene.gameMode.isFreshStartChallenge() && party[0].scene.gameData.getSpeciesCount(d => !!d.caughtAttr) > 100 ? 4 : 0, 4), - new WeightedModifierType(modifierTypes.SOOTHE_BELL, 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/test/abilities/stakeout.test.ts b/src/test/abilities/stakeout.test.ts new file mode 100644 index 00000000000..885169b284e --- /dev/null +++ b/src/test/abilities/stakeout.test.ts @@ -0,0 +1,85 @@ +import { BattlerIndex } from "#app/battle"; +import { isBetween } from "#app/utils"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Stakeout", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH, Moves.SURF ]) + .ability(Abilities.STAKEOUT) + .battleType("single") + .disableCrits() + .startingLevel(100) + .enemyLevel(100) + .enemySpecies(Species.SNORLAX) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset([ Moves.SPLASH, Moves.FLIP_TURN ]) + .startingWave(5); + }); + + it("should do double damage to a pokemon that switched out", async () => { + await game.classicMode.startBattle([ Species.MILOTIC ]); + + const [ enemy1, ] = game.scene.getEnemyParty(); + + game.move.select(Moves.SURF); + await game.forceEnemyMove(Moves.SPLASH); + await game.toNextTurn(); + const damage1 = enemy1.getInverseHp(); + enemy1.hp = enemy1.getMaxHp(); + + game.move.select(Moves.SPLASH); + game.forceEnemyToSwitch(); + await game.toNextTurn(); + + game.move.select(Moves.SURF); + game.forceEnemyToSwitch(); + await game.toNextTurn(); + + expect(enemy1.isFainted()).toBe(false); + expect(isBetween(enemy1.getInverseHp(), (damage1 * 2) - 5, (damage1 * 2) + 5)).toBe(true); + }); + + it("should do double damage to a pokemon that switched out via U-Turn/etc", async () => { + await game.classicMode.startBattle([ Species.MILOTIC ]); + + const [ enemy1, ] = game.scene.getEnemyParty(); + + game.move.select(Moves.SURF); + await game.forceEnemyMove(Moves.SPLASH); + await game.toNextTurn(); + const damage1 = enemy1.getInverseHp(); + enemy1.hp = enemy1.getMaxHp(); + + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.FLIP_TURN); + await game.toNextTurn(); + + game.move.select(Moves.SURF); + await game.forceEnemyMove(Moves.FLIP_TURN); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.toNextTurn(); + + expect(enemy1.isFainted()).toBe(false); + expect(isBetween(enemy1.getInverseHp(), (damage1 * 2) - 5, (damage1 * 2) + 5)).toBe(true); + }); +}); diff --git a/src/test/battlerTags/octolock.test.ts b/src/test/battlerTags/octolock.test.ts index ebd92dc6401..9efce220fe8 100644 --- a/src/test/battlerTags/octolock.test.ts +++ b/src/test/battlerTags/octolock.test.ts @@ -1,9 +1,8 @@ import BattleScene from "#app/battle-scene"; import { describe, expect, it, vi } from "vitest"; import Pokemon from "#app/field/pokemon"; -import { BattlerTag, BattlerTagLapseType, OctolockTag, TrappedTag } from "#app/data/battler-tags"; +import { BattlerTagLapseType, OctolockTag, TrappedTag } from "#app/data/battler-tags"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; -import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Stat } from "#enums/stat"; vi.mock("#app/battle-scene.js"); @@ -33,30 +32,4 @@ describe("BattlerTag - OctolockTag", () => { it ("traps its target (extends TrappedTag)", async () => { expect(new OctolockTag(1)).toBeInstanceOf(TrappedTag); }); - - it("can be added to pokemon who are not octolocked", async => { - const mockPokemon = { - getTag: vi.fn().mockReturnValue(undefined) as Pokemon["getTag"], - } as Pokemon; - - const subject = new OctolockTag(1); - - expect(subject.canAdd(mockPokemon)).toBeTruthy(); - - expect(mockPokemon.getTag).toHaveBeenCalledTimes(1); - expect(mockPokemon.getTag).toHaveBeenCalledWith(BattlerTagType.OCTOLOCK); - }); - - it("cannot be added to pokemon who are octolocked", async => { - const mockPokemon = { - getTag: vi.fn().mockReturnValue(new BattlerTag(null!, null!, null!, null!)) as Pokemon["getTag"], - } as Pokemon; - - const subject = new OctolockTag(1); - - expect(subject.canAdd(mockPokemon)).toBeFalsy(); - - expect(mockPokemon.getTag).toHaveBeenCalledTimes(1); - expect(mockPokemon.getTag).toHaveBeenCalledWith(BattlerTagType.OCTOLOCK); - }); }); diff --git a/src/test/moves/octolock.test.ts b/src/test/moves/octolock.test.ts index d80b71a51e1..6ca96eeb464 100644 --- a/src/test/moves/octolock.test.ts +++ b/src/test/moves/octolock.test.ts @@ -1,11 +1,8 @@ -import { Stat } from "#enums/stat"; import { TrappedTag } from "#app/data/battler-tags"; -import { CommandPhase } from "#app/phases/command-phase"; -import { MoveEndPhase } from "#app/phases/move-end-phase"; -import { TurnInitPhase } from "#app/phases/turn-init-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { Stat } from "#enums/stat"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -27,12 +24,13 @@ describe("Moves - Octolock", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleType("single") - .enemySpecies(Species.RATTATA) + game.override + .battleType("single") + .enemySpecies(Species.MAGIKARP) .enemyMoveset(Moves.SPLASH) .enemyAbility(Abilities.BALL_FETCH) .startingLevel(2000) - .moveset([ Moves.OCTOLOCK, Moves.SPLASH ]) + .moveset([ Moves.OCTOLOCK, Moves.SPLASH, Moves.TRICK_OR_TREAT ]) .ability(Abilities.BALL_FETCH); }); @@ -43,16 +41,15 @@ describe("Moves - Octolock", () => { // use Octolock and advance to init phase of next turn to check for stat changes game.move.select(Moves.OCTOLOCK); - await game.phaseInterceptor.to(TurnInitPhase); + await game.toNextTurn(); expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(-1); expect(enemyPokemon.getStatStage(Stat.SPDEF)).toBe(-1); // take a second turn to make sure stat changes occur again - await game.phaseInterceptor.to(CommandPhase); game.move.select(Moves.SPLASH); + await game.toNextTurn(); - await game.phaseInterceptor.to(TurnInitPhase); expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(-2); expect(enemyPokemon.getStatStage(Stat.SPDEF)).toBe(-2); }); @@ -65,7 +62,7 @@ describe("Moves - Octolock", () => { // use Octolock and advance to init phase of next turn to check for stat changes game.move.select(Moves.OCTOLOCK); - await game.phaseInterceptor.to(TurnInitPhase); + await game.toNextTurn(); expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(0); expect(enemyPokemon.getStatStage(Stat.SPDEF)).toBe(-1); @@ -79,7 +76,7 @@ describe("Moves - Octolock", () => { // use Octolock and advance to init phase of next turn to check for stat changes game.move.select(Moves.OCTOLOCK); - await game.phaseInterceptor.to(TurnInitPhase); + await game.toNextTurn(); expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(0); expect(enemyPokemon.getStatStage(Stat.SPDEF)).toBe(0); @@ -93,7 +90,7 @@ describe("Moves - Octolock", () => { // use Octolock and advance to init phase of next turn to check for stat changes game.move.select(Moves.OCTOLOCK); - await game.phaseInterceptor.to(TurnInitPhase); + await game.toNextTurn(); expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(0); expect(enemyPokemon.getStatStage(Stat.SPDEF)).toBe(0); @@ -110,7 +107,44 @@ describe("Moves - Octolock", () => { game.move.select(Moves.OCTOLOCK); // after Octolock - enemy should be trapped - await game.phaseInterceptor.to(MoveEndPhase); + await game.phaseInterceptor.to("MoveEndPhase"); expect(enemyPokemon.findTag(t => t instanceof TrappedTag)).toBeDefined(); }); + + it("does not work on ghost type pokemon", async () => { + game.override.enemyMoveset(Moves.OCTOLOCK); + await game.classicMode.startBattle([ Species.GASTLY ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + + // before Octolock - player should not be trapped + expect(playerPokemon.findTag(t => t instanceof TrappedTag)).toBeUndefined(); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + // after Octolock - player should still not be trapped, and no stat loss + expect(playerPokemon.findTag(t => t instanceof TrappedTag)).toBeUndefined(); + expect(playerPokemon.getStatStage(Stat.DEF)).toBe(0); + expect(playerPokemon.getStatStage(Stat.SPDEF)).toBe(0); + }); + + it("does not work on pokemon with added ghost type via Trick-or-Treat", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const enemy = game.scene.getEnemyPokemon()!; + + // before Octolock - pokemon should not be trapped + expect(enemy.findTag(t => t instanceof TrappedTag)).toBeUndefined(); + + game.move.select(Moves.TRICK_OR_TREAT); + await game.toNextTurn(); + game.move.select(Moves.OCTOLOCK); + await game.toNextTurn(); + + // after Octolock - pokemon should still not be trapped, and no stat loss + expect(enemy.findTag(t => t instanceof TrappedTag)).toBeUndefined(); + expect(enemy.getStatStage(Stat.DEF)).toBe(0); + expect(enemy.getStatStage(Stat.SPDEF)).toBe(0); + }); }); diff --git a/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts b/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts index a403a306b3d..e7ea6eea0ea 100644 --- a/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -266,6 +266,9 @@ describe("Clowning Around - Mystery Encounter", () => { // 5 Lucky Egg on lead (ultra) itemType = generateModifierType(scene, modifierTypes.LUCKY_EGG) as PokemonHeldItemModifierType; await addItemToPokemon(scene, scene.getPlayerParty()[0], 5, itemType); + // 3 Soothe Bell on lead (great tier, but counted as ultra by this ME) + itemType = generateModifierType(scene, modifierTypes.SOOTHE_BELL) as PokemonHeldItemModifierType; + await addItemToPokemon(scene, scene.getPlayerParty()[0], 3, itemType); // 5 Soul Dew on lead (rogue) itemType = generateModifierType(scene, modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType; await addItemToPokemon(scene, scene.getPlayerParty()[0], 5, itemType); @@ -286,7 +289,7 @@ describe("Clowning Around - Mystery Encounter", () => { const rogueCountAfter = leadItemsAfter .filter(m => m.type.tier === ModifierTier.ROGUE) .reduce((a, b) => a + b.stackCount, 0); - expect(ultraCountAfter).toBe(10); + expect(ultraCountAfter).toBe(13); expect(rogueCountAfter).toBe(7); const secondItemsAfter = scene.getPlayerParty()[1].getHeldItems(); diff --git a/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts b/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts index 7fc2490fcc9..87ccff71e22 100644 --- a/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts @@ -18,6 +18,7 @@ import { TheExpertPokemonBreederEncounter } from "#app/data/mystery-encounters/e import { TrainerType } from "#enums/trainer-type"; import { EggTier } from "#enums/egg-type"; import { PostMysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; +import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#app/data/balance/starters"; const namespace = "mysteryEncounters/theExpertPokemonBreeder"; const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ]; @@ -182,7 +183,10 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { await game.phaseInterceptor.to(PostMysteryEncounterPhase); const friendshipAfter = scene.currentBattle.mysteryEncounter!.misc.pokemon1.friendship; - expect(friendshipAfter).toBe(friendshipBefore + 20 + 2); // +2 extra for friendship gained from winning battle + // 20 from ME + extra from winning battle (that extra is not accurate to what happens in game. + // The Pokemon normally gets FRIENDSHIP_GAIN_FROM_BATTLE 3 times, once for each defeated Pokemon + // but due to how skipBattleRunMysteryEncounterRewardsPhase is implemented, it only receives it once) + expect(friendshipAfter).toBe(friendshipBefore + 20 + FRIENDSHIP_GAIN_FROM_BATTLE); }); }); @@ -261,7 +265,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { await game.phaseInterceptor.to(PostMysteryEncounterPhase); const friendshipAfter = scene.currentBattle.mysteryEncounter!.misc.pokemon2.friendship; - expect(friendshipAfter).toBe(friendshipBefore + 20 + 2); // +2 extra for friendship gained from winning battle + expect(friendshipAfter).toBe(friendshipBefore + 20 + FRIENDSHIP_GAIN_FROM_BATTLE); // 20 from ME + extra for friendship gained from winning battle }); }); @@ -340,7 +344,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { await game.phaseInterceptor.to(PostMysteryEncounterPhase); const friendshipAfter = scene.currentBattle.mysteryEncounter!.misc.pokemon3.friendship; - expect(friendshipAfter).toBe(friendshipBefore + 20 + 2); // +2 extra for friendship gained from winning battle + expect(friendshipAfter).toBe(friendshipBefore + 20 + FRIENDSHIP_GAIN_FROM_BATTLE); // 20 + extra for friendship gained from winning battle }); }); }); diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index ead24e6d92f..e0d432265a3 100644 --- a/src/ui/pokemon-info-container.ts +++ b/src/ui/pokemon-info-container.ts @@ -313,6 +313,11 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.pokemonShinyNewIcon.setShadowColor(getTextColor(TextStyle.SUMMARY_BLUE, true, this.scene.uiTheme)); const newShinyOrVariant = ((newShiny & caughtAttr) === BigInt(0)) || ((newVariant & caughtAttr) === BigInt(0)); this.pokemonShinyNewIcon.setVisible(!!newShinyOrVariant); + } else if ((caughtAttr & DexAttr.NON_SHINY) === BigInt(0) && ((caughtAttr & DexAttr.SHINY) === DexAttr.SHINY)) { //If the player has *only* caught any shiny variant of this species, not a non-shiny + this.pokemonShinyNewIcon.setVisible(true); + this.pokemonShinyNewIcon.setText("(+)"); + this.pokemonShinyNewIcon.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, this.scene.uiTheme)); + this.pokemonShinyNewIcon.setShadowColor(getTextColor(TextStyle.SUMMARY_BLUE, true, this.scene.uiTheme)); } else { this.pokemonShinyNewIcon.setVisible(false); } diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index a08b7ceaec6..be78e7e1b44 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -51,6 +51,7 @@ import { Abilities } from "#enums/abilities"; import { getPassiveCandyCount, getValueReductionCandyCounts, getSameSpeciesEggCandyCounts } from "#app/data/balance/starters"; import { BooleanHolder, capitalizeString, fixedInt, getLocalizedSpriteKey, isNullOrUndefined, NumberHolder, padInt, randIntRange, rgbHexToRgba, toReadableString } from "#app/utils"; import type { Nature } from "#enums/nature"; +import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; export type StarterSelectCallback = (starters: Starter[]) => void; @@ -1462,7 +1463,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const currentPartyValue = this.starterSpecies.map(s => s.generation).reduce((total: number, gen: number, i: number) => total += this.scene.gameData.getSpeciesStarterValue(this.starterSpecies[i].speciesId), 0); const newCost = this.scene.gameData.getSpeciesStarterValue(this.lastSpecies.speciesId); - if (!isDupe && isValidForChallenge.value && currentPartyValue + newCost <= this.getValueLimit() && this.starterSpecies.length < 6) { // this checks to make sure the pokemon doesn't exist in your party, it's valid for the challenge and that it won't go over the cost limit; if it meets all these criteria it will add it to your party + if (!isDupe && isValidForChallenge.value && currentPartyValue + newCost <= this.getValueLimit() && this.starterSpecies.length < PLAYER_PARTY_MAX_SIZE) { // this checks to make sure the pokemon doesn't exist in your party, it's valid for the challenge and that it won't go over the cost limit; if it meets all these criteria it will add it to your party options = [ { label: i18next.t("starterSelectUiHandler:addToParty"),