mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-27 02:32:21 +02:00
Compare commits
7 Commits
15106b83fb
...
b794662776
Author | SHA1 | Date | |
---|---|---|---|
|
b794662776 | ||
|
ba9378d1d8 | ||
|
b54a255c15 | ||
|
cff5a670b1 | ||
|
6ad32f3c40 | ||
|
2e823b1914 | ||
|
7f5e873457 |
@ -6,7 +6,7 @@ import { BattleStat, getBattleStatName } from "./battle-stat";
|
|||||||
import { MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase } from "../phases";
|
import { MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase } from "../phases";
|
||||||
import { getPokemonNameWithAffix } from "../messages";
|
import { getPokemonNameWithAffix } from "../messages";
|
||||||
import { Weather, WeatherType } from "./weather";
|
import { Weather, WeatherType } from "./weather";
|
||||||
import { BattlerTag, GroundedTag, GulpMissileTag } from "./battler-tags";
|
import { BattlerTag, GroundedTag, GulpMissileTag, SemiInvulnerableTag } from "./battler-tags";
|
||||||
import { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect";
|
import { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect";
|
||||||
import { Gender } from "./gender";
|
import { Gender } from "./gender";
|
||||||
import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, MoveAttr, MultiHitAttr, ChargeAttr, SacrificialAttr, SacrificialAttrOnHit } from "./move";
|
import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, MoveAttr, MultiHitAttr, ChargeAttr, SacrificialAttr, SacrificialAttrOnHit } from "./move";
|
||||||
@ -517,7 +517,7 @@ export class PostDefendGulpMissileAbAttr extends PostDefendAbAttr {
|
|||||||
*/
|
*/
|
||||||
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean | Promise<boolean> {
|
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean | Promise<boolean> {
|
||||||
const battlerTag = pokemon.getTag(GulpMissileTag);
|
const battlerTag = pokemon.getTag(GulpMissileTag);
|
||||||
if (!battlerTag || move.category === MoveCategory.STATUS) {
|
if (!battlerTag || move.category === MoveCategory.STATUS || pokemon.getTag(SemiInvulnerableTag)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5138,9 +5138,7 @@ export function initAbilities() {
|
|||||||
.attr(NoFusionAbilityAbAttr)
|
.attr(NoFusionAbilityAbAttr)
|
||||||
.attr(UncopiableAbilityAbAttr)
|
.attr(UncopiableAbilityAbAttr)
|
||||||
.attr(UnswappableAbilityAbAttr)
|
.attr(UnswappableAbilityAbAttr)
|
||||||
.attr(PostDefendGulpMissileAbAttr)
|
.attr(PostDefendGulpMissileAbAttr),
|
||||||
// Does not transform when Surf/Dive misses/is protected
|
|
||||||
.partial(),
|
|
||||||
new Ability(Abilities.STALWART, 8)
|
new Ability(Abilities.STALWART, 8)
|
||||||
.attr(BlockRedirectAbAttr),
|
.attr(BlockRedirectAbAttr),
|
||||||
new Ability(Abilities.STEAM_ENGINE, 8)
|
new Ability(Abilities.STEAM_ENGINE, 8)
|
||||||
|
@ -4356,7 +4356,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
|
|||||||
*/
|
*/
|
||||||
export class GulpMissileTagAttr extends MoveEffectAttr {
|
export class GulpMissileTagAttr extends MoveEffectAttr {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(true, MoveEffectTrigger.POST_APPLY);
|
super(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -6954,7 +6954,7 @@ export function initMoves() {
|
|||||||
.makesContact(false)
|
.makesContact(false)
|
||||||
.partial(),
|
.partial(),
|
||||||
new AttackMove(Moves.DIVE, Type.WATER, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 3)
|
new AttackMove(Moves.DIVE, Type.WATER, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 3)
|
||||||
.attr(ChargeAttr, ChargeAnim.DIVE_CHARGING, i18next.t("moveTriggers:hidUnderwater", {pokemonName: "{USER}"}), BattlerTagType.UNDERWATER)
|
.attr(ChargeAttr, ChargeAnim.DIVE_CHARGING, i18next.t("moveTriggers:hidUnderwater", {pokemonName: "{USER}"}), BattlerTagType.UNDERWATER, true)
|
||||||
.attr(GulpMissileTagAttr)
|
.attr(GulpMissileTagAttr)
|
||||||
.ignoresVirtual(),
|
.ignoresVirtual(),
|
||||||
new AttackMove(Moves.ARM_THRUST, Type.FIGHTING, MoveCategory.PHYSICAL, 15, 100, 20, -1, 0, 3)
|
new AttackMove(Moves.ARM_THRUST, Type.FIGHTING, MoveCategory.PHYSICAL, 15, 100, 20, -1, 0, 3)
|
||||||
|
@ -1081,6 +1081,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
(Overrides.OPP_PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE && !this.isPlayer())) {
|
(Overrides.OPP_PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE && !this.isPlayer())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Final boss does not have passive
|
||||||
|
if (this.scene.currentBattle?.battleSpec === BattleSpec.FINAL_BOSS && this instanceof EnemyPokemon) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return this.passive || this.isBoss();
|
return this.passive || this.isBoss();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import { BattleSpec } from "#enums/battle-spec";
|
|||||||
import { BattlePhase, MovePhase, PokemonHealPhase } from "./phases";
|
import { BattlePhase, MovePhase, PokemonHealPhase } from "./phases";
|
||||||
import { getTypeRgb } from "./data/type";
|
import { getTypeRgb } from "./data/type";
|
||||||
import { getPokemonNameWithAffix } from "./messages";
|
import { getPokemonNameWithAffix } from "./messages";
|
||||||
|
import { SemiInvulnerableTag } from "./data/battler-tags";
|
||||||
|
|
||||||
export class FormChangePhase extends EvolutionPhase {
|
export class FormChangePhase extends EvolutionPhase {
|
||||||
private formChange: SpeciesFormChange;
|
private formChange: SpeciesFormChange;
|
||||||
@ -194,7 +195,7 @@ export class QuietFormChangePhase extends BattlePhase {
|
|||||||
|
|
||||||
const preName = getPokemonNameWithAffix(this.pokemon);
|
const preName = getPokemonNameWithAffix(this.pokemon);
|
||||||
|
|
||||||
if (!this.pokemon.isOnField()) {
|
if (!this.pokemon.isOnField() || this.pokemon.getTag(SemiInvulnerableTag)) {
|
||||||
this.pokemon.changeForm(this.formChange).then(() => {
|
this.pokemon.changeForm(this.formChange).then(() => {
|
||||||
this.scene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), 1500);
|
this.scene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), 1500);
|
||||||
});
|
});
|
||||||
|
@ -41,6 +41,7 @@ export const starterSelectUiHandler: SimpleTranslationEntries = {
|
|||||||
"cycleAbility": ": Fähigkeit",
|
"cycleAbility": ": Fähigkeit",
|
||||||
"cycleNature": ": Wesen",
|
"cycleNature": ": Wesen",
|
||||||
"cycleVariant": ": Seltenheit",
|
"cycleVariant": ": Seltenheit",
|
||||||
|
"goFilter": ": Zu den Filtern",
|
||||||
"enablePassive": "Passiv-Skill aktivieren",
|
"enablePassive": "Passiv-Skill aktivieren",
|
||||||
"disablePassive": "Passiv-Skill deaktivieren",
|
"disablePassive": "Passiv-Skill deaktivieren",
|
||||||
"locked": "Gesperrt",
|
"locked": "Gesperrt",
|
||||||
|
@ -41,6 +41,7 @@ export const starterSelectUiHandler: SimpleTranslationEntries = {
|
|||||||
"cycleAbility": ": Ability",
|
"cycleAbility": ": Ability",
|
||||||
"cycleNature": ": Nature",
|
"cycleNature": ": Nature",
|
||||||
"cycleVariant": ": Variant",
|
"cycleVariant": ": Variant",
|
||||||
|
"goFilter": ": Go to filters",
|
||||||
"enablePassive": "Enable Passive",
|
"enablePassive": "Enable Passive",
|
||||||
"disablePassive": "Disable Passive",
|
"disablePassive": "Disable Passive",
|
||||||
"locked": "Locked",
|
"locked": "Locked",
|
||||||
|
@ -41,6 +41,7 @@ export const starterSelectUiHandler: SimpleTranslationEntries = {
|
|||||||
"cycleAbility": ": Habilidad",
|
"cycleAbility": ": Habilidad",
|
||||||
"cycleNature": ": Naturaleza",
|
"cycleNature": ": Naturaleza",
|
||||||
"cycleVariant": ": Variante",
|
"cycleVariant": ": Variante",
|
||||||
|
"goFilter": ": Ir a filtros",
|
||||||
"enablePassive": "Activar Pasiva",
|
"enablePassive": "Activar Pasiva",
|
||||||
"disablePassive": "Desactivar Pasiva",
|
"disablePassive": "Desactivar Pasiva",
|
||||||
"locked": "Bloqueado",
|
"locked": "Bloqueado",
|
||||||
|
@ -41,6 +41,7 @@ export const starterSelectUiHandler: SimpleTranslationEntries = {
|
|||||||
"cycleAbility": ": Talent",
|
"cycleAbility": ": Talent",
|
||||||
"cycleNature": ": Nature",
|
"cycleNature": ": Nature",
|
||||||
"cycleVariant": ": Variant",
|
"cycleVariant": ": Variant",
|
||||||
|
"goFilter": ": Aller aux filtres",
|
||||||
"enablePassive": "Activer Passif",
|
"enablePassive": "Activer Passif",
|
||||||
"disablePassive": "Désactiver Passif",
|
"disablePassive": "Désactiver Passif",
|
||||||
"locked": "Verrouillé",
|
"locked": "Verrouillé",
|
||||||
|
@ -41,6 +41,7 @@ export const starterSelectUiHandler: SimpleTranslationEntries = {
|
|||||||
"cycleAbility": ": Abilità",
|
"cycleAbility": ": Abilità",
|
||||||
"cycleNature": ": Natura",
|
"cycleNature": ": Natura",
|
||||||
"cycleVariant": ": Variante",
|
"cycleVariant": ": Variante",
|
||||||
|
"goFilter": ": Go to filters",
|
||||||
"enablePassive": "Attiva passiva",
|
"enablePassive": "Attiva passiva",
|
||||||
"disablePassive": "Disattiva passiva",
|
"disablePassive": "Disattiva passiva",
|
||||||
"locked": "Bloccato",
|
"locked": "Bloccato",
|
||||||
|
@ -3,171 +3,171 @@ import { AchievementTranslationEntries } from "#app/interfaces/locales.js";
|
|||||||
// Achievement translations for the when the player character is male
|
// Achievement translations for the when the player character is male
|
||||||
export const PGMachv: AchievementTranslationEntries = {
|
export const PGMachv: AchievementTranslationEntries = {
|
||||||
"Achievements": {
|
"Achievements": {
|
||||||
name: "Achievements",
|
name: "実績",
|
||||||
},
|
},
|
||||||
"Locked": {
|
"Locked": {
|
||||||
name: "Locked",
|
name: "なし",
|
||||||
},
|
},
|
||||||
|
|
||||||
"MoneyAchv": {
|
"MoneyAchv": {
|
||||||
description: "Accumulate a total of ₽{{moneyAmount}}",
|
description: "一回の ランで ₽{{moneyAmount}}を 稼ぐ",
|
||||||
},
|
},
|
||||||
"10K_MONEY": {
|
"10K_MONEY": {
|
||||||
name: "Money Haver",
|
name: "お金を持つ人",
|
||||||
},
|
},
|
||||||
"100K_MONEY": {
|
"100K_MONEY": {
|
||||||
name: "Rich",
|
name: "富豪",
|
||||||
},
|
},
|
||||||
"1M_MONEY": {
|
"1M_MONEY": {
|
||||||
name: "Millionaire",
|
name: "百万長者",
|
||||||
},
|
},
|
||||||
"10M_MONEY": {
|
"10M_MONEY": {
|
||||||
name: "One Percenter",
|
name: "超富裕層",
|
||||||
},
|
},
|
||||||
|
|
||||||
"DamageAchv": {
|
"DamageAchv": {
|
||||||
description: "Inflict {{damageAmount}} damage in one hit",
|
description: "一撃で {{damageAmount}}ダメージを 与える",
|
||||||
},
|
},
|
||||||
"250_DMG": {
|
"250_DMG": {
|
||||||
name: "Hard Hitter",
|
name: "力持ち",
|
||||||
},
|
},
|
||||||
"1000_DMG": {
|
"1000_DMG": {
|
||||||
name: "Harder Hitter",
|
name: "強者",
|
||||||
},
|
},
|
||||||
"2500_DMG": {
|
"2500_DMG": {
|
||||||
name: "That's a Lotta Damage!",
|
name: "カカロット",
|
||||||
},
|
},
|
||||||
"10000_DMG": {
|
"10000_DMG": {
|
||||||
name: "One Punch Man",
|
name: "ワンパンマン",
|
||||||
},
|
},
|
||||||
|
|
||||||
"HealAchv": {
|
"HealAchv": {
|
||||||
description: "Heal {{healAmount}} {{HP}} at once with a move, ability, or held item",
|
description: "一つの 技や 特性や 持っているアイテムで {{healAmount}}{{HP}}を 一気に 回復する",
|
||||||
},
|
},
|
||||||
"250_HEAL": {
|
"250_HEAL": {
|
||||||
name: "Novice Healer",
|
name: "回復発見者",
|
||||||
},
|
},
|
||||||
"1000_HEAL": {
|
"1000_HEAL": {
|
||||||
name: "Big Healer",
|
name: "大いなる治療者",
|
||||||
},
|
},
|
||||||
"2500_HEAL": {
|
"2500_HEAL": {
|
||||||
name: "Cleric",
|
name: "回復達人",
|
||||||
},
|
},
|
||||||
"10000_HEAL": {
|
"10000_HEAL": {
|
||||||
name: "Recovery Master",
|
name: "ジョーイさん",
|
||||||
},
|
},
|
||||||
|
|
||||||
"LevelAchv": {
|
"LevelAchv": {
|
||||||
description: "Level up a Pokémon to Lv{{level}}",
|
description: "一つの ポケモンを Lv{{level}}まで レベルアップする",
|
||||||
},
|
},
|
||||||
"LV_100": {
|
"LV_100": {
|
||||||
name: "But Wait, There's More!",
|
name: "まだまだだよ",
|
||||||
},
|
},
|
||||||
"LV_250": {
|
"LV_250": {
|
||||||
name: "Elite",
|
name: "天王",
|
||||||
},
|
},
|
||||||
"LV_1000": {
|
"LV_1000": {
|
||||||
name: "To Go Even Further Beyond",
|
name: "向こうの向こうを超え",
|
||||||
},
|
},
|
||||||
|
|
||||||
"RibbonAchv": {
|
"RibbonAchv": {
|
||||||
description: "Accumulate a total of {{ribbonAmount}} Ribbons",
|
description: "{{ribbonAmount}}巻の リボンを 積もる",
|
||||||
},
|
},
|
||||||
"10_RIBBONS": {
|
"10_RIBBONS": {
|
||||||
name: "Pokémon League Champion",
|
name: "ポケモンリーグチャンピオン",
|
||||||
},
|
},
|
||||||
"25_RIBBONS": {
|
"25_RIBBONS": {
|
||||||
name: "Great League Champion",
|
name: "スーパーリーグチャンピオン",
|
||||||
},
|
},
|
||||||
"50_RIBBONS": {
|
"50_RIBBONS": {
|
||||||
name: "Ultra League Champion",
|
name: "ハイパーリーグチャンピオン",
|
||||||
},
|
},
|
||||||
"75_RIBBONS": {
|
"75_RIBBONS": {
|
||||||
name: "Rogue League Champion",
|
name: "ローグリーグチャンピオン",
|
||||||
},
|
},
|
||||||
"100_RIBBONS": {
|
"100_RIBBONS": {
|
||||||
name: "Master League Champion",
|
name: "マスターリーグチャンピオン",
|
||||||
},
|
},
|
||||||
|
|
||||||
"TRANSFER_MAX_BATTLE_STAT": {
|
"TRANSFER_MAX_BATTLE_STAT": {
|
||||||
name: "Teamwork",
|
name: "同力",
|
||||||
description: "Baton pass to another party member with at least one stat maxed out",
|
description: "少なくとも 一つの 能力を 最大まで あげて 他の 手持ちポケモンに バトンタッチする",
|
||||||
},
|
},
|
||||||
"MAX_FRIENDSHIP": {
|
"MAX_FRIENDSHIP": {
|
||||||
name: "Friendmaxxing",
|
name: "マブ達",
|
||||||
description: "Reach max friendship on a Pokémon",
|
description: "一つの 手持ちポケモンの 仲良し度を 最大に 上げる",
|
||||||
},
|
},
|
||||||
"MEGA_EVOLVE": {
|
"MEGA_EVOLVE": {
|
||||||
name: "Megamorph",
|
name: "ザ・アブソリュート",
|
||||||
description: "Mega evolve a Pokémon",
|
description: "一つの 手持ちポケモンを メガシンカさせる",
|
||||||
},
|
},
|
||||||
"GIGANTAMAX": {
|
"GIGANTAMAX": {
|
||||||
name: "Absolute Unit",
|
name: "太―くて 堪らない",
|
||||||
description: "Gigantamax a Pokémon",
|
description: "一つの 手持ちポケモンを キョダイマックスさせる",
|
||||||
},
|
},
|
||||||
"TERASTALLIZE": {
|
"TERASTALLIZE": {
|
||||||
name: "STAB Enthusiast",
|
name: "一致好き",
|
||||||
description: "Terastallize a Pokémon",
|
description: "一つの 手持ちポケモンを テラスタルさせる",
|
||||||
},
|
},
|
||||||
"STELLAR_TERASTALLIZE": {
|
"STELLAR_TERASTALLIZE": {
|
||||||
name: "The Hidden Type",
|
name: "隠れたタイプ",
|
||||||
description: "Stellar Terastallize a Pokémon",
|
description: "一つの 手持ちポケモンを ステラ・テラスタルさせる",
|
||||||
},
|
},
|
||||||
"SPLICE": {
|
"SPLICE": {
|
||||||
name: "Infinite Fusion",
|
name: "インフィニット・フュジョン",
|
||||||
description: "Splice two Pokémon together with DNA Splicers",
|
description: "いでんしのくさびで 二つの ポケモンを 吸収合体させる",
|
||||||
},
|
},
|
||||||
"MINI_BLACK_HOLE": {
|
"MINI_BLACK_HOLE": {
|
||||||
name: "A Hole Lot of Items",
|
name: "アイテムホーリック",
|
||||||
description: "Acquire a Mini Black Hole",
|
description: "ミニブラックホールを 手に入れる",
|
||||||
},
|
},
|
||||||
"CATCH_MYTHICAL": {
|
"CATCH_MYTHICAL": {
|
||||||
name: "Mythical",
|
name: "幻",
|
||||||
description: "Catch a mythical Pokémon",
|
description: "幻の ポケモンを 捕まえる",
|
||||||
},
|
},
|
||||||
"CATCH_SUB_LEGENDARY": {
|
"CATCH_SUB_LEGENDARY": {
|
||||||
name: "(Sub-)Legendary",
|
name: "準・伝説",
|
||||||
description: "Catch a sub-legendary Pokémon",
|
description: "準伝説の ポケモンを 捕まえる",
|
||||||
},
|
},
|
||||||
"CATCH_LEGENDARY": {
|
"CATCH_LEGENDARY": {
|
||||||
name: "Legendary",
|
name: "ザ・伝説",
|
||||||
description: "Catch a legendary Pokémon",
|
description: "伝説の ポケモンを 捕まえる",
|
||||||
},
|
},
|
||||||
"SEE_SHINY": {
|
"SEE_SHINY": {
|
||||||
name: "Shiny",
|
name: "色とりどりに光る",
|
||||||
description: "Find a shiny Pokémon in the wild",
|
description: "野生の 色違いポケモンを みつける",
|
||||||
},
|
},
|
||||||
"SHINY_PARTY": {
|
"SHINY_PARTY": {
|
||||||
name: "That's Dedication",
|
name: "きらきら努力家",
|
||||||
description: "Have a full party of shiny Pokémon",
|
description: "手持ちポケモンは 全員 色違いポケモンに する",
|
||||||
},
|
},
|
||||||
"HATCH_MYTHICAL": {
|
"HATCH_MYTHICAL": {
|
||||||
name: "Mythical Egg",
|
name: "幻のタマゴ",
|
||||||
description: "Hatch a mythical Pokémon from an egg",
|
description: "幻の ポケモンを タマゴから 生まれる",
|
||||||
},
|
},
|
||||||
"HATCH_SUB_LEGENDARY": {
|
"HATCH_SUB_LEGENDARY": {
|
||||||
name: "Sub-Legendary Egg",
|
name: "準伝説のタマゴ",
|
||||||
description: "Hatch a sub-legendary Pokémon from an egg",
|
description: "準伝説の ポケモンを タマゴから 生まれる",
|
||||||
},
|
},
|
||||||
"HATCH_LEGENDARY": {
|
"HATCH_LEGENDARY": {
|
||||||
name: "Legendary Egg",
|
name: "伝説のタマゴ",
|
||||||
description: "Hatch a legendary Pokémon from an egg",
|
description: "伝説の ポケモンを タマゴから 生まれる",
|
||||||
},
|
},
|
||||||
"HATCH_SHINY": {
|
"HATCH_SHINY": {
|
||||||
name: "Shiny Egg",
|
name: "色違いタマゴ",
|
||||||
description: "Hatch a shiny Pokémon from an egg",
|
description: "色違いポケモンを タマゴから 生まれる",
|
||||||
},
|
},
|
||||||
"HIDDEN_ABILITY": {
|
"HIDDEN_ABILITY": {
|
||||||
name: "Hidden Potential",
|
name: "底力",
|
||||||
description: "Catch a Pokémon with a hidden ability",
|
description: "隠れ特性がある ポケモンを 捕まえる",
|
||||||
},
|
},
|
||||||
"PERFECT_IVS": {
|
"PERFECT_IVS": {
|
||||||
name: "Certificate of Authenticity",
|
name: "個体値の賞状",
|
||||||
description: "Get perfect IVs on a Pokémon",
|
description: "一つの ポケモンの 個体値を すべて 最大に する",
|
||||||
},
|
},
|
||||||
"CLASSIC_VICTORY": {
|
"CLASSIC_VICTORY": {
|
||||||
name: "Undefeated",
|
name: "無双",
|
||||||
description: "Beat the game in classic mode",
|
description: "クラシックモードを クリアする",
|
||||||
},
|
},
|
||||||
"UNEVOLVED_CLASSIC_VICTORY": {
|
"UNEVOLVED_CLASSIC_VICTORY": {
|
||||||
name: "Bring Your Child To Work Day",
|
name: "Bring Your Child To Work Day",
|
||||||
@ -175,102 +175,102 @@ export const PGMachv: AchievementTranslationEntries = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
"MONO_GEN_ONE": {
|
"MONO_GEN_ONE": {
|
||||||
name: "The Original Rival",
|
name: "原始",
|
||||||
description: "Complete the generation one only challenge.",
|
description: "1世代の 単一世代チャレンジを クリアする",
|
||||||
},
|
},
|
||||||
"MONO_GEN_TWO": {
|
"MONO_GEN_TWO": {
|
||||||
name: "Generation 1.5",
|
name: "懐かしいカンジョウ",
|
||||||
description: "Complete the generation two only challenge.",
|
description: "2世代の 単一世代チャレンジを クリアする",
|
||||||
},
|
},
|
||||||
"MONO_GEN_THREE": {
|
"MONO_GEN_THREE": {
|
||||||
name: "Too much water?",
|
name: "水浸し",
|
||||||
description: "Complete the generation three only challenge.",
|
description: "3世代の 単一世代チャレンジを クリアする",
|
||||||
},
|
},
|
||||||
"MONO_GEN_FOUR": {
|
"MONO_GEN_FOUR": {
|
||||||
name: "Is she really the hardest?",
|
name: "神々の地",
|
||||||
description: "Complete the generation four only challenge.",
|
description: "4世代の 単一世代チャレンジを クリアする",
|
||||||
},
|
},
|
||||||
"MONO_GEN_FIVE": {
|
"MONO_GEN_FIVE": {
|
||||||
name: "All Original",
|
name: "ニューヨーカー",
|
||||||
description: "Complete the generation five only challenge.",
|
description: "5世代の 単一世代チャレンジを クリアする",
|
||||||
},
|
},
|
||||||
"MONO_GEN_SIX": {
|
"MONO_GEN_SIX": {
|
||||||
name: "Almost Royalty",
|
name: "サヴァ・サヴァ",
|
||||||
description: "Complete the generation six only challenge.",
|
description: "6世代の 単一世代チャレンジを クリアする",
|
||||||
},
|
},
|
||||||
"MONO_GEN_SEVEN": {
|
"MONO_GEN_SEVEN": {
|
||||||
name: "Only Technically",
|
name: "アローラ・オエ",
|
||||||
description: "Complete the generation seven only challenge.",
|
description: "7世代の 単一世代チャレンジを クリアする",
|
||||||
},
|
},
|
||||||
"MONO_GEN_EIGHT": {
|
"MONO_GEN_EIGHT": {
|
||||||
name: "A Champion Time!",
|
name: "チャンピオン タイムを 楽しめ!",
|
||||||
description: "Complete the generation eight only challenge.",
|
description: "8世代の 単一世代チャレンジを クリアする",
|
||||||
},
|
},
|
||||||
"MONO_GEN_NINE": {
|
"MONO_GEN_NINE": {
|
||||||
name: "She was going easy on you",
|
name: "ネモに甘えたでしょう",
|
||||||
description: "Complete the generation nine only challenge.",
|
description: "9世代の 単一世代チャレンジを クリアする",
|
||||||
},
|
},
|
||||||
|
|
||||||
"MonoType": {
|
"MonoType": {
|
||||||
description: "Complete the {{type}} monotype challenge.",
|
description: "{{type}}タイプの 単一タイプチャレンジを クリアする",
|
||||||
},
|
},
|
||||||
"MONO_NORMAL": {
|
"MONO_NORMAL": {
|
||||||
name: "Extra Ordinary",
|
name: "凡人",
|
||||||
},
|
},
|
||||||
"MONO_FIGHTING": {
|
"MONO_FIGHTING": {
|
||||||
name: "I Know Kung Fu",
|
name: "八千以上だ!!",
|
||||||
},
|
},
|
||||||
"MONO_FLYING": {
|
"MONO_FLYING": {
|
||||||
name: "Angry Birds",
|
name: "翼をください",
|
||||||
},
|
},
|
||||||
"MONO_POISON": {
|
"MONO_POISON": {
|
||||||
name: "Kanto's Favourite",
|
name: "カントーの名物",
|
||||||
},
|
},
|
||||||
"MONO_GROUND": {
|
"MONO_GROUND": {
|
||||||
name: "Forecast: Earthquakes",
|
name: "自信でユラユラ",
|
||||||
},
|
},
|
||||||
"MONO_ROCK": {
|
"MONO_ROCK": {
|
||||||
name: "Brock Hard",
|
name: "タケシの挑戦状",
|
||||||
},
|
},
|
||||||
"MONO_BUG": {
|
"MONO_BUG": {
|
||||||
name: "You Like Jazz?",
|
name: "チョウチョウせん者",
|
||||||
},
|
},
|
||||||
"MONO_GHOST": {
|
"MONO_GHOST": {
|
||||||
name: "Who You Gonna Call?",
|
name: "貞子ちゃん",
|
||||||
},
|
},
|
||||||
"MONO_STEEL": {
|
"MONO_STEEL": {
|
||||||
name: "Iron Giant",
|
name: "ハガネーター",
|
||||||
},
|
},
|
||||||
"MONO_FIRE": {
|
"MONO_FIRE": {
|
||||||
name: "I Cast Fireball!",
|
name: "NIGHT OF FIRE",
|
||||||
},
|
},
|
||||||
"MONO_WATER": {
|
"MONO_WATER": {
|
||||||
name: "When It Rains, It Pours",
|
name: "土砂降リスト",
|
||||||
},
|
},
|
||||||
"MONO_GRASS": {
|
"MONO_GRASS": {
|
||||||
name: "Can't Touch This",
|
name: "www",
|
||||||
},
|
},
|
||||||
"MONO_ELECTRIC": {
|
"MONO_ELECTRIC": {
|
||||||
name: "Aim For The Horn!",
|
name: "パチピカペコ",
|
||||||
},
|
},
|
||||||
"MONO_PSYCHIC": {
|
"MONO_PSYCHIC": {
|
||||||
name: "Big Brain Energy",
|
name: "陽キャ",
|
||||||
},
|
},
|
||||||
"MONO_ICE": {
|
"MONO_ICE": {
|
||||||
name: "Walking On Thin Ice",
|
name: "ありのまま",
|
||||||
},
|
},
|
||||||
"MONO_DRAGON": {
|
"MONO_DRAGON": {
|
||||||
name: "Pseudo-Legend Club",
|
name: "龍が如く",
|
||||||
},
|
},
|
||||||
"MONO_DARK": {
|
"MONO_DARK": {
|
||||||
name: "It's Just A Phase",
|
name: "陰キャ",
|
||||||
},
|
},
|
||||||
"MONO_FAIRY": {
|
"MONO_FAIRY": {
|
||||||
name: "Hey! Listen!",
|
name: "あらハート満タンになった",
|
||||||
},
|
},
|
||||||
"FRESH_START": {
|
"FRESH_START": {
|
||||||
name: "First Try!",
|
name: "一発で!",
|
||||||
description: "Complete the fresh start challenge."
|
description: "出直しチャレンジを クリアする"
|
||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
@ -1,32 +1,32 @@
|
|||||||
import { TranslationEntries } from "#app/interfaces/locales";
|
import { TranslationEntries } from "#app/interfaces/locales";
|
||||||
|
|
||||||
export const challenges: TranslationEntries = {
|
export const challenges: TranslationEntries = {
|
||||||
"title": "チャレンジ じょうけん せってい",
|
"title": "チャレンジを 設定",
|
||||||
"illegalEvolution": "{{pokemon}} changed into an ineligble pokémon\nfor this challenge!",
|
"illegalEvolution": "{{pokemon}}は このチャレンジで\n対象外の ポケモンに なってしまった!",
|
||||||
"singleGeneration": {
|
"singleGeneration": {
|
||||||
"name": "Mono Gen",
|
"name": "単一世代",
|
||||||
"desc": "You can only use Pokémon from Generation {{gen}}.",
|
"desc": "{{gen}}世代からの ポケモンしか 使えません",
|
||||||
"desc_default": "You can only use Pokémon from the chosen generation.",
|
"desc_default": "選んだ 世代からの ポケモンしか 使えません",
|
||||||
"gen_1": "one",
|
"gen_1": "1",
|
||||||
"gen_2": "two",
|
"gen_2": "2",
|
||||||
"gen_3": "three",
|
"gen_3": "3",
|
||||||
"gen_4": "four",
|
"gen_4": "4",
|
||||||
"gen_5": "five",
|
"gen_5": "5",
|
||||||
"gen_6": "six",
|
"gen_6": "6",
|
||||||
"gen_7": "seven",
|
"gen_7": "7",
|
||||||
"gen_8": "eight",
|
"gen_8": "8",
|
||||||
"gen_9": "nine",
|
"gen_9": "9",
|
||||||
},
|
},
|
||||||
"singleType": {
|
"singleType": {
|
||||||
"name": "Mono Type",
|
"name": "単一タイプ",
|
||||||
"desc": "You can only use Pokémon with the {{type}} type.",
|
"desc": "{{type}}タイプの ポケモンしか 使えません",
|
||||||
"desc_default": "You can only use Pokémon of the chosen type."
|
"desc_default": "選んだ タイプの ポケモンしか 使えません"
|
||||||
//types in pokemon-info
|
//types in pokemon-info
|
||||||
},
|
},
|
||||||
"freshStart": {
|
"freshStart": {
|
||||||
"name": "Fresh Start",
|
"name": "出直し",
|
||||||
"desc": "You can only use the original starters, and only as if you had just started pokerogue.",
|
"desc": "ポケローグを 始めた ばかりの ような ままで ゲーム開始の 最初のパートナーしか 使えません",
|
||||||
"value.0": "Off",
|
"value.0": "オフ",
|
||||||
"value.1": "On",
|
"value.1": "オン",
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -41,6 +41,7 @@ export const starterSelectUiHandler: SimpleTranslationEntries = {
|
|||||||
"cycleAbility": ": 특성",
|
"cycleAbility": ": 특성",
|
||||||
"cycleNature": ": 성격",
|
"cycleNature": ": 성격",
|
||||||
"cycleVariant": ": 색상",
|
"cycleVariant": ": 색상",
|
||||||
|
"goFilter": ": 필터로 이동",
|
||||||
"enablePassive": "패시브 활성화",
|
"enablePassive": "패시브 활성화",
|
||||||
"disablePassive": "패시브 비활성화",
|
"disablePassive": "패시브 비활성화",
|
||||||
"locked": "잠김",
|
"locked": "잠김",
|
||||||
|
@ -41,6 +41,7 @@ export const starterSelectUiHandler: SimpleTranslationEntries = {
|
|||||||
"cycleAbility": ": » Habilidade",
|
"cycleAbility": ": » Habilidade",
|
||||||
"cycleNature": ": » Natureza",
|
"cycleNature": ": » Natureza",
|
||||||
"cycleVariant": ": » Variante",
|
"cycleVariant": ": » Variante",
|
||||||
|
"goFilter": ": Ir para filtros",
|
||||||
"enablePassive": "Ativar Passiva",
|
"enablePassive": "Ativar Passiva",
|
||||||
"disablePassive": "Desativar Passiva",
|
"disablePassive": "Desativar Passiva",
|
||||||
"locked": "Bloqueada",
|
"locked": "Bloqueada",
|
||||||
|
@ -41,6 +41,7 @@ export const starterSelectUiHandler: SimpleTranslationEntries = {
|
|||||||
"cycleAbility": ": 特性",
|
"cycleAbility": ": 特性",
|
||||||
"cycleNature": ": 性格",
|
"cycleNature": ": 性格",
|
||||||
"cycleVariant": ": 变种",
|
"cycleVariant": ": 变种",
|
||||||
|
"goFilter": ": 转到筛选",
|
||||||
"enablePassive": "启用被动",
|
"enablePassive": "启用被动",
|
||||||
"disablePassive": "禁用被动",
|
"disablePassive": "禁用被动",
|
||||||
"locked": "未解锁",
|
"locked": "未解锁",
|
||||||
|
@ -42,6 +42,7 @@ export const starterSelectUiHandler: SimpleTranslationEntries = {
|
|||||||
"cycleAbility": ": 特性",
|
"cycleAbility": ": 特性",
|
||||||
"cycleNature": ": 性格",
|
"cycleNature": ": 性格",
|
||||||
"cycleVariant": ": 變種",
|
"cycleVariant": ": 變種",
|
||||||
|
"goFilter": ": 轉到篩選",
|
||||||
"enablePassive": "啟用被動",
|
"enablePassive": "啟用被動",
|
||||||
"disablePassive": "禁用被動",
|
"disablePassive": "禁用被動",
|
||||||
"locked": "未解鎖",
|
"locked": "未解鎖",
|
||||||
|
138
src/phases.ts
138
src/phases.ts
@ -2957,9 +2957,11 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
constructor(scene: BattleScene, battlerIndex: BattlerIndex, targets: BattlerIndex[], move: PokemonMove) {
|
constructor(scene: BattleScene, battlerIndex: BattlerIndex, targets: BattlerIndex[], move: PokemonMove) {
|
||||||
super(scene, battlerIndex);
|
super(scene, battlerIndex);
|
||||||
this.move = move;
|
this.move = move;
|
||||||
// In double battles, if the right Pokemon selects a spread move and the left Pokemon dies
|
/**
|
||||||
// with no party members available to switch in, then the right Pokemon takes the index
|
* In double battles, if the right Pokemon selects a spread move and the left Pokemon dies
|
||||||
// of the left Pokemon and gets hit unless this is checked.
|
* with no party members available to switch in, then the right Pokemon takes the index
|
||||||
|
* of the left Pokemon and gets hit unless this is checked.
|
||||||
|
*/
|
||||||
if (targets.includes(battlerIndex) && this.move.getMove().moveTarget === MoveTarget.ALL_NEAR_OTHERS) {
|
if (targets.includes(battlerIndex) && this.move.getMove().moveTarget === MoveTarget.ALL_NEAR_OTHERS) {
|
||||||
const i = targets.indexOf(battlerIndex);
|
const i = targets.indexOf(battlerIndex);
|
||||||
targets.splice(i, i + 1);
|
targets.splice(i, i + 1);
|
||||||
@ -2970,40 +2972,72 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
start() {
|
start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
|
/** The Pokemon using this phase's invoked move */
|
||||||
const user = this.getUserPokemon();
|
const user = this.getUserPokemon();
|
||||||
|
/** All Pokemon targeted by this phase's invoked move */
|
||||||
const targets = this.getTargets();
|
const targets = this.getTargets();
|
||||||
|
|
||||||
|
/** If the user was somehow removed from the field, end this phase */
|
||||||
if (!user?.isOnField()) {
|
if (!user?.isOnField()) {
|
||||||
return super.end();
|
return super.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does an effect from this move override other effects on this turn?
|
||||||
|
* e.g. Charging moves (Fly, etc.) on their first turn of use.
|
||||||
|
*/
|
||||||
const overridden = new Utils.BooleanHolder(false);
|
const overridden = new Utils.BooleanHolder(false);
|
||||||
|
/** The {@linkcode Move} object from {@linkcode allMoves} invoked by this phase */
|
||||||
const move = this.move.getMove();
|
const move = this.move.getMove();
|
||||||
|
|
||||||
// Assume single target for override
|
// Assume single target for override
|
||||||
applyMoveAttrs(OverrideMoveEffectAttr, user, this.getTarget() ?? null, move, overridden, this.move.virtual).then(() => {
|
applyMoveAttrs(OverrideMoveEffectAttr, user, this.getTarget() ?? null, move, overridden, this.move.virtual).then(() => {
|
||||||
|
// If other effects were overriden, stop this phase before they can be applied
|
||||||
if (overridden.value) {
|
if (overridden.value) {
|
||||||
return this.end();
|
return this.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
user.lapseTags(BattlerTagLapseType.MOVE_EFFECT);
|
user.lapseTags(BattlerTagLapseType.MOVE_EFFECT);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this phase is for the first hit of the invoked move,
|
||||||
|
* resolve the move's total hit count. This block combines the
|
||||||
|
* effects of the move itself, Parental Bond, and Multi-Lens to do so.
|
||||||
|
*/
|
||||||
if (user.turnData.hitsLeft === undefined) {
|
if (user.turnData.hitsLeft === undefined) {
|
||||||
const hitCount = new Utils.IntegerHolder(1);
|
const hitCount = new Utils.IntegerHolder(1);
|
||||||
// Assume single target for multi hit
|
// Assume single target for multi hit
|
||||||
applyMoveAttrs(MultiHitAttr, user, this.getTarget() ?? null, move, hitCount);
|
applyMoveAttrs(MultiHitAttr, user, this.getTarget() ?? null, move, hitCount);
|
||||||
|
// If Parental Bond is applicable, double the hit count
|
||||||
applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, targets.length, hitCount, new Utils.IntegerHolder(0));
|
applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, targets.length, hitCount, new Utils.IntegerHolder(0));
|
||||||
|
// If Multi-Lens is applicable, multiply the hit count by 1 + the number of Multi-Lenses held by the user
|
||||||
if (move instanceof AttackMove && !move.hasAttr(FixedDamageAttr)) {
|
if (move instanceof AttackMove && !move.hasAttr(FixedDamageAttr)) {
|
||||||
this.scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, hitCount, new Utils.IntegerHolder(0));
|
this.scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, hitCount, new Utils.IntegerHolder(0));
|
||||||
}
|
}
|
||||||
user.turnData.hitsLeft = user.turnData.hitCount = hitCount.value;
|
// Set the user's relevant turnData fields to reflect the final hit count
|
||||||
|
user.turnData.hitCount = hitCount.value;
|
||||||
|
user.turnData.hitsLeft = hitCount.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log to be entered into the user's move history once the move result is resolved.
|
||||||
|
* Note that `result` (a {@linkcode MoveResult}) logs whether the move was successfully
|
||||||
|
* used in the sense of it not failing or missing; it does not account for the move's
|
||||||
|
* effectiveness (which is logged as a {@linkcode HitResult}).
|
||||||
|
*/
|
||||||
const moveHistoryEntry = { move: this.move.moveId, targets: this.targets, result: MoveResult.PENDING, virtual: this.move.virtual };
|
const moveHistoryEntry = { move: this.move.moveId, targets: this.targets, result: MoveResult.PENDING, virtual: this.move.virtual };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores results of hit checks of the invoked move against all targets, organized by battler index.
|
||||||
|
* @see {@linkcode hitCheck}
|
||||||
|
*/
|
||||||
const targetHitChecks = Object.fromEntries(targets.map(p => [p.getBattlerIndex(), this.hitCheck(p)]));
|
const targetHitChecks = Object.fromEntries(targets.map(p => [p.getBattlerIndex(), this.hitCheck(p)]));
|
||||||
const hasActiveTargets = targets.some(t => t.isActive(true));
|
const hasActiveTargets = targets.some(t => t.isActive(true));
|
||||||
|
/**
|
||||||
|
* If no targets are left for the move to hit (FAIL), or the invoked move is single-target
|
||||||
|
* (and not random target) and failed the hit check against its target (MISS), log the move
|
||||||
|
* as FAILed or MISSed (depending on the conditions above) and end this phase.
|
||||||
|
*/
|
||||||
if (!hasActiveTargets || (!move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]])) {
|
if (!hasActiveTargets || (!move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]])) {
|
||||||
this.stopMultiHit();
|
this.stopMultiHit();
|
||||||
if (hasActiveTargets) {
|
if (hasActiveTargets) {
|
||||||
@ -3018,6 +3052,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
return this.end();
|
return this.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** All move effect attributes are chained together in this array to be applied asynchronously. */
|
||||||
const applyAttrs: Promise<void>[] = [];
|
const applyAttrs: Promise<void>[] = [];
|
||||||
|
|
||||||
// Move animation only needs one target
|
// Move animation only needs one target
|
||||||
@ -3025,6 +3060,10 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
/** Has the move successfully hit a target (for damage) yet? */
|
/** Has the move successfully hit a target (for damage) yet? */
|
||||||
let hasHit: boolean = false;
|
let hasHit: boolean = false;
|
||||||
for (const target of targets) {
|
for (const target of targets) {
|
||||||
|
/**
|
||||||
|
* If the move missed a target, stop all future hits against that target
|
||||||
|
* and move on to the next target (if there is one).
|
||||||
|
*/
|
||||||
if (!targetHitChecks[target.getBattlerIndex()]) {
|
if (!targetHitChecks[target.getBattlerIndex()]) {
|
||||||
this.stopMultiHit(target);
|
this.stopMultiHit(target);
|
||||||
this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) }));
|
this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) }));
|
||||||
@ -3036,18 +3075,38 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Is the invoked move blocked by a protection effect on the target? */
|
||||||
const isProtected = !this.move.getMove().checkFlag(MoveFlags.IGNORE_PROTECT, user, target) && target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType));
|
const isProtected = !this.move.getMove().checkFlag(MoveFlags.IGNORE_PROTECT, user, target) && target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType));
|
||||||
|
|
||||||
|
/** Does this phase represent the invoked move's first strike? */
|
||||||
const firstHit = (user.turnData.hitsLeft === user.turnData.hitCount);
|
const firstHit = (user.turnData.hitsLeft === user.turnData.hitCount);
|
||||||
|
|
||||||
|
// Only log the move's result on the first strike
|
||||||
if (firstHit) {
|
if (firstHit) {
|
||||||
user.pushMoveHistory(moveHistoryEntry);
|
user.pushMoveHistory(moveHistoryEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Since all fail/miss checks have applied, the move is considered successfully applied.
|
||||||
|
* It's worth noting that if the move has no effect or is protected against, it's move
|
||||||
|
* result is still logged as a SUCCESS.
|
||||||
|
*/
|
||||||
moveHistoryEntry.result = MoveResult.SUCCESS;
|
moveHistoryEntry.result = MoveResult.SUCCESS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the result of applying the invoked move to the target.
|
||||||
|
* If the target is protected, the result is always `NO_EFFECT`.
|
||||||
|
* Otherwise, the hit result is based on type effectiveness, immunities,
|
||||||
|
* and other factors that may negate the attack or status application.
|
||||||
|
*
|
||||||
|
* Internally, the call to {@linkcode Pokemon.apply} is where damage is calculated
|
||||||
|
* (for attack moves) and the target's HP is updated. However, this isn't
|
||||||
|
* made visible to the user until the resulting {@linkcode DamagePhase}
|
||||||
|
* is invoked.
|
||||||
|
*/
|
||||||
const hitResult = !isProtected ? target.apply(user, move) : HitResult.NO_EFFECT;
|
const hitResult = !isProtected ? target.apply(user, move) : HitResult.NO_EFFECT;
|
||||||
|
|
||||||
|
/** Does {@linkcode hitResult} indicate that damage was dealt to the target? */
|
||||||
const dealsDamage = [
|
const dealsDamage = [
|
||||||
HitResult.EFFECTIVE,
|
HitResult.EFFECTIVE,
|
||||||
HitResult.SUPER_EFFECTIVE,
|
HitResult.SUPER_EFFECTIVE,
|
||||||
@ -3055,28 +3114,53 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
HitResult.ONE_HIT_KO
|
HitResult.ONE_HIT_KO
|
||||||
].includes(hitResult);
|
].includes(hitResult);
|
||||||
|
|
||||||
|
/** Is this target the first one hit by the move on its current strike? */
|
||||||
const firstTarget = dealsDamage && !hasHit;
|
const firstTarget = dealsDamage && !hasHit;
|
||||||
if (firstTarget) {
|
if (firstTarget) {
|
||||||
hasHit = true;
|
hasHit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Does this phase represent the invoked move's last strike? */
|
||||||
const lastHit = (user.turnData.hitsLeft === 1 || !this.getTarget()?.isActive());
|
const lastHit = (user.turnData.hitsLeft === 1 || !this.getTarget()?.isActive());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the user can change forms by using the invoked move,
|
||||||
|
* it only changes forms after the move's last hit
|
||||||
|
* (see Relic Song's interaction with Parental Bond when used by Meloetta).
|
||||||
|
*/
|
||||||
if (lastHit) {
|
if (lastHit) {
|
||||||
this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger);
|
this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Promise that applys *all* effects from the invoked move's MoveEffectAttrs.
|
||||||
|
* These are ordered by trigger type (see {@linkcode MoveEffectTrigger}), and each trigger
|
||||||
|
* type requires different conditions to be met with respect to the move's hit result.
|
||||||
|
*/
|
||||||
applyAttrs.push(new Promise(resolve => {
|
applyAttrs.push(new Promise(resolve => {
|
||||||
|
// Apply all effects with PRE_MOVE triggers (if the target isn't immune to the move)
|
||||||
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit) && hitResult !== HitResult.NO_EFFECT,
|
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit) && hitResult !== HitResult.NO_EFFECT,
|
||||||
user, target, move).then(() => {
|
user, target, move).then(() => {
|
||||||
|
// All other effects require the move to not have failed or have been cancelled to trigger
|
||||||
if (hitResult !== HitResult.FAIL) {
|
if (hitResult !== HitResult.FAIL) {
|
||||||
|
/** Are the move's effects tied to the first turn of a charge move? */
|
||||||
const chargeEffect = !!move.getAttrs(ChargeAttr).find(ca => ca.usedChargeEffect(user, this.getTarget() ?? null, move));
|
const chargeEffect = !!move.getAttrs(ChargeAttr).find(ca => ca.usedChargeEffect(user, this.getTarget() ?? null, move));
|
||||||
// Charge attribute with charge effect takes all effect attributes and applies them to charge stage, so ignore them if this is present
|
/**
|
||||||
|
* If the invoked move's effects are meant to trigger during the move's "charge turn,"
|
||||||
|
* ignore all effects after this point.
|
||||||
|
* Otherwise, apply all self-targeted POST_APPLY effects.
|
||||||
|
*/
|
||||||
Utils.executeIf(!chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY
|
Utils.executeIf(!chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY
|
||||||
&& attr.selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, move)).then(() => {
|
&& attr.selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, move)).then(() => {
|
||||||
|
// All effects past this point require the move to have hit the target
|
||||||
if (hitResult !== HitResult.NO_EFFECT) {
|
if (hitResult !== HitResult.NO_EFFECT) {
|
||||||
|
// Apply all non-self-targeted POST_APPLY effects
|
||||||
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY
|
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY
|
||||||
&& !(attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, this.move.getMove()).then(() => {
|
&& !(attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, this.move.getMove()).then(() => {
|
||||||
|
/**
|
||||||
|
* If the move hit, and the target doesn't have Shield Dust,
|
||||||
|
* apply the chance to flinch the target gained from King's Rock
|
||||||
|
*/
|
||||||
if (dealsDamage && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr)) {
|
if (dealsDamage && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr)) {
|
||||||
const flinched = new Utils.BooleanHolder(false);
|
const flinched = new Utils.BooleanHolder(false);
|
||||||
user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched);
|
user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched);
|
||||||
@ -3084,15 +3168,23 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
target.addTag(BattlerTagType.FLINCHED, undefined, this.move.moveId, user.id);
|
target.addTag(BattlerTagType.FLINCHED, undefined, this.move.moveId, user.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// If the move was not protected against, apply all HIT effects
|
||||||
Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT
|
Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT
|
||||||
&& (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit) && (!attr.firstTargetOnly || firstTarget), user, target, this.move.getMove()).then(() => {
|
&& (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit) && (!attr.firstTargetOnly || firstTarget), user, target, this.move.getMove()).then(() => {
|
||||||
|
// Apply the target's post-defend ability effects (as long as the target is active or can otherwise apply them)
|
||||||
return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move.getMove(), hitResult).then(() => {
|
return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move.getMove(), hitResult).then(() => {
|
||||||
|
// If the invoked move is an enemy attack, apply the enemy's status effect-inflicting tags and tokens
|
||||||
target.lapseTag(BattlerTagType.BEAK_BLAST_CHARGING);
|
target.lapseTag(BattlerTagType.BEAK_BLAST_CHARGING);
|
||||||
if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) {
|
if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) {
|
||||||
user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target);
|
user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target);
|
||||||
}
|
}
|
||||||
})).then(() => {
|
})).then(() => {
|
||||||
|
// Apply the user's post-attack ability effects
|
||||||
applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult).then(() => {
|
applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult).then(() => {
|
||||||
|
/**
|
||||||
|
* If the invoked move is an attack, apply the user's chance to
|
||||||
|
* steal an item from the target granted by Grip Claw
|
||||||
|
*/
|
||||||
if (this.move.getMove() instanceof AttackMove) {
|
if (this.move.getMove() instanceof AttackMove) {
|
||||||
this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target);
|
this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target);
|
||||||
}
|
}
|
||||||
@ -3112,7 +3204,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
// Trigger effect which should only apply one time on the last hit after all targeted effects have already applied
|
// Apply the move's POST_TARGET effects on the move's last hit, after all targeted effects have resolved
|
||||||
const postTarget = (user.turnData.hitsLeft === 1 || !this.getTarget()?.isActive()) ?
|
const postTarget = (user.turnData.hitsLeft === 1 || !this.getTarget()?.isActive()) ?
|
||||||
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_TARGET, user, null, move) :
|
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_TARGET, user, null, move) :
|
||||||
null;
|
null;
|
||||||
@ -3125,6 +3217,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for all move effects to finish applying, then end this phase
|
||||||
Promise.allSettled(applyAttrs).then(() => this.end());
|
Promise.allSettled(applyAttrs).then(() => this.end());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -3134,6 +3227,13 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
const move = this.move.getMove();
|
const move = this.move.getMove();
|
||||||
move.type = move.defaultType;
|
move.type = move.defaultType;
|
||||||
const user = this.getUserPokemon();
|
const user = this.getUserPokemon();
|
||||||
|
/**
|
||||||
|
* If this phase isn't for the invoked move's last strike,
|
||||||
|
* unshift another MoveEffectPhase for the next strike.
|
||||||
|
* Otherwise, queue a message indicating the number of times the move has struck
|
||||||
|
* (if the move has struck more than once), then apply the heal from Shell Bell
|
||||||
|
* to the user.
|
||||||
|
*/
|
||||||
if (user) {
|
if (user) {
|
||||||
if (user.turnData.hitsLeft && --user.turnData.hitsLeft >= 1 && this.getTarget()?.isActive()) {
|
if (user.turnData.hitsLeft && --user.turnData.hitsLeft >= 1 && this.getTarget()?.isActive()) {
|
||||||
this.scene.unshiftPhase(this.getNewHitPhase());
|
this.scene.unshiftPhase(this.getNewHitPhase());
|
||||||
@ -3152,6 +3252,11 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
super.end();
|
super.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves whether this phase's invoked move hits or misses the given target
|
||||||
|
* @param target {@linkcode Pokemon} the Pokemon targeted by the invoked move
|
||||||
|
* @returns `true` if the move does not miss the target; `false` otherwise
|
||||||
|
*/
|
||||||
hitCheck(target: Pokemon): boolean {
|
hitCheck(target: Pokemon): boolean {
|
||||||
// Moves targeting the user and entry hazards can't miss
|
// Moves targeting the user and entry hazards can't miss
|
||||||
if ([MoveTarget.USER, MoveTarget.ENEMY_SIDE].includes(this.move.getMove().moveTarget)) {
|
if ([MoveTarget.USER, MoveTarget.ENEMY_SIDE].includes(this.move.getMove().moveTarget)) {
|
||||||
@ -3182,8 +3287,8 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hiddenTag = target.getTag(SemiInvulnerableTag);
|
const semiInvulnerableTag = target.getTag(SemiInvulnerableTag);
|
||||||
if (hiddenTag && !this.move.getMove().getAttrs(HitsTagAttr).some(hta => hta.tagType === hiddenTag.tagType)) {
|
if (semiInvulnerableTag && !this.move.getMove().getAttrs(HitsTagAttr).some(hta => hta.tagType === semiInvulnerableTag.tagType)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3199,6 +3304,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
return rand <= moveAccuracy * (accuracyMultiplier!); // TODO: is this bang correct?
|
return rand <= moveAccuracy * (accuracyMultiplier!); // TODO: is this bang correct?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the {@linkcode Pokemon} using this phase's invoked move */
|
||||||
getUserPokemon(): Pokemon | undefined {
|
getUserPokemon(): Pokemon | undefined {
|
||||||
if (this.battlerIndex > BattlerIndex.ENEMY_2) {
|
if (this.battlerIndex > BattlerIndex.ENEMY_2) {
|
||||||
return this.scene.getPokemonById(this.battlerIndex) ?? undefined;
|
return this.scene.getPokemonById(this.battlerIndex) ?? undefined;
|
||||||
@ -3206,14 +3312,20 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
return (this.player ? this.scene.getPlayerField() : this.scene.getEnemyField())[this.fieldIndex];
|
return (this.player ? this.scene.getPlayerField() : this.scene.getEnemyField())[this.fieldIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns an array of all {@linkcode Pokemon} targeted by this phase's invoked move */
|
||||||
getTargets(): Pokemon[] {
|
getTargets(): Pokemon[] {
|
||||||
return this.scene.getField(true).filter(p => this.targets.indexOf(p.getBattlerIndex()) > -1);
|
return this.scene.getField(true).filter(p => this.targets.indexOf(p.getBattlerIndex()) > -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the first target of this phase's invoked move */
|
||||||
getTarget(): Pokemon | undefined {
|
getTarget(): Pokemon | undefined {
|
||||||
return this.getTargets().find(() => true);
|
return this.getTargets()[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the given {@linkcode Pokemon} from this phase's target list
|
||||||
|
* @param target {@linkcode Pokemon} the Pokemon to be removed
|
||||||
|
*/
|
||||||
removeTarget(target: Pokemon): void {
|
removeTarget(target: Pokemon): void {
|
||||||
const targetIndex = this.targets.findIndex(ind => ind === target.getBattlerIndex());
|
const targetIndex = this.targets.findIndex(ind => ind === target.getBattlerIndex());
|
||||||
if (targetIndex !== -1) {
|
if (targetIndex !== -1) {
|
||||||
@ -3221,6 +3333,11 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevents subsequent strikes of this phase's invoked move from occurring
|
||||||
|
* @param target {@linkcode Pokemon} if defined, only stop subsequent
|
||||||
|
* strikes against this Pokemon
|
||||||
|
*/
|
||||||
stopMultiHit(target?: Pokemon): void {
|
stopMultiHit(target?: Pokemon): void {
|
||||||
/** If given a specific target, remove the target from subsequent strikes */
|
/** If given a specific target, remove the target from subsequent strikes */
|
||||||
if (target) {
|
if (target) {
|
||||||
@ -3236,6 +3353,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns a new MoveEffectPhase with the same properties as this phase */
|
||||||
getNewHitPhase() {
|
getNewHitPhase() {
|
||||||
return new MoveEffectPhase(this.scene, this.battlerIndex, this.targets, this.move);
|
return new MoveEffectPhase(this.scene, this.battlerIndex, this.targets, this.move);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { BattlerTagType } from "#app/enums/battler-tag-type.js";
|
import { BattlerTagType } from "#app/enums/battler-tag-type.js";
|
||||||
import {
|
import {
|
||||||
|
BerryPhase,
|
||||||
MoveEndPhase,
|
MoveEndPhase,
|
||||||
TurnEndPhase,
|
TurnEndPhase,
|
||||||
TurnStartPhase,
|
TurnStartPhase,
|
||||||
@ -14,7 +15,6 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite
|
|||||||
import { SPLASH_ONLY } from "../utils/testUtils";
|
import { SPLASH_ONLY } from "../utils/testUtils";
|
||||||
import { BattleStat } from "#app/data/battle-stat.js";
|
import { BattleStat } from "#app/data/battle-stat.js";
|
||||||
import { StatusEffect } from "#app/enums/status-effect.js";
|
import { StatusEffect } from "#app/enums/status-effect.js";
|
||||||
import { GulpMissileTag } from "#app/data/battler-tags.js";
|
|
||||||
import Pokemon from "#app/field/pokemon.js";
|
import Pokemon from "#app/field/pokemon.js";
|
||||||
|
|
||||||
describe("Abilities - Gulp Missile", () => {
|
describe("Abilities - Gulp Missile", () => {
|
||||||
@ -84,6 +84,17 @@ describe("Abilities - Gulp Missile", () => {
|
|||||||
expect(cramorant.formIndex).toBe(GORGING_FORM);
|
expect(cramorant.formIndex).toBe(GORGING_FORM);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("changes form during Dive's charge turn", async () => {
|
||||||
|
await game.startBattle([Species.CRAMORANT]);
|
||||||
|
const cramorant = game.scene.getPlayerPokemon()!;
|
||||||
|
|
||||||
|
game.doAttack(getMovePosition(game.scene, 0, Moves.DIVE));
|
||||||
|
await game.phaseInterceptor.to(MoveEndPhase);
|
||||||
|
|
||||||
|
expect(cramorant.getTag(BattlerTagType.GULP_MISSILE_ARROKUDA)).toBeDefined();
|
||||||
|
expect(cramorant.formIndex).toBe(GULPING_FORM);
|
||||||
|
});
|
||||||
|
|
||||||
it("deals ¼ of the attacker's maximum HP when hit by a damaging attack", async () => {
|
it("deals ¼ of the attacker's maximum HP when hit by a damaging attack", async () => {
|
||||||
game.override.enemyMoveset(Array(4).fill(Moves.TACKLE));
|
game.override.enemyMoveset(Array(4).fill(Moves.TACKLE));
|
||||||
await game.startBattle([Species.CRAMORANT]);
|
await game.startBattle([Species.CRAMORANT]);
|
||||||
@ -165,29 +176,16 @@ describe("Abilities - Gulp Missile", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("does not activate the ability when underwater", async () => {
|
it("does not activate the ability when underwater", async () => {
|
||||||
game.override
|
game.override.enemyMoveset(Array(4).fill(Moves.SURF));
|
||||||
.enemyMoveset(Array(4).fill(Moves.SURF))
|
|
||||||
.enemySpecies(Species.REGIELEKI)
|
|
||||||
.enemyAbility(Abilities.BALL_FETCH)
|
|
||||||
.enemyLevel(5);
|
|
||||||
await game.startBattle([Species.CRAMORANT]);
|
await game.startBattle([Species.CRAMORANT]);
|
||||||
|
|
||||||
const cramorant = game.scene.getPlayerPokemon()!;
|
const cramorant = game.scene.getPlayerPokemon()!;
|
||||||
|
|
||||||
game.doAttack(getMovePosition(game.scene, 0, Moves.DIVE));
|
game.doAttack(getMovePosition(game.scene, 0, Moves.DIVE));
|
||||||
await game.toNextTurn();
|
await game.phaseInterceptor.to(BerryPhase, false);
|
||||||
|
|
||||||
// Turn 2 underwater, enemy moves first
|
expect(cramorant.getTag(BattlerTagType.GULP_MISSILE_ARROKUDA)).toBeDefined();
|
||||||
game.doAttack(getMovePosition(game.scene, 0, Moves.DIVE));
|
expect(cramorant.formIndex).toBe(GULPING_FORM);
|
||||||
await game.phaseInterceptor.to(MoveEndPhase);
|
|
||||||
|
|
||||||
expect(cramorant.formIndex).toBe(NORMAL_FORM);
|
|
||||||
expect(cramorant.getTag(GulpMissileTag)).toBeUndefined();
|
|
||||||
|
|
||||||
// Turn 2 Cramorant comes out and changes form
|
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
|
||||||
expect(cramorant.formIndex).not.toBe(NORMAL_FORM);
|
|
||||||
expect(cramorant.getTag(GulpMissileTag)).toBeDefined();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("prevents effect damage but inflicts secondary effect on attacker with Magic Guard", async () => {
|
it("prevents effect damage but inflicts secondary effect on attacker with Magic Guard", async () => {
|
||||||
|
@ -56,6 +56,14 @@ describe("Final Boss", () => {
|
|||||||
expect(game.scene.getEnemyPokemon()!.species.speciesId).not.toBe(Species.ETERNATUS);
|
expect(game.scene.getEnemyPokemon()!.species.speciesId).not.toBe(Species.ETERNATUS);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should not have passive enabled on Eternatus", async () => {
|
||||||
|
await runToFinalBossEncounter(game, [Species.BIDOOF]);
|
||||||
|
|
||||||
|
const eternatus = game.scene.getEnemyPokemon();
|
||||||
|
expect(eternatus?.species.speciesId).toBe(Species.ETERNATUS);
|
||||||
|
expect(eternatus?.hasPassive()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it.todo("should change form on direct hit down to last boss fragment", () => {});
|
it.todo("should change form on direct hit down to last boss fragment", () => {});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ export class UiInputs {
|
|||||||
[Button.ACTION]: () => this.buttonAb(Button.ACTION),
|
[Button.ACTION]: () => this.buttonAb(Button.ACTION),
|
||||||
[Button.CANCEL]: () => this.buttonAb(Button.CANCEL),
|
[Button.CANCEL]: () => this.buttonAb(Button.CANCEL),
|
||||||
[Button.MENU]: () => this.buttonMenu(),
|
[Button.MENU]: () => this.buttonMenu(),
|
||||||
[Button.STATS]: () => this.buttonStats(true),
|
[Button.STATS]: () => this.buttonGoToFilter(Button.STATS),
|
||||||
[Button.CYCLE_SHINY]: () => this.buttonCycleOption(Button.CYCLE_SHINY),
|
[Button.CYCLE_SHINY]: () => this.buttonCycleOption(Button.CYCLE_SHINY),
|
||||||
[Button.CYCLE_FORM]: () => this.buttonCycleOption(Button.CYCLE_FORM),
|
[Button.CYCLE_FORM]: () => this.buttonCycleOption(Button.CYCLE_FORM),
|
||||||
[Button.CYCLE_GENDER]: () => this.buttonCycleOption(Button.CYCLE_GENDER),
|
[Button.CYCLE_GENDER]: () => this.buttonCycleOption(Button.CYCLE_GENDER),
|
||||||
@ -139,6 +139,17 @@ export class UiInputs {
|
|||||||
p.toggleStats(pressed);
|
p.toggleStats(pressed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buttonGoToFilter(button: Button): void {
|
||||||
|
const whitelist = [StarterSelectUiHandler];
|
||||||
|
const uiHandler = this.scene.ui?.getHandler();
|
||||||
|
if (whitelist.some(handler => uiHandler instanceof handler)) {
|
||||||
|
this.scene.ui.processInput(button);
|
||||||
|
} else {
|
||||||
|
this.buttonStats(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buttonInfo(pressed: boolean = true): void {
|
buttonInfo(pressed: boolean = true): void {
|
||||||
if (this.scene.showMovesetFlyout ) {
|
if (this.scene.showMovesetFlyout ) {
|
||||||
for (const p of this.scene.getField().filter(p => p?.isActive(true))) {
|
for (const p of this.scene.getField().filter(p => p?.isActive(true))) {
|
||||||
|
@ -70,8 +70,9 @@ const languageSettings: { [key: string]: LanguageSetting } = {
|
|||||||
instructionTextSize: "38px",
|
instructionTextSize: "38px",
|
||||||
},
|
},
|
||||||
"de":{
|
"de":{
|
||||||
starterInfoTextSize: "56px",
|
starterInfoTextSize: "48px",
|
||||||
instructionTextSize: "35px",
|
instructionTextSize: "35px",
|
||||||
|
starterInfoXPos: 33,
|
||||||
},
|
},
|
||||||
"es":{
|
"es":{
|
||||||
starterInfoTextSize: "56px",
|
starterInfoTextSize: "56px",
|
||||||
@ -79,7 +80,7 @@ const languageSettings: { [key: string]: LanguageSetting } = {
|
|||||||
},
|
},
|
||||||
"fr":{
|
"fr":{
|
||||||
starterInfoTextSize: "54px",
|
starterInfoTextSize: "54px",
|
||||||
instructionTextSize: "42px",
|
instructionTextSize: "35px",
|
||||||
},
|
},
|
||||||
"it":{
|
"it":{
|
||||||
starterInfoTextSize: "56px",
|
starterInfoTextSize: "56px",
|
||||||
@ -91,9 +92,10 @@ const languageSettings: { [key: string]: LanguageSetting } = {
|
|||||||
starterInfoXPos: 33,
|
starterInfoXPos: 33,
|
||||||
},
|
},
|
||||||
"zh":{
|
"zh":{
|
||||||
starterInfoTextSize: "40px",
|
starterInfoTextSize: "47px",
|
||||||
instructionTextSize: "42px",
|
instructionTextSize: "38px",
|
||||||
starterInfoYOffset: 2
|
starterInfoYOffset: 1,
|
||||||
|
starterInfoXPos: 24,
|
||||||
},
|
},
|
||||||
"pt":{
|
"pt":{
|
||||||
starterInfoTextSize: "48px",
|
starterInfoTextSize: "48px",
|
||||||
@ -258,18 +260,21 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
private pokemonShinyIcon: Phaser.GameObjects.Sprite;
|
private pokemonShinyIcon: Phaser.GameObjects.Sprite;
|
||||||
|
|
||||||
private instructionsContainer: Phaser.GameObjects.Container;
|
private instructionsContainer: Phaser.GameObjects.Container;
|
||||||
|
private filterInstructionsContainer: Phaser.GameObjects.Container;
|
||||||
private shinyIconElement: Phaser.GameObjects.Sprite;
|
private shinyIconElement: Phaser.GameObjects.Sprite;
|
||||||
private formIconElement: Phaser.GameObjects.Sprite;
|
private formIconElement: Phaser.GameObjects.Sprite;
|
||||||
private abilityIconElement: Phaser.GameObjects.Sprite;
|
private abilityIconElement: Phaser.GameObjects.Sprite;
|
||||||
private genderIconElement: Phaser.GameObjects.Sprite;
|
private genderIconElement: Phaser.GameObjects.Sprite;
|
||||||
private natureIconElement: Phaser.GameObjects.Sprite;
|
private natureIconElement: Phaser.GameObjects.Sprite;
|
||||||
private variantIconElement: Phaser.GameObjects.Sprite;
|
private variantIconElement: Phaser.GameObjects.Sprite;
|
||||||
|
private goFilterIconElement: Phaser.GameObjects.Sprite;
|
||||||
private shinyLabel: Phaser.GameObjects.Text;
|
private shinyLabel: Phaser.GameObjects.Text;
|
||||||
private formLabel: Phaser.GameObjects.Text;
|
private formLabel: Phaser.GameObjects.Text;
|
||||||
private genderLabel: Phaser.GameObjects.Text;
|
private genderLabel: Phaser.GameObjects.Text;
|
||||||
private abilityLabel: Phaser.GameObjects.Text;
|
private abilityLabel: Phaser.GameObjects.Text;
|
||||||
private natureLabel: Phaser.GameObjects.Text;
|
private natureLabel: Phaser.GameObjects.Text;
|
||||||
private variantLabel: Phaser.GameObjects.Text;
|
private variantLabel: Phaser.GameObjects.Text;
|
||||||
|
private goFilterLabel: Phaser.GameObjects.Text;
|
||||||
|
|
||||||
private starterSelectMessageBox: Phaser.GameObjects.NineSlice;
|
private starterSelectMessageBox: Phaser.GameObjects.NineSlice;
|
||||||
private starterSelectMessageBoxContainer: Phaser.GameObjects.Container;
|
private starterSelectMessageBoxContainer: Phaser.GameObjects.Container;
|
||||||
@ -329,7 +334,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
//variables to keep track of the dynamically rendered list of instruction prompts for starter select
|
//variables to keep track of the dynamically rendered list of instruction prompts for starter select
|
||||||
private instructionRowX = 0;
|
private instructionRowX = 0;
|
||||||
private instructionRowY = 0;
|
private instructionRowY = 0;
|
||||||
private instructionRowTextOffset = 12;
|
private instructionRowTextOffset = 9;
|
||||||
|
private filterInstructionRowX = 0;
|
||||||
|
private filterInstructionRowY = 0;
|
||||||
|
|
||||||
private starterSelectCallback: StarterSelectCallback | null;
|
private starterSelectCallback: StarterSelectCallback | null;
|
||||||
|
|
||||||
@ -825,8 +832,19 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
this.variantLabel = addTextObject(this.scene, this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("starterSelectUiHandler:cycleVariant"), TextStyle.PARTY, { fontSize: instructionTextSize });
|
this.variantLabel = addTextObject(this.scene, this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("starterSelectUiHandler:cycleVariant"), TextStyle.PARTY, { fontSize: instructionTextSize });
|
||||||
this.variantLabel.setName("text-variant-label");
|
this.variantLabel.setName("text-variant-label");
|
||||||
|
|
||||||
|
this.goFilterIconElement = new Phaser.GameObjects.Sprite(this.scene, this.filterInstructionRowX, this.filterInstructionRowY, "keyboard", "C.png");
|
||||||
|
this.goFilterIconElement.setName("sprite-goFilter-icon-element");
|
||||||
|
this.goFilterIconElement.setScale(0.675);
|
||||||
|
this.goFilterIconElement.setOrigin(0.0, 0.0);
|
||||||
|
this.goFilterLabel = addTextObject(this.scene, this.filterInstructionRowX + this.instructionRowTextOffset, this.filterInstructionRowY, i18next.t("starterSelectUiHandler:goFilter"), TextStyle.PARTY, { fontSize: instructionTextSize });
|
||||||
|
this.goFilterLabel.setName("text-goFilter-label");
|
||||||
|
|
||||||
this.hideInstructions();
|
this.hideInstructions();
|
||||||
|
|
||||||
|
this.filterInstructionsContainer = this.scene.add.container(50, 5);
|
||||||
|
this.filterInstructionsContainer.setVisible(true);
|
||||||
|
this.starterSelectContainer.add(this.filterInstructionsContainer);
|
||||||
|
|
||||||
this.starterSelectMessageBoxContainer = this.scene.add.container(0, this.scene.game.canvas.height / 6);
|
this.starterSelectMessageBoxContainer = this.scene.add.container(0, this.scene.game.canvas.height / 6);
|
||||||
this.starterSelectMessageBoxContainer.setVisible(false);
|
this.starterSelectMessageBoxContainer.setVisible(false);
|
||||||
this.starterSelectContainer.add(this.starterSelectMessageBoxContainer);
|
this.starterSelectContainer.add(this.starterSelectMessageBoxContainer);
|
||||||
@ -1175,6 +1193,16 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
this.tryExit();
|
this.tryExit();
|
||||||
success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
|
} else if (button === Button.STATS) {
|
||||||
|
// if stats button is pressed, go to filter directly
|
||||||
|
if (!this.filterMode) {
|
||||||
|
this.startCursorObj.setVisible(false);
|
||||||
|
this.starterIconsCursorObj.setVisible(false);
|
||||||
|
this.setSpecies(null);
|
||||||
|
this.filterBarCursor = 0;
|
||||||
|
this.setFilterMode(true);
|
||||||
|
this.filterBar.toggleDropDown(this.filterBarCursor);
|
||||||
|
}
|
||||||
} else if (this.startCursorObj.visible) { // this checks to see if the start button is selected
|
} else if (this.startCursorObj.visible) { // this checks to see if the start button is selected
|
||||||
switch (button) {
|
switch (button) {
|
||||||
case Button.ACTION:
|
case Button.ACTION:
|
||||||
@ -2026,6 +2054,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
case SettingKeyboard.Button_Cycle_Variant:
|
case SettingKeyboard.Button_Cycle_Variant:
|
||||||
iconPath = "V.png";
|
iconPath = "V.png";
|
||||||
break;
|
break;
|
||||||
|
case SettingKeyboard.Button_Stats:
|
||||||
|
iconPath = "C.png";
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -2045,11 +2076,36 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateFilterButtonIcon(iconSetting, gamepadType, iconElement, controlLabel): void {
|
||||||
|
let iconPath;
|
||||||
|
// touch controls cannot be rebound as is, and are just emulating a keyboard event.
|
||||||
|
// Additionally, since keyboard controls can be rebound (and will be displayed when they are), we need to have special handling for the touch controls
|
||||||
|
if (gamepadType === "touch") {
|
||||||
|
gamepadType = "keyboard";
|
||||||
|
iconPath = "C.png";
|
||||||
|
} else {
|
||||||
|
iconPath = this.scene.inputController?.getIconForLatestInputRecorded(iconSetting);
|
||||||
|
}
|
||||||
|
iconElement.setTexture(gamepadType, iconPath);
|
||||||
|
iconElement.setPosition(this.filterInstructionRowX, this.filterInstructionRowY);
|
||||||
|
controlLabel.setPosition(this.filterInstructionRowX + this.instructionRowTextOffset, this.filterInstructionRowY);
|
||||||
|
iconElement.setVisible(true);
|
||||||
|
controlLabel.setVisible(true);
|
||||||
|
this.filterInstructionsContainer.add([iconElement, controlLabel]);
|
||||||
|
this.filterInstructionRowY += 8;
|
||||||
|
if (this.filterInstructionRowY >= 24) {
|
||||||
|
this.filterInstructionRowY = 0;
|
||||||
|
this.filterInstructionRowX += 50;
|
||||||
|
}
|
||||||
|
}
|
||||||
updateInstructions(): void {
|
updateInstructions(): void {
|
||||||
this.instructionRowX = 0;
|
this.instructionRowX = 0;
|
||||||
this.instructionRowY = 0;
|
this.instructionRowY = 0;
|
||||||
|
this.filterInstructionRowX = 0;
|
||||||
|
this.filterInstructionRowY = 0;
|
||||||
this.hideInstructions();
|
this.hideInstructions();
|
||||||
this.instructionsContainer.removeAll();
|
this.instructionsContainer.removeAll();
|
||||||
|
this.filterInstructionsContainer.removeAll();
|
||||||
let gamepadType;
|
let gamepadType;
|
||||||
if (this.scene.inputMethod === "gamepad") {
|
if (this.scene.inputMethod === "gamepad") {
|
||||||
gamepadType = this.scene.inputController.getConfig(this.scene.inputController.selectedDevice[Device.GAMEPAD]).padType;
|
gamepadType = this.scene.inputController.getConfig(this.scene.inputController.selectedDevice[Device.GAMEPAD]).padType;
|
||||||
@ -2057,6 +2113,10 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
gamepadType = this.scene.inputMethod;
|
gamepadType = this.scene.inputMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!gamepadType) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.speciesStarterDexEntry?.caughtAttr) {
|
if (this.speciesStarterDexEntry?.caughtAttr) {
|
||||||
if (this.canCycleShiny) {
|
if (this.canCycleShiny) {
|
||||||
this.updateButtonIcon(SettingKeyboard.Button_Cycle_Shiny, gamepadType, this.shinyIconElement, this.shinyLabel);
|
this.updateButtonIcon(SettingKeyboard.Button_Cycle_Shiny, gamepadType, this.shinyIconElement, this.shinyLabel);
|
||||||
@ -2077,6 +2137,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
this.updateButtonIcon(SettingKeyboard.Button_Cycle_Variant, gamepadType, this.variantIconElement, this.variantLabel);
|
this.updateButtonIcon(SettingKeyboard.Button_Cycle_Variant, gamepadType, this.variantIconElement, this.variantLabel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if filter mode is inactivated and gamepadType is not undefined, update the button icons
|
||||||
|
if (!this.filterMode) {
|
||||||
|
this.updateFilterButtonIcon(SettingKeyboard.Button_Stats, gamepadType, this.goFilterIconElement, this.goFilterLabel);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getValueLimit(): integer {
|
getValueLimit(): integer {
|
||||||
@ -2398,6 +2464,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
this.setCursor(filterMode ? this.filterBarCursor : this.cursor);
|
this.setCursor(filterMode ? this.filterBarCursor : this.cursor);
|
||||||
if (filterMode) {
|
if (filterMode) {
|
||||||
this.setSpecies(null);
|
this.setSpecies(null);
|
||||||
|
this.updateInstructions();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -3260,6 +3327,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
this.natureLabel.setVisible(false);
|
this.natureLabel.setVisible(false);
|
||||||
this.variantIconElement.setVisible(false);
|
this.variantIconElement.setVisible(false);
|
||||||
this.variantLabel.setVisible(false);
|
this.variantLabel.setVisible(false);
|
||||||
|
this.goFilterIconElement.setVisible(false);
|
||||||
|
this.goFilterLabel.setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
clear(): void {
|
clear(): void {
|
||||||
|
Loading…
Reference in New Issue
Block a user