diff --git a/public/images/items.json b/public/images/items.json index 33dcf8f5e9a..7f8eaf31e0b 100644 --- a/public/images/items.json +++ b/public/images/items.json @@ -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$" } } diff --git a/public/images/items.png b/public/images/items.png index 6d9434d0454..d9efd889f1f 100644 Binary files a/public/images/items.png and b/public/images/items.png differ diff --git a/public/images/items/baton.png b/public/images/items/baton.png new file mode 100644 index 00000000000..43ec2b8a456 Binary files /dev/null and b/public/images/items/baton.png differ diff --git a/public/images/items/stick.png b/public/images/items/leek.png similarity index 100% rename from public/images/items/stick.png rename to public/images/items/leek.png diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index d1eb152db80..aea16061f84 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -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) { diff --git a/src/locales/de/modifier-type.ts b/src/locales/de/modifier-type.ts index 1cd0d480293..2451224ef25 100644 --- a/src/locales/de/modifier-type.ts +++ b/src/locales/de/modifier-type.ts @@ -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)." }, diff --git a/src/locales/en/modifier-type.ts b/src/locales/en/modifier-type.ts index bbaf9898c05..b3f34a20386 100644 --- a/src/locales/en/modifier-type.ts +++ b/src/locales/en/modifier-type.ts @@ -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)." }, diff --git a/src/locales/es/modifier-type.ts b/src/locales/es/modifier-type.ts index 8bc2f665d4c..c3a2fb5d66a 100644 --- a/src/locales/es/modifier-type.ts +++ b/src/locales/es/modifier-type.ts @@ -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)." }, diff --git a/src/locales/fr/modifier-type.ts b/src/locales/fr/modifier-type.ts index 8d508179bfd..be9aa17b9ea 100644 --- a/src/locales/fr/modifier-type.ts +++ b/src/locales/fr/modifier-type.ts @@ -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 d’un Pokémon pouvant évoluer." }, "SOUL_DEW": { name: "Rosée Âme", description: "Augmente de 10% l’influence de la nature d’un Pokémon sur ses statistiques (cumulatif)." }, diff --git a/src/locales/it/modifier-type.ts b/src/locales/it/modifier-type.ts index 58d2434ee6a..54ff0ba0e45 100644 --- a/src/locales/it/modifier-type.ts +++ b/src/locales/it/modifier-type.ts @@ -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)." }, diff --git a/src/locales/ko/modifier-type.ts b/src/locales/ko/modifier-type.ts index b54ae287413..cc09d7763a9 100644 --- a/src/locales/ko/modifier-type.ts +++ b/src/locales/ko/modifier-type.ts @@ -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% 증가한다. (합연산)" }, diff --git a/src/locales/pt_BR/modifier-type.ts b/src/locales/pt_BR/modifier-type.ts index 558d519446c..db46ad3f567 100644 --- a/src/locales/pt_BR/modifier-type.ts +++ b/src/locales/pt_BR/modifier-type.ts @@ -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)." }, diff --git a/src/locales/zh_CN/modifier-type.ts b/src/locales/zh_CN/modifier-type.ts index 4ca1348265f..8d37e7336dc 100644 --- a/src/locales/zh_CN/modifier-type.ts +++ b/src/locales/zh_CN/modifier-type.ts @@ -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%宝可梦性格对数值的影响 (加算)。" }, diff --git a/src/locales/zh_TW/modifier-type.ts b/src/locales/zh_TW/modifier-type.ts index 715bf781ace..5e8a32161ce 100644 --- a/src/locales/zh_TW/modifier-type.ts +++ b/src/locales/zh_TW/modifier-type.ts @@ -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: "進化的神奇石塊。攜帶後,還能進化的寶可夢的 防禦和特防就會提高。" diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 8fb7544fba2..d5bbd3b225f 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -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), diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 2e9efd2544d..26dd9eb94cf 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -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) */ diff --git a/src/test/items/leek.test.ts b/src/test/items/leek.test.ts new file mode 100644 index 00000000000..d81b6b8541f --- /dev/null +++ b/src/test/items/leek.test.ts @@ -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); +}); diff --git a/src/test/items/scope_lens.test.ts b/src/test/items/scope_lens.test.ts new file mode 100644 index 00000000000..8a90809d64e --- /dev/null +++ b/src/test/items/scope_lens.test.ts @@ -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); +});