[Item] Add Scope Lens and Leek (#2666)

* [Item] Add Scope Lens and Leek

* Add Entry to pt_BR

* Localize for pt_BR

Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br>

* Fix & Clean Unit Tests

---------

Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br>
This commit is contained in:
Amani H. 2024-07-18 10:20:55 -04:00 committed by GitHub
parent dd693ae2d3
commit 99f4a9dd25
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 528 additions and 92 deletions

View File

@ -2068,7 +2068,7 @@
}
},
{
"filename": "rare_candy",
"filename": "leek",
"rotated": false,
"trimmed": true,
"sourceSize": {
@ -2089,7 +2089,7 @@
}
},
{
"filename": "rarer_candy",
"filename": "rare_candy",
"rotated": false,
"trimmed": true,
"sourceSize": {
@ -2110,7 +2110,7 @@
}
},
{
"filename": "stick",
"filename": "rarer_candy",
"rotated": false,
"trimmed": true,
"sourceSize": {
@ -5659,16 +5659,16 @@
}
},
{
"filename": "candy",
"filename": "baton",
"rotated": false,
"trimmed": true,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 32
"w": 18,
"h": 18
},
"spriteSourceSize": {
"x": 7,
"y": 11,
"x": 0,
"y": 0,
"w": 18,
"h": 18
},
@ -6436,7 +6436,7 @@
}
},
{
"filename": "dark_stone",
"filename": "candy",
"rotated": false,
"trimmed": true,
"sourceSize": {
@ -6445,7 +6445,7 @@
},
"spriteSourceSize": {
"x": 7,
"y": 7,
"y": 11,
"w": 18,
"h": 18
},
@ -6478,7 +6478,7 @@
}
},
{
"filename": "flame_orb",
"filename": "dark_stone",
"rotated": false,
"trimmed": true,
"sourceSize": {
@ -6499,7 +6499,7 @@
}
},
{
"filename": "light_ball",
"filename": "flame_orb",
"rotated": false,
"trimmed": true,
"sourceSize": {
@ -6562,7 +6562,7 @@
}
},
{
"filename": "light_stone",
"filename": "light_ball",
"rotated": false,
"trimmed": true,
"sourceSize": {
@ -6877,7 +6877,7 @@
}
},
{
"filename": "toxic_orb",
"filename": "light_stone",
"rotated": false,
"trimmed": true,
"sourceSize": {
@ -7023,6 +7023,27 @@
"h": 18
}
},
{
"filename": "toxic_orb",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 32,
"h": 32
},
"spriteSourceSize": {
"x": 7,
"y": 7,
"w": 18,
"h": 18
},
"frame": {
"x": 115,
"y": 375,
"w": 18,
"h": 18
}
},
{
"filename": "relic_band",
"rotated": false,
@ -7038,8 +7059,8 @@
"h": 16
},
"frame": {
"x": 115,
"y": 375,
"x": 114,
"y": 393,
"w": 17,
"h": 16
}
@ -7059,8 +7080,8 @@
"h": 16
},
"frame": {
"x": 114,
"y": 391,
"x": 131,
"y": 399,
"w": 16,
"h": 16
}
@ -7080,7 +7101,7 @@
"h": 16
},
"frame": {
"x": 130,
"x": 147,
"y": 399,
"w": 16,
"h": 16
@ -7101,7 +7122,7 @@
"h": 16
},
"frame": {
"x": 146,
"x": 163,
"y": 399,
"w": 16,
"h": 16
@ -7122,7 +7143,7 @@
"h": 16
},
"frame": {
"x": 162,
"x": 179,
"y": 399,
"w": 16,
"h": 16
@ -7143,7 +7164,7 @@
"h": 16
},
"frame": {
"x": 178,
"x": 195,
"y": 399,
"w": 16,
"h": 16
@ -7164,7 +7185,7 @@
"h": 16
},
"frame": {
"x": 194,
"x": 211,
"y": 399,
"w": 16,
"h": 16
@ -7185,7 +7206,7 @@
"h": 16
},
"frame": {
"x": 210,
"x": 227,
"y": 399,
"w": 16,
"h": 16
@ -7206,7 +7227,7 @@
"h": 16
},
"frame": {
"x": 226,
"x": 243,
"y": 399,
"w": 16,
"h": 16
@ -7227,7 +7248,7 @@
"h": 16
},
"frame": {
"x": 242,
"x": 259,
"y": 399,
"w": 16,
"h": 16
@ -7248,7 +7269,7 @@
"h": 16
},
"frame": {
"x": 258,
"x": 275,
"y": 399,
"w": 16,
"h": 16
@ -7269,7 +7290,7 @@
"h": 16
},
"frame": {
"x": 274,
"x": 291,
"y": 399,
"w": 16,
"h": 16
@ -7290,7 +7311,7 @@
"h": 16
},
"frame": {
"x": 290,
"x": 307,
"y": 399,
"w": 16,
"h": 16
@ -7311,7 +7332,7 @@
"h": 16
},
"frame": {
"x": 306,
"x": 323,
"y": 399,
"w": 16,
"h": 16
@ -7332,7 +7353,7 @@
"h": 16
},
"frame": {
"x": 322,
"x": 339,
"y": 399,
"w": 16,
"h": 16
@ -7353,28 +7374,7 @@
"h": 16
},
"frame": {
"x": 338,
"y": 399,
"w": 16,
"h": 16
}
},
{
"filename": "kangaskhanite",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 32,
"h": 32
},
"spriteSourceSize": {
"x": 8,
"y": 8,
"w": 16,
"h": 16
},
"frame": {
"x": 132,
"x": 133,
"y": 375,
"w": 16,
"h": 16
@ -7402,7 +7402,7 @@
}
},
{
"filename": "latiasite",
"filename": "kangaskhanite",
"rotated": false,
"trimmed": true,
"sourceSize": {
@ -7423,7 +7423,7 @@
}
},
{
"filename": "latiosite",
"filename": "latiasite",
"rotated": false,
"trimmed": true,
"sourceSize": {
@ -7437,14 +7437,14 @@
"h": 16
},
"frame": {
"x": 148,
"x": 149,
"y": 376,
"w": 16,
"h": 16
}
},
{
"filename": "lopunnite",
"filename": "latiosite",
"rotated": false,
"trimmed": true,
"sourceSize": {
@ -7465,7 +7465,7 @@
}
},
{
"filename": "lucarionite",
"filename": "lopunnite",
"rotated": false,
"trimmed": true,
"sourceSize": {
@ -7486,7 +7486,7 @@
}
},
{
"filename": "manectite",
"filename": "lucarionite",
"rotated": false,
"trimmed": true,
"sourceSize": {
@ -7507,7 +7507,7 @@
}
},
{
"filename": "mawilite",
"filename": "manectite",
"rotated": false,
"trimmed": true,
"sourceSize": {
@ -7527,6 +7527,27 @@
"h": 16
}
},
{
"filename": "mawilite",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 32,
"h": 32
},
"spriteSourceSize": {
"x": 8,
"y": 8,
"w": 16,
"h": 16
},
"frame": {
"x": 165,
"y": 377,
"w": 16,
"h": 16
}
},
{
"filename": "medichamite",
"rotated": false,
@ -7542,7 +7563,7 @@
"h": 16
},
"frame": {
"x": 164,
"x": 181,
"y": 377,
"w": 16,
"h": 16
@ -7563,7 +7584,7 @@
"h": 16
},
"frame": {
"x": 180,
"x": 197,
"y": 377,
"w": 16,
"h": 16
@ -7584,7 +7605,7 @@
"h": 16
},
"frame": {
"x": 196,
"x": 213,
"y": 377,
"w": 16,
"h": 16
@ -7605,7 +7626,7 @@
"h": 16
},
"frame": {
"x": 212,
"x": 229,
"y": 377,
"w": 16,
"h": 16
@ -7626,8 +7647,8 @@
"h": 16
},
"frame": {
"x": 228,
"y": 377,
"x": 349,
"y": 327,
"w": 16,
"h": 16
}
@ -7648,7 +7669,7 @@
},
"frame": {
"x": 349,
"y": 327,
"y": 343,
"w": 16,
"h": 16
}
@ -7669,7 +7690,7 @@
},
"frame": {
"x": 349,
"y": 343,
"y": 359,
"w": 16,
"h": 16
}
@ -7690,7 +7711,7 @@
},
"frame": {
"x": 349,
"y": 359,
"y": 375,
"w": 16,
"h": 16
}
@ -7710,8 +7731,8 @@
"h": 16
},
"frame": {
"x": 349,
"y": 375,
"x": 365,
"y": 330,
"w": 16,
"h": 16
}
@ -7731,8 +7752,8 @@
"h": 16
},
"frame": {
"x": 354,
"y": 391,
"x": 381,
"y": 330,
"w": 16,
"h": 16
}
@ -7753,7 +7774,7 @@
},
"frame": {
"x": 365,
"y": 330,
"y": 346,
"w": 16,
"h": 16
}
@ -7773,8 +7794,8 @@
"h": 16
},
"frame": {
"x": 381,
"y": 330,
"x": 365,
"y": 362,
"w": 16,
"h": 16
}
@ -7794,7 +7815,7 @@
"h": 16
},
"frame": {
"x": 365,
"x": 381,
"y": 346,
"w": 16,
"h": 16
@ -7815,7 +7836,7 @@
"h": 16
},
"frame": {
"x": 365,
"x": 381,
"y": 362,
"w": 16,
"h": 16
@ -7836,8 +7857,8 @@
"h": 16
},
"frame": {
"x": 381,
"y": 346,
"x": 397,
"y": 342,
"w": 16,
"h": 16
}
@ -7857,8 +7878,8 @@
"h": 16
},
"frame": {
"x": 381,
"y": 362,
"x": 397,
"y": 358,
"w": 16,
"h": 16
}
@ -7878,8 +7899,8 @@
"h": 16
},
"frame": {
"x": 397,
"y": 342,
"x": 365,
"y": 378,
"w": 16,
"h": 16
}
@ -7899,8 +7920,8 @@
"h": 16
},
"frame": {
"x": 397,
"y": 358,
"x": 381,
"y": 378,
"w": 16,
"h": 16
}
@ -7941,8 +7962,8 @@
"h": 16
},
"frame": {
"x": 370,
"y": 378,
"x": 397,
"y": 390,
"w": 16,
"h": 16
}
@ -7953,6 +7974,6 @@
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:60db8f4653a650759cd9189e91c38a40:439307cbef9c000f6c45603b2d82d107:110e074689c9edd2c54833ce2e4d9270$"
"smartupdate": "$TexturePacker:SmartUpdate:d3848d1a2f1d71413dd485f5f629a4eb:a418dc4833fcd357930c1512c8417df7:110e074689c9edd2c54833ce2e4d9270$"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 869 B

View File

Before

Width:  |  Height:  |  Size: 230 B

After

Width:  |  Height:  |  Size: 230 B

View File

@ -10,7 +10,7 @@ import * as Utils from "../utils";
import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type";
import { getLevelTotalExp } from "../data/exp";
import { Stat } from "../data/pokemon-stat";
import { DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, PokemonBaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempBattleStatBoosterModifier, StatBoosterModifier, TerastallizeModifier } from "../modifier/modifier";
import { DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, PokemonBaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempBattleStatBoosterModifier, StatBoosterModifier, CritBoosterModifier, TerastallizeModifier } from "../modifier/modifier";
import { PokeballType } from "../data/pokeball";
import { Gender } from "../data/gender";
import { initMoveAnim, loadMoveAnimAssets } from "../data/battle-anims";
@ -1810,6 +1810,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} else {
const critLevel = new Utils.IntegerHolder(0);
applyMoveAttrs(HighCritAttr, source, this, move, critLevel);
this.scene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critLevel);
this.scene.applyModifiers(TempBattleStatBoosterModifier, source.isPlayer(), TempBattleStat.CRIT, critLevel);
const bonusCrit = new Utils.BooleanHolder(false);
if (applyAbAttrs(BonusCritAbAttr, source, null, bonusCrit)) {
@ -1820,6 +1821,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (source.getTag(BattlerTagType.CRIT_BOOST)) {
critLevel.value += 2;
}
console.log(`crit stage: +${critLevel.value}`);
const critChance = [24, 8, 2, 1][Math.max(0, Math.min(critLevel.value, 3))];
isCritical = !source.getTag(BattlerTagType.NO_CRIT) && (critChance === 1 || !this.scene.randBattleSeedInt(critChance));
if (Overrides.NEVER_CRIT_OVERRIDE) {

View File

@ -182,6 +182,9 @@ export const modifierType: ModifierTypeTranslationEntries = {
"SOOTHE_BELL": { name: "Sanftglocke" },
"SCOPE_LENS": { name: "Scope-Linse", description: "Ein Item zum Tragen. Es erhöht die Volltrefferquote." },
"LEEK": { name: "Lauchstange", description: "Ein Item, das von Porenta getragen werden kann. Diese lange Lauchstange erhöht die Volltrefferquote stark." },
"EVIOLITE": { name: "Evolith", description: "Ein mysteriöser Klumpen, der die Vert. u. Spez.-Vert. von Pokémon erhöht, die sich noch entwickeln können." },
"SOUL_DEW": { name: "Seelentau", description: "Erhöht den Einfluss des Wesens eines Pokemon auf seine Werte um 10% (additiv)." },

View File

@ -182,6 +182,9 @@ export const modifierType: ModifierTypeTranslationEntries = {
"SOOTHE_BELL": { name: "Soothe Bell" },
"SCOPE_LENS": { name: "Scope Lens", description: "It's a lens for scoping out weak points. It boosts the holder's critical-hit ratio."},
"LEEK": { name: "Leek", description: "This very long and stiff stalk of leek boosts the critical-hit ratio of Farfetch'd's moves."},
"EVIOLITE": { name: "Eviolite", description: "This mysterious evolutionary lump boosts the Defense and Sp. Def stats when held by a Pokémon that can still evolve." },
"SOUL_DEW": { name: "Soul Dew", description: "Increases the influence of a Pokémon's nature on its stats by 10% (additive)." },

View File

@ -182,6 +182,9 @@ export const modifierType: ModifierTypeTranslationEntries = {
"SOOTHE_BELL": { name: "Camp. Alivio" },
"SCOPE_LENS": { name: "Periscopio", description: "Aumenta la probabilidad de asestar un golpe crítico." },
"LEEK": { name: "Puerro", description: "Puerro muy largo y duro que aumenta la probabilidad de asestar un golpe crítico. Debe llevarlo Farfetch'd." },
"EVIOLITE": { name: "Mineral Evolutivo", description: "Roca misteriosa. El Pokémon portador aumentará su Defensa y su Defensa Especial si aún puede evolucionar." },
"SOUL_DEW": { name: "Rocío bondad", description: "Aumenta la influencia de la naturaleza de un Pokémon en sus estadísticas en un 10% (aditivo)." },

View File

@ -182,6 +182,9 @@ export const modifierType: ModifierTypeTranslationEntries = {
"SOOTHE_BELL": { name: "Grelot Zen" },
"SCOPE_LENS": { name: "Lentilscope", description: "Une lentille qui augmente le taux de critiques du porteur." },
"LEEK": { name: "Poireau", description: "Objet à faire tenir à Canarticho. Un poireau très long et solide qui augmente son taux de critiques." },
"EVIOLITE": { name: "Évoluroc", description: "Un étrange concentré dévolution qui augmente la Défense et la Défense Spéciale dun Pokémon pouvant évoluer." },
"SOUL_DEW": { name: "Rosée Âme", description: "Augmente de 10% linfluence de la nature dun Pokémon sur ses statistiques (cumulatif)." },

View File

@ -181,6 +181,10 @@ export const modifierType: ModifierTypeTranslationEntries = {
"GOLDEN_EGG": { name: "Uovo dorato" },
"SOOTHE_BELL": { name: "Calmanella" },
"SCOPE_LENS": { name: "Mirino", description: "Lente che aumenta la probabilità di sferrare brutti colpi." },
"LEEK": { name: "Porro", description: "Strumento da dare a Farfetch'd. Lungo gambo di porro che aumenta la probabilità di sferrare brutti colpi." },
"EVIOLITE": { name: "Evolcondensa", description: "Misteriosa materia evolutiva. Aumenta la Difesa e la Difesa Speciale di un Pokémon che può ancora evolversi." },
"SOUL_DEW": { name: "Cuorugiada", description: "Aumenta del 10% l'influenza della natura di un Pokémon sulle sue statistiche (cumulativo)." },

View File

@ -182,6 +182,9 @@ export const modifierType: ModifierTypeTranslationEntries = {
"SOOTHE_BELL": { name: "평온의방울" },
"SCOPE_LENS": { name: "초점렌즈", description: "약점이 보이는 렌즈. 지니게 한 포켓몬의 기술이 급소에 맞기 쉬워진다." },
"LEEK": { name: "대파", description: "매우 길고 단단한 줄기. 파오리에게 지니게 하면 기술이 급소에 맞기 쉬워진다." },
"EVIOLITE": { name: "진화의휘석", description: "진화의 이상한 덩어리. 지니게 하면 진화 전 포켓몬의 방어와 특수방어가 올라간다." },
"SOUL_DEW": { name: "마음의물방울", description: "지닌 포켓몬의 성격의 효과가 10% 증가한다. (합연산)" },

View File

@ -182,6 +182,9 @@ export const modifierType: ModifierTypeTranslationEntries = {
"SOOTHE_BELL": { name: "Guizo" },
"SCOPE_LENS": { name: "Lentes de Mira", description: "Estas lentes facilitam o foco em pontos fracos. Aumenta a chance de acerto crítico de quem a segurar."},
"LEEK": { name: "Alho-poró", description: "Esse talo de alho-poró muito longo e rígido aumenta a taxa de acerto crítico dos movimentos do Farfetch'd."},
"EVIOLITE": { name: "Eviolita", description: "Esse misterioso caroço evolutivo aumenta os atributos de Defesa e Def. Esp. quando segurado por um Pokémon que ainda pode evoluir." },
"SOUL_DEW": { name: "Joia da Alma", description: "Aumenta a influência da natureza de um Pokémon em seus atributos em 10% (cumulativo)." },

View File

@ -182,6 +182,9 @@ export const modifierType: ModifierTypeTranslationEntries = {
"SOOTHE_BELL": { name: "安抚之铃" },
"SCOPE_LENS": { name: "焦点镜", description: "能看见弱点的镜片。携带它的宝可梦的招式 会变得容易击中要害。" },
"LEEK": { name: "大葱", description: "非常长且坚硬的茎。让大葱鸭携带后,\n招式会变得容易击中要害。" },
"EVIOLITE": { name: "进化奇石", description: "携带后,还能进化的宝可梦的\n防御和特防就会提高。" },
"SOUL_DEW": { name: "心之水滴", description: "增加10%宝可梦性格对数值的影响 (加算)。" },

View File

@ -187,6 +187,14 @@ export const modifierType: ModifierTypeTranslationEntries = {
LUCKY_EGG: { name: "幸運蛋" },
GOLDEN_EGG: { name: "金蛋" },
SOOTHE_BELL: { name: "安撫之鈴" },
SCOPE_LENS: {
name: "焦點鏡",
description: "能看見弱點的鏡片。攜帶它的寶可夢的招式 會變得容易擊中要害。"
},
LEEK: {
name: "大蔥",
description: "非常長且堅硬的莖。讓大蔥鴨攜帶後,招式會 變得容易擊中要害。"
},
EVIOLITE: {
name: "進化奇石",
description: "進化的神奇石塊。攜帶後,還能進化的寶可夢的 防禦和特防就會提高。"

View File

@ -1353,6 +1353,9 @@ export const modifierTypes = {
SOOTHE_BELL: () => new PokemonFriendshipBoosterModifierType("modifierType:ModifierType.SOOTHE_BELL", "soothe_bell"),
SCOPE_LENS: () => new PokemonHeldItemModifierType("modifierType:ModifierType.SCOPE_LENS", "scope_lens", (type, args) => new Modifiers.CritBoosterModifier(type, (args[0] as Pokemon).id, 1)),
LEEK: () => new PokemonHeldItemModifierType("modifierType:ModifierType.LEEK", "leek", (type, args) => new Modifiers.SpeciesCritBoosterModifier(type, (args[0] as Pokemon).id, 2, [Species.FARFETCHD, Species.GALAR_FARFETCHD, Species.SIRFETCHD])),
EVIOLITE: () => new PokemonHeldItemModifierType("modifierType:ModifierType.EVIOLITE", "eviolite", (type, args) => new Modifiers.EvolutionStatBoosterModifier(type, (args[0] as Pokemon).id, [Stat.DEF, Stat.SPDEF], 1.5)),
SOUL_DEW: () => new PokemonHeldItemModifierType("modifierType:ModifierType.SOUL_DEW", "soul_dew", (type, args) => new Modifiers.PokemonNatureWeightModifier(type, (args[0] as Pokemon).id)),
@ -1389,7 +1392,7 @@ export const modifierTypes = {
TOXIC_ORB: () => new PokemonHeldItemModifierType("modifierType:ModifierType.TOXIC_ORB", "toxic_orb", (type, args) => new Modifiers.TurnStatusEffectModifier(type, (args[0] as Pokemon).id)),
FLAME_ORB: () => new PokemonHeldItemModifierType("modifierType:ModifierType.FLAME_ORB", "flame_orb", (type, args) => new Modifiers.TurnStatusEffectModifier(type, (args[0] as Pokemon).id)),
BATON: () => new PokemonHeldItemModifierType("modifierType:ModifierType.BATON", "stick", (type, args) => new Modifiers.SwitchEffectTransferModifier(type, (args[0] as Pokemon).id)),
BATON: () => new PokemonHeldItemModifierType("modifierType:ModifierType.BATON", "baton", (type, args) => new Modifiers.SwitchEffectTransferModifier(type, (args[0] as Pokemon).id)),
SHINY_CHARM: () => new ModifierType("modifierType:ModifierType.SHINY_CHARM", "shiny_charm", (type, _args) => new Modifiers.ShinyRateBoosterModifier(type)),
ABILITY_CHARM: () => new ModifierType("modifierType:ModifierType.ABILITY_CHARM", "ability_charm", (type, _args) => new Modifiers.HiddenAbilityRateBoosterModifier(type)),
@ -1540,6 +1543,11 @@ const modifierPool: ModifierPool = {
new WeightedModifierType(modifierTypes.AMULET_COIN, skipInLastClassicWaveOrDefault(3)),
//new WeightedModifierType(modifierTypes.EVIOLITE, (party: Pokemon[]) => party.some(p => ((p.getSpeciesForm(true).speciesId in pokemonEvolutions) || (p.isFusion() && (p.getFusionSpeciesForm(true).speciesId in pokemonEvolutions))) && !p.getHeldItems().some(i => i instanceof Modifiers.EvolutionStatBoosterModifier)) ? 10 : 0),
new WeightedModifierType(modifierTypes.SPECIES_STAT_BOOSTER, 12),
new WeightedModifierType(modifierTypes.LEEK, (party: Pokemon[]) => {
const checkedSpecies = [ Species.FARFETCHD, Species.GALAR_FARFETCHD, Species.SIRFETCHD ];
// If a party member doesn't already have a Leek and is one of the relevant species, Leek can appear
return party.some(p => !p.getHeldItems().some(i => i instanceof Modifiers.SpeciesCritBoosterModifier) && (checkedSpecies.includes(p.getSpeciesForm(true).speciesId) || (p.isFusion() && checkedSpecies.includes(p.getFusionSpeciesForm(true).speciesId)))) ? 12 : 0;
}, 12),
new WeightedModifierType(modifierTypes.TOXIC_ORB, (party: Pokemon[]) => {
const checkedAbilities = [Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.TOXIC_BOOST, Abilities.POISON_HEAL, Abilities.MAGIC_GUARD];
const checkedMoves = [Moves.FACADE, Moves.TRICK, Moves.FLING, Moves.SWITCHEROO, Moves.PSYCHO_SHIFT];
@ -1575,6 +1583,7 @@ const modifierPool: ModifierPool = {
new WeightedModifierType(modifierTypes.SHELL_BELL, 3),
new WeightedModifierType(modifierTypes.BERRY_POUCH, 4),
new WeightedModifierType(modifierTypes.GRIP_CLAW, 5),
new WeightedModifierType(modifierTypes.SCOPE_LENS, 4),
new WeightedModifierType(modifierTypes.BATON, 2),
new WeightedModifierType(modifierTypes.SOUL_DEW, 7),
//new WeightedModifierType(modifierTypes.OVAL_CHARM, 6),

View File

@ -874,6 +874,97 @@ export class SpeciesStatBoosterModifier extends StatBoosterModifier {
}
}
/**
* Modifier used for held items that apply critical-hit stage boost(s).
* @extends PokemonHeldItemModifier
* @see {@linkcode apply}
*/
export class CritBoosterModifier extends PokemonHeldItemModifier {
/** The amount of stages by which the held item increases the current critical-hit stage value */
protected stageIncrement: number;
constructor(type: ModifierType, pokemonId: integer, stageIncrement: number, stackCount?: integer) {
super(type, pokemonId, stackCount);
this.stageIncrement = stageIncrement;
}
clone() {
return new CritBoosterModifier(this.type, this.pokemonId, this.stageIncrement, this.stackCount);
}
getArgs(): any[] {
return super.getArgs().concat(this.stageIncrement);
}
matchType(modifier: Modifier): boolean {
if (modifier instanceof CritBoosterModifier) {
return (modifier as CritBoosterModifier).stageIncrement === this.stageIncrement;
}
return false;
}
/**
* Increases the current critical-hit stage value by {@linkcode stageIncrement}.
* @param args [0] {@linkcode Pokemon} N/A
* [1] {@linkcode Utils.IntegerHolder} that holds the resulting critical-hit level
* @returns true if the critical-hit stage boost applies successfully, false otherwise
*/
apply(args: any[]): boolean {
const critStage = args[1] as Utils.NumberHolder;
critStage.value += this.stageIncrement;
return true;
}
getMaxHeldItemCount(_pokemon: Pokemon): number {
return 1;
}
}
/**
* Modifier used for held items that apply critical-hit stage boost(s)
* if the holder is of a specific {@linkcode Species}.
* @extends CritBoosterModifier
* @see {@linkcode shouldApply}
*/
export class SpeciesCritBoosterModifier extends CritBoosterModifier {
/** The species that the held item's critical-hit stage boost applies to */
private species: Species[];
constructor(type: ModifierType, pokemonId: integer, stageIncrement: number, species: Species[], stackCount?: integer) {
super(type, pokemonId, stageIncrement, stackCount);
this.species = species;
}
clone() {
return new SpeciesCritBoosterModifier(this.type, this.pokemonId, this.stageIncrement, this.species, this.stackCount);
}
getArgs(): any[] {
return [ ...super.getArgs(), this.species ];
}
matchType(modifier: Modifier): boolean {
return modifier instanceof SpeciesCritBoosterModifier;
}
/**
* Checks if the holder's {@linkcode Species} (or its fused species) is listed
* in {@linkcode species}.
* @param args [0] {@linkcode Pokemon} that holds the held item
* [1] {@linkcode Utils.IntegerHolder} N/A
* @returns true if the critical-hit level can be incremented, false otherwise
*/
shouldApply(args: any[]) {
const holder = args[0] as Pokemon;
return super.shouldApply(args) && (this.species.includes(holder.getSpeciesForm(true).speciesId) || (holder.isFusion() && this.species.includes(holder.getFusionSpeciesForm(true).speciesId)));
}
}
/**
* Applies Specific Type item boosts (e.g., Magnet)
*/

205
src/test/items/leek.test.ts Normal file
View File

@ -0,0 +1,205 @@
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import Phase from "phaser";
import GameManager from "#app/test/utils/gameManager";
import overrides from "#app/overrides";
import { Species } from "#enums/species";
import { Moves } from "#enums/moves";
import { CritBoosterModifier } from "#app/modifier/modifier";
import { modifierTypes } from "#app/modifier/modifier-type";
import * as Utils from "#app/utils";
import { MoveEffectPhase, TurnStartPhase } from "#app/phases";
import { BattlerIndex } from "#app/battle";
describe("Items - Leek", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phase.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MAGIKARP);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([ Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH ]);
vi.spyOn(overrides, "NEVER_CRIT_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
});
it("LEEK activates in battle correctly", async() => {
vi.spyOn(overrides, "STARTING_HELD_ITEMS_OVERRIDE", "get").mockReturnValue([{ name: "LEEK" }]);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([ Moves.POUND ]);
const consoleSpy = vi.spyOn(console, "log");
await game.startBattle([
Species.FARFETCHD
]);
game.doAttack(0);
await game.phaseInterceptor.to(TurnStartPhase, false);
vi.spyOn(game.scene.getCurrentPhase() as TurnStartPhase, "getOrder").mockReturnValue([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
await game.phaseInterceptor.to(MoveEffectPhase);
expect(consoleSpy).toHaveBeenCalledWith("Applied", "Leek", "");
}, 20000);
it("LEEK held by FARFETCHD", async() => {
await game.startBattle([
Species.FARFETCHD
]);
const partyMember = game.scene.getPlayerPokemon();
// Making sure modifier is not applied without holding item
const critLevel = new Utils.IntegerHolder(0);
partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
expect(critLevel.value).toBe(0);
// Giving Leek to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.LEEK().newModifier(partyMember), true);
partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
expect(critLevel.value).toBe(2);
}, 20000);
it("LEEK held by GALAR_FARFETCHD", async() => {
await game.startBattle([
Species.GALAR_FARFETCHD
]);
const partyMember = game.scene.getPlayerPokemon();
// Making sure modifier is not applied without holding item
const critLevel = new Utils.IntegerHolder(0);
partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
expect(critLevel.value).toBe(0);
// Giving Leek to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.LEEK().newModifier(partyMember), true);
partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
expect(critLevel.value).toBe(2);
}, 20000);
it("LEEK held by SIRFETCHD", async() => {
await game.startBattle([
Species.SIRFETCHD
]);
const partyMember = game.scene.getPlayerPokemon();
// Making sure modifier is not applied without holding item
const critLevel = new Utils.IntegerHolder(0);
partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
expect(critLevel.value).toBe(0);
// Giving Leek to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.LEEK().newModifier(partyMember), true);
partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
expect(critLevel.value).toBe(2);
}, 20000);
it("LEEK held by fused FARFETCHD line (base)", async() => {
// Randomly choose from the Farfetch'd line
const species = [ Species.FARFETCHD, Species.GALAR_FARFETCHD, Species.SIRFETCHD ];
await game.startBattle([
species[Utils.randInt(species.length)],
Species.PIKACHU,
]);
const party = game.scene.getParty();
const partyMember = party[0];
const ally = party[1];
// Fuse party members (taken from PlayerPokemon.fuse(...) function)
partyMember.fusionSpecies = ally.species;
partyMember.fusionFormIndex = ally.formIndex;
partyMember.fusionAbilityIndex = ally.abilityIndex;
partyMember.fusionShiny = ally.shiny;
partyMember.fusionVariant = ally.variant;
partyMember.fusionGender = ally.gender;
partyMember.fusionLuck = ally.luck;
// Making sure modifier is not applied without holding item
const critLevel = new Utils.IntegerHolder(0);
partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
expect(critLevel.value).toBe(0);
// Giving Leek to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.LEEK().newModifier(partyMember), true);
partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
expect(critLevel.value).toBe(2);
}, 20000);
it("LEEK held by fused FARFETCHD line (part)", async() => {
// Randomly choose from the Farfetch'd line
const species = [ Species.FARFETCHD, Species.GALAR_FARFETCHD, Species.SIRFETCHD ];
await game.startBattle([
Species.PIKACHU,
species[Utils.randInt(species.length)]
]);
const party = game.scene.getParty();
const partyMember = party[0];
const ally = party[1];
// Fuse party members (taken from PlayerPokemon.fuse(...) function)
partyMember.fusionSpecies = ally.species;
partyMember.fusionFormIndex = ally.formIndex;
partyMember.fusionAbilityIndex = ally.abilityIndex;
partyMember.fusionShiny = ally.shiny;
partyMember.fusionVariant = ally.variant;
partyMember.fusionGender = ally.gender;
partyMember.fusionLuck = ally.luck;
// Making sure modifier is not applied without holding item
const critLevel = new Utils.IntegerHolder(0);
partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
expect(critLevel.value).toBe(0);
// Giving Leek to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.LEEK().newModifier(partyMember), true);
partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
expect(critLevel.value).toBe(2);
}, 20000);
it("LEEK not held by FARFETCHD line", async() => {
await game.startBattle([
Species.PIKACHU
]);
const partyMember = game.scene.getPlayerPokemon();
// Making sure modifier is not applied without holding item
const critLevel = new Utils.IntegerHolder(0);
partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
expect(critLevel.value).toBe(0);
// Giving Leek to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.LEEK().newModifier(partyMember), true);
partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
expect(critLevel.value).toBe(0);
}, 20000);
});

View File

@ -0,0 +1,75 @@
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import Phase from "phaser";
import GameManager from "#app/test/utils/gameManager";
import overrides from "#app/overrides";
import { Species } from "#enums/species";
import { Moves } from "#enums/moves";
import { CritBoosterModifier } from "#app/modifier/modifier";
import { modifierTypes } from "#app/modifier/modifier-type";
import * as Utils from "#app/utils";
import { MoveEffectPhase, TurnStartPhase } from "#app/phases";
import { BattlerIndex } from "#app/battle";
describe("Items - Scope Lens", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phase.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MAGIKARP);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([ Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH ]);
vi.spyOn(overrides, "NEVER_CRIT_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
}, 20000);
it("SCOPE_LENS activates in battle correctly", async() => {
vi.spyOn(overrides, "STARTING_HELD_ITEMS_OVERRIDE", "get").mockReturnValue([{ name: "SCOPE_LENS" }]);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([ Moves.POUND ]);
const consoleSpy = vi.spyOn(console, "log");
await game.startBattle([
Species.GASTLY
]);
game.doAttack(0);
await game.phaseInterceptor.to(TurnStartPhase, false);
vi.spyOn(game.scene.getCurrentPhase() as TurnStartPhase, "getOrder").mockReturnValue([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
await game.phaseInterceptor.to(MoveEffectPhase);
expect(consoleSpy).toHaveBeenCalledWith("Applied", "Scope Lens", "");
}, 20000);
it("SCOPE_LENS held by random pokemon", async() => {
await game.startBattle([
Species.GASTLY
]);
const partyMember = game.scene.getPlayerPokemon();
// Making sure modifier is not applied without holding item
const critLevel = new Utils.IntegerHolder(0);
partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
expect(critLevel.value).toBe(0);
// Giving Scope Lens to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SCOPE_LENS().newModifier(partyMember), true);
partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
expect(critLevel.value).toBe(1);
}, 20000);
});