Merge branch 'beta' into spectral-thief-implementation

This commit is contained in:
geeilhan 2025-02-11 02:56:16 +01:00 committed by GitHub
commit 2ee2075142
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 357 additions and 136 deletions

View File

@ -5,6 +5,7 @@ import importX from 'eslint-plugin-import-x';
export default [ export default [
{ {
name: "eslint-config",
files: ["src/**/*.{ts,tsx,js,jsx}"], files: ["src/**/*.{ts,tsx,js,jsx}"],
ignores: ["dist/*", "build/*", "coverage/*", "public/*", ".github/*", "node_modules/*", ".vscode/*"], ignores: ["dist/*", "build/*", "coverage/*", "public/*", ".github/*", "node_modules/*", ".vscode/*"],
languageOptions: { languageOptions: {
@ -48,5 +49,22 @@ export default [
"no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }], // Disallows multiple empty lines "no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }], // Disallows multiple empty lines
"@typescript-eslint/consistent-type-imports": "error", // Enforces type-only imports wherever possible "@typescript-eslint/consistent-type-imports": "error", // Enforces type-only imports wherever possible
} }
},
{
name: "eslint-tests",
files: ["src/test/**/**.test.ts"],
languageOptions: {
parser: parser,
parserOptions: {
"project": ["./tsconfig.json"]
}
},
plugins: {
"@typescript-eslint": tseslint
},
rules: {
"@typescript-eslint/no-floating-promises": "error", // Require Promise-like statements to be handled appropriately. - https://typescript-eslint.io/rules/no-floating-promises/
"@typescript-eslint/no-misused-promises": "error", // Disallow Promises in places not designed to handle them. - https://typescript-eslint.io/rules/no-misused-promises/
}
} }
] ]

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -1752,7 +1752,7 @@ export class HighestStatBoostTag extends AbilityBattlerTag {
super.onAdd(pokemon); super.onAdd(pokemon);
let highestStat: EffectiveStat; let highestStat: EffectiveStat;
EFFECTIVE_STATS.map(s => pokemon.getEffectiveStat(s)).reduce((highestValue: number, value: number, i: number) => { EFFECTIVE_STATS.map(s => pokemon.getEffectiveStat(s, undefined, undefined, undefined, undefined, undefined, undefined, true)).reduce((highestValue: number, value: number, i: number) => {
if (value > highestValue) { if (value > highestValue) {
highestStat = EFFECTIVE_STATS[i]; highestStat = EFFECTIVE_STATS[i];
return value; return value;
@ -1763,15 +1763,7 @@ export class HighestStatBoostTag extends AbilityBattlerTag {
highestStat = highestStat!; // tell TS compiler it's defined! highestStat = highestStat!; // tell TS compiler it's defined!
this.stat = highestStat; this.stat = highestStat;
switch (this.stat) { this.multiplier = this.stat === Stat.SPD ? 1.5 : 1.3;
case Stat.SPD:
this.multiplier = 1.5;
break;
default:
this.multiplier = 1.3;
break;
}
globalScene.queueMessage(i18next.t("battlerTags:highestStatBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), statName: i18next.t(getStatKey(highestStat)) }), null, false, null, true); globalScene.queueMessage(i18next.t("battlerTags:highestStatBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), statName: i18next.t(getStatKey(highestStat)) }), null, false, null, true);
} }

View File

@ -4622,7 +4622,8 @@ export class TeraMoveCategoryAttr extends VariableMoveCategoryAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const category = (args[0] as Utils.NumberHolder); const category = (args[0] as Utils.NumberHolder);
if (user.isTerastallized() && user.getEffectiveStat(Stat.ATK, target, move) > user.getEffectiveStat(Stat.SPATK, target, move)) { if (user.isTerastallized() && user.getEffectiveStat(Stat.ATK, target, move, true, true, false, false, true) >
user.getEffectiveStat(Stat.SPATK, target, move, true, true, false, false, true)) {
category.value = MoveCategory.PHYSICAL; category.value = MoveCategory.PHYSICAL;
return true; return true;
} }
@ -10968,8 +10969,7 @@ export function initMoves() {
.attr(TeraMoveCategoryAttr) .attr(TeraMoveCategoryAttr)
.attr(TeraBlastTypeAttr) .attr(TeraBlastTypeAttr)
.attr(TeraBlastPowerAttr) .attr(TeraBlastPowerAttr)
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, true, { condition: (user, target, move) => user.isTerastallized() && user.isOfType(Type.STELLAR) }) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, true, { condition: (user, target, move) => user.isTerastallized() && user.isOfType(Type.STELLAR) }),
.partial(), /** Does not ignore abilities that affect stats, relevant in determining the move's category {@see TeraMoveCategoryAttr} */
new SelfStatusMove(Moves.SILK_TRAP, Type.BUG, -1, 10, -1, 4, 9) new SelfStatusMove(Moves.SILK_TRAP, Type.BUG, -1, 10, -1, 4, 9)
.attr(ProtectAttr, BattlerTagType.SILK_TRAP) .attr(ProtectAttr, BattlerTagType.SILK_TRAP)
.condition(failIfLastCondition), .condition(failIfLastCondition),

View File

@ -41,8 +41,6 @@ export const FunAndGamesEncounter: MysteryEncounter =
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new MoneyRequirement(0, 1.5)) // Cost equal to 1 Max Potion to play .withSceneRequirement(new MoneyRequirement(0, 1.5)) // Cost equal to 1 Max Potion to play
.withAutoHideIntroVisuals(false) .withAutoHideIntroVisuals(false)
// Allows using move without a visible enemy pokemon
.withBattleAnimationsWithoutTargets(true)
// The Wobbuffet won't use moves // The Wobbuffet won't use moves
.withSkipEnemyBattleTurns(true) .withSkipEnemyBattleTurns(true)
// Will skip COMMAND selection menu and go straight to FIGHT (move select) menu // Will skip COMMAND selection menu and go straight to FIGHT (move select) menu

View File

@ -887,16 +887,21 @@ export function getRandomEncounterSpecies(level: number, isBoss: boolean = false
let bossSpecies: PokemonSpecies; let bossSpecies: PokemonSpecies;
let isEventEncounter = false; let isEventEncounter = false;
const eventEncounters = globalScene.eventManager.getEventEncounters(); const eventEncounters = globalScene.eventManager.getEventEncounters();
let formIndex;
if (eventEncounters.length > 0 && randSeedInt(2) === 1) { if (eventEncounters.length > 0 && randSeedInt(2) === 1) {
const eventEncounter = randSeedItem(eventEncounters); const eventEncounter = randSeedItem(eventEncounters);
const levelSpecies = getPokemonSpecies(eventEncounter.species).getWildSpeciesForLevel(level, !eventEncounter.blockEvolution, isBoss, globalScene.gameMode); const levelSpecies = getPokemonSpecies(eventEncounter.species).getWildSpeciesForLevel(level, !eventEncounter.blockEvolution, isBoss, globalScene.gameMode);
isEventEncounter = true; isEventEncounter = true;
bossSpecies = getPokemonSpecies(levelSpecies); bossSpecies = getPokemonSpecies(levelSpecies);
formIndex = eventEncounter.formIndex;
} else { } else {
bossSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, level, 0, getPartyLuckValue(globalScene.getPlayerParty()), isBoss); bossSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, level, 0, getPartyLuckValue(globalScene.getPlayerParty()), isBoss);
} }
const ret = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, isBoss); const ret = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, isBoss);
if (formIndex) {
ret.formIndex = formIndex;
}
//Reroll shiny for event encounters //Reroll shiny for event encounters
if (isEventEncounter && !ret.shiny) { if (isEventEncounter && !ret.shiny) {

View File

@ -980,11 +980,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param ignoreOppAbility during an attack, determines whether the opposing Pokemon's abilities should be ignored during the stat calculation. * @param ignoreOppAbility during an attack, determines whether the opposing Pokemon's abilities should be ignored during the stat calculation.
* @param isCritical determines whether a critical hit has occurred or not (`false` by default) * @param isCritical determines whether a critical hit has occurred or not (`false` by default)
* @param simulated if `true`, nullifies any effects that produce any changes to game state from triggering * @param simulated if `true`, nullifies any effects that produce any changes to game state from triggering
* @param ignoreHeldItems determines whether this Pokemon's held items should be ignored during the stat calculation, default `false`
* @returns the final in-battle value of a stat * @returns the final in-battle value of a stat
*/ */
getEffectiveStat(stat: EffectiveStat, opponent?: Pokemon, move?: Move, ignoreAbility: boolean = false, ignoreOppAbility: boolean = false, isCritical: boolean = false, simulated: boolean = true): number { getEffectiveStat(stat: EffectiveStat, opponent?: Pokemon, move?: Move, ignoreAbility: boolean = false, ignoreOppAbility: boolean = false, isCritical: boolean = false, simulated: boolean = true, ignoreHeldItems: boolean = false): number {
const statValue = new Utils.NumberHolder(this.getStat(stat, false)); const statValue = new Utils.NumberHolder(this.getStat(stat, false));
if (!ignoreHeldItems) {
globalScene.applyModifiers(StatBoosterModifier, this.isPlayer(), this, stat, statValue); globalScene.applyModifiers(StatBoosterModifier, this.isPlayer(), this, stat, statValue);
}
// The Ruin abilities here are never ignored, but they reveal themselves on summon anyway // The Ruin abilities here are never ignored, but they reveal themselves on summon anyway
const fieldApplied = new Utils.BooleanHolder(false); const fieldApplied = new Utils.BooleanHolder(false);
@ -998,7 +1001,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyStatMultiplierAbAttrs(StatMultiplierAbAttr, this, stat, statValue, simulated); applyStatMultiplierAbAttrs(StatMultiplierAbAttr, this, stat, statValue, simulated);
} }
let ret = statValue.value * this.getStatStageMultiplier(stat, opponent, move, ignoreOppAbility, isCritical, simulated); let ret = statValue.value * this.getStatStageMultiplier(stat, opponent, move, ignoreOppAbility, isCritical, simulated, ignoreHeldItems);
switch (stat) { switch (stat) {
case Stat.ATK: case Stat.ATK:
@ -2520,9 +2523,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param ignoreOppAbility determines whether the effects of the opponent's abilities (i.e. Unaware) should be ignored (`false` by default) * @param ignoreOppAbility determines whether the effects of the opponent's abilities (i.e. Unaware) should be ignored (`false` by default)
* @param isCritical determines whether a critical hit has occurred or not (`false` by default) * @param isCritical determines whether a critical hit has occurred or not (`false` by default)
* @param simulated determines whether effects are applied without altering game state (`true` by default) * @param simulated determines whether effects are applied without altering game state (`true` by default)
* @param ignoreHeldItems determines whether this Pokemon's held items should be ignored during the stat calculation, default `false`
* @return the stat stage multiplier to be used for effective stat calculation * @return the stat stage multiplier to be used for effective stat calculation
*/ */
getStatStageMultiplier(stat: EffectiveStat, opponent?: Pokemon, move?: Move, ignoreOppAbility: boolean = false, isCritical: boolean = false, simulated: boolean = true): number { getStatStageMultiplier(stat: EffectiveStat, opponent?: Pokemon, move?: Move, ignoreOppAbility: boolean = false, isCritical: boolean = false, simulated: boolean = true, ignoreHeldItems: boolean = false): number {
const statStage = new Utils.IntegerHolder(this.getStatStage(stat)); const statStage = new Utils.IntegerHolder(this.getStatStage(stat));
const ignoreStatStage = new Utils.BooleanHolder(false); const ignoreStatStage = new Utils.BooleanHolder(false);
@ -2549,7 +2553,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (!ignoreStatStage.value) { if (!ignoreStatStage.value) {
const statStageMultiplier = new Utils.NumberHolder(Math.max(2, 2 + statStage.value) / Math.max(2, 2 - statStage.value)); const statStageMultiplier = new Utils.NumberHolder(Math.max(2, 2 + statStage.value) / Math.max(2, 2 - statStage.value));
if (!ignoreHeldItems) {
globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), stat, statStageMultiplier); globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), stat, statStageMultiplier);
}
return Math.min(statStageMultiplier.value, 4); return Math.min(statStageMultiplier.value, 4);
} }
return 1; return 1;
@ -4395,8 +4401,12 @@ export class PlayerPokemon extends Pokemon {
].filter(d => !!d); ].filter(d => !!d);
const amount = new Utils.NumberHolder(friendship); const amount = new Utils.NumberHolder(friendship);
globalScene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount); globalScene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount);
const candyFriendshipMultiplier = globalScene.eventManager.getClassicFriendshipMultiplier(); const candyFriendshipMultiplier = globalScene.gameMode.isClassic ? globalScene.eventManager.getClassicFriendshipMultiplier() : 1;
const starterAmount = new Utils.NumberHolder(Math.floor(amount.value * (globalScene.gameMode.isClassic ? candyFriendshipMultiplier : 1) / (fusionStarterSpeciesId ? 2 : 1))); const fusionReduction = fusionStarterSpeciesId
? globalScene.eventManager.areFusionsBoosted() ? 1.5 // Divide candy gain for fusions by 1.5 during events
: 2 // 2 for fusions outside events
: 1; // 1 for non-fused mons
const starterAmount = new Utils.NumberHolder(Math.floor(amount.value * candyFriendshipMultiplier / fusionReduction));
// Add friendship to this PlayerPokemon // Add friendship to this PlayerPokemon
this.friendship = Math.min(this.friendship + amount.value, 255); this.friendship = Math.min(this.friendship + amount.value, 255);

View File

@ -249,9 +249,9 @@ export class LoadingScene extends SceneBase {
} }
const availableLangs = [ "en", "de", "it", "fr", "ja", "ko", "es-ES", "pt-BR", "zh-CN" ]; const availableLangs = [ "en", "de", "it", "fr", "ja", "ko", "es-ES", "pt-BR", "zh-CN" ];
if (lang && availableLangs.includes(lang)) { if (lang && availableLangs.includes(lang)) {
this.loadImage("yearofthesnakeevent-" + lang, "events"); this.loadImage("valentines2025event-" + lang, "events");
} else { } else {
this.loadImage("yearofthesnakeevent-en", "events"); this.loadImage("valentines2025event-en", "events");
} }
this.loadAtlas("statuses", ""); this.loadAtlas("statuses", "");

View File

@ -1720,7 +1720,16 @@ const modifierPool: ModifierPool = {
}, 4), }, 4),
new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3), new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3),
new WeightedModifierType(modifierTypes.TERA_SHARD, 1), new WeightedModifierType(modifierTypes.TERA_SHARD, 1),
new WeightedModifierType(modifierTypes.DNA_SPLICERS, (party: Pokemon[]) => globalScene.gameMode.isSplicedOnly && party.filter(p => !p.fusionSpecies).length > 1 ? 4 : 0), new WeightedModifierType(modifierTypes.DNA_SPLICERS, (party: Pokemon[]) => {
if (party.filter(p => !p.fusionSpecies).length > 1) {
if (globalScene.gameMode.isSplicedOnly) {
return 4;
} else if (globalScene.gameMode.isClassic && globalScene.eventManager.areFusionsBoosted()) {
return 1;
}
}
return 0;
}, 4),
new WeightedModifierType(modifierTypes.VOUCHER, (_party: Pokemon[], rerollCount: number) => !globalScene.gameMode.isDaily ? Math.max(1 - rerollCount, 0) : 0, 1), new WeightedModifierType(modifierTypes.VOUCHER, (_party: Pokemon[], rerollCount: number) => !globalScene.gameMode.isDaily ? Math.max(1 - rerollCount, 0) : 0, 1),
].map(m => { ].map(m => {
m.setTier(ModifierTier.GREAT); return m; m.setTier(ModifierTier.GREAT); return m;
@ -1879,7 +1888,7 @@ const modifierPool: ModifierPool = {
new WeightedModifierType(modifierTypes.MULTI_LENS, 18), new WeightedModifierType(modifierTypes.MULTI_LENS, 18),
new WeightedModifierType(modifierTypes.VOUCHER_PREMIUM, (_party: Pokemon[], rerollCount: number) => new WeightedModifierType(modifierTypes.VOUCHER_PREMIUM, (_party: Pokemon[], rerollCount: number) =>
!globalScene.gameMode.isDaily && !globalScene.gameMode.isEndless && !globalScene.gameMode.isSplicedOnly ? Math.max(5 - rerollCount * 2, 0) : 0, 5), !globalScene.gameMode.isDaily && !globalScene.gameMode.isEndless && !globalScene.gameMode.isSplicedOnly ? Math.max(5 - rerollCount * 2, 0) : 0, 5),
new WeightedModifierType(modifierTypes.DNA_SPLICERS, (party: Pokemon[]) => !globalScene.gameMode.isSplicedOnly && party.filter(p => !p.fusionSpecies).length > 1 ? 24 : 0, 24), new WeightedModifierType(modifierTypes.DNA_SPLICERS, (party: Pokemon[]) => !(globalScene.gameMode.isClassic && globalScene.eventManager.areFusionsBoosted()) && !globalScene.gameMode.isSplicedOnly && party.filter(p => !p.fusionSpecies).length > 1 ? 24 : 0, 24),
new WeightedModifierType(modifierTypes.MINI_BLACK_HOLE, () => (globalScene.gameMode.isDaily || (!globalScene.gameMode.isFreshStartChallenge() && globalScene.gameData.isUnlocked(Unlockables.MINI_BLACK_HOLE))) ? 1 : 0, 1), new WeightedModifierType(modifierTypes.MINI_BLACK_HOLE, () => (globalScene.gameMode.isDaily || (!globalScene.gameMode.isFreshStartChallenge() && globalScene.gameData.isUnlocked(Unlockables.MINI_BLACK_HOLE))) ? 1 : 0, 1),
].map(m => { ].map(m => {
m.setTier(ModifierTier.MASTER); return m; m.setTier(ModifierTier.MASTER); return m;
@ -2538,7 +2547,7 @@ export function getPartyLuckValue(party: Pokemon[]): number {
return DailyLuck.value; return DailyLuck.value;
} }
const eventSpecies = globalScene.eventManager.getEventLuckBoostedSpecies(); const eventSpecies = globalScene.eventManager.getEventLuckBoostedSpecies();
const luck = Phaser.Math.Clamp(party.map(p => p.isAllowedInBattle() ? p.getLuck() + (eventSpecies.includes(p.species.speciesId) ? 1 : 0) : 0) const luck = Phaser.Math.Clamp(party.map(p => p.isAllowedInBattle() ? p.getLuck() + (eventSpecies.includes(p.species.speciesId) ? 3 : 0) : 0)
.reduce((total: number, value: number) => total += value, 0), 0, 14); .reduce((total: number, value: number) => total += value, 0), 0, 14);
return Math.min(globalScene.eventManager.getEventLuckBoost() + (luck ?? 0), 14); return Math.min(globalScene.eventManager.getEventLuckBoost() + (luck ?? 0), 14);
} }

View File

@ -0,0 +1,66 @@
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Nature } from "#enums/nature";
import { Species } from "#enums/species";
import { Stat } from "#enums/stat";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { BattlerIndex } from "#app/battle";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Abilities - Protosynthesis", () => {
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.TACKLE ])
.ability(Abilities.PROTOSYNTHESIS)
.battleType("single")
.disableCrits()
.enemySpecies(Species.MAGIKARP)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH);
});
it("should not consider temporary items when determining which stat to boost", async() => {
// Mew has uniform base stats
game.override.startingModifier([{ name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.DEF }])
.enemyMoveset(Moves.SUNNY_DAY)
.startingLevel(100)
.enemyLevel(100);
await game.classicMode.startBattle([ Species.MEW ]);
const mew = game.scene.getPlayerPokemon()!;
// Nature of starting mon is randomized. We need to fix it to a neutral nature for the automated test.
mew.setNature(Nature.HARDY);
const enemy = game.scene.getEnemyPokemon()!;
const def_before_boost = mew.getEffectiveStat(Stat.DEF, undefined, undefined, false, undefined, false, false, true);
const atk_before_boost = mew.getEffectiveStat(Stat.ATK, undefined, undefined, false, undefined, false, false, true);
const initialHp = enemy.hp;
game.move.select(Moves.TACKLE);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
await game.toNextTurn();
const unboosted_dmg = initialHp - enemy.hp;
enemy.hp = initialHp;
const def_after_boost = mew.getEffectiveStat(Stat.DEF, undefined, undefined, false, undefined, false, false, true);
const atk_after_boost = mew.getEffectiveStat(Stat.ATK, undefined, undefined, false, undefined, false, false, true);
game.move.select(Moves.TACKLE);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
await game.toNextTurn();
const boosted_dmg = initialHp - enemy.hp;
expect(boosted_dmg).toBeGreaterThan(unboosted_dmg);
expect(def_after_boost).toEqual(def_before_boost);
expect(atk_after_boost).toBeGreaterThan(atk_before_boost);
});
});

View File

@ -53,11 +53,11 @@ describe("Abilities - Shield Dust", () => {
expect(move.id).toBe(Moves.AIR_SLASH); expect(move.id).toBe(Moves.AIR_SLASH);
const chance = new NumberHolder(move.chance); const chance = new NumberHolder(move.chance);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getFirstTarget(), false); await applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getFirstTarget(), false);
applyPreDefendAbAttrs(IgnoreMoveEffectsAbAttr, phase.getFirstTarget()!, phase.getUserPokemon()!, null, null, false, chance); await applyPreDefendAbAttrs(IgnoreMoveEffectsAbAttr, phase.getFirstTarget()!, phase.getUserPokemon()!, null, null, false, chance);
expect(chance.value).toBe(0); expect(chance.value).toBe(0);
}, 20000); });
//TODO King's Rock Interaction Unit Test //TODO King's Rock Interaction Unit Test
}); });

View File

@ -45,9 +45,9 @@ describe("Abilities - Unseen Fist", () => {
it( it(
"should not apply if the source has Long Reach", "should not apply if the source has Long Reach",
() => { async () => {
game.override.passiveAbility(Abilities.LONG_REACH); game.override.passiveAbility(Abilities.LONG_REACH);
testUnseenFistHitResult(game, Moves.QUICK_ATTACK, Moves.PROTECT, false); await testUnseenFistHitResult(game, Moves.QUICK_ATTACK, Moves.PROTECT, false);
} }
); );
@ -67,7 +67,7 @@ describe("Abilities - Unseen Fist", () => {
game.override.enemyLevel(1); game.override.enemyLevel(1);
game.override.moveset([ Moves.TACKLE ]); game.override.moveset([ Moves.TACKLE ]);
await game.startBattle(); await game.classicMode.startBattle();
const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!;
enemyPokemon.addTag(BattlerTagType.SUBSTITUTE, 0, Moves.NONE, enemyPokemon.id); enemyPokemon.addTag(BattlerTagType.SUBSTITUTE, 0, Moves.NONE, enemyPokemon.id);
@ -86,7 +86,7 @@ async function testUnseenFistHitResult(game: GameManager, attackMove: Moves, pro
game.override.moveset([ attackMove ]); game.override.moveset([ attackMove ]);
game.override.enemyMoveset([ protectMove, protectMove, protectMove, protectMove ]); game.override.enemyMoveset([ protectMove, protectMove, protectMove, protectMove ]);
await game.startBattle(); await game.classicMode.startBattle();
const leadPokemon = game.scene.getPlayerPokemon()!; const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);

View File

@ -20,8 +20,8 @@ const pokemonName = "PKM";
const sourceText = "SOURCE"; const sourceText = "SOURCE";
describe("Status Effect Messages", () => { describe("Status Effect Messages", () => {
beforeAll(() => { beforeAll(async () => {
i18next.init(); await i18next.init();
}); });
describe("NONE", () => { describe("NONE", () => {

View File

@ -40,10 +40,10 @@ describe("Evolution", () => {
eevee.abilityIndex = 2; eevee.abilityIndex = 2;
trapinch.abilityIndex = 2; trapinch.abilityIndex = 2;
eevee.evolve(pokemonEvolutions[Species.EEVEE][6], eevee.getSpeciesForm()); await eevee.evolve(pokemonEvolutions[Species.EEVEE][6], eevee.getSpeciesForm());
expect(eevee.abilityIndex).toBe(2); expect(eevee.abilityIndex).toBe(2);
trapinch.evolve(pokemonEvolutions[Species.TRAPINCH][0], trapinch.getSpeciesForm()); await trapinch.evolve(pokemonEvolutions[Species.TRAPINCH][0], trapinch.getSpeciesForm());
expect(trapinch.abilityIndex).toBe(1); expect(trapinch.abilityIndex).toBe(1);
}); });
@ -55,10 +55,10 @@ describe("Evolution", () => {
bulbasaur.abilityIndex = 0; bulbasaur.abilityIndex = 0;
charmander.abilityIndex = 1; charmander.abilityIndex = 1;
bulbasaur.evolve(pokemonEvolutions[Species.BULBASAUR][0], bulbasaur.getSpeciesForm()); await bulbasaur.evolve(pokemonEvolutions[Species.BULBASAUR][0], bulbasaur.getSpeciesForm());
expect(bulbasaur.abilityIndex).toBe(0); expect(bulbasaur.abilityIndex).toBe(0);
charmander.evolve(pokemonEvolutions[Species.CHARMANDER][0], charmander.getSpeciesForm()); await charmander.evolve(pokemonEvolutions[Species.CHARMANDER][0], charmander.getSpeciesForm());
expect(charmander.abilityIndex).toBe(1); expect(charmander.abilityIndex).toBe(1);
}); });
@ -68,7 +68,7 @@ describe("Evolution", () => {
const squirtle = game.scene.getPlayerPokemon()!; const squirtle = game.scene.getPlayerPokemon()!;
squirtle.abilityIndex = 5; squirtle.abilityIndex = 5;
squirtle.evolve(pokemonEvolutions[Species.SQUIRTLE][0], squirtle.getSpeciesForm()); await squirtle.evolve(pokemonEvolutions[Species.SQUIRTLE][0], squirtle.getSpeciesForm());
expect(squirtle.abilityIndex).toBe(0); expect(squirtle.abilityIndex).toBe(0);
}); });
@ -80,7 +80,7 @@ describe("Evolution", () => {
nincada.metBiome = -1; nincada.metBiome = -1;
nincada.gender = 1; nincada.gender = 1;
nincada.evolve(pokemonEvolutions[Species.NINCADA][0], nincada.getSpeciesForm()); await nincada.evolve(pokemonEvolutions[Species.NINCADA][0], nincada.getSpeciesForm());
const ninjask = game.scene.getPlayerParty()[0]; const ninjask = game.scene.getPlayerParty()[0];
const shedinja = game.scene.getPlayerParty()[1]; const shedinja = game.scene.getPlayerParty()[1];
expect(ninjask.abilityIndex).toBe(2); expect(ninjask.abilityIndex).toBe(2);

View File

@ -31,7 +31,7 @@ describe("Items - Light Ball", () => {
it("LIGHT_BALL activates in battle correctly", async() => { it("LIGHT_BALL activates in battle correctly", async() => {
game.override.startingHeldItems([{ name: "SPECIES_STAT_BOOSTER", type: "LIGHT_BALL" }]); game.override.startingHeldItems([{ name: "SPECIES_STAT_BOOSTER", type: "LIGHT_BALL" }]);
const consoleSpy = vi.spyOn(console, "log"); const consoleSpy = vi.spyOn(console, "log");
await game.startBattle([ await game.classicMode.startBattle([
Species.PIKACHU Species.PIKACHU
]); ]);
@ -64,7 +64,7 @@ describe("Items - Light Ball", () => {
}); });
it("LIGHT_BALL held by PIKACHU", async() => { it("LIGHT_BALL held by PIKACHU", async() => {
await game.startBattle([ await game.classicMode.startBattle([
Species.PIKACHU Species.PIKACHU
]); ]);
@ -83,7 +83,7 @@ describe("Items - Light Ball", () => {
expect(spAtkValue.value / spAtkStat).toBe(1); expect(spAtkValue.value / spAtkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "LIGHT_BALL" ])!.newModifier(partyMember), true); await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "LIGHT_BALL" ])!.newModifier(partyMember), true);
game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue);
@ -92,7 +92,7 @@ describe("Items - Light Ball", () => {
}, 20000); }, 20000);
it("LIGHT_BALL held by fused PIKACHU (base)", async() => { it("LIGHT_BALL held by fused PIKACHU (base)", async() => {
await game.startBattle([ await game.classicMode.startBattle([
Species.PIKACHU, Species.PIKACHU,
Species.MAROWAK Species.MAROWAK
]); ]);
@ -122,7 +122,7 @@ describe("Items - Light Ball", () => {
expect(spAtkValue.value / spAtkStat).toBe(1); expect(spAtkValue.value / spAtkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "LIGHT_BALL" ])!.newModifier(partyMember), true); await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "LIGHT_BALL" ])!.newModifier(partyMember), true);
game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue);
@ -161,7 +161,7 @@ describe("Items - Light Ball", () => {
expect(spAtkValue.value / spAtkStat).toBe(1); expect(spAtkValue.value / spAtkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "LIGHT_BALL" ])!.newModifier(partyMember), true); await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "LIGHT_BALL" ])!.newModifier(partyMember), true);
game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue);
@ -189,7 +189,7 @@ describe("Items - Light Ball", () => {
expect(spAtkValue.value / spAtkStat).toBe(1); expect(spAtkValue.value / spAtkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "LIGHT_BALL" ])!.newModifier(partyMember), true); await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "LIGHT_BALL" ])!.newModifier(partyMember), true);
game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue);

View File

@ -31,7 +31,7 @@ describe("Items - Metal Powder", () => {
it("METAL_POWDER activates in battle correctly", async() => { it("METAL_POWDER activates in battle correctly", async() => {
game.override.startingHeldItems([{ name: "SPECIES_STAT_BOOSTER", type: "METAL_POWDER" }]); game.override.startingHeldItems([{ name: "SPECIES_STAT_BOOSTER", type: "METAL_POWDER" }]);
const consoleSpy = vi.spyOn(console, "log"); const consoleSpy = vi.spyOn(console, "log");
await game.startBattle([ await game.classicMode.startBattle([
Species.DITTO Species.DITTO
]); ]);
@ -79,7 +79,7 @@ describe("Items - Metal Powder", () => {
expect(defValue.value / defStat).toBe(1); expect(defValue.value / defStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "METAL_POWDER" ])!.newModifier(partyMember), true); await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "METAL_POWDER" ])!.newModifier(partyMember), true);
game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue);
expect(defValue.value / defStat).toBe(2); expect(defValue.value / defStat).toBe(2);
@ -112,7 +112,7 @@ describe("Items - Metal Powder", () => {
expect(defValue.value / defStat).toBe(1); expect(defValue.value / defStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "METAL_POWDER" ])!.newModifier(partyMember), true); await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "METAL_POWDER" ])!.newModifier(partyMember), true);
game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue);
expect(defValue.value / defStat).toBe(2); expect(defValue.value / defStat).toBe(2);
@ -145,7 +145,7 @@ describe("Items - Metal Powder", () => {
expect(defValue.value / defStat).toBe(1); expect(defValue.value / defStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "METAL_POWDER" ])!.newModifier(partyMember), true); await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "METAL_POWDER" ])!.newModifier(partyMember), true);
game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue);
expect(defValue.value / defStat).toBe(2); expect(defValue.value / defStat).toBe(2);
@ -167,7 +167,7 @@ describe("Items - Metal Powder", () => {
expect(defValue.value / defStat).toBe(1); expect(defValue.value / defStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "METAL_POWDER" ])!.newModifier(partyMember), true); await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "METAL_POWDER" ])!.newModifier(partyMember), true);
game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue);
expect(defValue.value / defStat).toBe(1); expect(defValue.value / defStat).toBe(1);

View File

@ -31,7 +31,7 @@ describe("Items - Quick Powder", () => {
it("QUICK_POWDER activates in battle correctly", async() => { it("QUICK_POWDER activates in battle correctly", async() => {
game.override.startingHeldItems([{ name: "SPECIES_STAT_BOOSTER", type: "QUICK_POWDER" }]); game.override.startingHeldItems([{ name: "SPECIES_STAT_BOOSTER", type: "QUICK_POWDER" }]);
const consoleSpy = vi.spyOn(console, "log"); const consoleSpy = vi.spyOn(console, "log");
await game.startBattle([ await game.classicMode.startBattle([
Species.DITTO Species.DITTO
]); ]);
@ -64,7 +64,7 @@ describe("Items - Quick Powder", () => {
}); });
it("QUICK_POWDER held by DITTO", async() => { it("QUICK_POWDER held by DITTO", async() => {
await game.startBattle([ await game.classicMode.startBattle([
Species.DITTO Species.DITTO
]); ]);
@ -79,14 +79,14 @@ describe("Items - Quick Powder", () => {
expect(spdValue.value / spdStat).toBe(1); expect(spdValue.value / spdStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "QUICK_POWDER" ])!.newModifier(partyMember), true); await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "QUICK_POWDER" ])!.newModifier(partyMember), true);
game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue);
expect(spdValue.value / spdStat).toBe(2); expect(spdValue.value / spdStat).toBe(2);
}, 20000); });
it("QUICK_POWDER held by fused DITTO (base)", async() => { it("QUICK_POWDER held by fused DITTO (base)", async() => {
await game.startBattle([ await game.classicMode.startBattle([
Species.DITTO, Species.DITTO,
Species.MAROWAK Species.MAROWAK
]); ]);
@ -112,14 +112,14 @@ describe("Items - Quick Powder", () => {
expect(spdValue.value / spdStat).toBe(1); expect(spdValue.value / spdStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "QUICK_POWDER" ])!.newModifier(partyMember), true); await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "QUICK_POWDER" ])!.newModifier(partyMember), true);
game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue);
expect(spdValue.value / spdStat).toBe(2); expect(spdValue.value / spdStat).toBe(2);
}, 20000); });
it("QUICK_POWDER held by fused DITTO (part)", async() => { it("QUICK_POWDER held by fused DITTO (part)", async() => {
await game.startBattle([ await game.classicMode.startBattle([
Species.MAROWAK, Species.MAROWAK,
Species.DITTO Species.DITTO
]); ]);
@ -145,14 +145,14 @@ describe("Items - Quick Powder", () => {
expect(spdValue.value / spdStat).toBe(1); expect(spdValue.value / spdStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "QUICK_POWDER" ])!.newModifier(partyMember), true); await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "QUICK_POWDER" ])!.newModifier(partyMember), true);
game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue);
expect(spdValue.value / spdStat).toBe(2); expect(spdValue.value / spdStat).toBe(2);
}, 20000); });
it("QUICK_POWDER not held by DITTO", async() => { it("QUICK_POWDER not held by DITTO", async() => {
await game.startBattle([ await game.classicMode.startBattle([
Species.MAROWAK Species.MAROWAK
]); ]);
@ -167,9 +167,9 @@ describe("Items - Quick Powder", () => {
expect(spdValue.value / spdStat).toBe(1); expect(spdValue.value / spdStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "QUICK_POWDER" ])!.newModifier(partyMember), true); await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "QUICK_POWDER" ])!.newModifier(partyMember), true);
game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue);
expect(spdValue.value / spdStat).toBe(1); expect(spdValue.value / spdStat).toBe(1);
}, 20000); });
}); });

View File

@ -31,7 +31,7 @@ describe("Items - Thick Club", () => {
it("THICK_CLUB activates in battle correctly", async() => { it("THICK_CLUB activates in battle correctly", async() => {
game.override.startingHeldItems([{ name: "SPECIES_STAT_BOOSTER", type: "THICK_CLUB" }]); game.override.startingHeldItems([{ name: "SPECIES_STAT_BOOSTER", type: "THICK_CLUB" }]);
const consoleSpy = vi.spyOn(console, "log"); const consoleSpy = vi.spyOn(console, "log");
await game.startBattle([ await game.classicMode.startBattle([
Species.CUBONE Species.CUBONE
]); ]);
@ -64,7 +64,7 @@ describe("Items - Thick Club", () => {
}); });
it("THICK_CLUB held by CUBONE", async() => { it("THICK_CLUB held by CUBONE", async() => {
await game.startBattle([ await game.classicMode.startBattle([
Species.CUBONE Species.CUBONE
]); ]);
@ -79,14 +79,14 @@ describe("Items - Thick Club", () => {
expect(atkValue.value / atkStat).toBe(1); expect(atkValue.value / atkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true);
game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
expect(atkValue.value / atkStat).toBe(2); expect(atkValue.value / atkStat).toBe(2);
}, 20000); });
it("THICK_CLUB held by MAROWAK", async() => { it("THICK_CLUB held by MAROWAK", async() => {
await game.startBattle([ await game.classicMode.startBattle([
Species.MAROWAK Species.MAROWAK
]); ]);
@ -101,14 +101,14 @@ describe("Items - Thick Club", () => {
expect(atkValue.value / atkStat).toBe(1); expect(atkValue.value / atkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true);
game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
expect(atkValue.value / atkStat).toBe(2); expect(atkValue.value / atkStat).toBe(2);
}, 20000); });
it("THICK_CLUB held by ALOLA_MAROWAK", async() => { it("THICK_CLUB held by ALOLA_MAROWAK", async() => {
await game.startBattle([ await game.classicMode.startBattle([
Species.ALOLA_MAROWAK Species.ALOLA_MAROWAK
]); ]);
@ -123,18 +123,18 @@ describe("Items - Thick Club", () => {
expect(atkValue.value / atkStat).toBe(1); expect(atkValue.value / atkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true);
game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
expect(atkValue.value / atkStat).toBe(2); expect(atkValue.value / atkStat).toBe(2);
}, 20000); });
it("THICK_CLUB held by fused CUBONE line (base)", async() => { it("THICK_CLUB held by fused CUBONE line (base)", async() => {
// Randomly choose from the Cubone line // Randomly choose from the Cubone line
const species = [ Species.CUBONE, Species.MAROWAK, Species.ALOLA_MAROWAK ]; const species = [ Species.CUBONE, Species.MAROWAK, Species.ALOLA_MAROWAK ];
const randSpecies = Utils.randInt(species.length); const randSpecies = Utils.randInt(species.length);
await game.startBattle([ await game.classicMode.startBattle([
species[randSpecies], species[randSpecies],
Species.PIKACHU Species.PIKACHU
]); ]);
@ -160,18 +160,18 @@ describe("Items - Thick Club", () => {
expect(atkValue.value / atkStat).toBe(1); expect(atkValue.value / atkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true);
game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
expect(atkValue.value / atkStat).toBe(2); expect(atkValue.value / atkStat).toBe(2);
}, 20000); });
it("THICK_CLUB held by fused CUBONE line (part)", async() => { it("THICK_CLUB held by fused CUBONE line (part)", async() => {
// Randomly choose from the Cubone line // Randomly choose from the Cubone line
const species = [ Species.CUBONE, Species.MAROWAK, Species.ALOLA_MAROWAK ]; const species = [ Species.CUBONE, Species.MAROWAK, Species.ALOLA_MAROWAK ];
const randSpecies = Utils.randInt(species.length); const randSpecies = Utils.randInt(species.length);
await game.startBattle([ await game.classicMode.startBattle([
Species.PIKACHU, Species.PIKACHU,
species[randSpecies] species[randSpecies]
]); ]);
@ -197,14 +197,14 @@ describe("Items - Thick Club", () => {
expect(atkValue.value / atkStat).toBe(1); expect(atkValue.value / atkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true);
game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
expect(atkValue.value / atkStat).toBe(2); expect(atkValue.value / atkStat).toBe(2);
}, 20000); });
it("THICK_CLUB not held by CUBONE", async() => { it("THICK_CLUB not held by CUBONE", async() => {
await game.startBattle([ await game.classicMode.startBattle([
Species.PIKACHU Species.PIKACHU
]); ]);
@ -219,9 +219,9 @@ describe("Items - Thick Club", () => {
expect(atkValue.value / atkStat).toBe(1); expect(atkValue.value / atkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true);
game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
expect(atkValue.value / atkStat).toBe(1); expect(atkValue.value / atkStat).toBe(1);
}, 20000); });
}); });

View File

@ -45,14 +45,10 @@ describe("Moves - Dragon Rage", () => {
game.override.enemyPassiveAbility(Abilities.BALL_FETCH); game.override.enemyPassiveAbility(Abilities.BALL_FETCH);
game.override.enemyLevel(100); game.override.enemyLevel(100);
await game.startBattle(); await game.classicMode.startBattle();
partyPokemon = game.scene.getPlayerParty()[0]; partyPokemon = game.scene.getPlayerParty()[0];
enemyPokemon = game.scene.getEnemyPokemon()!; enemyPokemon = game.scene.getEnemyPokemon()!;
// remove berries
game.scene.removePartyMemberModifiers(0);
game.scene.clearEnemyHeldItemModifiers();
}); });
it("ignores weaknesses", async () => { it("ignores weaknesses", async () => {

View File

@ -41,14 +41,10 @@ describe("Moves - Fissure", () => {
game.override.enemyPassiveAbility(Abilities.BALL_FETCH); game.override.enemyPassiveAbility(Abilities.BALL_FETCH);
game.override.enemyLevel(100); game.override.enemyLevel(100);
await game.startBattle(); await game.classicMode.startBattle();
partyPokemon = game.scene.getPlayerParty()[0]; partyPokemon = game.scene.getPlayerParty()[0];
enemyPokemon = game.scene.getEnemyPokemon()!; enemyPokemon = game.scene.getEnemyPokemon()!;
// remove berries
game.scene.removePartyMemberModifiers(0);
game.scene.clearEnemyHeldItemModifiers();
}); });
it("ignores damage modification from abilities, for example FUR_COAT", async () => { it("ignores damage modification from abilities, for example FUR_COAT", async () => {

View File

@ -1,6 +1,6 @@
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { allMoves } from "#app/data/move"; import { allMoves, TeraMoveCategoryAttr } from "#app/data/move";
import { Type } from "#enums/type"; import { Type } from "#enums/type";
import { Abilities } from "#app/enums/abilities"; import { Abilities } from "#app/enums/abilities";
import { HitResult } from "#app/field/pokemon"; import { HitResult } from "#app/field/pokemon";
@ -14,6 +14,7 @@ describe("Moves - Tera Blast", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
let game: GameManager; let game: GameManager;
const moveToCheck = allMoves[Moves.TERA_BLAST]; const moveToCheck = allMoves[Moves.TERA_BLAST];
const teraBlastAttr = moveToCheck.getAttrs(TeraMoveCategoryAttr)[0];
beforeAll(() => { beforeAll(() => {
phaserGame = new Phaser.Game({ phaserGame = new Phaser.Game({
@ -86,19 +87,86 @@ describe("Moves - Tera Blast", () => {
expect(enemyPokemon.apply).toHaveReturnedWith(HitResult.SUPER_EFFECTIVE); expect(enemyPokemon.apply).toHaveReturnedWith(HitResult.SUPER_EFFECTIVE);
}); });
// Currently abilities are bugged and can't see when a move's category is changed it("uses the higher ATK for damage calculation", async () => {
it.todo("uses the higher stat of the user's Atk and SpAtk for damage calculation", async () => {
game.override.enemyAbility(Abilities.TOXIC_DEBRIS);
await game.startBattle(); await game.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!; const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.stats[Stat.ATK] = 100; playerPokemon.stats[Stat.ATK] = 100;
playerPokemon.stats[Stat.SPATK] = 1; playerPokemon.stats[Stat.SPATK] = 1;
vi.spyOn(teraBlastAttr, "apply");
game.move.select(Moves.TERA_BLAST); game.move.select(Moves.TERA_BLAST);
await game.phaseInterceptor.to("TurnEndPhase"); await game.toNextTurn();
expect(game.scene.getEnemyPokemon()!.battleData.abilityRevealed).toBe(true); expect(teraBlastAttr.apply).toHaveLastReturnedWith(true);
}, 20000); });
it("uses the higher SPATK for damage calculation", async () => {
await game.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.stats[Stat.ATK] = 1;
playerPokemon.stats[Stat.SPATK] = 100;
vi.spyOn(teraBlastAttr, "apply");
game.move.select(Moves.TERA_BLAST);
await game.toNextTurn();
expect(teraBlastAttr.apply).toHaveLastReturnedWith(false);
});
it("should stay as a special move if ATK turns lower than SPATK mid-turn", async () => {
game.override.enemyMoveset([ Moves.CHARM ]);
await game.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.stats[Stat.ATK] = 51;
playerPokemon.stats[Stat.SPATK] = 50;
vi.spyOn(teraBlastAttr, "apply");
game.move.select(Moves.TERA_BLAST);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.toNextTurn();
expect(teraBlastAttr.apply).toHaveLastReturnedWith(false);
});
it("does not change its move category from stat changes due to held items", async () => {
game.override
.startingHeldItems([{ name: "SPECIES_STAT_BOOSTER", type: "THICK_CLUB" }])
.starterSpecies(Species.CUBONE);
await game.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.stats[Stat.ATK] = 50;
playerPokemon.stats[Stat.SPATK] = 51;
vi.spyOn(teraBlastAttr, "apply");
game.move.select(Moves.TERA_BLAST);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.toNextTurn();
expect(teraBlastAttr.apply).toHaveLastReturnedWith(false);
});
it("does not change its move category from stat changes due to abilities", async () => {
game.override.ability(Abilities.HUGE_POWER);
await game.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.stats[Stat.ATK] = 50;
playerPokemon.stats[Stat.SPATK] = 51;
vi.spyOn(teraBlastAttr, "apply");
game.move.select(Moves.TERA_BLAST);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.toNextTurn();
expect(teraBlastAttr.apply).toHaveLastReturnedWith(false);
});
it("causes stat drops if user is Stellar tera type", async () => { it("causes stat drops if user is Stellar tera type", async () => {
game.override.startingHeldItems([{ name: "TERA_SHARD", type: Type.STELLAR }]); game.override.startingHeldItems([{ name: "TERA_SHARD", type: Type.STELLAR }]);

View File

@ -132,7 +132,7 @@ describe("Moves - Toxic Spikes", () => {
const sessionData : SessionSaveData = gameData["getSessionSaveData"](); const sessionData : SessionSaveData = gameData["getSessionSaveData"]();
localStorage.setItem("sessionTestData", encrypt(JSON.stringify(sessionData), true)); localStorage.setItem("sessionTestData", encrypt(JSON.stringify(sessionData), true));
const recoveredData : SessionSaveData = gameData.parseSessionData(decrypt(localStorage.getItem("sessionTestData")!, true)); const recoveredData : SessionSaveData = gameData.parseSessionData(decrypt(localStorage.getItem("sessionTestData")!, true));
gameData.loadSession(0, recoveredData); await gameData.loadSession(0, recoveredData);
expect(sessionData.arena.tags).toEqual(recoveredData.arena.tags); expect(sessionData.arena.tags).toEqual(recoveredData.arena.tags);
localStorage.removeItem("sessionTestData"); localStorage.removeItem("sessionTestData");

View File

@ -48,12 +48,12 @@ describe("Mystery Encounter Utils", () => {
expect(result.species.speciesId).toBe(Species.ARCEUS); expect(result.species.speciesId).toBe(Species.ARCEUS);
}); });
it("gets a fainted pokemon from player party if isAllowedInBattle is false", () => { it("gets a fainted pokemon from player party if isAllowedInBattle is false", async () => {
// Both pokemon fainted // Both pokemon fainted
scene.getPlayerParty().forEach(p => { scene.getPlayerParty().forEach(p => {
p.hp = 0; p.hp = 0;
p.trySetStatus(StatusEffect.FAINT); p.trySetStatus(StatusEffect.FAINT);
p.updateInfo(); void p.updateInfo();
}); });
// Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal) // Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal)
@ -68,12 +68,12 @@ describe("Mystery Encounter Utils", () => {
expect(result.species.speciesId).toBe(Species.ARCEUS); expect(result.species.speciesId).toBe(Species.ARCEUS);
}); });
it("gets an unfainted legal pokemon from player party if isAllowed is true and isFainted is false", () => { it("gets an unfainted legal pokemon from player party if isAllowed is true and isFainted is false", async () => {
// Only faint 1st pokemon // Only faint 1st pokemon
const party = scene.getPlayerParty(); const party = scene.getPlayerParty();
party[0].hp = 0; party[0].hp = 0;
party[0].trySetStatus(StatusEffect.FAINT); party[0].trySetStatus(StatusEffect.FAINT);
party[0].updateInfo(); await party[0].updateInfo();
// Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal) // Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal)
game.override.seed("random"); game.override.seed("random");
@ -87,12 +87,12 @@ describe("Mystery Encounter Utils", () => {
expect(result.species.speciesId).toBe(Species.MANAPHY); expect(result.species.speciesId).toBe(Species.MANAPHY);
}); });
it("returns last unfainted pokemon if doNotReturnLastAbleMon is false", () => { it("returns last unfainted pokemon if doNotReturnLastAbleMon is false", async () => {
// Only faint 1st pokemon // Only faint 1st pokemon
const party = scene.getPlayerParty(); const party = scene.getPlayerParty();
party[0].hp = 0; party[0].hp = 0;
party[0].trySetStatus(StatusEffect.FAINT); party[0].trySetStatus(StatusEffect.FAINT);
party[0].updateInfo(); await party[0].updateInfo();
// Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal) // Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal)
game.override.seed("random"); game.override.seed("random");
@ -106,12 +106,12 @@ describe("Mystery Encounter Utils", () => {
expect(result.species.speciesId).toBe(Species.MANAPHY); expect(result.species.speciesId).toBe(Species.MANAPHY);
}); });
it("never returns last unfainted pokemon if doNotReturnLastAbleMon is true", () => { it("never returns last unfainted pokemon if doNotReturnLastAbleMon is true", async () => {
// Only faint 1st pokemon // Only faint 1st pokemon
const party = scene.getPlayerParty(); const party = scene.getPlayerParty();
party[0].hp = 0; party[0].hp = 0;
party[0].trySetStatus(StatusEffect.FAINT); party[0].trySetStatus(StatusEffect.FAINT);
party[0].updateInfo(); await party[0].updateInfo();
// Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal) // Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal)
game.override.seed("random"); game.override.seed("random");
@ -152,12 +152,12 @@ describe("Mystery Encounter Utils", () => {
expect(result.species.speciesId).toBe(Species.ARCEUS); expect(result.species.speciesId).toBe(Species.ARCEUS);
}); });
it("returns highest level unfainted if unfainted is true", () => { it("returns highest level unfainted if unfainted is true", async () => {
const party = scene.getPlayerParty(); const party = scene.getPlayerParty();
party[0].level = 100; party[0].level = 100;
party[0].hp = 0; party[0].hp = 0;
party[0].trySetStatus(StatusEffect.FAINT); party[0].trySetStatus(StatusEffect.FAINT);
party[0].updateInfo(); await party[0].updateInfo();
party[1].level = 10; party[1].level = 10;
const result = getHighestLevelPlayerPokemon(true); const result = getHighestLevelPlayerPokemon(true);
@ -191,12 +191,12 @@ describe("Mystery Encounter Utils", () => {
expect(result.species.speciesId).toBe(Species.ARCEUS); expect(result.species.speciesId).toBe(Species.ARCEUS);
}); });
it("returns lowest level unfainted if unfainted is true", () => { it("returns lowest level unfainted if unfainted is true", async () => {
const party = scene.getPlayerParty(); const party = scene.getPlayerParty();
party[0].level = 10; party[0].level = 10;
party[0].hp = 0; party[0].hp = 0;
party[0].trySetStatus(StatusEffect.FAINT); party[0].trySetStatus(StatusEffect.FAINT);
party[0].updateInfo(); await party[0].updateInfo();
party[1].level = 100; party[1].level = 100;
const result = getLowestLevelPlayerPokemon(true); const result = getLowestLevelPlayerPokemon(true);

View File

@ -2,8 +2,6 @@ import { BerryType } from "#app/enums/berry-type";
import { Button } from "#app/enums/buttons"; import { Button } from "#app/enums/buttons";
import { Moves } from "#app/enums/moves"; import { Moves } from "#app/enums/moves";
import { Species } from "#app/enums/species"; import { Species } from "#app/enums/species";
import { BattleEndPhase } from "#app/phases/battle-end-phase";
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
import PartyUiHandler, { PartyUiMode } from "#app/ui/party-ui-handler"; import PartyUiHandler, { PartyUiMode } from "#app/ui/party-ui-handler";
import { Mode } from "#app/ui/ui"; import { Mode } from "#app/ui/ui";
@ -12,7 +10,6 @@ import Phaser from "phaser";
import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("UI - Transfer Items", () => { describe("UI - Transfer Items", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
let game: GameManager; let game: GameManager;
@ -41,7 +38,7 @@ describe("UI - Transfer Items", () => {
game.override.enemySpecies(Species.MAGIKARP); game.override.enemySpecies(Species.MAGIKARP);
game.override.enemyMoveset([ Moves.SPLASH ]); game.override.enemyMoveset([ Moves.SPLASH ]);
await game.startBattle([ Species.RAYQUAZA, Species.RAYQUAZA, Species.RAYQUAZA ]); await game.classicMode.startBattle([ Species.RAYQUAZA, Species.RAYQUAZA, Species.RAYQUAZA ]);
game.move.select(Moves.DRAGON_CLAW); game.move.select(Moves.DRAGON_CLAW);
@ -52,10 +49,10 @@ describe("UI - Transfer Items", () => {
handler.setCursor(1); handler.setCursor(1);
handler.processInput(Button.ACTION); handler.processInput(Button.ACTION);
game.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER); void game.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER);
}); });
await game.phaseInterceptor.to(BattleEndPhase); await game.phaseInterceptor.to("BattleEndPhase");
}); });
it("check red tint for held item limit in transfer menu", async () => { it("check red tint for held item limit in transfer menu", async () => {
@ -72,7 +69,7 @@ describe("UI - Transfer Items", () => {
game.phaseInterceptor.unlock(); game.phaseInterceptor.unlock();
}); });
await game.phaseInterceptor.to(SelectModifierPhase); await game.phaseInterceptor.to("SelectModifierPhase");
}, 20000); }, 20000);
it("check transfer option for pokemon to transfer to", async () => { it("check transfer option for pokemon to transfer to", async () => {
@ -91,6 +88,6 @@ describe("UI - Transfer Items", () => {
game.phaseInterceptor.unlock(); game.phaseInterceptor.unlock();
}); });
await game.phaseInterceptor.to(SelectModifierPhase); await game.phaseInterceptor.to("SelectModifierPhase");
}, 20000); }, 20000);
}); });

View File

@ -27,6 +27,7 @@ interface EventBanner {
interface EventEncounter { interface EventEncounter {
species: Species; species: Species;
blockEvolution?: boolean; blockEvolution?: boolean;
formIndex?: number;
} }
interface EventMysteryEncounterTier { interface EventMysteryEncounterTier {
@ -49,6 +50,7 @@ interface TimedEvent extends EventBanner {
weather?: WeatherPoolEntry[]; weather?: WeatherPoolEntry[];
mysteryEncounterTierChanges?: EventMysteryEncounterTier[]; mysteryEncounterTierChanges?: EventMysteryEncounterTier[];
luckBoostedSpecies?: Species[]; luckBoostedSpecies?: Species[];
boostFusions?: boolean; //MODIFIER REWORK PLEASE
} }
const timedEvents: TimedEvent[] = [ const timedEvents: TimedEvent[] = [
@ -144,6 +146,40 @@ const timedEvents: TimedEvent[] = [
Species.ROARING_MOON, Species.ROARING_MOON,
Species.BLOODMOON_URSALUNA Species.BLOODMOON_URSALUNA
] ]
},
{
name: "Valentine",
eventType: EventType.SHINY,
startDate: new Date(Date.UTC(2025, 1, 10)),
endDate: new Date(Date.UTC(2025, 1, 21)),
boostFusions: true,
shinyMultiplier: 2,
bannerKey: "valentines2025event-",
scale: 0.21,
availableLangs: [ "en", "de", "it", "fr", "ja", "ko", "es-ES", "pt-BR", "zh-CN" ],
eventEncounters: [
{ species: Species.NIDORAN_F },
{ species: Species.NIDORAN_M },
{ species: Species.IGGLYBUFF },
{ species: Species.SMOOCHUM },
{ species: Species.VOLBEAT },
{ species: Species.ILLUMISE },
{ species: Species.ROSELIA },
{ species: Species.LUVDISC },
{ species: Species.WOOBAT },
{ species: Species.FRILLISH },
{ species: Species.ALOMOMOLA },
{ species: Species.FURFROU, formIndex: 1 }, // Heart trim
{ species: Species.ESPURR },
{ species: Species.SPRITZEE },
{ species: Species.SWIRLIX },
{ species: Species.APPLIN },
{ species: Species.MILCERY },
{ species: Species.INDEEDEE },
{ species: Species.TANDEMAUS },
{ species: Species.ENAMORUS }
],
luckBoostedSpecies: [ Species.LUVDISC ]
} }
]; ];
@ -297,6 +333,10 @@ export class TimedEventManager {
}); });
return ret; return ret;
} }
areFusionsBoosted(): boolean {
return timedEvents.some((te) => this.isActive(te) && te.boostFusions);
}
} }
export class TimedEventDisplay extends Phaser.GameObjects.Container { export class TimedEventDisplay extends Phaser.GameObjects.Container {

View File

@ -96,6 +96,9 @@ export default class SummaryUiHandler extends UiHandler {
private friendshipText: Phaser.GameObjects.Text; private friendshipText: Phaser.GameObjects.Text;
private friendshipIcon: Phaser.GameObjects.Sprite; private friendshipIcon: Phaser.GameObjects.Sprite;
private friendshipOverlay: Phaser.GameObjects.Sprite; private friendshipOverlay: Phaser.GameObjects.Sprite;
private permStatsContainer: Phaser.GameObjects.Container;
private ivContainer: Phaser.GameObjects.Container;
private statsContainer: Phaser.GameObjects.Container;
private descriptionScrollTween: Phaser.Tweens.Tween | null; private descriptionScrollTween: Phaser.Tweens.Tween | null;
private moveCursorBlinkTimer: Phaser.Time.TimerEvent | null; private moveCursorBlinkTimer: Phaser.Time.TimerEvent | null;
@ -534,6 +537,10 @@ export default class SummaryUiHandler extends UiHandler {
this.passiveContainer.nameText?.setVisible(!this.passiveContainer.descriptionText?.visible); this.passiveContainer.nameText?.setVisible(!this.passiveContainer.descriptionText?.visible);
this.passiveContainer.descriptionText?.setVisible(!this.passiveContainer.descriptionText.visible); this.passiveContainer.descriptionText?.setVisible(!this.passiveContainer.descriptionText.visible);
this.passiveContainer.labelImage.setVisible(!this.passiveContainer.labelImage.visible); this.passiveContainer.labelImage.setVisible(!this.passiveContainer.labelImage.visible);
} else if (this.cursor === Page.STATS) {
//Show IVs
this.permStatsContainer.setVisible(!this.permStatsContainer.visible);
this.ivContainer.setVisible(!this.ivContainer.visible);
} }
} else if (button === Button.CANCEL) { } else if (button === Button.CANCEL) {
if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) { if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) {
@ -877,8 +884,13 @@ export default class SummaryUiHandler extends UiHandler {
profileContainer.add(memoText); profileContainer.add(memoText);
break; break;
case Page.STATS: case Page.STATS:
const statsContainer = globalScene.add.container(0, -pageBg.height); this.statsContainer = globalScene.add.container(0, -pageBg.height);
pageContainer.add(statsContainer); pageContainer.add(this.statsContainer);
this.permStatsContainer = globalScene.add.container(27, 56);
this.statsContainer.add(this.permStatsContainer);
this.ivContainer = globalScene.add.container(27, 56);
this.statsContainer.add(this.ivContainer);
this.statsContainer.setVisible(true);
PERMANENT_STATS.forEach((stat, s) => { PERMANENT_STATS.forEach((stat, s) => {
const statName = i18next.t(getStatKey(stat)); const statName = i18next.t(getStatKey(stat));
@ -887,18 +899,27 @@ export default class SummaryUiHandler extends UiHandler {
const natureStatMultiplier = getNatureStatMultiplier(this.pokemon?.getNature()!, s); // TODO: is this bang correct? const natureStatMultiplier = getNatureStatMultiplier(this.pokemon?.getNature()!, s); // TODO: is this bang correct?
const statLabel = addTextObject(27 + 115 * colIndex + (colIndex === 1 ? 5 : 0), 56 + 16 * rowIndex, statName, natureStatMultiplier === 1 ? TextStyle.SUMMARY : natureStatMultiplier > 1 ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY_BLUE); const statLabel = addTextObject(115 * colIndex + (colIndex === 1 ? 5 : 0), 16 * rowIndex, statName, natureStatMultiplier === 1 ? TextStyle.SUMMARY : natureStatMultiplier > 1 ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY_BLUE);
const ivLabel = addTextObject(115 * colIndex + (colIndex === 1 ? 5 : 0), 16 * rowIndex, statName, this.pokemon?.ivs[stat] === 31 ? TextStyle.SUMMARY_GOLD : TextStyle.SUMMARY);
statLabel.setOrigin(0.5, 0); statLabel.setOrigin(0.5, 0);
statsContainer.add(statLabel); ivLabel.setOrigin(0.5, 0);
this.permStatsContainer.add(statLabel);
this.ivContainer.add(ivLabel);
const statValueText = stat !== Stat.HP const statValueText = stat !== Stat.HP
? Utils.formatStat(this.pokemon?.getStat(stat)!) // TODO: is this bang correct? ? Utils.formatStat(this.pokemon?.getStat(stat)!) // TODO: is this bang correct?
: `${Utils.formatStat(this.pokemon?.hp!, true)}/${Utils.formatStat(this.pokemon?.getMaxHp()!, true)}`; // TODO: are those bangs correct? : `${Utils.formatStat(this.pokemon?.hp!, true)}/${Utils.formatStat(this.pokemon?.getMaxHp()!, true)}`; // TODO: are those bangs correct?
const ivText = `${this.pokemon?.ivs[stat]}/31`;
const statValue = addTextObject(120 + 88 * colIndex, 56 + 16 * rowIndex, statValueText, TextStyle.WINDOW_ALT); const statValue = addTextObject(93 + 88 * colIndex, 16 * rowIndex, statValueText, TextStyle.WINDOW_ALT);
statValue.setOrigin(1, 0); statValue.setOrigin(1, 0);
statsContainer.add(statValue); this.permStatsContainer.add(statValue);
const ivValue = addTextObject(93 + 88 * colIndex, 16 * rowIndex, ivText, TextStyle.WINDOW_ALT);
ivValue.setOrigin(1, 0);
this.ivContainer.add(ivValue);
}); });
this.ivContainer.setVisible(false);
const itemModifiers = (globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier const itemModifiers = (globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& m.pokemonId === this.pokemon?.id, this.playerParty) as PokemonHeldItemModifier[]) && m.pokemonId === this.pokemon?.id, this.playerParty) as PokemonHeldItemModifier[])
@ -908,7 +929,7 @@ export default class SummaryUiHandler extends UiHandler {
const icon = item.getIcon(true); const icon = item.getIcon(true);
icon.setPosition((i % 17) * 12 + 3, 14 * Math.floor(i / 17) + 15); icon.setPosition((i % 17) * 12 + 3, 14 * Math.floor(i / 17) + 15);
statsContainer.add(icon); this.statsContainer.add(icon);
icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 32), Phaser.Geom.Rectangle.Contains); icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 32), Phaser.Geom.Rectangle.Contains);
icon.on("pointerover", () => globalScene.ui.showTooltip(item.type.name, item.type.getDescription(), true)); icon.on("pointerover", () => globalScene.ui.showTooltip(item.type.name, item.type.getDescription(), true));
@ -924,26 +945,26 @@ export default class SummaryUiHandler extends UiHandler {
const expLabel = addTextObject(6, 112, i18next.t("pokemonSummary:expPoints"), TextStyle.SUMMARY); const expLabel = addTextObject(6, 112, i18next.t("pokemonSummary:expPoints"), TextStyle.SUMMARY);
expLabel.setOrigin(0, 0); expLabel.setOrigin(0, 0);
statsContainer.add(expLabel); this.statsContainer.add(expLabel);
const nextLvExpLabel = addTextObject(6, 128, i18next.t("pokemonSummary:nextLv"), TextStyle.SUMMARY); const nextLvExpLabel = addTextObject(6, 128, i18next.t("pokemonSummary:nextLv"), TextStyle.SUMMARY);
nextLvExpLabel.setOrigin(0, 0); nextLvExpLabel.setOrigin(0, 0);
statsContainer.add(nextLvExpLabel); this.statsContainer.add(nextLvExpLabel);
const expText = addTextObject(208, 112, pkmExp.toString(), TextStyle.WINDOW_ALT); const expText = addTextObject(208, 112, pkmExp.toString(), TextStyle.WINDOW_ALT);
expText.setOrigin(1, 0); expText.setOrigin(1, 0);
statsContainer.add(expText); this.statsContainer.add(expText);
const nextLvExp = pkmLvl < globalScene.getMaxExpLevel() const nextLvExp = pkmLvl < globalScene.getMaxExpLevel()
? getLevelTotalExp(pkmLvl + 1, pkmSpeciesGrowthRate) - pkmExp ? getLevelTotalExp(pkmLvl + 1, pkmSpeciesGrowthRate) - pkmExp
: 0; : 0;
const nextLvExpText = addTextObject(208, 128, nextLvExp.toString(), TextStyle.WINDOW_ALT); const nextLvExpText = addTextObject(208, 128, nextLvExp.toString(), TextStyle.WINDOW_ALT);
nextLvExpText.setOrigin(1, 0); nextLvExpText.setOrigin(1, 0);
statsContainer.add(nextLvExpText); this.statsContainer.add(nextLvExpText);
const expOverlay = globalScene.add.image(140, 145, "summary_stats_overlay_exp"); const expOverlay = globalScene.add.image(140, 145, "summary_stats_overlay_exp");
expOverlay.setOrigin(0, 0); expOverlay.setOrigin(0, 0);
statsContainer.add(expOverlay); this.statsContainer.add(expOverlay);
const expMaskRect = globalScene.make.graphics({}); const expMaskRect = globalScene.make.graphics({});
expMaskRect.setScale(6); expMaskRect.setScale(6);
@ -954,6 +975,11 @@ export default class SummaryUiHandler extends UiHandler {
const expMask = expMaskRect.createGeometryMask(); const expMask = expMaskRect.createGeometryMask();
expOverlay.setMask(expMask); expOverlay.setMask(expMask);
this.abilityPrompt = globalScene.add.image(0, 0, !globalScene.inputController?.gamepadSupport ? "summary_profile_prompt_z" : "summary_profile_prompt_a");
this.abilityPrompt.setPosition(8, 47);
this.abilityPrompt.setVisible(true);
this.abilityPrompt.setOrigin(0, 0);
this.statsContainer.add(this.abilityPrompt);
break; break;
case Page.MOVES: case Page.MOVES:
this.movesContainer = globalScene.add.container(5, -pageBg.height + 26); this.movesContainer = globalScene.add.container(5, -pageBg.height + 26);