From 54ee2f3f10be3bb3ca13b33ef275e720aa2040ed Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Tue, 30 Jul 2024 21:09:50 -0700 Subject: [PATCH 01/23] [Refactor] Remove single-use `EndureAttr` (#3260) --- src/data/move.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index 3754b0acd3b..5ccafa5017f 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -4427,12 +4427,6 @@ export class ProtectAttr extends AddBattlerTagAttr { } } -export class EndureAttr extends ProtectAttr { - constructor() { - super(BattlerTagType.ENDURING); - } -} - export class IgnoreAccuracyAttr extends AddBattlerTagAttr { constructor() { super(BattlerTagType.IGNORE_ACCURACY, true, false, 2); @@ -6549,7 +6543,7 @@ export function initMoves() { .attr(HitHealAttr) .triageMove(), new SelfStatusMove(Moves.ENDURE, Type.NORMAL, -1, 10, -1, 4, 2) - .attr(EndureAttr), + .attr(ProtectAttr, BattlerTagType.ENDURING), new StatusMove(Moves.CHARM, Type.FAIRY, 100, 20, -1, 0, 2) .attr(StatChangeAttr, BattleStat.ATK, -2), new AttackMove(Moves.ROLLOUT, Type.ROCK, MoveCategory.PHYSICAL, 30, 90, 20, -1, 0, 2) From 495284ad4746e12b539c20c51951da8f50de3c8e Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Tue, 30 Jul 2024 21:11:43 -0700 Subject: [PATCH 02/23] [Refactor] Consolidate the switch cases in `IvyCudgelTypeAttr` (#3261) --- src/data/move.ts | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index 5ccafa5017f..f90aef585d6 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -3837,26 +3837,18 @@ export class IvyCudgelTypeAttr extends VariableMoveTypeAttr { switch (form) { case 1: // Wellspring Mask - move.type = Type.WATER; - break; - case 2: // Hearthflame Mask - move.type = Type.FIRE; - break; - case 3: // Cornerstone Mask - move.type = Type.ROCK; - break; - case 4: // Teal Mask Tera - move.type = Type.GRASS; - break; case 5: // Wellspring Mask Tera move.type = Type.WATER; break; + case 2: // Hearthflame Mask case 6: // Hearthflame Mask Tera move.type = Type.FIRE; break; + case 3: // Cornerstone Mask case 7: // Cornerstone Mask Tera move.type = Type.ROCK; break; + case 4: // Teal Mask Tera default: move.type = Type.GRASS; break; From 59a4546a3f56a9fbfd3469990360967bbde64c5a Mon Sep 17 00:00:00 2001 From: "Adrian T." <68144167+torranx@users.noreply.github.com> Date: Wed, 31 Jul 2024 16:52:01 +0800 Subject: [PATCH 03/23] add missing changes to sweet veil(#3265) --- src/data/ability.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index a811da27b9f..491a14ba621 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2540,6 +2540,7 @@ export class PreApplyBattlerTagAbAttr extends AbAttr { */ export class PreApplyBattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr { private immuneTagType: BattlerTagType; + private battlerTag: BattlerTag; constructor(immuneTagType: BattlerTagType) { super(); @@ -2550,6 +2551,7 @@ export class PreApplyBattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr { applyPreApplyBattlerTag(pokemon: Pokemon, passive: boolean, tag: BattlerTag, cancelled: Utils.BooleanHolder, args: any[]): boolean { if (tag.tagType === this.immuneTagType) { cancelled.value = true; + this.battlerTag = tag; return true; } @@ -2560,7 +2562,7 @@ export class PreApplyBattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr { return i18next.t("abilityTriggers:battlerTagImmunity", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName, - battlerTagName: (args[0] as BattlerTag).getDescriptor() + battlerTagName: this.battlerTag.getDescriptor() }); } } @@ -4837,7 +4839,7 @@ export function initAbilities() { .attr(MoveTypeChangeAttr, Type.ICE, 1.2, (user, target, move) => move.type === Type.NORMAL), new Ability(Abilities.SWEET_VEIL, 6) .attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.SLEEP) - .attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY) + .attr(UserFieldBattlerTagImmunityAbAttr, BattlerTagType.DROWSY) .ignorable() .partial(), // Mold Breaker ally should not be affected by Sweet Veil new Ability(Abilities.STANCE_CHANGE, 6) From e416a26cfc74d25c47c71139009dc0e35e62cb68 Mon Sep 17 00:00:00 2001 From: "Adrian T." <68144167+torranx@users.noreply.github.com> Date: Wed, 31 Jul 2024 18:44:12 +0800 Subject: [PATCH 04/23] add missing changes from Sweet Veil PR part 2 (#3267) --- src/field/pokemon.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index b4c6598efb5..71e89d60cbd 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -23,7 +23,7 @@ import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoo import { WeatherType } from "../data/weather"; import { TempBattleStat } from "../data/temp-battle-stat"; import { ArenaTagSide, WeakenMoveScreenTag } from "../data/arena-tag"; -import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldBattleStatMultiplierAbAttrs, FieldMultiplyBattleStatAbAttr, AddSecondStrikeAbAttr, IgnoreOpponentEvasionAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr } from "../data/ability"; +import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldBattleStatMultiplierAbAttrs, FieldMultiplyBattleStatAbAttr, AddSecondStrikeAbAttr, IgnoreOpponentEvasionAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr } from "../data/ability"; import PokemonData from "../system/pokemon-data"; import { BattlerIndex } from "../battle"; import { Mode } from "../ui/ui"; @@ -2243,7 +2243,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const newTag = getBattlerTag(tagType, turnCount, sourceMove, sourceId); const cancelled = new Utils.BooleanHolder(false); - applyPreApplyBattlerTagAbAttrs(PreApplyBattlerTagAbAttr, this, newTag, cancelled); + applyPreApplyBattlerTagAbAttrs(BattlerTagImmunityAbAttr, this, newTag, cancelled); const userField = this.getAlliedField(); userField.forEach(pokemon => applyPreApplyBattlerTagAbAttrs(UserFieldBattlerTagImmunityAbAttr, pokemon, newTag, cancelled)); From 7f8ddb514dfb7c2ed13017df9226af13094a9cd2 Mon Sep 17 00:00:00 2001 From: Enoch Date: Wed, 31 Jul 2024 22:00:24 +0900 Subject: [PATCH 05/23] [Localization] Localize prevent berry ability phase and translate (#3231) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * localization prevent berry phase (unnerve) * Update src/locales/fr/ability-trigger.ts Co-authored-by: Lugiad' * Update src/locales/de/ability-trigger.ts Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> * Update src/locales/zh_CN/ability-trigger.ts Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> * Update src/locales/zh_TW/ability-trigger.ts Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> * Update src/locales/pt_BR/ability-trigger.ts Co-authored-by: José Ricardo Fleury Oliveira * Update src/locales/de/ability-trigger.ts Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> * Update src/locales/it/ability-trigger.ts Co-authored-by: Niccolò <123510358+NicusPulcis@users.noreply.github.com> * Update src/locales/es/ability-trigger.ts --------- Co-authored-by: 송지원 Co-authored-by: Lugiad' Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> Co-authored-by: José Ricardo Fleury Oliveira Co-authored-by: Niccolò <123510358+NicusPulcis@users.noreply.github.com> --- src/locales/de/ability-trigger.ts | 1 + src/locales/en/ability-trigger.ts | 1 + src/locales/es/ability-trigger.ts | 1 + src/locales/fr/ability-trigger.ts | 1 + src/locales/it/ability-trigger.ts | 1 + src/locales/ko/ability-trigger.ts | 1 + src/locales/pt_BR/ability-trigger.ts | 1 + src/locales/zh_CN/ability-trigger.ts | 1 + src/locales/zh_TW/ability-trigger.ts | 1 + src/phases.ts | 4 ++-- 10 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/locales/de/ability-trigger.ts b/src/locales/de/ability-trigger.ts index 0285866307b..88dc9f9f027 100644 --- a/src/locales/de/ability-trigger.ts +++ b/src/locales/de/ability-trigger.ts @@ -59,4 +59,5 @@ export const abilityTriggers: SimpleTranslationEntries = { "postSummonSwordOfRuin": "Unheilsschwert von {{pokemonNameWithAffix}} schwächt {{statName}} aller Pokémon im Umkreis!", "postSummonTabletsOfRuin": "Unheilstafeln von {{pokemonNameWithAffix}} schwächt {{statName}} aller Pokémon im Umkreis!", "postSummonBeadsOfRuin": "Unheilsjuwelen von {{pokemonNameWithAffix}} schwächt {{statName}} aller Pokémon im Umkreis!", + "preventBerryUse": "{{pokemonNameWithAffix}} kriegt vor Anspannung keine Beeren mehr runter!", } as const; diff --git a/src/locales/en/ability-trigger.ts b/src/locales/en/ability-trigger.ts index 8e35ea4deb9..ce41a964922 100644 --- a/src/locales/en/ability-trigger.ts +++ b/src/locales/en/ability-trigger.ts @@ -59,4 +59,5 @@ export const abilityTriggers: SimpleTranslationEntries = { "postSummonSwordOfRuin": "{{pokemonNameWithAffix}}'s Sword of Ruin lowered the {{statName}}\nof all surrounding Pokémon!", "postSummonTabletsOfRuin": "{{pokemonNameWithAffix}}'s Tablets of Ruin lowered the {{statName}}\nof all surrounding Pokémon!", "postSummonBeadsOfRuin": "{{pokemonNameWithAffix}}'s Beads of Ruin lowered the {{statName}}\nof all surrounding Pokémon!", + "preventBerryUse": "{{pokemonNameWithAffix}} is too\nnervous to eat berries!", } as const; diff --git a/src/locales/es/ability-trigger.ts b/src/locales/es/ability-trigger.ts index d5d487235e9..6b1f66a11e3 100644 --- a/src/locales/es/ability-trigger.ts +++ b/src/locales/es/ability-trigger.ts @@ -59,4 +59,5 @@ export const abilityTriggers: SimpleTranslationEntries = { "postSummonSwordOfRuin": "{{pokemonNameWithAffix}}'s Sword of Ruin lowered the {{statName}}\nof all surrounding Pokémon!", "postSummonTabletsOfRuin": "{{pokemonNameWithAffix}}'s Tablets of Ruin lowered the {{statName}}\nof all surrounding Pokémon!", "postSummonBeadsOfRuin": "{{pokemonNameWithAffix}}'s Beads of Ruin lowered the {{statName}}\nof all surrounding Pokémon!", + "preventBerryUse": "{{pokemonNameWithAffix}} está muy nervioso y no puede comer bayas!", } as const; diff --git a/src/locales/fr/ability-trigger.ts b/src/locales/fr/ability-trigger.ts index b063a2aca68..f6b9c306cd1 100644 --- a/src/locales/fr/ability-trigger.ts +++ b/src/locales/fr/ability-trigger.ts @@ -59,4 +59,5 @@ export const abilityTriggers: SimpleTranslationEntries = { "postSummonSwordOfRuin": "L’Épée du Fléau de {{pokemonNameWithAffix}}\naffaiblit la {{statName}} des Pokémon alentour !", "postSummonTabletsOfRuin": "Le Bois du Fléau de {{pokemonNameWithAffix}}\naffaiblit l’{{statName}} des Pokémon alentour !", "postSummonBeadsOfRuin": "Les Perles du Fléau de {{pokemonNameWithAffix}}\naffaiblissent la {{statName}} des Pokémon alentour !", + "preventBerryUse": "{{pokemonNameWithAffix}} est tendu\net ne peut plus manger de Baies !", } as const; diff --git a/src/locales/it/ability-trigger.ts b/src/locales/it/ability-trigger.ts index 4e965eacf32..37472dbdeab 100644 --- a/src/locales/it/ability-trigger.ts +++ b/src/locales/it/ability-trigger.ts @@ -59,4 +59,5 @@ export const abilityTriggers: SimpleTranslationEntries = { "postSummonSwordOfRuin": "{{pokemonNameWithAffix}}'s Sword of Ruin lowered the {{statName}}\nof all surrounding Pokémon!", "postSummonTabletsOfRuin": "{{pokemonNameWithAffix}}'s Tablets of Ruin lowered the {{statName}}\nof all surrounding Pokémon!", "postSummonBeadsOfRuin": "{{pokemonNameWithAffix}}'s Beads of Ruin lowered the {{statName}}\nof all surrounding Pokémon!", + "preventBerryUse": "{{pokemonNameWithAffix}} non riesce a\nmangiare le bacche per l'agitazione!", } as const; diff --git a/src/locales/ko/ability-trigger.ts b/src/locales/ko/ability-trigger.ts index 2c28704fc24..61be21bc7ec 100644 --- a/src/locales/ko/ability-trigger.ts +++ b/src/locales/ko/ability-trigger.ts @@ -59,4 +59,5 @@ export const abilityTriggers: SimpleTranslationEntries = { "postSummonSwordOfRuin": "{{pokemonNameWithAffix}}의 재앙의검에 의해\n주위의 {{statName}}[[가]] 약해졌다!", "postSummonTabletsOfRuin": "{{pokemonNameWithAffix}}의 재앙의목간에 의해\n주위의 {{statName}}[[가]] 약해졌다!", "postSummonBeadsOfRuin": "{{pokemonNameWithAffix}}의 재앙의구슬에 의해\n주위의 {{statName}}[[가]] 약해졌다!", + "preventBerryUse": "{{pokemonNameWithAffix}}[[는]] 긴장해서\n나무열매를 먹을 수 없게 되었다!", } as const; diff --git a/src/locales/pt_BR/ability-trigger.ts b/src/locales/pt_BR/ability-trigger.ts index 43413284404..f5d9511f3f6 100644 --- a/src/locales/pt_BR/ability-trigger.ts +++ b/src/locales/pt_BR/ability-trigger.ts @@ -59,4 +59,5 @@ export const abilityTriggers: SimpleTranslationEntries = { "postSummonSwordOfRuin": "Sword of Ruin de {{pokemonNameWithAffix}} reduziu a {{statName}}\nde todos os Pokémon em volta!", "postSummonTabletsOfRuin": "Tablets of Ruin de {{pokemonNameWithAffix}} reduziu o {{statName}}\nde todos os Pokémon em volta!", "postSummonBeadsOfRuin": "Beads of Ruin de {{pokemonNameWithAffix}} reduziu a {{statName}}\nde todos os Pokémon em volta!", + "preventBerryUse": "{{pokemonNameWithAffix}} está nervoso\ndemais para comer frutas!", } as const; diff --git a/src/locales/zh_CN/ability-trigger.ts b/src/locales/zh_CN/ability-trigger.ts index dc64f75a217..0f2201049d2 100644 --- a/src/locales/zh_CN/ability-trigger.ts +++ b/src/locales/zh_CN/ability-trigger.ts @@ -59,4 +59,5 @@ export const abilityTriggers: SimpleTranslationEntries = { "postSummonSwordOfRuin": "{{pokemonNameWithAffix}}的灾祸之剑\n令周围的宝可梦的{{statName}}减弱了!", "postSummonTabletsOfRuin": "{{pokemonNameWithAffix}}的灾祸之简\n令周围的宝可梦的{{statName}}减弱了!", "postSummonBeadsOfRuin": "{{pokemonNameWithAffix}}的灾祸之玉\n令周围的宝可梦的{{statName}}减弱了!", + "preventBerryUse": "{{pokemonNameWithAffix}}因太紧张\n而无法食用树果!", } as const; diff --git a/src/locales/zh_TW/ability-trigger.ts b/src/locales/zh_TW/ability-trigger.ts index c0b253933ca..baa20614a44 100644 --- a/src/locales/zh_TW/ability-trigger.ts +++ b/src/locales/zh_TW/ability-trigger.ts @@ -59,4 +59,5 @@ export const abilityTriggers: SimpleTranslationEntries = { "postSummonSwordOfRuin": "{{pokemonNameWithAffix}}'s Sword of Ruin lowered the {{statName}}\nof all surrounding Pokémon!", "postSummonTabletsOfRuin": "{{pokemonNameWithAffix}}'s Tablets of Ruin lowered the {{statName}}\nof all surrounding Pokémon!", "postSummonBeadsOfRuin": "{{pokemonNameWithAffix}}'s Beads of Ruin lowered the {{statName}}\nof all surrounding Pokémon!", + "preventBerryUse": "{{pokemonNameWithAffix}}因太緊張\n而無法食用樹果!", } as const; diff --git a/src/phases.ts b/src/phases.ts index 6f4f796cea4..c41ad333b9d 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -20,7 +20,7 @@ import { ModifierTier } from "./modifier/modifier-tier"; import { FusePokemonModifierType, ModifierPoolType, ModifierType, ModifierTypeFunc, ModifierTypeOption, PokemonModifierType, PokemonMoveModifierType, PokemonPpRestoreModifierType, PokemonPpUpModifierType, RememberMoveModifierType, TmModifierType, getDailyRunStarterModifiers, getEnemyBuffModifierForWave, getModifierType, getPlayerModifierTypeOptions, getPlayerShopModifierTypeOptionsForWave, modifierTypes, regenerateModifierPoolThresholds } from "./modifier/modifier-type"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import { BattlerTagLapseType, CenterOfAttentionTag, EncoreTag, ProtectedTag, SemiInvulnerableTag, TrappedTag } from "./data/battler-tags"; -import { getPokemonMessage, getPokemonNameWithAffix } from "./messages"; +import { getPokemonNameWithAffix } from "./messages"; import { Starter } from "./ui/starter-select-ui-handler"; import { Gender } from "./data/gender"; import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather"; @@ -2410,7 +2410,7 @@ export class BerryPhase extends FieldPhase { pokemon.getOpponents().map((opp) => applyAbAttrs(PreventBerryUseAbAttr, opp, cancelled)); if (cancelled.value) { - pokemon.scene.queueMessage(getPokemonMessage(pokemon, " is too\nnervous to eat berries!")); + pokemon.scene.queueMessage(i18next.t("abilityTriggers:preventBerryUse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } else { this.scene.unshiftPhase( new CommonAnimPhase(this.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.USE_ITEM) From 9531e64c0d06ba86fb41b455fc7bd22926b1188b Mon Sep 17 00:00:00 2001 From: Leo Kim <47556641+KimJeongSun@users.noreply.github.com> Date: Thu, 1 Aug 2024 01:00:46 +0900 Subject: [PATCH 06/23] [Enhancement] Optimize updateScroll function in starter select UI (#3268) * optimize updateScroll function to update only the container within the screen * fix eslint --- src/ui/starter-select-ui-handler.ts | 123 +++++++++++++++------------- 1 file changed, 65 insertions(+), 58 deletions(-) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 6693db0e9a4..e0f895b89c4 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -2057,76 +2057,83 @@ export default class StarterSelectUiHandler extends MessageUiHandler { updateScroll = () => { const maxColumns = 9; const maxRows = 9; + const onScreenFirstIndex = this.scrollCursor * 9; + const onScreenLastIndex = Math.min(this.filteredStarterContainers.length - 1, onScreenFirstIndex + 81); this.starterSelectScrollBar.setPage(this.scrollCursor); let pokerusCursorIndex = 0; this.filteredStarterContainers.forEach((container, i) => { - const pos = calcStarterPosition(i, this.scrollCursor); - container.setPosition(pos.x, pos.y); - - if (i < (maxRows + this.scrollCursor) * maxColumns && i >= this.scrollCursor * maxColumns) { - container.setVisible(true); - } else { + if (i < onScreenFirstIndex || i > onScreenLastIndex) { container.setVisible(false); - } - - if (this.pokerusSpecies.includes(container.species)) { - this.pokerusCursorObjs[pokerusCursorIndex].setPosition(pos.x - 1, pos.y + 1); + return; + } else { + const pos = calcStarterPosition(i, this.scrollCursor); + container.setPosition(pos.x, pos.y); if (i < (maxRows + this.scrollCursor) * maxColumns && i >= this.scrollCursor * maxColumns) { - this.pokerusCursorObjs[pokerusCursorIndex].setVisible(true); + container.setVisible(true); } else { - this.pokerusCursorObjs[pokerusCursorIndex].setVisible(false); - } - pokerusCursorIndex++; - } - - if (this.starterSpecies.includes(container.species)) { - this.starterCursorObjs[this.starterSpecies.indexOf(container.species)].setPosition(pos.x - 1, pos.y + 1); - - if (i < (maxRows + this.scrollCursor) * maxColumns && i >= this.scrollCursor * maxColumns) { - this.starterCursorObjs[this.starterSpecies.indexOf(container.species)].setVisible(true); - } else { - this.starterCursorObjs[this.starterSpecies.indexOf(container.species)].setVisible(false); - } - } - - const speciesId = container.species.speciesId; - this.updateStarterValueLabel(container); - - container.label.setVisible(true); - const speciesVariants = speciesId && this.scene.gameData.dexData[speciesId].caughtAttr & DexAttr.SHINY - ? [ DexAttr.DEFAULT_VARIANT, DexAttr.VARIANT_2, DexAttr.VARIANT_3 ].filter(v => !!(this.scene.gameData.dexData[speciesId].caughtAttr & v)) - : []; - for (let v = 0; v < 3; v++) { - const hasVariant = speciesVariants.length > v; - container.shinyIcons[v].setVisible(hasVariant); - if (hasVariant) { - container.shinyIcons[v].setTint(getVariantTint(speciesVariants[v] === DexAttr.DEFAULT_VARIANT ? 0 : speciesVariants[v] === DexAttr.VARIANT_2 ? 1 : 2)); - } - } - - container.starterPassiveBgs.setVisible(!!this.scene.gameData.starterData[speciesId].passiveAttr); - container.hiddenAbilityIcon.setVisible(!!this.scene.gameData.dexData[speciesId].caughtAttr && !!(this.scene.gameData.starterData[speciesId].abilityAttr & 4)); - container.classicWinIcon.setVisible(this.scene.gameData.starterData[speciesId].classicWinCount > 0); - - // 'Candy Icon' mode - if (this.scene.candyUpgradeDisplay === 0) { - - if (!starterColors[speciesId]) { - // Default to white if no colors are found - starterColors[speciesId] = [ "ffffff", "ffffff" ]; + container.setVisible(false); } - // Set the candy colors - container.candyUpgradeIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(starterColors[speciesId][0]))); - container.candyUpgradeOverlayIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(starterColors[speciesId][1]))); + if (this.pokerusSpecies.includes(container.species)) { + this.pokerusCursorObjs[pokerusCursorIndex].setPosition(pos.x - 1, pos.y + 1); - this.setUpgradeIcon(container); - } else if (this.scene.candyUpgradeDisplay === 1) { - container.candyUpgradeIcon.setVisible(false); - container.candyUpgradeOverlayIcon.setVisible(false); + if (i < (maxRows + this.scrollCursor) * maxColumns && i >= this.scrollCursor * maxColumns) { + this.pokerusCursorObjs[pokerusCursorIndex].setVisible(true); + } else { + this.pokerusCursorObjs[pokerusCursorIndex].setVisible(false); + } + pokerusCursorIndex++; + } + + if (this.starterSpecies.includes(container.species)) { + this.starterCursorObjs[this.starterSpecies.indexOf(container.species)].setPosition(pos.x - 1, pos.y + 1); + + if (i < (maxRows + this.scrollCursor) * maxColumns && i >= this.scrollCursor * maxColumns) { + this.starterCursorObjs[this.starterSpecies.indexOf(container.species)].setVisible(true); + } else { + this.starterCursorObjs[this.starterSpecies.indexOf(container.species)].setVisible(false); + } + } + + const speciesId = container.species.speciesId; + this.updateStarterValueLabel(container); + + container.label.setVisible(true); + const speciesVariants = speciesId && this.scene.gameData.dexData[speciesId].caughtAttr & DexAttr.SHINY + ? [ DexAttr.DEFAULT_VARIANT, DexAttr.VARIANT_2, DexAttr.VARIANT_3 ].filter(v => !!(this.scene.gameData.dexData[speciesId].caughtAttr & v)) + : []; + for (let v = 0; v < 3; v++) { + const hasVariant = speciesVariants.length > v; + container.shinyIcons[v].setVisible(hasVariant); + if (hasVariant) { + container.shinyIcons[v].setTint(getVariantTint(speciesVariants[v] === DexAttr.DEFAULT_VARIANT ? 0 : speciesVariants[v] === DexAttr.VARIANT_2 ? 1 : 2)); + } + } + + container.starterPassiveBgs.setVisible(!!this.scene.gameData.starterData[speciesId].passiveAttr); + container.hiddenAbilityIcon.setVisible(!!this.scene.gameData.dexData[speciesId].caughtAttr && !!(this.scene.gameData.starterData[speciesId].abilityAttr & 4)); + container.classicWinIcon.setVisible(this.scene.gameData.starterData[speciesId].classicWinCount > 0); + + // 'Candy Icon' mode + if (this.scene.candyUpgradeDisplay === 0) { + + if (!starterColors[speciesId]) { + // Default to white if no colors are found + starterColors[speciesId] = [ "ffffff", "ffffff" ]; + } + + // Set the candy colors + container.candyUpgradeIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(starterColors[speciesId][0]))); + container.candyUpgradeOverlayIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(starterColors[speciesId][1]))); + + this.setUpgradeIcon(container); + } else if (this.scene.candyUpgradeDisplay === 1) { + container.candyUpgradeIcon.setVisible(false); + container.candyUpgradeOverlayIcon.setVisible(false); + } } }); }; From bfc44ea35ebd01c8dd7e78b8005dc5c2491f3bd7 Mon Sep 17 00:00:00 2001 From: Tempoanon <163687446+Tempo-anon@users.noreply.github.com> Date: Wed, 31 Jul 2024 12:06:49 -0400 Subject: [PATCH 07/23] [Bug] Make mbh transferrable again --- src/modifier/modifier.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 9c47867d931..230ebc9251d 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -2334,7 +2334,7 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { * @see {@linkcode modifierTypes[MINI_BLACK_HOLE]} */ export class TurnHeldItemTransferModifier extends HeldItemTransferModifier { - readonly isTransferrable: boolean = false; + readonly isTransferrable: boolean = true; constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) { super(type, pokemonId, stackCount); } From e30b9607c5fa695df7805c6649c8eaf855c24b28 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Wed, 31 Jul 2024 13:56:01 -0700 Subject: [PATCH 08/23] [Misc] Removes the temporary save-fix code that swaps abilities (#3274) --- src/system/game-data.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/system/game-data.ts b/src/system/game-data.ts index bbc74ee13e3..6b8f183e94a 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -41,7 +41,6 @@ import { Moves } from "#enums/moves"; import { PlayerGender } from "#enums/player-gender"; import { Species } from "#enums/species"; import { applyChallenges, ChallengeType } from "#app/data/challenge.js"; -import { Abilities } from "#app/enums/abilities.js"; export const defaultStarterSpecies: Species[] = [ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE, @@ -852,14 +851,6 @@ export class GameData { const handleSessionData = async (sessionDataStr: string) => { try { const sessionData = this.parseSessionData(sessionDataStr); - for (let i = 0; i <= 5; i++) { - const speciesToCheck = getPokemonSpecies(sessionData.party[i]?.species); - if (sessionData.party[i]?.abilityIndex === 1) { - if (speciesToCheck.ability1 === speciesToCheck.ability2 && speciesToCheck.abilityHidden !== Abilities.NONE && speciesToCheck.abilityHidden !== speciesToCheck.ability1) { - sessionData.party[i].abilityIndex = 2; - } - } - } resolve(sessionData); } catch (err) { reject(err); From fdbdb862bcf8e52fac55bda13b8f813a7300a6b7 Mon Sep 17 00:00:00 2001 From: Opaque02 <66582645+Opaque02@users.noreply.github.com> Date: Thu, 1 Aug 2024 11:50:47 +1000 Subject: [PATCH 09/23] [QoL] Making the transfer button show which pokemon can have items be transferred to them (#2503) * Basic version of code for updating party ui name to be red when at max stack * Updated text to be red when pokemon is at max stack * Removed some comments and fixed a private property issue * Fixed a const instead of a let statement, and added some comments on the logic * Accidentally broke last commit. Intentionally fixed this commit * Updated the code to not have red text for pokemon name as that was too confusing with fainted pokemon. Now the party slot has a descriptor label which can be used to set the text to show if the pokemon is able/not able to receive the transfer items * Updated transfer logic to use new isTransferrable property instead of getTransferrable method and merged with latest --- src/ui/party-ui-handler.ts | 116 ++++++++++++++++++++++++++++--------- 1 file changed, 88 insertions(+), 28 deletions(-) diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 9bb8162ce2a..80ce318532b 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -288,6 +288,36 @@ export default class PartyUiHandler extends MessageUiHandler { const pokemon = this.scene.getParty()[this.cursor]; if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER && !this.transferMode && option !== PartyOption.CANCEL) { this.startTransfer(); + + let ableToTransfer: string; + for (let p = 0; p < this.scene.getParty().length; p++) { // this fore look goes through each of the party pokemon + const newPokemon = this.scene.getParty()[p]; + // this next line gets all of the transferable items from pokemon [p]; it does this by getting all the held modifiers that are transferable and checking to see if they belong to pokemon [p] + const getTransferrableItemsFromPokemon = (newPokemon: PlayerPokemon) => + this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).isTransferrable && (m as PokemonHeldItemModifier).pokemonId === newPokemon.id) as PokemonHeldItemModifier[]; + // this next bit checks to see if the the selected item from the original transfer pokemon exists on the new pokemon [p]; this returns undefined if the new pokemon doesn't have the item at all, otherwise it returns the pokemonHeldItemModifier for that item + const matchingModifier = newPokemon.scene.findModifier(m => m instanceof PokemonHeldItemModifier && m.pokemonId === newPokemon.id && m.matchType(getTransferrableItemsFromPokemon(pokemon)[this.transferOptionCursor])) as PokemonHeldItemModifier; + const partySlot = this.partySlots.filter(m => m.getPokemon() === newPokemon)[0]; // this gets pokemon [p] for us + if (p !== this.transferCursor) { // this skips adding the able/not able labels on the pokemon doing the transfer + if (matchingModifier) { // if matchingModifier exists then the item exists on the new pokemon + if (matchingModifier.getMaxStackCount(this.scene) === matchingModifier.stackCount) { // checks to see if the stack of items is at max stack; if so, set the description label to "Not able" + ableToTransfer = "Not able"; + } else { // if the pokemon isn't at max stack, make the label "Able" + ableToTransfer = "Able"; + } + } else { // if matchingModifier doesn't exist, that means the pokemon doesn't have any of the item, and we need to show "Able" + ableToTransfer = "Able"; + } + } else { // this else relates to the transfer pokemon. We set the text to be blank so there's no "Able"/"Not able" text + ableToTransfer = ""; + } + partySlot.slotHpBar.setVisible(false); + partySlot.slotHpOverlay.setVisible(false); + partySlot.slotHpText.setVisible(false); + partySlot.slotDescriptionLabel.setText(ableToTransfer); + partySlot.slotDescriptionLabel.setVisible(true); + } + this.clearOptions(); ui.playSelect(); return true; @@ -947,6 +977,12 @@ export default class PartyUiHandler extends MessageUiHandler { this.transferMode = false; this.transferAll = false; this.partySlots[this.transferCursor].setTransfer(false); + for (let i = 0; i < this.partySlots.length; i++) { + this.partySlots[i].slotDescriptionLabel.setVisible(false); + this.partySlots[i].slotHpBar.setVisible(true); + this.partySlots[i].slotHpOverlay.setVisible(true); + this.partySlots[i].slotHpText.setVisible(true); + } } doRelease(slotIndex: integer): void { @@ -1041,6 +1077,12 @@ class PartySlot extends Phaser.GameObjects.Container { private slotBg: Phaser.GameObjects.Image; private slotPb: Phaser.GameObjects.Sprite; + public slotName: Phaser.GameObjects.Text; + public slotHpBar: Phaser.GameObjects.Image; + public slotHpOverlay: Phaser.GameObjects.Sprite; + public slotHpText: Phaser.GameObjects.Text; + public slotDescriptionLabel: Phaser.GameObjects.Text; // this is used to show text instead of the HP bar i.e. for showing "Able"/"Not Able" for TMs when you try to learn them + private pokemonIcon: Phaser.GameObjects.Container; private iconAnimHandler: PokemonIconAnimHandler; @@ -1057,6 +1099,10 @@ class PartySlot extends Phaser.GameObjects.Container { this.setup(partyUiMode, tmMoveId); } + getPokemon(): PlayerPokemon { + return this.pokemon; + } + setup(partyUiMode: PartyUiMode, tmMoveId: Moves) { const battlerCount = (this.scene as BattleScene).currentBattle.getBattlerCount(); @@ -1095,19 +1141,19 @@ class PartySlot extends Phaser.GameObjects.Container { nameSizeTest.destroy(); - const slotName = addTextObject(this.scene, 0, 0, displayName, TextStyle.PARTY); - slotName.setPositionRelative(slotBg, this.slotIndex >= battlerCount ? 21 : 24, this.slotIndex >= battlerCount ? 2 : 10); - slotName.setOrigin(0, 0); + this.slotName = addTextObject(this.scene, 0, 0, displayName, TextStyle.PARTY); + this.slotName.setPositionRelative(slotBg, this.slotIndex >= battlerCount ? 21 : 24, this.slotIndex >= battlerCount ? 2 : 10); + this.slotName.setOrigin(0, 0); const slotLevelLabel = this.scene.add.image(0, 0, "party_slot_overlay_lv"); - slotLevelLabel.setPositionRelative(slotName, 8, 12); + slotLevelLabel.setPositionRelative(this.slotName, 8, 12); slotLevelLabel.setOrigin(0, 0); const slotLevelText = addTextObject(this.scene, 0, 0, this.pokemon.level.toString(), this.pokemon.level < (this.scene as BattleScene).getMaxExpLevel() ? TextStyle.PARTY : TextStyle.PARTY_RED); slotLevelText.setPositionRelative(slotLevelLabel, 9, 0); slotLevelText.setOrigin(0, 0.25); - slotInfoContainer.add([ slotName, slotLevelLabel, slotLevelText ]); + slotInfoContainer.add([this.slotName, slotLevelLabel, slotLevelText ]); const genderSymbol = getGenderSymbol(this.pokemon.getGender(true)); @@ -1118,7 +1164,7 @@ class PartySlot extends Phaser.GameObjects.Container { if (this.slotIndex >= battlerCount) { slotGenderText.setPositionRelative(slotLevelLabel, 36, 0); } else { - slotGenderText.setPositionRelative(slotName, 76 - (this.pokemon.fusionSpecies ? 8 : 0), 3); + slotGenderText.setPositionRelative(this.slotName, 76 - (this.pokemon.fusionSpecies ? 8 : 0), 3); } slotGenderText.setOrigin(0, 0.25); @@ -1132,7 +1178,7 @@ class PartySlot extends Phaser.GameObjects.Container { if (this.slotIndex >= battlerCount) { splicedIcon.setPositionRelative(slotLevelLabel, 36 + (genderSymbol ? 8 : 0), 0.5); } else { - splicedIcon.setPositionRelative(slotName, 76, 3.5); + splicedIcon.setPositionRelative(this.slotName, 76, 3.5); } slotInfoContainer.add(splicedIcon); @@ -1152,7 +1198,7 @@ class PartySlot extends Phaser.GameObjects.Container { const shinyStar = this.scene.add.image(0, 0, `shiny_star_small${doubleShiny ? "_1" : ""}`); shinyStar.setOrigin(0, 0); - shinyStar.setPositionRelative(slotName, -9, 3); + shinyStar.setPositionRelative(this.slotName, -9, 3); shinyStar.setTint(getVariantTint(!doubleShiny ? this.pokemon.getVariant() : this.pokemon.variant)); slotInfoContainer.add(shinyStar); @@ -1167,24 +1213,40 @@ class PartySlot extends Phaser.GameObjects.Container { } } + this.slotHpBar = this.scene.add.image(0, 0, "party_slot_hp_bar"); + this.slotHpBar.setPositionRelative(slotBg, this.slotIndex >= battlerCount ? 72 : 8, this.slotIndex >= battlerCount ? 6 : 31); + this.slotHpBar.setOrigin(0, 0); + this.slotHpBar.setVisible(false); + + const hpRatio = this.pokemon.getHpRatio(); + + this.slotHpOverlay = this.scene.add.sprite(0, 0, "party_slot_hp_overlay", hpRatio > 0.5 ? "high" : hpRatio > 0.25 ? "medium" : "low"); + this.slotHpOverlay.setPositionRelative(this.slotHpBar, 16, 2); + this.slotHpOverlay.setOrigin(0, 0); + this.slotHpOverlay.setScale(hpRatio, 1); + this.slotHpOverlay.setVisible(false); + + this.slotHpText = addTextObject(this.scene, 0, 0, `${this.pokemon.hp}/${this.pokemon.getMaxHp()}`, TextStyle.PARTY); + this.slotHpText.setPositionRelative(this.slotHpBar, this.slotHpBar.width - 3, this.slotHpBar.height - 2); + this.slotHpText.setOrigin(1, 0); + this.slotHpText.setVisible(false); + + this.slotDescriptionLabel = addTextObject(this.scene, 0, 0, "", TextStyle.MESSAGE); + this.slotDescriptionLabel.setPositionRelative(slotBg, this.slotIndex >= battlerCount ? 94 : 32, this.slotIndex >= battlerCount ? 16 : 46); + this.slotDescriptionLabel.setOrigin(0, 1); + this.slotDescriptionLabel.setVisible(false); + + slotInfoContainer.add([this.slotHpBar, this.slotHpOverlay, this.slotHpText, this.slotDescriptionLabel]); + if (partyUiMode !== PartyUiMode.TM_MODIFIER) { - const slotHpBar = this.scene.add.image(0, 0, "party_slot_hp_bar"); - slotHpBar.setPositionRelative(slotBg, this.slotIndex >= battlerCount ? 72 : 8, this.slotIndex >= battlerCount ? 6 : 31); - slotHpBar.setOrigin(0, 0); - - const hpRatio = this.pokemon.getHpRatio(); - - const slotHpOverlay = this.scene.add.sprite(0, 0, "party_slot_hp_overlay", hpRatio > 0.5 ? "high" : hpRatio > 0.25 ? "medium" : "low"); - slotHpOverlay.setPositionRelative(slotHpBar, 16, 2); - slotHpOverlay.setOrigin(0, 0); - slotHpOverlay.setScale(hpRatio, 1); - - const slotHpText = addTextObject(this.scene, 0, 0, `${this.pokemon.hp}/${this.pokemon.getMaxHp()}`, TextStyle.PARTY); - slotHpText.setPositionRelative(slotHpBar, slotHpBar.width - 3, slotHpBar.height - 2); - slotHpText.setOrigin(1, 0); - - slotInfoContainer.add([ slotHpBar, slotHpOverlay, slotHpText ]); + this.slotDescriptionLabel.setVisible(false); + this.slotHpBar.setVisible(true); + this.slotHpOverlay.setVisible(true); + this.slotHpText.setVisible(true); } else { + this.slotHpBar.setVisible(false); + this.slotHpOverlay.setVisible(false); + this.slotHpText.setVisible(false); let slotTmText: string; switch (true) { case (this.pokemon.compatibleTms.indexOf(tmMoveId) === -1): @@ -1198,11 +1260,9 @@ class PartySlot extends Phaser.GameObjects.Container { break; } - const slotTmLabel = addTextObject(this.scene, 0, 0, slotTmText, TextStyle.MESSAGE); - slotTmLabel.setPositionRelative(slotBg, this.slotIndex >= battlerCount ? 94 : 32, this.slotIndex >= battlerCount ? 16 : 46); - slotTmLabel.setOrigin(0, 1); + this.slotDescriptionLabel.setText(slotTmText); + this.slotDescriptionLabel.setVisible(true); - slotInfoContainer.add(slotTmLabel); } } From 1d616d075601b0be1371c839ce5f8bdffc2785b7 Mon Sep 17 00:00:00 2001 From: Lugiad Date: Thu, 1 Aug 2024 03:52:11 +0200 Subject: [PATCH 10/23] [Localization] Some missing French translations in menu-ui-handler and achv (#3276) * Update menu-ui-handler.ts * Update achv.ts --- src/locales/fr/achv.ts | 281 +++++++++++++++++++++++++++++- src/locales/fr/menu-ui-handler.ts | 8 +- 2 files changed, 276 insertions(+), 13 deletions(-) diff --git a/src/locales/fr/achv.ts b/src/locales/fr/achv.ts index 4ff9bf20f51..bc6a862942d 100644 --- a/src/locales/fr/achv.ts +++ b/src/locales/fr/achv.ts @@ -22,7 +22,7 @@ export const PGMachv: AchievementTranslationEntries = { name: "Banquier", }, "10M_MONEY": { - name: "Évadé·e fiscal·e", + name: "Évadé fiscal", }, "DamageAchv": { @@ -45,7 +45,7 @@ export const PGMachv: AchievementTranslationEntries = { description: "Soigner {{healAmount}} {{HP}} en une fois avec une capacité, un talent ou un objet tenu", }, "250_HEAL": { - name: "Infirmier·ère", + name: "Infirmier", }, "1000_HEAL": { name: "Médecin", @@ -74,19 +74,19 @@ export const PGMachv: AchievementTranslationEntries = { description: "Accumuler un total de {{ribbonAmount}} Rubans", }, "10_RIBBONS": { - name: "Maitre·sse de la Ligue", + name: "Maitre de la Ligue", }, "25_RIBBONS": { - name: "Super Maitre·sse de la Ligue", + name: "Super Maitre de la Ligue", }, "50_RIBBONS": { - name: "Hyper Maitre·sse de la Ligue", + name: "Hyper Maitre de la Ligue", }, "75_RIBBONS": { - name: "Rogue Maitre·sse de la Ligue", + name: "Rogue Maitre de la Ligue", }, "100_RIBBONS": { - name: "Master Maitre·sse de la Ligue", + name: "Master Maitre de la Ligue", }, "TRANSFER_MAX_BATTLE_STAT": { @@ -166,7 +166,7 @@ export const PGMachv: AchievementTranslationEntries = { description: "Avoir des IV parfaits sur un Pokémon", }, "CLASSIC_VICTORY": { - name: "Invaincu·e", + name: "Invaincu", description: "Terminer le jeu en mode classique", }, @@ -267,4 +267,267 @@ export const PGMachv: AchievementTranslationEntries = { } as const; // Achievement translations for the when the player character is female (it for now uses the same translations as the male version) -export const PGFachv: AchievementTranslationEntries = PGMachv; +export const PGFachv: AchievementTranslationEntries = { + "Achievements": { + name: "Succès", + }, + "Locked": { + name: "Verrouillé", + }, + + "MoneyAchv": { + description: "Récolter un total de {{moneyAmount}} ₽", + }, + "10K_MONEY": { + name: "Épargnante", + }, + "100K_MONEY": { + name: "Je possède des thunes", + }, + "1M_MONEY": { + name: "Banquière", + }, + "10M_MONEY": { + name: "Évadée fiscale", + }, + + "DamageAchv": { + description: "Infliger {{damageAmount}} de dégâts en un coup", + }, + "250_DMG": { + name: "Caïd", + }, + "1000_DMG": { + name: "Boxeuse", + }, + "2500_DMG": { + name: "Distributrice de pains", + }, + "10000_DMG": { + name: "One Punch Woman", + }, + + "HealAchv": { + description: "Soigner {{healAmount}} {{HP}} en une fois avec une capacité, un talent ou un objet tenu", + }, + "250_HEAL": { + name: "Infirmière", + }, + "1000_HEAL": { + name: "Médecin", + }, + "2500_HEAL": { + name: "Clerc", + }, + "10000_HEAL": { + name: "Centre Pokémon", + }, + + "LevelAchv": { + description: "Monter un Pokémon au N.{{level}}", + }, + "LV_100": { + name: "Et c’est pas fini !", + }, + "LV_250": { + name: "Élite", + }, + "LV_1000": { + name: "Vers l’infini et au-delà", + }, + + "RibbonAchv": { + description: "Accumuler un total de {{ribbonAmount}} Rubans", + }, + "10_RIBBONS": { + name: "Maitresse de la Ligue", + }, + "25_RIBBONS": { + name: "Super Maitresse de la Ligue", + }, + "50_RIBBONS": { + name: "Hyper Maitresse de la Ligue", + }, + "75_RIBBONS": { + name: "Rogue Maitresse de la Ligue", + }, + "100_RIBBONS": { + name: "Master Maitresse de la Ligue", + }, + + "TRANSFER_MAX_BATTLE_STAT": { + name: "Travail d’équipe", + description: "Utiliser Relais avec au moins une statistique montée à fond", + }, + "MAX_FRIENDSHIP": { + name: "Copinage", + description: "Atteindre le niveau de bonheur maximal avec un Pokémon", + }, + "MEGA_EVOLVE": { + name: "Mégamorph", + description: "Méga-évoluer un Pokémon", + }, + "GIGANTAMAX": { + name: "Kaijū", + description: "Gigamaxer un Pokémon", + }, + "TERASTALLIZE": { + name: "J’aime les STAB", + description: "Téracristalliser un Pokémon", + }, + "STELLAR_TERASTALLIZE": { + name: "Le type enfoui", + description: "Téracristalliser un Pokémon en type Stellaire", + }, + "SPLICE": { + name: "Infinite Fusion", + description: "Fusionner deux Pokémon avec le Pointeau ADN", + }, + "MINI_BLACK_HOLE": { + name: "Item-stellar", + description: "Obtenir un Mini Trou Noir", + }, + "CATCH_MYTHICAL": { + name: "Fabuleux", + description: "Capturer un Pokémon fabuleux", + }, + "CATCH_SUB_LEGENDARY": { + name: "(Semi-)Légendaire", + description: "Capturer un Pokémon semi-légendaire", + }, + "CATCH_LEGENDARY": { + name: "Légendaire", + description: "Capturer un Pokémon légendaire", + }, + "SEE_SHINY": { + name: "Chromatique", + description: "Trouver un Pokémon sauvage chromatique", + }, + "SHINY_PARTY": { + name: "Shasseuse", + description: "Avoir une équipe exclusivement composée de Pokémon chromatiques", + }, + "HATCH_MYTHICAL": { + name: "Œuf fabuleux", + description: "Obtenir un Pokémon fabuleux dans un Œuf", + }, + "HATCH_SUB_LEGENDARY": { + name: "Œuf semi-légendaire", + description: "Obtenir un Pokémon semi-légendaire dans un Œuf", + }, + "HATCH_LEGENDARY": { + name: "Œuf légendaire", + description: "Obtenir un Pokémon légendaire dans un Œuf", + }, + "HATCH_SHINY": { + name: "Œuf chromatique", + description: "Obtenir un Pokémon chromatique dans un Œuf", + }, + "HIDDEN_ABILITY": { + name: "Potentiel enfoui", + description: "Capturer un Pokémon possédant un talent caché", + }, + "PERFECT_IVS": { + name: "Certificat d’authenticité", + description: "Avoir des IV parfaits sur un Pokémon", + }, + "CLASSIC_VICTORY": { + name: "Invaincue", + description: "Terminer le jeu en mode classique", + }, + + "MONO_GEN_ONE": { + name: "Le rival originel", + description: "Terminer un challenge avec uniquement des Pokémon de 1re génération.", + }, + "MONO_GEN_TWO": { + name: "Entre tradition et modernité", + description: "Terminer un challenge avec uniquement des Pokémon de 2e génération.", + }, + "MONO_GEN_THREE": { + name: "Too much water ?", + description: "Terminer un challenge avec uniquement des Pokémon de 3e génération.", + }, + "MONO_GEN_FOUR": { + name: "Réellement la plus difficile ?", + description: "Terminer un challenge avec uniquement des Pokémon de 4e génération.", + }, + "MONO_GEN_FIVE": { + name: "Recast complet", + description: "Terminer un challenge avec uniquement des Pokémon de 5e génération.", + }, + "MONO_GEN_SIX": { + name: "Aristocrate", + description: "Terminer un challenge avec uniquement des Pokémon de 6e génération.", + }, + "MONO_GEN_SEVEN": { + name: "Seulement techniquement", + description: "Terminer un challenge avec uniquement des Pokémon de 7e génération.", + }, + "MONO_GEN_EIGHT": { + name: "L’heure de gloire", + description: "Terminer un challenge avec uniquement des Pokémon de 8e génération.", + }, + "MONO_GEN_NINE": { + name: "Ça va, c’était EZ", + description: "Terminer un challenge avec uniquement des Pokémon de 9e génération.", + }, + + "MonoType": { + description: "Terminer un challenge en monotype {{type}}.", + }, + "MONO_NORMAL": { + name: "Extraordinairement banal", + }, + "MONO_FIGHTING": { + name: "Je connais le kung-fu", + }, + "MONO_FLYING": { + name: "Angry Birds", + }, + "MONO_POISON": { + name: "Touche moi je t’empoisonne !", + }, + "MONO_GROUND": { + name: "Prévisions : Séisme", + }, + "MONO_ROCK": { + name: "Comme un roc", + }, + "MONO_BUG": { + name: "Une chenille !", + }, + "MONO_GHOST": { + name: "SOS Fantômes", + }, + "MONO_STEEL": { + name: "De type Acier !", + }, + "MONO_FIRE": { + name: "Allumer le feu", + }, + "MONO_WATER": { + name: "Vacances en Bretagne", + }, + "MONO_GRASS": { + name: "Ne pas toucher !", + }, + "MONO_ELECTRIC": { + name: "À la masse", + }, + "MONO_PSYCHIC": { + name: "Grocervo", + }, + "MONO_ICE": { + name: "Froid comme la glace", + }, + "MONO_DRAGON": { + name: "Légendes du club, ou presque", + }, + "MONO_DARK": { + name: "Ça va lui passer", + }, + "MONO_FAIRY": { + name: "Hey ! Listen !", + }, +} as const; diff --git a/src/locales/fr/menu-ui-handler.ts b/src/locales/fr/menu-ui-handler.ts index 70cf05fe984..1270ce6d361 100644 --- a/src/locales/fr/menu-ui-handler.ts +++ b/src/locales/fr/menu-ui-handler.ts @@ -18,10 +18,10 @@ export const menuUiHandler: SimpleTranslationEntries = { "exportSlotSelect": "Sélectionnez l’emplacement depuis lequel exporter les données.", "importData": "Importer données", "exportData": "Exporter données", - "linkDiscord": "Link Discord", - "unlinkDiscord": "Unlink Discord", - "linkGoogle": "Link Google", - "unlinkGoogle": "Unlink Google", + "linkDiscord": "Lier à Discord", + "unlinkDiscord": "Délier Discord", + "linkGoogle": "Lier à Google", + "unlinkGoogle": "Délier Google", "cancel": "Retour", "losingProgressionWarning": "Vous allez perdre votre progression depuis le début du combat. Continuer ?", "noEggs": "Vous ne faites actuellement\néclore aucun Œuf !" From 7dda6f5cc2d207fe04241a190ff95efc485ea70e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Ricardo=20Fleury=20Oliveira?= Date: Wed, 31 Jul 2024 22:57:20 -0300 Subject: [PATCH 11/23] [Localization] Update pt_br arena-tag.ts and fixed typo in modifier.ts (#3277) --- src/locales/pt_BR/arena-tag.ts | 92 +++++++++++++++++----------------- src/locales/pt_BR/modifier.ts | 2 +- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/locales/pt_BR/arena-tag.ts b/src/locales/pt_BR/arena-tag.ts index 8bc2302368a..0d3b8ff587f 100644 --- a/src/locales/pt_BR/arena-tag.ts +++ b/src/locales/pt_BR/arena-tag.ts @@ -1,50 +1,50 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales"; export const arenaTag: SimpleTranslationEntries = { - "yourTeam": "your team", - "opposingTeam": "the opposing team", - "arenaOnRemove": "{{moveName}}'s effect wore off.", - "arenaOnRemovePlayer": "{{moveName}}'s effect wore off\non your side.", - "arenaOnRemoveEnemy": "{{moveName}}'s effect wore off\non the foe's side.", - "mistOnAdd": "{{pokemonNameWithAffix}}'s team became\nshrouded in mist!", - "mistApply": "The mist prevented\nthe lowering of stats!", - "reflectOnAdd": "Reflect reduced the damage of physical moves.", - "reflectOnAddPlayer": "Reflect reduced the damage of physical moves on your side.", - "reflectOnAddEnemy": "Reflect reduced the damage of physical moves on the foe's side.", - "lightScreenOnAdd": "Light Screen reduced the damage of special moves.", - "lightScreenOnAddPlayer": "Light Screen reduced the damage of special moves on your side.", - "lightScreenOnAddEnemy": "Light Screen reduced the damage of special moves on the foe's side.", - "auroraVeilOnAdd": "Aurora Veil reduced the damage of moves.", - "auroraVeilOnAddPlayer": "Aurora Veil reduced the damage of moves on your side.", - "auroraVeilOnAddEnemy": "Aurora Veil reduced the damage of moves on the foe's side.", - "conditionalProtectOnAdd": "{{moveName}} protected team!", - "conditionalProtectOnAddPlayer": "{{moveName}} protected your team!", - "conditionalProtectOnAddEnemy": "{{moveName}} protected the\nopposing team!", - "conditionalProtectApply": "{{moveName}} protected {{pokemonNameWithAffix}}!", - "matBlockOnAdd": "{{pokemonNameWithAffix}} intends to flip up a mat\nand block incoming attacks!", - "wishTagOnAdd": "{{pokemonNameWithAffix}}'s wish\ncame true!", - "mudSportOnAdd": "Electricity's power was weakened!", - "mudSportOnRemove": "The effects of Mud Sport\nhave faded.", - "waterSportOnAdd": "Fire's power was weakened!", - "waterSportOnRemove": "The effects of Water Sport\nhave faded.", - "spikesOnAdd": "{{moveName}} were scattered\nall around {{opponentDesc}}'s feet!", - "spikesActivateTrap": "{{pokemonNameWithAffix}} is hurt\nby the spikes!", - "toxicSpikesOnAdd": "{{moveName}} were scattered\nall around {{opponentDesc}}'s feet!", - "toxicSpikesActivateTrapPoison": "{{pokemonNameWithAffix}} absorbed the {{moveName}}!", - "stealthRockOnAdd": "Pointed stones float in the air\naround {{opponentDesc}}!", - "stealthRockActivateTrap": "Pointed stones dug into\n{{pokemonNameWithAffix}}!", - "stickyWebOnAdd": "A {{moveName}} has been laid out on the ground around the opposing team!", - "stickyWebActivateTrap": "The opposing {{pokemonName}} was caught in a sticky web!", - "trickRoomOnAdd": "{{pokemonNameWithAffix}} twisted\nthe dimensions!", - "trickRoomOnRemove": "The twisted dimensions\nreturned to normal!", - "gravityOnAdd": "Gravity intensified!", - "gravityOnRemove": "Gravity returned to normal!", - "tailwindOnAdd": "The Tailwind blew from behind team!", - "tailwindOnAddPlayer": "The Tailwind blew from behind\nyour team!", - "tailwindOnAddEnemy": "The Tailwind blew from behind\nthe opposing team!", - "tailwindOnRemove": "Team's Tailwind petered out!", - "tailwindOnRemovePlayer": "Your team's Tailwind petered out!", - "tailwindOnRemoveEnemy": "The opposing team's Tailwind petered out!", - "happyHourOnAdd": "Everyone is caught up in the happy atmosphere!", - "happyHourOnRemove": "The atmosphere returned to normal.", + "yourTeam": "sua equipe", + "opposingTeam": "a equipe adversária", + "arenaOnRemove": "O efeito de {{moveName}} acabou.", + "arenaOnRemovePlayer": "O efeito de {{moveName}} acabou\nem sua equipe.", + "arenaOnRemoveEnemy": "O efeito de {{moveName}} acabou\nna equipe adversária.", + "mistOnAdd": "A equipe de {{pokemonNameWithAffix}} está protegida\npela névoa!", + "mistApply": "A névoa previne\na diminuição de atributos!", + "reflectOnAdd": "Reflect reduziu o dano de movimentos físicos.", + "reflectOnAddPlayer": "Reflect reduziu o dano de movimentos físicos em sua equipe.", + "reflectOnAddEnemy": "Reflect reduziu o dano de movimentos físicos na equipe adversária.", + "lightScreenOnAdd": "Light Screen reduziu o dano de movimentos especiais.", + "lightScreenOnAddPlayer": "Light Screen reduziu o dano de movimentos especiais em sua equipe.", + "lightScreenOnAddEnemy": "Light Screen reduziu o dano de movimentos especiais na equipe adversária.", + "auroraVeilOnAdd": "Aurora Veil reduziu o dano de movimentos.", + "auroraVeilOnAddPlayer": "Aurora Veil reduziu o dano de movimentos em sua equipe.", + "auroraVeilOnAddEnemy": "Aurora Veil reduziu o dano de movimentos na equipe adversária.", + "conditionalProtectOnAdd": "{{moveName}} protegeu a equipe!", + "conditionalProtectOnAddPlayer": "{{moveName}} protegeu sua equipe!", + "conditionalProtectOnAddEnemy": "{{moveName}} protegeu a\nequipe adversária!", + "conditionalProtectApply": "{{moveName}} protegeu {{pokemonNameWithAffix}}!", + "matBlockOnAdd": "{{pokemonNameWithAffix}} pretende levantar um tapete\npara bloquear ataques!", + "wishTagOnAdd": "O desejo de {{pokemonNameWithAffix}}\nfoi concedido!", + "mudSportOnAdd": "O poder de movimentos elétricos foi enfraquecido!", + "mudSportOnRemove": "Os efeitos de Mud Sport\nsumiram.", + "waterSportOnAdd": "O poder de movimentos de fogo foi enfraquecido!", + "waterSportOnRemove": "Os efeitos de Water Sport\nsumiram.", + "spikesOnAdd": "{{moveName}} foram espalhados\nno chão ao redor de {{opponentDesc}}!", + "spikesActivateTrap": "{{pokemonNameWithAffix}} foi ferido\npelos espinhos!", + "toxicSpikesOnAdd": "{{moveName}} foram espalhados\nno chão ao redor de {{opponentDesc}}!", + "toxicSpikesActivateTrapPoison": "{{pokemonNameWithAffix}} absorveu os {{moveName}}!", + "stealthRockOnAdd": "Pedras pontiagudas estão flutuando\nao redor de {{opponentDesc}}!", + "stealthRockActivateTrap": "{{pokemonNameWithAffix}} foi atingido\npor pedras pontiagudas!", + "stickyWebOnAdd": "Uma {{moveName}} foi espalhada no chão ao redor da equipe adversária!", + "stickyWebActivateTrap": "{{pokemonName}} adversário foi pego por uma Sticky Web!", + "trickRoomOnAdd": "{{pokemonNameWithAffix}} distorceu\nas dimensões!", + "trickRoomOnRemove": "As dimensões distorcidas\nretornaram ao normal!", + "gravityOnAdd": "A gravidade aumentou!", + "gravityOnRemove": "A gravidade retornou ao normal!", + "tailwindOnAdd": "O Tailwind soprou de trás de sua equipe!", + "tailwindOnAddPlayer": "O Tailwind soprou de trás de\nsua equipe!", + "tailwindOnAddEnemy": "O Tailwind soprou de trás da\nequipe adversária!", + "tailwindOnRemove": "O Tailwind de sua equipe acabou!", + "tailwindOnRemovePlayer": "O Tailwind de sua equipe acabou!", + "tailwindOnRemoveEnemy": "O Tailwind da equipe adversária acabou!", + "happyHourOnAdd": "Todos foram envolvidos por uma atmosfera alegre!", + "happyHourOnRemove": "A atmosfera retornou ao normal.", } as const; diff --git a/src/locales/pt_BR/modifier.ts b/src/locales/pt_BR/modifier.ts index eef08db25ae..168665205c3 100644 --- a/src/locales/pt_BR/modifier.ts +++ b/src/locales/pt_BR/modifier.ts @@ -10,5 +10,5 @@ export const modifier: SimpleTranslationEntries = { "turnHeldItemTransferApply": "{{itemName}} de {{pokemonNameWithAffix}} foi absorvido(a)\npelo {{typeName}} de {{pokemonName}}!", "contactHeldItemTransferApply": "{{itemName}} de {{pokemonNameWithAffix}} foi pego(a)\npela {{typeName}} de {{pokemonName}}!", "enemyTurnHealApply": "{{pokemonNameWithAffix}}\nrestaurou um pouco de seus PS!", - "bypassSpeedChanceApply": "{{pokemonName}} se move mais rápido que o normal graças a sua {{itemName}}!", + "bypassSpeedChanceApply": "{{pokemonName}} se move mais rápido que o normal graças à sua {{itemName}}!", } as const; From b0f8f0e9011c4d74cfc5a218e0d26a63ff511af4 Mon Sep 17 00:00:00 2001 From: chaosgrimmon <31082757+chaosgrimmon@users.noreply.github.com> Date: Thu, 1 Aug 2024 10:25:50 -0400 Subject: [PATCH 12/23] [Sprite] Add female Ursaring variant palettes (#3282) * [Sprite] Female Ursaring variant fronts Taken from male counterpart * [Sprite] Female Ursaring variant backs Taken from male counterpart * [Sprite] Female Ursaring variants --- .../images/pokemon/variant/_masterlist.json | 10 ++++ .../pokemon/variant/back/female/217.json | 38 ++++++++++++++ public/images/pokemon/variant/female/217.json | 50 +++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 public/images/pokemon/variant/back/female/217.json create mode 100644 public/images/pokemon/variant/female/217.json diff --git a/public/images/pokemon/variant/_masterlist.json b/public/images/pokemon/variant/_masterlist.json index c936fc8d0a1..d03d8d7f1f5 100644 --- a/public/images/pokemon/variant/_masterlist.json +++ b/public/images/pokemon/variant/_masterlist.json @@ -3265,6 +3265,11 @@ 1, 1 ], + "217": [ + 1, + 1, + 1 + ], "229": [ 0, 1, @@ -6658,6 +6663,11 @@ 1, 1 ], + "217": [ + 1, + 1, + 1 + ], "229": [ 0, 1, diff --git a/public/images/pokemon/variant/back/female/217.json b/public/images/pokemon/variant/back/female/217.json new file mode 100644 index 00000000000..7ce80e163b6 --- /dev/null +++ b/public/images/pokemon/variant/back/female/217.json @@ -0,0 +1,38 @@ +{ + "0": { + "422919": "112114", + "7b7b8c": "7b7b8c", + "101010": "101010", + "945221": "2f6324", + "ffffff": "ffffff", + "634229": "1d3d26", + "b5b5bd": "b5b5bd", + "f7c563": "fecd85", + "c59c4a": "cd9343", + "dedede": "dedede" + }, + "1": { + "422919": "2d0e1f", + "7b7b8c": "7b7b8c", + "101010": "101010", + "945221": "8c2a37", + "ffffff": "ffffff", + "634229": "6b1d38", + "b5b5bd": "b5b5bd", + "f7c563": "f2cab8", + "c59c4a": "c48e81", + "dedede": "dedede" + }, + "2": { + "422919": "111433", + "7b7b8c": "7b7b8c", + "101010": "101010", + "945221": "323760", + "ffffff": "ffffff", + "634229": "1e2249", + "b5b5bd": "b5b5bd", + "f7c563": "5ccaf2", + "c59c4a": "45a2f9", + "dedede": "dedede" + } +} \ No newline at end of file diff --git a/public/images/pokemon/variant/female/217.json b/public/images/pokemon/variant/female/217.json new file mode 100644 index 00000000000..e09efa5de60 --- /dev/null +++ b/public/images/pokemon/variant/female/217.json @@ -0,0 +1,50 @@ +{ + "0": { + "7b7b8c": "7b7b8c", + "101010": "101010", + "634229": "1d3d26", + "ffffff": "ffffff", + "945221": "2f6324", + "422919": "112114", + "dedede": "dedede", + "bd7342": "6a8a46", + "ffef84": "f7ffa5", + "b5b5bd": "b5b5bd", + "c59c4a": "ceb552", + "f7c563": "f7de7b", + "841931": "a52942", + "de3a5a": "ef526b" + }, + "1": { + "7b7b8c": "7b7b8c", + "101010": "101010", + "634229": "6b1d38", + "ffffff": "ffffff", + "945221": "8c2a37", + "422919": "2d0e1f", + "dedede": "dedede", + "bd7342": "b74543", + "ffef84": "f9eddb", + "b5b5bd": "b5b5bd", + "c59c4a": "c48e81", + "f7c563": "f2cab8", + "841931": "841931", + "de3a5a": "de3a5a" + }, + "2": { + "7b7b8c": "7b7b8c", + "101010": "101010", + "634229": "1e2249", + "ffffff": "ffffff", + "945221": "323760", + "422919": "111433", + "dedede": "dedede", + "bd7342": "46527a", + "ffef84": "adf2f7", + "b5b5bd": "b5b5bd", + "c59c4a": "45a2f9", + "f7c563": "5ccaf2", + "841931": "a52942", + "de3a5a": "ef526b" + } +} \ No newline at end of file From c1595bf2b76d887cafac43eb7fb955e5c1ee2548 Mon Sep 17 00:00:00 2001 From: allen925 <62000482+allen925@users.noreply.github.com> Date: Thu, 1 Aug 2024 09:35:11 -0700 Subject: [PATCH 13/23] [Bug] fix stuck/sprite issue with double battle player pokemon both died same turn then wins battle with one bench pokemon only (#3266) --- src/phases.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/phases.ts b/src/phases.ts index c41ad333b9d..d77ff7c29df 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -3789,11 +3789,10 @@ export class FaintPhase extends PokemonPhase { const nonFaintedPartyMemberCount = nonFaintedLegalPartyMembers.length; if (!nonFaintedPartyMemberCount) { this.scene.unshiftPhase(new GameOverPhase(this.scene)); - } else if (nonFaintedPartyMemberCount >= this.scene.currentBattle.getBattlerCount() || (this.scene.currentBattle.double && !nonFaintedLegalPartyMembers[0].isActive(true))) { - this.scene.pushPhase(new SwitchPhase(this.scene, this.fieldIndex, true, false)); - } - if (nonFaintedPartyMemberCount === 1 && this.scene.currentBattle.double) { + } else if (nonFaintedPartyMemberCount === 1 && this.scene.currentBattle.double) { this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true)); + } else if (nonFaintedPartyMemberCount >= this.scene.currentBattle.getBattlerCount()) { + this.scene.pushPhase(new SwitchPhase(this.scene, this.fieldIndex, true, false)); } } else { this.scene.unshiftPhase(new VictoryPhase(this.scene, this.battlerIndex)); From 69e4ed9284b3889760e868de8e7a12f26214ecd4 Mon Sep 17 00:00:00 2001 From: Leo Kim <47556641+KimJeongSun@users.noreply.github.com> Date: Fri, 2 Aug 2024 10:57:36 +0900 Subject: [PATCH 14/23] [Bug] Fix bug of remaining pokerus and starter cursor on scrolling (#3293) --- src/ui/starter-select-ui-handler.ts | 59 ++++++++++++++--------------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index e0f895b89c4..0b9210af646 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -1025,16 +1025,17 @@ export default class StarterSelectUiHandler extends MessageUiHandler { return false; } + const maxColumns = 9; + const maxRows = 9; const numberOfStarters = this.filteredStarterContainers.length; - const numOfRows = Math.ceil(numberOfStarters / 9); - const currentRow = Math.floor(this.cursor / 9); - const onScreenFirstIndex = this.scrollCursor * 9; // this is first starter index on the screen - const onScreenLastIndex = Math.min(onScreenFirstIndex + 9*9, numberOfStarters) - 1; // this is the last starter index on the screen + const numOfRows = Math.ceil(numberOfStarters / maxColumns); + const currentRow = Math.floor(this.cursor / maxColumns); + const onScreenFirstIndex = this.scrollCursor * maxColumns; // this is first starter index on the screen + const onScreenLastIndex = Math.min(this.filteredStarterContainers.length - 1, onScreenFirstIndex + maxRows * maxColumns - 1); // this is the last starter index on the screen const onScreenNumberOfStarters = onScreenLastIndex - onScreenFirstIndex + 1; - const onScreenNumberOfRows = Math.ceil(onScreenNumberOfStarters / 9); - // const onScreenFirstRow = Math.floor(onScreenFirstIndex / 9); - const onScreenCurrentRow = Math.floor((this.cursor - onScreenFirstIndex) / 9); - + const onScreenNumberOfRows = Math.ceil(onScreenNumberOfStarters / maxColumns); + // const onScreenFirstRow = Math.floor(onScreenFirstIndex / maxColumns); + const onScreenCurrentRow = Math.floor((this.cursor - onScreenFirstIndex) / maxColumns); // console.log("this.cursor: ", this.cursor, "this.scrollCursor" , this.scrollCursor, "numberOfStarters: ", numberOfStarters, "numOfRows: ", numOfRows, "currentRow: ", currentRow, "onScreenFirstIndex: ", onScreenFirstIndex, "onScreenLastIndex: ", onScreenLastIndex, "onScreenNumberOfStarters: ", onScreenNumberOfStarters, "onScreenNumberOfRow: ", onScreenNumberOfRows, "onScreenCurrentRow: ", onScreenCurrentRow); @@ -2057,45 +2058,41 @@ export default class StarterSelectUiHandler extends MessageUiHandler { updateScroll = () => { const maxColumns = 9; const maxRows = 9; - const onScreenFirstIndex = this.scrollCursor * 9; - const onScreenLastIndex = Math.min(this.filteredStarterContainers.length - 1, onScreenFirstIndex + 81); + const onScreenFirstIndex = this.scrollCursor * maxColumns; + const onScreenLastIndex = Math.min(this.filteredStarterContainers.length - 1, onScreenFirstIndex + maxRows * maxColumns -1); this.starterSelectScrollBar.setPage(this.scrollCursor); let pokerusCursorIndex = 0; this.filteredStarterContainers.forEach((container, i) => { + const pos = calcStarterPosition(i, this.scrollCursor); + container.setPosition(pos.x, pos.y); if (i < onScreenFirstIndex || i > onScreenLastIndex) { container.setVisible(false); - return; - } else { - const pos = calcStarterPosition(i, this.scrollCursor); - container.setPosition(pos.x, pos.y); - - if (i < (maxRows + this.scrollCursor) * maxColumns && i >= this.scrollCursor * maxColumns) { - container.setVisible(true); - } else { - container.setVisible(false); - } if (this.pokerusSpecies.includes(container.species)) { this.pokerusCursorObjs[pokerusCursorIndex].setPosition(pos.x - 1, pos.y + 1); - - if (i < (maxRows + this.scrollCursor) * maxColumns && i >= this.scrollCursor * maxColumns) { - this.pokerusCursorObjs[pokerusCursorIndex].setVisible(true); - } else { - this.pokerusCursorObjs[pokerusCursorIndex].setVisible(false); - } + this.pokerusCursorObjs[pokerusCursorIndex].setVisible(false); pokerusCursorIndex++; } if (this.starterSpecies.includes(container.species)) { this.starterCursorObjs[this.starterSpecies.indexOf(container.species)].setPosition(pos.x - 1, pos.y + 1); + this.starterCursorObjs[this.starterSpecies.indexOf(container.species)].setVisible(false); + } + return; + } else { + container.setVisible(true); - if (i < (maxRows + this.scrollCursor) * maxColumns && i >= this.scrollCursor * maxColumns) { - this.starterCursorObjs[this.starterSpecies.indexOf(container.species)].setVisible(true); - } else { - this.starterCursorObjs[this.starterSpecies.indexOf(container.species)].setVisible(false); - } + if (this.pokerusSpecies.includes(container.species)) { + this.pokerusCursorObjs[pokerusCursorIndex].setPosition(pos.x - 1, pos.y + 1); + this.pokerusCursorObjs[pokerusCursorIndex].setVisible(true); + pokerusCursorIndex++; + } + + if (this.starterSpecies.includes(container.species)) { + this.starterCursorObjs[this.starterSpecies.indexOf(container.species)].setPosition(pos.x - 1, pos.y + 1); + this.starterCursorObjs[this.starterSpecies.indexOf(container.species)].setVisible(true); } const speciesId = container.species.speciesId; From 17cc8b5c5e067113f1efe87b43022f01a196fad6 Mon Sep 17 00:00:00 2001 From: cam Date: Thu, 1 Aug 2024 22:01:09 -0400 Subject: [PATCH 15/23] [Sprite] Victreebil's stem not connected to the head (#3292) * [Fix][Sprite] 4083 Galarian Farfetch'd exp Fixed cropped feet on exp for default & shiny. Rearranged spritesheet to fit new size. Both sritesheets have identical sprite framing. Both json are identical. * [Sprite] 71- Fixed missing stem fixed missing stem segment on victreebil's head default and shiny png positions are identical json are identical --- public/images/pokemon/back/71.json | 1834 ++++----------------- public/images/pokemon/back/71.png | Bin 6995 -> 7433 bytes public/images/pokemon/back/shiny/71.json | 1836 ++++------------------ public/images/pokemon/back/shiny/71.png | Bin 6994 -> 7433 bytes 4 files changed, 609 insertions(+), 3061 deletions(-) diff --git a/public/images/pokemon/back/71.json b/public/images/pokemon/back/71.json index b3f5a0792db..195188d13e7 100644 --- a/public/images/pokemon/back/71.json +++ b/public/images/pokemon/back/71.json @@ -1,1532 +1,306 @@ { - "textures": [ - { - "image": "71.png", - "format": "RGBA8888", - "size": { - "w": 333, - "h": 333 - }, - "scale": 1, - "frames": [ - { - "filename": "0015.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 70 - }, - "frame": { - "x": 0, - "y": 0, - "w": 69, - "h": 70 - } - }, - { - "filename": "0016.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 70 - }, - "frame": { - "x": 0, - "y": 0, - "w": 69, - "h": 70 - } - }, - { - "filename": "0051.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 70 - }, - "frame": { - "x": 0, - "y": 0, - "w": 69, - "h": 70 - } - }, - { - "filename": "0052.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 70 - }, - "frame": { - "x": 0, - "y": 0, - "w": 69, - "h": 70 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 70 - }, - "frame": { - "x": 69, - "y": 0, - "w": 69, - "h": 70 - } - }, - { - "filename": "0022.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 70 - }, - "frame": { - "x": 69, - "y": 0, - "w": 69, - "h": 70 - } - }, - { - "filename": "0057.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 70 - }, - "frame": { - "x": 69, - "y": 0, - "w": 69, - "h": 70 - } - }, - { - "filename": "0058.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 70 - }, - "frame": { - "x": 69, - "y": 0, - "w": 69, - "h": 70 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 67, - "h": 70 - }, - "frame": { - "x": 138, - "y": 0, - "w": 67, - "h": 70 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 67, - "h": 70 - }, - "frame": { - "x": 138, - "y": 0, - "w": 67, - "h": 70 - } - }, - { - "filename": "0039.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 67, - "h": 70 - }, - "frame": { - "x": 138, - "y": 0, - "w": 67, - "h": 70 - } - }, - { - "filename": "0040.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 67, - "h": 70 - }, - "frame": { - "x": 138, - "y": 0, - "w": 67, - "h": 70 - } - }, - { - "filename": "0033.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 67, - "h": 70 - }, - "frame": { - "x": 205, - "y": 0, - "w": 67, - "h": 70 - } - }, - { - "filename": "0034.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 67, - "h": 70 - }, - "frame": { - "x": 205, - "y": 0, - "w": 67, - "h": 70 - } - }, - { - "filename": "0069.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 67, - "h": 70 - }, - "frame": { - "x": 205, - "y": 0, - "w": 67, - "h": 70 - } - }, - { - "filename": "0070.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 67, - "h": 70 - }, - "frame": { - "x": 205, - "y": 0, - "w": 67, - "h": 70 - } - }, - { - "filename": "0017.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 69 - }, - "frame": { - "x": 0, - "y": 70, - "w": 69, - "h": 69 - } - }, - { - "filename": "0018.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 69 - }, - "frame": { - "x": 0, - "y": 70, - "w": 69, - "h": 69 - } - }, - { - "filename": "0053.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 69 - }, - "frame": { - "x": 0, - "y": 70, - "w": 69, - "h": 69 - } - }, - { - "filename": "0054.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 69 - }, - "frame": { - "x": 0, - "y": 70, - "w": 69, - "h": 69 - } - }, - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 69 - }, - "frame": { - "x": 69, - "y": 70, - "w": 69, - "h": 69 - } - }, - { - "filename": "0020.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 69 - }, - "frame": { - "x": 69, - "y": 70, - "w": 69, - "h": 69 - } - }, - { - "filename": "0055.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 69 - }, - "frame": { - "x": 69, - "y": 70, - "w": 69, - "h": 69 - } - }, - { - "filename": "0056.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 69 - }, - "frame": { - "x": 69, - "y": 70, - "w": 69, - "h": 69 - } - }, - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 68, - "h": 69 - }, - "frame": { - "x": 138, - "y": 70, - "w": 68, - "h": 69 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 68, - "h": 69 - }, - "frame": { - "x": 138, - "y": 70, - "w": 68, - "h": 69 - } - }, - { - "filename": "0037.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 68, - "h": 69 - }, - "frame": { - "x": 138, - "y": 70, - "w": 68, - "h": 69 - } - }, - { - "filename": "0038.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 68, - "h": 69 - }, - "frame": { - "x": 138, - "y": 70, - "w": 68, - "h": 69 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 68, - "h": 69 - }, - "frame": { - "x": 206, - "y": 70, - "w": 68, - "h": 69 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 68, - "h": 69 - }, - "frame": { - "x": 206, - "y": 70, - "w": 68, - "h": 69 - } - }, - { - "filename": "0049.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 68, - "h": 69 - }, - "frame": { - "x": 206, - "y": 70, - "w": 68, - "h": 69 - } - }, - { - "filename": "0050.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 68, - "h": 69 - }, - "frame": { - "x": 206, - "y": 70, - "w": 68, - "h": 69 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 68, - "h": 69 - }, - "frame": { - "x": 0, - "y": 139, - "w": 68, - "h": 69 - } - }, - { - "filename": "0024.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 68, - "h": 69 - }, - "frame": { - "x": 0, - "y": 139, - "w": 68, - "h": 69 - } - }, - { - "filename": "0059.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 68, - "h": 69 - }, - "frame": { - "x": 0, - "y": 139, - "w": 68, - "h": 69 - } - }, - { - "filename": "0060.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 68, - "h": 69 - }, - "frame": { - "x": 0, - "y": 139, - "w": 68, - "h": 69 - } - }, - { - "filename": "0035.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 68, - "h": 69 - }, - "frame": { - "x": 68, - "y": 139, - "w": 68, - "h": 69 - } - }, - { - "filename": "0036.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 68, - "h": 69 - }, - "frame": { - "x": 68, - "y": 139, - "w": 68, - "h": 69 - } - }, - { - "filename": "0071.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 68, - "h": 69 - }, - "frame": { - "x": 68, - "y": 139, - "w": 68, - "h": 69 - } - }, - { - "filename": "0072.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 68, - "h": 69 - }, - "frame": { - "x": 68, - "y": 139, - "w": 68, - "h": 69 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 67, - "h": 68 - }, - "frame": { - "x": 136, - "y": 139, - "w": 67, - "h": 68 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 67, - "h": 68 - }, - "frame": { - "x": 136, - "y": 139, - "w": 67, - "h": 68 - } - }, - { - "filename": "0047.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 67, - "h": 68 - }, - "frame": { - "x": 136, - "y": 139, - "w": 67, - "h": 68 - } - }, - { - "filename": "0048.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 67, - "h": 68 - }, - "frame": { - "x": 136, - "y": 139, - "w": 67, - "h": 68 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 66, - "h": 69 - }, - "frame": { - "x": 203, - "y": 139, - "w": 66, - "h": 69 - } - }, - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 66, - "h": 69 - }, - "frame": { - "x": 203, - "y": 139, - "w": 66, - "h": 69 - } - }, - { - "filename": "0041.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 66, - "h": 69 - }, - "frame": { - "x": 203, - "y": 139, - "w": 66, - "h": 69 - } - }, - { - "filename": "0042.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 66, - "h": 69 - }, - "frame": { - "x": 203, - "y": 139, - "w": 66, - "h": 69 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 66, - "h": 69 - }, - "frame": { - "x": 136, - "y": 207, - "w": 66, - "h": 69 - } - }, - { - "filename": "0032.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 66, - "h": 69 - }, - "frame": { - "x": 136, - "y": 207, - "w": 66, - "h": 69 - } - }, - { - "filename": "0067.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 66, - "h": 69 - }, - "frame": { - "x": 136, - "y": 207, - "w": 66, - "h": 69 - } - }, - { - "filename": "0068.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 66, - "h": 69 - }, - "frame": { - "x": 136, - "y": 207, - "w": 66, - "h": 69 - } - }, - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 66, - "h": 68 - }, - "frame": { - "x": 202, - "y": 208, - "w": 66, - "h": 68 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 66, - "h": 68 - }, - "frame": { - "x": 202, - "y": 208, - "w": 66, - "h": 68 - } - }, - { - "filename": "0043.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 66, - "h": 68 - }, - "frame": { - "x": 202, - "y": 208, - "w": 66, - "h": 68 - } - }, - { - "filename": "0044.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 66, - "h": 68 - }, - "frame": { - "x": 202, - "y": 208, - "w": 66, - "h": 68 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 64, - "h": 67 - }, - "frame": { - "x": 268, - "y": 208, - "w": 64, - "h": 67 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 64, - "h": 67 - }, - "frame": { - "x": 268, - "y": 208, - "w": 64, - "h": 67 - } - }, - { - "filename": "0045.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 64, - "h": 67 - }, - "frame": { - "x": 268, - "y": 208, - "w": 64, - "h": 67 - } - }, - { - "filename": "0046.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 64, - "h": 67 - }, - "frame": { - "x": 268, - "y": 208, - "w": 64, - "h": 67 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 64, - "h": 67 - }, - "frame": { - "x": 269, - "y": 139, - "w": 64, - "h": 67 - } - }, - { - "filename": "0028.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 64, - "h": 67 - }, - "frame": { - "x": 269, - "y": 139, - "w": 64, - "h": 67 - } - }, - { - "filename": "0063.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 64, - "h": 67 - }, - "frame": { - "x": 269, - "y": 139, - "w": 64, - "h": 67 - } - }, - { - "filename": "0064.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 64, - "h": 67 - }, - "frame": { - "x": 269, - "y": 139, - "w": 64, - "h": 67 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 66, - "h": 68 - }, - "frame": { - "x": 0, - "y": 208, - "w": 66, - "h": 68 - } - }, - { - "filename": "0026.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 66, - "h": 68 - }, - "frame": { - "x": 0, - "y": 208, - "w": 66, - "h": 68 - } - }, - { - "filename": "0061.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 66, - "h": 68 - }, - "frame": { - "x": 0, - "y": 208, - "w": 66, - "h": 68 - } - }, - { - "filename": "0062.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 66, - "h": 68 - }, - "frame": { - "x": 0, - "y": 208, - "w": 66, - "h": 68 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 66, - "h": 68 - }, - "frame": { - "x": 66, - "y": 208, - "w": 66, - "h": 68 - } - }, - { - "filename": "0030.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 66, - "h": 68 - }, - "frame": { - "x": 66, - "y": 208, - "w": 66, - "h": 68 - } - }, - { - "filename": "0065.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 66, - "h": 68 - }, - "frame": { - "x": 66, - "y": 208, - "w": 66, - "h": 68 - } - }, - { - "filename": "0066.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 66, - "h": 68 - }, - "frame": { - "x": 66, - "y": 208, - "w": 66, - "h": 68 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:751712009fd74222715d70b43a4283e8:5955b5b390b7b5ccf4dce1abfcc58af2:699363ed1732140836e97f90bcfd26b2$" - } + "textures": + [ + { + "image": "71.png", + "format": "RGB8888", + "size": { "w": 338, + "h": 269 }, + "scale": 1, + "frames": [ + { + "filename": "0001.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 0, "w": 66, "h": 68 }, + "frame": { "x": 272, "y": 0, "w": 66, "h": 68 } + }, + { + "filename": "0002.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 0, "w": 66, "h": 68 }, + "frame": { "x": 272, "y": 0, "w": 66, "h": 68 } + }, + { + "filename": "0003.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 66, "h": 68 }, + "frame": { "x": 0, "y": 68, "w": 66, "h": 68 } + }, + { + "filename": "0004.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 66, "h": 68 }, + "frame": { "x": 0, "y": 68, "w": 66, "h": 68 } + }, + { + "filename": "0005.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 2, "w": 64, "h": 67 }, + "frame": { "x": 128, "y": 136, "w": 64, "h": 67 } + }, + { + "filename": "0006.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 2, "w": 64, "h": 67 }, + "frame": { "x": 128, "y": 136, "w": 64, "h": 67 } + }, + { + "filename": "0007.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 2, "w": 64, "h": 68 }, + "frame": { "x": 263, "y": 135, "w": 64, "h": 68 } + }, + { + "filename": "0008.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 2, "w": 64, "h": 68 }, + "frame": { "x": 263, "y": 135, "w": 64, "h": 68 } + }, + { + "filename": "0009.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 62, "h": 66 }, + "frame": { "x": 128, "y": 203, "w": 62, "h": 66 } + }, + { + "filename": "0010.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 62, "h": 66 }, + "frame": { "x": 128, "y": 203, "w": 62, "h": 66 } + }, + { + "filename": "0011.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 3, "w": 65, "h": 67 }, + "frame": { "x": 198, "y": 135, "w": 65, "h": 67 } + }, + { + "filename": "0012.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 3, "w": 65, "h": 67 }, + "frame": { "x": 198, "y": 135, "w": 65, "h": 67 } + }, + { + "filename": "0013.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 2, "w": 66, "h": 67 }, + "frame": { "x": 198, "y": 68, "w": 66, "h": 67 } + }, + { + "filename": "0014.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 2, "w": 66, "h": 67 }, + "frame": { "x": 198, "y": 68, "w": 66, "h": 67 } + }, + { + "filename": "0015.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 68, "h": 68 }, + "frame": { "x": 0, "y": 0, "w": 68, "h": 68 } + }, + { + "filename": "0016.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 68, "h": 68 }, + "frame": { "x": 0, "y": 0, "w": 68, "h": 68 } + }, + { + "filename": "0017.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 0, "w": 68, "h": 68 }, + "frame": { "x": 68, "y": 0, "w": 68, "h": 68 } + }, + { + "filename": "0018.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 0, "w": 68, "h": 68 }, + "frame": { "x": 68, "y": 0, "w": 68, "h": 68 } + }, + { + "filename": "0019.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 0, "w": 68, "h": 68 }, + "frame": { "x": 136, "y": 0, "w": 68, "h": 68 } + }, + { + "filename": "0020.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 0, "w": 68, "h": 68 }, + "frame": { "x": 136, "y": 0, "w": 68, "h": 68 } + }, + { + "filename": "0021.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 68, "h": 68 }, + "frame": { "x": 204, "y": 0, "w": 68, "h": 68 } + }, + { + "filename": "0022.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 68, "h": 68 }, + "frame": { "x": 204, "y": 0, "w": 68, "h": 68 } + }, + { + "filename": "0023.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 2, "w": 66, "h": 67 }, + "frame": { "x": 264, "y": 68, "w": 66, "h": 67 } + }, + { + "filename": "0024.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 2, "w": 66, "h": 67 }, + "frame": { "x": 264, "y": 68, "w": 66, "h": 67 } + }, + { + "filename": "0025.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 2, "w": 64, "h": 68 }, + "frame": { "x": 0, "y": 136, "w": 64, "h": 68 } + }, + { + "filename": "0026.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 2, "w": 64, "h": 68 }, + "frame": { "x": 0, "y": 136, "w": 64, "h": 68 } + }, + { + "filename": "0027.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 62, "h": 66 }, + "frame": { "x": 256, "y": 203, "w": 62, "h": 66 } + }, + { + "filename": "0028.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 62, "h": 66 }, + "frame": { "x": 256, "y": 203, "w": 62, "h": 66 } + }, + { + "filename": "0029.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 2, "w": 64, "h": 68 }, + "frame": { "x": 64, "y": 136, "w": 64, "h": 68 } + }, + { + "filename": "0030.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 2, "w": 64, "h": 68 }, + "frame": { "x": 64, "y": 136, "w": 64, "h": 68 } + }, + { + "filename": "0031.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 2, "w": 64, "h": 67 }, + "frame": { "x": 192, "y": 202, "w": 64, "h": 67 } + }, + { + "filename": "0032.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 2, "w": 64, "h": 67 }, + "frame": { "x": 192, "y": 202, "w": 64, "h": 67 } + }, + { + "filename": "0033.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 66, "h": 68 }, + "frame": { "x": 66, "y": 68, "w": 66, "h": 68 } + }, + { + "filename": "0034.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 66, "h": 68 }, + "frame": { "x": 66, "y": 68, "w": 66, "h": 68 } + }, + { + "filename": "0035.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 0, "w": 66, "h": 68 }, + "frame": { "x": 132, "y": 68, "w": 66, "h": 68 } + }, + { + "filename": "0036.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 0, "w": 66, "h": 68 }, + "frame": { "x": 132, "y": 68, "w": 66, "h": 68 }} + ], + + "meta": { + "app": "https://www.aseprite.org/", + "version": "1.3.7-x64" + } } +] +} \ No newline at end of file diff --git a/public/images/pokemon/back/71.png b/public/images/pokemon/back/71.png index 3e3424c865eceb3abcd7f4164741936c111022aa..24a546f61662b304d8444a19c206ba6921600323 100644 GIT binary patch literal 7433 zcmYj$1ymGF)Hbzr3rNZ?EU|PW4NHe~cPdMWARwZ|0@BU0fP~c2Eg;<;0!xF4QqqkA z5bJJdVDhOMmj{{DV*(D!!z zqgef;IoCf&i2M8dKfMt({}jCSwGA*H#{ai;>BcBxU@*F>D=8TIWgTSi`EPQPDwFu~ zr3eqV-7m*CYXaf&-u5Pzu;CRGvDV zIsa)JAw*~=0*bwP`ujIE-q>?+9Y-|K6Bj_G!j(vk(-W6_ATO|wrGALUKYL>je-sPyA_Q-N8Dz&A)JULsy4#& zdIx1gL2L^32dhIZlZ)@Y+0A-j-za}27YmSUI~MV{KEzNWjU=N>Agj+Ts;xOlXx*^~ z&rVzEd8u(hV*&EjaIDv2qIz;}&rr^jTSXe&o=Doa;5~Y_wCYVuF-NQ(zrRHyP{vR@ zePHZRX72O_z0`m5P50-&Lx0EXr=5&fdYhb(hEdSHX|e9b&@_t*l1=f7nZ3CQUd)sJ zGSIntckwB&*z5yE(u{Jo;FvP`K)99$?fir|I+EoJD#W5@M}?Q4r7T8UF?m#gIDlOu zcJKD~gJ{;!a==>3x0!gM*vJY#mpDDXd;u{>`vn2(DRkcBcKPJUis@APbxW!b*+XFD zZ~yxN`s!Semxyvt)Yy!2f#8rb)x$zi7=JxO@d?OB1g9qo-{_qF){>pDe4_6gN7Iq< z+TUbJde%JUt=)X|_NI_eS1H?}0B6)EvJ5C`7!Ad|a`n`xQ&|mz5RBTonoiXLtU=L^ zn-Y^N<;FO|8KQ!CIw2YBS`nPm9ILIAXV=<%FVPhQFBiY^3OQxX|o2D1;4U(f24G8|27=0YH)MxY6vrr*tJ z64X+<`9Hy2BedaIW?*)%^oWzQYwJhokOu$jy@S!=a4Cg&7# zzr~9xkhi*wx|Jt9ykiz)O{JYrIHo#~*JB$7C)vfAF%A>Kvdlsx%v}^$UK3}UEobib zF-=^edeY6IFEEPKQdfYT&}A?{Cx9@&=Jy8puoI}HK|41W@1LqtRJAJgW^7Adh5pca zSo=o|bAHWxzE3p@b(N$B8(Pe$Y zdk6Cm$Zz;5$@~k7@?wDQkErLzV{svJ&T|F?CZj%T;miOw0$hW~SzE*Qf3OT2Nt_81 z@oS*<7#e?c45)u{@4b@Eq9t*MEuQX2E!XdlhDTy?b(IDGxpb2lO`;su&-$_~`}O%* zWT4uCz|7Pd>EocbENI-Hr8r?`9tHcv(Tp!WB3i5prsg*4KTTLilhW918EM2=CNHIz z$9w-Ol#mw>ziWyiTXQBDB&x>L-oJ8I$fgha;ZlBDXUUfeLZ0>NhPRaccz1)EhG;Iw zl#qu9DV#d*52-RDYLD^in7@)@+Xv@Z*W)_ZG|_*@Kk^%qqgYPQd)~K^c)wWVT}q+mOa_RYc);N@b0AyD;wZ7*aAWf*A;<`TY&|2YM_$DIi65A2}C2cWrO_ z;Y-Q)f12X3`hKzshJ5UspSMW5#`PSGKPPP4fq_+3JX8gnW4GYC+ccRhdbC2Z^R9<5 z%SVxd4GfQ1QU|u3^F%cGUQe*^lxex>+U?`_T{I_UD&Pt&K2hCCbp?~dh{mM!2TFz^;C79i@W%3`#`*Ke{CUhPYKzx%woQv*)MCP=GvQ=*K4_-x=IWnI|z7myri>+xsZquPb851whJeRa!2H6M3==%di zgRXjrw^M`vf!<(gKvr_FndOe&h8quq+GIr!__JxZ4KDS` z#1elb1F{roTYHbRdA_IpaY>PRw9pUWDPn$inrb2PGyIkazSp%D77~!zBW|GaLRkkX z&GV$DaF&wt&MU2hrw#{#;Z^#gTRUS3#Sz1}*quCgn)aHQLR^c4VsMt*mdXutpHOCY zIz{|J=Vp^p(UKg}b>tDN2*hZ&@t_1iVM+hvVjBB}$Cdnt%jgEWBuh;_t@V<07YmNh zYC7Nl<19ekjdvF4sa50n%C)@Ms>M6k&mk;nG=*j4w87abxA?KIg&u{GIlA@92klsJEi_dy~Y10bg0Oek&agIXKBv=KxETT<1u}yL?ER=%yY3}9(2l^e+D`^;-kp# zJiT-K$T>e4B2QLmWn|vs{R=p<5G0!K^dwC?jrf0|Lpqi-?+W0Bp^i=AW;d74>n5oJYSI4 zIfUGR2)#wZ^vLnLxZV$EseO4-<>X%{uRy8x)j6rt?UCh!ZE1arMzlu*-=n;Hzror$ z-8}gb=H2y4YCnc|jJIk>Y_#pdvK`nW5vs<{!%gc0B`10yI?@Y0^6MK2SX&xJ8&M!9rrO*P;f{1GjaOi*gn)?QJaF(I`BCQI2a zFg>*Ly7spfiDIySZLtVaEYzQ*`qc;(`{gJGKsK$WAvsiTA{5Fp|8T77ovuclMl9MbdnTvl+NQ#NTT#qH-Id+g-u=qfWoe zu7mS;Uuigxiw>P4IxpnQlR;fD=WnpgMf)!pOAo19Crz+F0U(-9#yD6Rq>he5m2wu@ z8{`Ovw^T@5iLXS!D__Q%=FJz2P0>ottWipBmO7cDu>iVM(cqOV*@e0s!5D3`(S}kd z;3RJo6U4M+@?=H@MJkg4xS#0|FU02!`p zm^kI{gktIs&(fZWgt^3~fo`0?Kpo(yI4Jjz5sl8w?2LG*zZcQ2XcK!}u>-(@$6X}s zX0wm5V`(5UL^9=(i3K%iS?HI;>j;r>k=a_RlC*}~W$RnwzAd#HQX~rDG^znJ9np*Kc8}%S8?!u64~e`7`|ke=A+58k+w^n#!j&J`?NT zVOtjAQp@j-&{!`;bP_Wvyph`o-=;P%RYr2pIvB8o1J!lrsKaWhde}dxxTH?E!r9`g z3y@$hr@5b_T?W|&{V7h`{LN&%=w|AtyRn7itrH_RvjwR$=EWR)W@tkrV_(6P;C|O{ zk1Y`-YCu4-$$0X2#bjToRB-?Dwn~}p4aB}u|I~O z(A6ht;)I;k=jR)%zxX-Yrv35#0YDUr<6bssu{T_kmX-Kul6NA0$%=SdtflQBH- z%~jbvdR*suMRgE343>Ld1+kic;&MXDyK=*e^L$9Aql%5|z-X&nZ&HJI^8wnTh57~4 zhV>M5sRIOT1nbR5t}O7!b~sP&A9V_gL=45*U0&hNI7-kLiketE@kDkpc0|c8TC4ib z+@27xE_fc1Xe;_cj!#0$)wqF)6R<6T!lyQaJYJ@uJXMC4`#(SQ-x&8Z8qVF`SbX5k z5utXbM!2W|M+r)%k{yz}zp)9#*eI5z*NLPVL+xa=i5Gn+;i5c20eD49F3-k_X~!It zJU(^t-1P69$k$Hkf*rb!QlEs`S?P674t=PFS~!^}U%4KDKmrNEn1wdWuVB-o$ab1V!y zwqQ44OT5f8w&6*8)8zf$$#GtDh9w7VI33iNJGUJFGDG|vhj)N&7=E7R7GgHjf;iCI zD#ssN&?ojzt)b;6^)^T*Pp1Wt@Mg~~O+JyrAJZ*@kF>~#A^n{Njgmc$8e#5kWF&Ap zqwrs7LbpqMc#RiJg|@57J5GWzATdi^ZSi7wbsUn<#od{#*Gv%J2=E}LK&%L2HhA=A z6`NO*9CVvY{)E)kJ8h5>l)yUVdeLkdwT38^N5aECh748GHq#|9bL8faB(q)7JJmhY zTz&GemtFc}L%jGie3;aRNXT%SqGEJczd3KfJ2#NP;|#)WuD<;cf%Ie?21l#1X>{Wt zAJ*#$eSQ&1&3Fy3Rv^w}*M|Co+L*2^@@7$1ljubzWZyCn-_bToNptZ@EB;u9q?VA^ zVET%k`UbM>pJ-Iq{*!7yiRdKmuKjq8wYBsgfbx3p1BxiOZ{Bq9MY-pvlD@;}SC6}8 zPS!M8HfVljWpQwuvmsHIH`rftsPnaeEPvv|W7mH5%22Fd@;TkXnnk{US4vQGli;9Y>l&P0$0N#6))T#R%!VC^SXMq%(f|4 zJdLVtxXv2yiGPT4{Bup6ifP-_r;{E8>lMAh(2$QEis=kvGph=BG4PkgBmoGS ztizs-YfQMxMM+n{is#e0GzDN$e6?7W8xUoEnc>yt;45udgk8-b4%|$j6A9Cu)Wyx_ zE`N#+|Hex|{(}e5w;FY7PhKX3d_N*4gC#5y`e%klQXEkm7sharuw+BhBk#*im&xOiE(hbZk@Rdb|?lds?HnD(1D)MuTZPy;oE*)Pnwhah}sx6ZxP`*z<~^ zN>xeWf?M4q4fZKBo1#wF`aDHqp=b7yfBV8LRR+C~@kPRA^@JqN+hlmDW%wRZ=a^um192l;k%b*i7Dv{cTC7V#l0e`(318XSP^s2WVoq5G4O+>TO@`kVDv;)MNyh zrbuS(P6UKO`}I+nbz&sUr!OAuF3DD-_i&gQ;cqdCV98_2@esQTgkw3fPDYF}UHI_P z_7J2@#e`DdwfR|evF4e=v+Vk=Up1R^RNzw-61&KH_S2Fapk97@}Z1h!W zikYtYQ`A)@apT~~={x+fi0Hgs3kN2)b}P;9q7@BW= zK8OE-xuj4YrsqFvo57e ziqC9+S2G9Tsd?m+;(-$_LwSbtQA?-jvrR1j@YkRbm9mOv373ra_=qBP)fAe$etq_ZBK;XkNXp72xzc>s6k#1K zZKEy~88P^=UNEssE_`|2+L9}O`N~X?l=g@>HQYg~h z{Mz(p9$+X$z}4z|QeblrTgM9fj+mwTF2oum1Gt{3zOVO8jjiIww!@~@D@g=(4vf7U zsh0aRDm-9>|CN2i5%bni&7KGE{jo?Jc8%TbMO&apUn~~++>6vz;gdL*%3#6_kJd2* z|K$s^whq~TIcca0CjK7UkNd{p+$g^Tns?suX>ML=>BaR0vv@v8@V(Yz17^db++$V^ ze&wr!%rfhH&jQDd?P5_U@xTP9!bHw?4&edbD${#U)^$zRTPiXIZIib9Z1YE+DT5yO zrSn1Gf^CPZm9B&Uw%5L~fegey*exQj+AVn8LW(qZ+Uxh^UK(Dak!n(FuL`ln&*{dW z9qRheKA;#X9{B-C&T%)w~!7|| z!1-I_*1IzmU_eWz5@Vr>`p$t)qh~;C$KB^}5^3`wviQ(*U*Wn3I;Jt%_3K|t*E$S< z`Cnf4k##3kcm=Q60Z8<+S^L=5yw01tyYRt@sHzlw7En8W_w?9fJ^${6wX0te=A9L~ za7Shd;P_~3x(SiPyH-`6V^&Gs72(aE4@B(?4!LmqP{U;LI_DvT82h~w( z?OsB6NV}mPWnk*@4EgBU3>q)aQ-dbQVHe@(w^GJg2~D7g)$yD-LbD3mS?EYma&Ja< zVz{`KVjM96u*mo zGh{hQfK}(dO#I>&-e7O(13J@hM#My*wQ=c4uZ^>qSc4%3NeC7stcr2S>U zpWE;EOsXpav?o%d*duC-Mw*6uaz2-0&m029pd)-BxG@2Gf++ zM3F3P7UG75mWAD{q7M|a(UPb)6W0k0QhvQ$`d6!+Y4!t$8VNg`?bjEdgt4|pgnffL zI&DroiZjFV&!cg~D(?%E7K#Zt_b4-@osAIRNgfVqIs(m2cPcpA7YO@LPyFMXR@o|b z+`M>nxy}9C0;ls`s-mFrSpVB!B-qRhB!PBZ1sJ?nA1*#Ut&g=TK;3Z72dz!6UJxWG hB7=?+?y9DPFjs#h;&J?4|Bi?eGqLyHo2Fuv20^IJ+p1Zrs2Qqi)lOoRpf!Tpv_);&nnfwKTUFHF zGgT!<+pm89hVOG-&-FUj>viAfIp==Pxz7&=XJ(>LPs2q6008I>P)G{^fXw*cOHF>2 zX_Ol|xe5wqMpn94@#=xNsTr1syIDxaa{^NO!^^`Jn>X@1`uqFs1s6Ij4pS@+r|+L0 zX8ry9_p}#NcSRrzwlJ{-AU0+^006ED1EiK!=AXPx!F>6Ry5UgKNeJt55`K4>IPQF;Y-$}k@Fx3Ax6M*Ju=;vLOh z;vJm1v{b2=jbh+(HXB|ZZK%Roh;8VrV|_Y-bffc-qXQ^J@)$&j|60Or8K&HxKr#RI z{>B{&0JJ&rVX>XG?zrxUQVp+~bge`|)>bAXU9pi+`0W;42c{$Wo+5I|S#HH@N9ZLJ zP}<&0(~(4ZBKDpSDWsWvYDlccPQ{ldbTNEFF=D7MxmhQ&(rwKRe~$Gxl4l+hL;py7 zno`6o8+nvTmPjl5>E>ESl^!`9VEhuhzq~GVnx4)^Vq*)v z)VnuQU$WmE->t8;W(dZyy5{Zf_+I{@T%4ENbEf^v_xJ2(dQwxQgJ4g6$O0WHG+Gf`>$N~O`xysXDWL#b9PNTki$*p7 z&D$X}m(9d+$Z}P?(@Lzu_oUj1p8eg-IW5OBw@zn?K;4{LDlCJa&COjMo%rq5ex|SS z+xA6jq~BIhuQS9c@VuF67;Tp?T@P=mWyr@WIx3&gU7B(pi@00s(f&9ai@L8>vpF<{ zMP^7u-528c1No$9jt_WIFG6FD0@p}Pa3CA~Atji_ki6P=ztfnPD|11C9$E%%XF4fS z3Qk!wGfZLT=lnM`ZrciecbywMzfAUvCOopmA9wau;0|1pJ4s$L^X0nW6vA({_Y#Kg z)=5litiP>359HQX@oTF0Rx}7yqR?ZP7u;vmEp|jp`IPMIctqyj#%uXiRzoJ-)8KeU z@P1E32$0*%>w|_!;0MB|KZ6N5)z8;nM@Enoo;`?xCwrAUq2P_B6|RMcguOEw=HKq(;|@5r%nGZ-{9-xln}$ zJH>;Cj7aFQ3x=~Yh6fM^T!?8}B!x9GjnX3INog}$qhH0?LP+&|iChAg_w-qO@RYdN z5#J>DrVRCs5Xlv9Phn#`g^}fZ5k}XFo9MWfE69yeL)!Fmm$CMUY`)$%fx4Y^XNg>v z!0#hB1$&yc4XMY>x_YNq#m}@D6Qofgg2$DDg_~BEA{zBY`mvh(0QKtjHM|b0LTV?5 z?)@6O_TZ!sRv_>X`Rx)8P+d*?&W{lpOb1o?9MzgM^@{dZt7L61HC{}PHUhGFlPDr( zfcQw`tgTjHm&YAZYc|h!`~Y$UmB;S+4^0f*1PF;j7b99nh*SkI$ z)@=RokZE9N!7k71rmM3cP(F@hFQB$YMp!` z^qm+?ySwxlbZe0p9XSkxwGqZ*g{N^Lh zjCrvefsj>y8LVhl_26N)^{%$jS-Kj;u*wckaW2WIt#jqj!{PcXa-}ak%3C#cOor9k z3Tgfsk;mD^#m}FsCObVN#R6zX@bW~%pIL@^^;jKViGQ9STwM*AOOt&Z9&)KYlW@ft z5B(Js^*2)Y;!up`-%)VxG(e(de{*#h1h2-Q;;tyC1#R%8tGHTT7UBQDPPUuhw2}Su z|4(n-zEhC@j>0x3CWN~bJy02k4<1o+coyg_jC}3{Q`+mhb&y%!eyagPTEV7InTEg& zzc?uDrAo^2bf@J!TjXZa6$o10CfH?CdmOo=u6Pi?YRxVmTGd~h0sB2 zl@7&p3XR2`+$zotcSIqxVA>3@|3dpcGq&7MPQOCT$BC_aQ39o`J<@zD!A;IOb=7A) z-tt0==!AEh8a2S#WHVUJ4ImjN^#=X3L zjt=to{MR2D)my}u#N_V&-s8KXR|9C){n8mv?t9i$dVE(F`bIC^1OGCTo+>QAvszT> zs|xP$ILVW`)x2<=>`XmN|u5fax05c%*wHag_j;LLlVXIiV*`s zs*rGB%3MWnE=L*(7~aXRHf8D&KPS7VT{z2{#IC zDy2pAk)iqMN7#R(CK;a#SG~aO59ZYakV->-puP%N9k8!uerfBoJv9^(Ai))l`rUd8 zSLQGq^>PnLKo`0%^Lc-~o@2DXq-3oY*7#0QvE)Wa5BV1O{Rf;Rh(j=TURhUjGMjjh zf98hlSN+e#kr>Og^NpUAVQ>YVK3sQM_GnmN;c!UjoI{;;2oVdT`8Mh8cn2 zhcXxOG#ITi_JsX<4Y7)yDM}}2yE8~dt2XwE?y$iBffG|@{LQd;KR(8-+VPBDR@)`D zJzT0aNJSsSONonLZABfI*ZhYtHO*rHYoM50>jS zODoYjvgHL>M+wh%Rb$ba*NKCUu(#6DnJK+d*O69SxZZX(<6)D9w2y&THLQbMr6ezH zLnkXaeQ%vJJ@n0o+5qilErw4!b6~JKr>0HxL+s#nrG(+!Sx$9MJzS5)739SgRu> zzUr0yqW@627+fWjA zC1;qNOMCidNo@FR2UhFhsB%p1aq zQ<`@mQb!fuhZ^1`7?tX7e|hFf&uBuGVrfF%Ef;Py2}f`F6Wd_ z23P$WN*vH0^k;Za_ox>t-1;XgW(EZ~ywoIiQrS&g?g4B|y_N_LP-PLDK7S{jr2y}Q z{@|xvs;^7cZB9VLeC~E9>*Ubo#nXqb-M7;UC5VKZH%mXFsufmPxb2t?5A`zcKB}y6 z#!KLOsi;0mt-z^B8(H{WS3LcB#b=aCUP~M_cQ?g~uA`cV;mQJ(b#@UocYe&NF z{Tvr~^YMguSJ-a-(j(UX(&~}SYh`rBnJns>N*GS$Qp`=zZs2TW!hQVniVe5jE3cy` zqg>mqs}^GG@Yl=kJt8vrLSk#+`)C3m(HlFQVy43~1r z4YL!(U7Q)M!t%?_}chcJJWP-MeK1SVDxGPvc_!x z^1rLgwp%boxYXwrG+mtePoMjORAsKmjxDzwzRj`^mGR+D5b6AOe3!H9?8YNWdIq$u zJ7@xJma1s$9aEr#CtQ9{=w%}kpwnNRw4+*}YjyoYPdI)U2|uqWzD?|scKi#rEVu$u zNbs-04~-9iD$h3j8pO=5Fe*454K%4W7-VgwD5`o2>Hp4}&F()4(G_xYi^Yho9^bq1Acd>!-SY z#gJ-m5M#8{`LDv2jp-m`iu4%L`+S0tOETk%gH-n#{GEpV5`5DY5P9cm-BZv}jzX+p z)v53vx#^RaS`MeMYh*s@>dem^Bu8cvUwAy?xv`d%etESO0m9DG9JBlpklS^ z1+D1hLBf;a#*H#(nN|3H#`M`p1&m#vTkJy?7s9)#$RM9iZYT@7rqG~JM6j8>QB;aS z47RCLC-(6rgyr4(;4g-lWQ}TcThmKvzijCSjFYavWkfL4t90{;vjcZNfPhcj7JvYb zJ`UZXH8FzxY+?)$#l@*NDibg=vUUm26h@mm3A=(U+LhQOl3Bjr=pM?@Av-V)SlX8< zV}oer`o-LX%c(S?;CBL$9Vb=XYOQ z*aB3XA$yA2E@`qB{v%_43M{5+Bq*~>x>y<^UkG!ZG~$>2Ms7vLV8gvr08;i%S=%j* zm!|oBlK%~(b+Y{XPl_q|qL@t-s-qX%;~kr3yD4yyAK(^6eDAmmYxwT{((1}No*_v9 zGjx*faw$JwY)8!K@~X6_??M~R?$dnPM1Aa4zg*e5eW%;B(OoBD(I+?a_BE4J!P~m> zzQQAz{KmBN8Q^C(S_-O9X>Ag-W-IW+^g8Q2dJjcdYSTn^`eSrbr0t}D&%U!9IiRn( z$GG+ZnQnS8d)FgyQ7!_2QB9wB(^58!!9YcCif%6n1VvQ^EyzyJuT5vO+jc}=ZmfeC zFoAF3g04ExSl^?>E7^}a7Lvyt4E&U4Oj%rI^n+eq`_aJtOHh{(Spf6y^ZhS`tz6}- zOUE!M6CT3294NQ~*Fg*J{<2V*OkgT^T0N;p_0PKlyj9+vjU@kVm{5QL#!cnNoPD_I zEfWYv6LnNvfg)cNQXX`C9$e`zP%yEfdCRdMQ|8LJ9L6Xo)2*IDsyqNBDt3q`gyoRc zkY|K=Xb#k)xHO_!(m5=vD5Qlb4CJ+QUd#_J#83(uCdyg^4Q@@;p@owvk8DScr^yVY zePFpz*68C@(g<-{Y+=aw*-wIwJsq|qm>Y*5hcF`xM*SfoPqDu#)x0mpD(}k~hobAi9Sn}Bj#P&>ecFWG{1(fLJ z_h)xMKkXYNFth*CgV+e|P!D^7-!rcr`xQhI%}F81Y``nWx=&gKl`|gJq2i(+{vfRT zVp&T}_w89?AplH2%ttZ6wg(G5vc(XpyQ3)~kIV2JJ2gM*P1NSy%k>ug-KVSH7p(q` zPv({=lmD_fi5Wt7@^QHb|&7vF&ZFfqB9oQW-1ahk|L{2$ zuH}O05>QM_-!eef zYE8dH6-Xe8(%2-XUo(64X5S@kc*0IIB^{XlCO6BrSI^*ppjr9Bp7T^u2WDCwSDvfg zGQM0%(;>3|AFJv_G$I^4c5W;>D`KJ8sme3*ds=K2C9IF!L6QMG#CGeU(e`o?k60G> z8mhD~k5ImNW>)GZ4H*|30nyHHORKICK~mZX4Cj5J;1cQ>%A4~dX?i6hg6vK`p$AjyHeBS?dimx2TEO0SIZldwNV@P=e+^Ti4~?ihr;5_K?b=jDz2d_oB!3{+t?)Vv4w zX;AS_au%(kdJ5;-pyUaP7%VN_3SpnS@4=RWo2mo9j)@9ni~rc4!`faYpeKP(DvlE$ zM*nO|rpt9Sfr0Ar(+tIYO+H5VnX0QF%4Gf~p%lXv!Fg+!v&GyDkpa&~#gDrt3GlkF#ZD1F2~%Zrn8fph;?Yaozks>YfEw9JN~ee?`62!jUfZjC4^4eeno4ni#Rb z9#wgjShqyW!060fk_WOnIB+uTyK|#g#f?3dCf1}fS_^c>3G_I+&@{P%3g}34ZLs96 zP># zqPAFo%H3hHao5(b++K;*3l{t}dY7>xORId;O<>Alezwj3G&)Yj8%QNp{ZxYNv$yh? z(5sO^$?WEFz3!cn>9_!GslJrkdsi#4cB!5Z?3I1zcg8J?9l?8O|3s!i@Eg~BEFk@3 zI$}4cGq+ZM1+=B$`9YQ(xPR7C2>@xN6YrvRhf*BMh$E8at%480R~yhL~*$KyQxJ%mvkQ$GftpyJR3<&{zJYbY!am-DreHniCf zG*nMp_Bet$82%^?fWJ|NXcc9@Hxpo`1Ur8KP7`qcaxJdl!dE)Kl!|&qiHcKTHrtNw zM1;<|@0(xLr7L&*V?Km65=WlO@rY!TSIKt1p>hy!)|`cG?Of{+RO8IsS`(7XiShT- z@Sx9!y|Rv8FYDj&MHI~`bGSA|_ZmlBqrw_SOwq~QZk3WFALR^ zJyGcS-7)*qFkcHpVbTs{=90l6Uc(|Dt?HY1!*|CsS@VN4(jNHG^=7Y=HejP#TXpkS z`_G=n`PnLAN?|F|qz|%nE#(0RP__@Q-^y_(tf3Fb11vx|VoGe?NgHL*&{xcYpxF?? zz~wo!_1~StxE$kmbEGV7^!1#V=ub8CSLhmizs6+NX)!Q{jj`=Lo4FuA?ZtR3qW2vu z=-9nObAi&?x*Ps#JX1Gv@PEEd*=ZM7D6^$Q0hGna?=BJ6kKJAQr68WF5q7nZ7j#pZ z<&jr*!n!nkCt=NZ#C)r+iIJYV2^e8ljmU|CyOdoQ7>7s8Zz|Qb-_uxwcv6rWluk*h zkrL;C(9-vKs`h3^R_bFk6-B|@Tl|e4BYD^FPgmRhzcv2Q?*1kFc82wNI=Q{@?{3UM L*92Ls?GpQcGA{Cd diff --git a/public/images/pokemon/back/shiny/71.json b/public/images/pokemon/back/shiny/71.json index d40924360b0..7407c0befe1 100644 --- a/public/images/pokemon/back/shiny/71.json +++ b/public/images/pokemon/back/shiny/71.json @@ -1,1532 +1,306 @@ { - "textures": [ - { - "image": "71.png", - "format": "RGBA8888", - "size": { - "w": 333, - "h": 333 - }, - "scale": 1, - "frames": [ - { - "filename": "0015.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 70 - }, - "frame": { - "x": 0, - "y": 0, - "w": 69, - "h": 70 - } - }, - { - "filename": "0016.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 70 - }, - "frame": { - "x": 0, - "y": 0, - "w": 69, - "h": 70 - } - }, - { - "filename": "0051.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 70 - }, - "frame": { - "x": 0, - "y": 0, - "w": 69, - "h": 70 - } - }, - { - "filename": "0052.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 70 - }, - "frame": { - "x": 0, - "y": 0, - "w": 69, - "h": 70 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 70 - }, - "frame": { - "x": 69, - "y": 0, - "w": 69, - "h": 70 - } - }, - { - "filename": "0022.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 70 - }, - "frame": { - "x": 69, - "y": 0, - "w": 69, - "h": 70 - } - }, - { - "filename": "0057.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 70 - }, - "frame": { - "x": 69, - "y": 0, - "w": 69, - "h": 70 - } - }, - { - "filename": "0058.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 70 - }, - "frame": { - "x": 69, - "y": 0, - "w": 69, - "h": 70 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 67, - "h": 70 - }, - "frame": { - "x": 138, - "y": 0, - "w": 67, - "h": 70 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 67, - "h": 70 - }, - "frame": { - "x": 138, - "y": 0, - "w": 67, - "h": 70 - } - }, - { - "filename": "0039.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 67, - "h": 70 - }, - "frame": { - "x": 138, - "y": 0, - "w": 67, - "h": 70 - } - }, - { - "filename": "0040.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 67, - "h": 70 - }, - "frame": { - "x": 138, - "y": 0, - "w": 67, - "h": 70 - } - }, - { - "filename": "0033.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 67, - "h": 70 - }, - "frame": { - "x": 205, - "y": 0, - "w": 67, - "h": 70 - } - }, - { - "filename": "0034.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 67, - "h": 70 - }, - "frame": { - "x": 205, - "y": 0, - "w": 67, - "h": 70 - } - }, - { - "filename": "0069.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 67, - "h": 70 - }, - "frame": { - "x": 205, - "y": 0, - "w": 67, - "h": 70 - } - }, - { - "filename": "0070.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 67, - "h": 70 - }, - "frame": { - "x": 205, - "y": 0, - "w": 67, - "h": 70 - } - }, - { - "filename": "0017.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 69 - }, - "frame": { - "x": 0, - "y": 70, - "w": 69, - "h": 69 - } - }, - { - "filename": "0018.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 69 - }, - "frame": { - "x": 0, - "y": 70, - "w": 69, - "h": 69 - } - }, - { - "filename": "0053.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 69 - }, - "frame": { - "x": 0, - "y": 70, - "w": 69, - "h": 69 - } - }, - { - "filename": "0054.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 69 - }, - "frame": { - "x": 0, - "y": 70, - "w": 69, - "h": 69 - } - }, - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 69 - }, - "frame": { - "x": 69, - "y": 70, - "w": 69, - "h": 69 - } - }, - { - "filename": "0020.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 69 - }, - "frame": { - "x": 69, - "y": 70, - "w": 69, - "h": 69 - } - }, - { - "filename": "0055.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 69 - }, - "frame": { - "x": 69, - "y": 70, - "w": 69, - "h": 69 - } - }, - { - "filename": "0056.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 69, - "h": 69 - }, - "frame": { - "x": 69, - "y": 70, - "w": 69, - "h": 69 - } - }, - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 68, - "h": 69 - }, - "frame": { - "x": 138, - "y": 70, - "w": 68, - "h": 69 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 68, - "h": 69 - }, - "frame": { - "x": 138, - "y": 70, - "w": 68, - "h": 69 - } - }, - { - "filename": "0037.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 68, - "h": 69 - }, - "frame": { - "x": 138, - "y": 70, - "w": 68, - "h": 69 - } - }, - { - "filename": "0038.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 68, - "h": 69 - }, - "frame": { - "x": 138, - "y": 70, - "w": 68, - "h": 69 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 68, - "h": 69 - }, - "frame": { - "x": 206, - "y": 70, - "w": 68, - "h": 69 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 68, - "h": 69 - }, - "frame": { - "x": 206, - "y": 70, - "w": 68, - "h": 69 - } - }, - { - "filename": "0049.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 68, - "h": 69 - }, - "frame": { - "x": 206, - "y": 70, - "w": 68, - "h": 69 - } - }, - { - "filename": "0050.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 68, - "h": 69 - }, - "frame": { - "x": 206, - "y": 70, - "w": 68, - "h": 69 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 68, - "h": 69 - }, - "frame": { - "x": 0, - "y": 139, - "w": 68, - "h": 69 - } - }, - { - "filename": "0024.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 68, - "h": 69 - }, - "frame": { - "x": 0, - "y": 139, - "w": 68, - "h": 69 - } - }, - { - "filename": "0059.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 68, - "h": 69 - }, - "frame": { - "x": 0, - "y": 139, - "w": 68, - "h": 69 - } - }, - { - "filename": "0060.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 68, - "h": 69 - }, - "frame": { - "x": 0, - "y": 139, - "w": 68, - "h": 69 - } - }, - { - "filename": "0035.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 68, - "h": 69 - }, - "frame": { - "x": 68, - "y": 139, - "w": 68, - "h": 69 - } - }, - { - "filename": "0036.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 68, - "h": 69 - }, - "frame": { - "x": 68, - "y": 139, - "w": 68, - "h": 69 - } - }, - { - "filename": "0071.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 68, - "h": 69 - }, - "frame": { - "x": 68, - "y": 139, - "w": 68, - "h": 69 - } - }, - { - "filename": "0072.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 68, - "h": 69 - }, - "frame": { - "x": 68, - "y": 139, - "w": 68, - "h": 69 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 67, - "h": 68 - }, - "frame": { - "x": 136, - "y": 139, - "w": 67, - "h": 68 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 67, - "h": 68 - }, - "frame": { - "x": 136, - "y": 139, - "w": 67, - "h": 68 - } - }, - { - "filename": "0047.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 67, - "h": 68 - }, - "frame": { - "x": 136, - "y": 139, - "w": 67, - "h": 68 - } - }, - { - "filename": "0048.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 67, - "h": 68 - }, - "frame": { - "x": 136, - "y": 139, - "w": 67, - "h": 68 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 66, - "h": 69 - }, - "frame": { - "x": 203, - "y": 139, - "w": 66, - "h": 69 - } - }, - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 66, - "h": 69 - }, - "frame": { - "x": 203, - "y": 139, - "w": 66, - "h": 69 - } - }, - { - "filename": "0041.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 66, - "h": 69 - }, - "frame": { - "x": 203, - "y": 139, - "w": 66, - "h": 69 - } - }, - { - "filename": "0042.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 66, - "h": 69 - }, - "frame": { - "x": 203, - "y": 139, - "w": 66, - "h": 69 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 66, - "h": 69 - }, - "frame": { - "x": 136, - "y": 207, - "w": 66, - "h": 69 - } - }, - { - "filename": "0032.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 66, - "h": 69 - }, - "frame": { - "x": 136, - "y": 207, - "w": 66, - "h": 69 - } - }, - { - "filename": "0067.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 66, - "h": 69 - }, - "frame": { - "x": 136, - "y": 207, - "w": 66, - "h": 69 - } - }, - { - "filename": "0068.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 66, - "h": 69 - }, - "frame": { - "x": 136, - "y": 207, - "w": 66, - "h": 69 - } - }, - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 66, - "h": 68 - }, - "frame": { - "x": 202, - "y": 208, - "w": 66, - "h": 68 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 66, - "h": 68 - }, - "frame": { - "x": 202, - "y": 208, - "w": 66, - "h": 68 - } - }, - { - "filename": "0043.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 66, - "h": 68 - }, - "frame": { - "x": 202, - "y": 208, - "w": 66, - "h": 68 - } - }, - { - "filename": "0044.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 66, - "h": 68 - }, - "frame": { - "x": 202, - "y": 208, - "w": 66, - "h": 68 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 64, - "h": 67 - }, - "frame": { - "x": 268, - "y": 208, - "w": 64, - "h": 67 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 64, - "h": 67 - }, - "frame": { - "x": 268, - "y": 208, - "w": 64, - "h": 67 - } - }, - { - "filename": "0045.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 64, - "h": 67 - }, - "frame": { - "x": 268, - "y": 208, - "w": 64, - "h": 67 - } - }, - { - "filename": "0046.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 64, - "h": 67 - }, - "frame": { - "x": 268, - "y": 208, - "w": 64, - "h": 67 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 64, - "h": 67 - }, - "frame": { - "x": 269, - "y": 139, - "w": 64, - "h": 67 - } - }, - { - "filename": "0028.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 64, - "h": 67 - }, - "frame": { - "x": 269, - "y": 139, - "w": 64, - "h": 67 - } - }, - { - "filename": "0063.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 64, - "h": 67 - }, - "frame": { - "x": 269, - "y": 139, - "w": 64, - "h": 67 - } - }, - { - "filename": "0064.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 64, - "h": 67 - }, - "frame": { - "x": 269, - "y": 139, - "w": 64, - "h": 67 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 66, - "h": 68 - }, - "frame": { - "x": 0, - "y": 208, - "w": 66, - "h": 68 - } - }, - { - "filename": "0026.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 66, - "h": 68 - }, - "frame": { - "x": 0, - "y": 208, - "w": 66, - "h": 68 - } - }, - { - "filename": "0061.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 66, - "h": 68 - }, - "frame": { - "x": 0, - "y": 208, - "w": 66, - "h": 68 - } - }, - { - "filename": "0062.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 66, - "h": 68 - }, - "frame": { - "x": 0, - "y": 208, - "w": 66, - "h": 68 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 66, - "h": 68 - }, - "frame": { - "x": 66, - "y": 208, - "w": 66, - "h": 68 - } - }, - { - "filename": "0030.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 66, - "h": 68 - }, - "frame": { - "x": 66, - "y": 208, - "w": 66, - "h": 68 - } - }, - { - "filename": "0065.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 66, - "h": 68 - }, - "frame": { - "x": 66, - "y": 208, - "w": 66, - "h": 68 - } - }, - { - "filename": "0066.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 69, - "h": 70 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 66, - "h": 68 - }, - "frame": { - "x": 66, - "y": 208, - "w": 66, - "h": 68 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:f8d42fbc7e4634a1aafab7a9441fe58f:13cd2160ec05982d017bab80e7a8b69a:699363ed1732140836e97f90bcfd26b2$" - } -} + "textures": + [ + { + "image": "71.png", + "format": "RGB8888", + "size": { "w": 338, + "h": 269 }, + "scale": 1, + "frames": [ + { + "filename": "0001.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 0, "w": 66, "h": 68 }, + "frame": { "x": 272, "y": 0, "w": 66, "h": 68 } + }, + { + "filename": "0002.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 0, "w": 66, "h": 68 }, + "frame": { "x": 272, "y": 0, "w": 66, "h": 68 } + }, + { + "filename": "0003.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 66, "h": 68 }, + "frame": { "x": 0, "y": 68, "w": 66, "h": 68 } + }, + { + "filename": "0004.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 66, "h": 68 }, + "frame": { "x": 0, "y": 68, "w": 66, "h": 68 } + }, + { + "filename": "0005.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 2, "w": 64, "h": 67 }, + "frame": { "x": 128, "y": 136, "w": 64, "h": 67 } + }, + { + "filename": "0006.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 2, "w": 64, "h": 67 }, + "frame": { "x": 128, "y": 136, "w": 64, "h": 67 } + }, + { + "filename": "0007.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 2, "w": 64, "h": 68 }, + "frame": { "x": 263, "y": 135, "w": 64, "h": 68 } + }, + { + "filename": "0008.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 2, "w": 64, "h": 68 }, + "frame": { "x": 263, "y": 135, "w": 64, "h": 68 } + }, + { + "filename": "0009.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 62, "h": 66 }, + "frame": { "x": 128, "y": 203, "w": 62, "h": 66 } + }, + { + "filename": "0010.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 62, "h": 66 }, + "frame": { "x": 128, "y": 203, "w": 62, "h": 66 } + }, + { + "filename": "0011.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 3, "w": 65, "h": 67 }, + "frame": { "x": 198, "y": 135, "w": 65, "h": 67 } + }, + { + "filename": "0012.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 3, "w": 65, "h": 67 }, + "frame": { "x": 198, "y": 135, "w": 65, "h": 67 } + }, + { + "filename": "0013.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 2, "w": 66, "h": 67 }, + "frame": { "x": 198, "y": 68, "w": 66, "h": 67 } + }, + { + "filename": "0014.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 2, "w": 66, "h": 67 }, + "frame": { "x": 198, "y": 68, "w": 66, "h": 67 } + }, + { + "filename": "0015.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 68, "h": 68 }, + "frame": { "x": 0, "y": 0, "w": 68, "h": 68 } + }, + { + "filename": "0016.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 68, "h": 68 }, + "frame": { "x": 0, "y": 0, "w": 68, "h": 68 } + }, + { + "filename": "0017.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 0, "w": 68, "h": 68 }, + "frame": { "x": 68, "y": 0, "w": 68, "h": 68 } + }, + { + "filename": "0018.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 0, "w": 68, "h": 68 }, + "frame": { "x": 68, "y": 0, "w": 68, "h": 68 } + }, + { + "filename": "0019.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 0, "w": 68, "h": 68 }, + "frame": { "x": 136, "y": 0, "w": 68, "h": 68 } + }, + { + "filename": "0020.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 0, "w": 68, "h": 68 }, + "frame": { "x": 136, "y": 0, "w": 68, "h": 68 } + }, + { + "filename": "0021.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 68, "h": 68 }, + "frame": { "x": 204, "y": 0, "w": 68, "h": 68 } + }, + { + "filename": "0022.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 68, "h": 68 }, + "frame": { "x": 204, "y": 0, "w": 68, "h": 68 } + }, + { + "filename": "0023.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 2, "w": 66, "h": 67 }, + "frame": { "x": 264, "y": 68, "w": 66, "h": 67 } + }, + { + "filename": "0024.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 2, "w": 66, "h": 67 }, + "frame": { "x": 264, "y": 68, "w": 66, "h": 67 } + }, + { + "filename": "0025.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 2, "w": 64, "h": 68 }, + "frame": { "x": 0, "y": 136, "w": 64, "h": 68 } + }, + { + "filename": "0026.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 2, "w": 64, "h": 68 }, + "frame": { "x": 0, "y": 136, "w": 64, "h": 68 } + }, + { + "filename": "0027.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 62, "h": 66 }, + "frame": { "x": 256, "y": 203, "w": 62, "h": 66 } + }, + { + "filename": "0028.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 62, "h": 66 }, + "frame": { "x": 256, "y": 203, "w": 62, "h": 66 } + }, + { + "filename": "0029.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 2, "w": 64, "h": 68 }, + "frame": { "x": 64, "y": 136, "w": 64, "h": 68 } + }, + { + "filename": "0030.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 2, "w": 64, "h": 68 }, + "frame": { "x": 64, "y": 136, "w": 64, "h": 68 } + }, + { + "filename": "0031.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 2, "w": 64, "h": 67 }, + "frame": { "x": 192, "y": 202, "w": 64, "h": 67 } + }, + { + "filename": "0032.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 2, "w": 64, "h": 67 }, + "frame": { "x": 192, "y": 202, "w": 64, "h": 67 } + }, + { + "filename": "0033.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 66, "h": 68 }, + "frame": { "x": 66, "y": 68, "w": 66, "h": 68 } + }, + { + "filename": "0034.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 66, "h": 68 }, + "frame": { "x": 66, "y": 68, "w": 66, "h": 68 } + }, + { + "filename": "0035.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 0, "w": 66, "h": 68 }, + "frame": { "x": 132, "y": 68, "w": 66, "h": 68 } + }, + { + "filename": "0036.png", + "sourceSize": { "w": 69, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 0, "w": 66, "h": 68 }, + "frame": { "x": 132, "y": 68, "w": 66, "h": 68 }} + ], + + "meta": { + "app": "https://www.aseprite.org/", + "version": "1.3.7-x64" + } + } + ] + } \ No newline at end of file diff --git a/public/images/pokemon/back/shiny/71.png b/public/images/pokemon/back/shiny/71.png index 03244865da7b80be0f693c3f36fac7497dff2199..99663f744aca3fa385adabc9a6ef77d5f9bef4b3 100644 GIT binary patch literal 7433 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 O1<+L2Mb@ZVMgAXcG|&0~ From 89d38a3b6bae56d8e4e86a29ddad33908a9b3b56 Mon Sep 17 00:00:00 2001 From: flx-sta <50131232+flx-sta@users.noreply.github.com> Date: Thu, 1 Aug 2024 19:10:43 -0700 Subject: [PATCH 16/23] [Bug] fixed switch out bug when fainted single -> double (#3288) Mirror of PR #1333. Thanks to 0zuzu (https://github.com/0zuzu) for providing the original fix. Co-authored-by: 0zuzu <63069680+0zuzu@users.noreply.github.com> --- src/phases.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/phases.ts b/src/phases.ts index d77ff7c29df..dfadcaf2767 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -4449,9 +4449,10 @@ export class SwitchPhase extends BattlePhase { start() { super.start(); + const availablePartyMembers = this.scene.getParty().filter(p => !p.isFainted()); // Skip modal switch if impossible - if (this.isModal && !this.scene.getParty().filter(p => p.isAllowedInBattle() && !p.isActive(true)).length) { + if (this.isModal && (!availablePartyMembers.filter(p => !p.isActive(true)).length || (!this.scene.currentBattle.started && availablePartyMembers.length === 1))) { return super.end(); } @@ -4461,7 +4462,7 @@ export class SwitchPhase extends BattlePhase { } // Override field index to 0 in case of double battle where 2/3 remaining legal party members fainted at once - const fieldIndex = this.scene.currentBattle.getBattlerCount() === 1 || this.scene.getParty().filter(p => p.isAllowedInBattle()).length > 1 ? this.fieldIndex : 0; + const fieldIndex = this.scene.currentBattle.getBattlerCount() === 1 || availablePartyMembers.length > 1 ? this.fieldIndex : 0; this.scene.ui.setMode(Mode.PARTY, this.isModal ? PartyUiMode.FAINT_SWITCH : PartyUiMode.POST_BATTLE_SWITCH, fieldIndex, (slotIndex: integer, option: PartyOption) => { if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6) { From aeafe0fddd85fa46c1d7df256dda9276a18c409d Mon Sep 17 00:00:00 2001 From: "Amani H." <109637146+xsn34kzx@users.noreply.github.com> Date: Thu, 1 Aug 2024 22:20:27 -0400 Subject: [PATCH 17/23] [Refactor/Bug] Use Bit Shifting & Masking (#3278) --- src/data/pokemon-species.ts | 2 +- src/field/pokemon.ts | 4 +-- src/modifier/modifier.ts | 4 +-- src/system/game-data.ts | 24 +++++++-------- src/ui/pokemon-info-container.ts | 12 ++++---- src/ui/starter-select-ui-handler.ts | 6 ++-- src/utils.ts | 46 ++++++++--------------------- 7 files changed, 39 insertions(+), 59 deletions(-) diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 3b09f8f7698..bd13dd75473 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -435,7 +435,7 @@ export abstract class PokemonSpeciesForm { for (const moveId of moveset) { if (speciesEggMoves.hasOwnProperty(rootSpeciesId)) { const eggMoveIndex = speciesEggMoves[rootSpeciesId].findIndex(m => m === moveId); - if (eggMoveIndex > -1 && eggMoves & Math.pow(2, eggMoveIndex)) { + if (eggMoveIndex > -1 && (eggMoves & (1 << eggMoveIndex))) { continue; } } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 71e89d60cbd..2ed1b7e7355 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1389,8 +1389,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return false; } - const rand1 = Utils.binToDec(Utils.decToBin(this.id).substring(0, 16)); - const rand2 = Utils.binToDec(Utils.decToBin(this.id).substring(16, 32)); + const rand1 = (this.id & 0xFFFF0000) >>> 16; + const rand2 = (this.id & 0x0000FFFF); const E = this.scene.gameData.trainerId ^ this.scene.gameData.secretId; const F = rand1 ^ rand2; diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 230ebc9251d..91041dc7564 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1576,11 +1576,11 @@ export class PokemonNatureChangeModifier extends ConsumablePokemonModifier { const pokemon = args[0] as Pokemon; pokemon.natureOverride = this.nature; let speciesId = pokemon.species.speciesId; - pokemon.scene.gameData.dexData[speciesId].natureAttr |= Math.pow(2, this.nature + 1); + pokemon.scene.gameData.dexData[speciesId].natureAttr |= 1 << (this.nature + 1); while (pokemonPrevolutions.hasOwnProperty(speciesId)) { speciesId = pokemonPrevolutions[speciesId]; - pokemon.scene.gameData.dexData[speciesId].natureAttr |= Math.pow(2, this.nature + 1); + pokemon.scene.gameData.dexData[speciesId].natureAttr |= 1 << (this.nature + 1); } return true; diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 6b8f183e94a..161b5a2c49b 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -349,7 +349,7 @@ export class GameData { this.scene.ui.savingIcon.show(); const data = this.getSystemSaveData(); - const maxIntAttrValue = Math.pow(2, 31); + const maxIntAttrValue = 0x80000000; const systemData = JSON.stringify(data, (k: any, v: any) => typeof v === "bigint" ? v <= maxIntAttrValue ? Number(v) : v.toString() : v); localStorage.setItem(`data_${loggedInUser.username}`, encrypt(systemData, bypassLogin)); @@ -1163,7 +1163,7 @@ export class GameData { } const sessionData = useCachedSession ? this.parseSessionData(decrypt(localStorage.getItem(`sessionData${scene.sessionSlotId ? scene.sessionSlotId : ""}_${loggedInUser.username}`), bypassLogin)) : this.getSessionSaveData(scene); - const maxIntAttrValue = Math.pow(2, 31); + const maxIntAttrValue = 0x80000000; const systemData = useCachedSystem ? this.parseSystemData(decrypt(localStorage.getItem(`data_${loggedInUser.username}`), bypassLogin)) : this.getSystemSaveData(); const request = { @@ -1368,7 +1368,7 @@ export class GameData { const entry = data[defaultStarterSpecies[ds]] as DexEntry; entry.seenAttr = defaultStarterAttr; entry.caughtAttr = defaultStarterAttr; - entry.natureAttr = Math.pow(2, defaultStarterNatures[ds] + 1); + entry.natureAttr = 1 << (defaultStarterNatures[ds] + 1); for (const i in entry.ivs) { entry.ivs[i] = 10; } @@ -1435,10 +1435,10 @@ export class GameData { dexEntry.caughtAttr |= dexAttr; if (speciesStarters.hasOwnProperty(species.speciesId)) { this.starterData[species.speciesId].abilityAttr |= pokemon.abilityIndex !== 1 || pokemon.species.ability2 - ? Math.pow(2, pokemon.abilityIndex) + ? 1 << pokemon.abilityIndex : AbilityAttr.ABILITY_HIDDEN; } - dexEntry.natureAttr |= Math.pow(2, pokemon.nature + 1); + dexEntry.natureAttr |= 1 << (pokemon.nature + 1); const hasPrevolution = pokemonPrevolutions.hasOwnProperty(species.speciesId); const newCatch = !caughtAttr; @@ -1474,7 +1474,7 @@ export class GameData { } if (!hasPrevolution && (!pokemon.scene.gameMode.isDaily || hasNewAttr || fromEgg)) { - this.addStarterCandy(species, (1 * (pokemon.isShiny() ? 5 * Math.pow(2, pokemon.variant || 0) : 1)) * (fromEgg || pokemon.isBoss() ? 2 : 1)); + this.addStarterCandy(species, (1 * (pokemon.isShiny() ? 5 * (1 << (pokemon.variant ?? 0)) : 1)) * (fromEgg || pokemon.isBoss() ? 2 : 1)); } } @@ -1545,7 +1545,7 @@ export class GameData { this.starterData[speciesId].eggMoves = 0; } - const value = Math.pow(2, eggMoveIndex); + const value = 1 << eggMoveIndex; if (this.starterData[speciesId].eggMoves & value) { resolve(false); @@ -1637,7 +1637,7 @@ export class GameData { getSpeciesDefaultNature(species: PokemonSpecies): Nature { const dexEntry = this.dexData[species.speciesId]; for (let n = 0; n < 25; n++) { - if (dexEntry.natureAttr & Math.pow(2, n + 1)) { + if (dexEntry.natureAttr & (1 << (n + 1))) { return n as Nature; } } @@ -1645,7 +1645,7 @@ export class GameData { } getSpeciesDefaultNatureAttr(species: PokemonSpecies): integer { - return Math.pow(2, this.getSpeciesDefaultNature(species)); + return 1 << (this.getSpeciesDefaultNature(species)); } getDexAttrLuck(dexAttr: bigint): integer { @@ -1655,7 +1655,7 @@ export class GameData { getNaturesForAttr(natureAttr: integer): Nature[] { const ret: Nature[] = []; for (let n = 0; n < 25; n++) { - if (natureAttr & Math.pow(2, n + 1)) { + if (natureAttr & (1 << (n + 1))) { ret.push(n); } } @@ -1697,7 +1697,7 @@ export class GameData { } getFormAttr(formIndex: integer): bigint { - return BigInt(Math.pow(2, 7 + formIndex)); + return BigInt(1 << (7 + formIndex)); } consolidateDexData(dexData: DexData): void { @@ -1707,7 +1707,7 @@ export class GameData { entry.hatchedCount = 0; } if (!entry.hasOwnProperty("natureAttr") || (entry.caughtAttr && !entry.natureAttr)) { - entry.natureAttr = this.defaultDexData[k].natureAttr || Math.pow(2, Utils.randInt(25, 1)); + entry.natureAttr = this.defaultDexData[k].natureAttr || (1 << Utils.randInt(25, 1)); } } } diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index 1e958ae53b7..b2ee5a9164a 100644 --- a/src/ui/pokemon-info-container.ts +++ b/src/ui/pokemon-info-container.ts @@ -216,7 +216,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.pokemonGenderText.setShadowColor(getGenderColor(pokemon.gender, true)); this.pokemonGenderText.setVisible(true); - const newGender = BigInt(Math.pow(2, pokemon.gender)) * DexAttr.MALE; + const newGender = BigInt(1 << pokemon.gender) * DexAttr.MALE; this.pokemonGenderNewText.setText("(+)"); this.pokemonGenderNewText.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, this.scene.uiTheme)); this.pokemonGenderNewText.setShadowColor(getTextColor(TextStyle.SUMMARY_BLUE, true, this.scene.uiTheme)); @@ -229,7 +229,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { if (pokemon.species.forms?.[pokemon.formIndex]?.formName) { this.pokemonFormLabelText.setVisible(true); this.pokemonFormText.setVisible(true); - const newForm = BigInt(Math.pow(2, pokemon.formIndex)) * DexAttr.DEFAULT_FORM; + const newForm = BigInt(1 << pokemon.formIndex) * DexAttr.DEFAULT_FORM; if ((newForm & caughtAttr) === BigInt(0)) { this.pokemonFormLabelText.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, this.scene.uiTheme)); @@ -266,7 +266,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { */ const opponentPokemonOneNormalAbility = (pokemon.species.getAbilityCount() === 2); const opponentPokemonAbilityIndex = (opponentPokemonOneNormalAbility && pokemon.abilityIndex === 1) ? 2 : pokemon.abilityIndex; - const opponentPokemonAbilityAttr = Math.pow(2, opponentPokemonAbilityIndex); + const opponentPokemonAbilityAttr = 1 << opponentPokemonAbilityIndex; const rootFormHasHiddenAbility = pokemon.scene.gameData.starterData[pokemon.species.getRootSpeciesId()].abilityAttr & opponentPokemonAbilityAttr; @@ -281,7 +281,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.pokemonNatureText.setText(getNatureName(pokemon.getNature(), true, false, false, this.scene.uiTheme)); const dexNatures = pokemon.scene.gameData.dexData[pokemon.species.speciesId].natureAttr; - const newNature = Math.pow(2, pokemon.nature + 1); + const newNature = 1 << (pokemon.nature + 1); if (!(dexNatures & newNature)) { this.pokemonNatureLabelText.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, this.scene.uiTheme)); @@ -305,8 +305,8 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.pokemonShinyIcon.on("pointerover", () => (this.scene as BattleScene).ui.showTooltip(null, `${i18next.t("common:shinyOnHover")}${shinyDescriptor ? ` (${shinyDescriptor})` : ""}`, true)); this.pokemonShinyIcon.on("pointerout", () => (this.scene as BattleScene).ui.hideTooltip()); - const newShiny = BigInt(Math.pow(2, (pokemon.shiny ? 1 : 0))); - const newVariant = BigInt(Math.pow(2, pokemon.variant + 4)); + const newShiny = BigInt(1 << (pokemon.shiny ? 1 : 0)); + const newVariant = BigInt(1 << (pokemon.variant + 4)); this.pokemonShinyNewIcon.setText("(+)"); this.pokemonShinyNewIcon.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, this.scene.uiTheme)); diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 0b9210af646..08111908dda 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -2611,7 +2611,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.speciesStarterMoves.push(...levelMoves.filter(lm => lm[0] > 0 && lm[0] <= 5).map(lm => lm[1])); if (speciesEggMoves.hasOwnProperty(species.speciesId)) { for (let em = 0; em < 4; em++) { - if (this.scene.gameData.starterData[species.speciesId].eggMoves & Math.pow(2, em)) { + if (this.scene.gameData.starterData[species.speciesId].eggMoves & (1 << em)) { this.speciesStarterMoves.push(speciesEggMoves[species.speciesId][em]); } } @@ -2623,7 +2623,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { ? speciesMoveData as StarterMoveset : (speciesMoveData as StarterFormMoveData)[formIndex] : null; - const availableStarterMoves = this.speciesStarterMoves.concat(speciesEggMoves.hasOwnProperty(species.speciesId) ? speciesEggMoves[species.speciesId].filter((_, em: integer) => this.scene.gameData.starterData[species.speciesId].eggMoves & Math.pow(2, em)) : []); + const availableStarterMoves = this.speciesStarterMoves.concat(speciesEggMoves.hasOwnProperty(species.speciesId) ? speciesEggMoves[species.speciesId].filter((_, em: integer) => this.scene.gameData.starterData[species.speciesId].eggMoves & (1 << em)) : []); this.starterMoveset = (moveData || (this.speciesStarterMoves.slice(0, 4) as StarterMoveset)).filter(m => availableStarterMoves.find(sm => sm === m)) as StarterMoveset; // Consolidate move data if it contains an incompatible move if (this.starterMoveset.length < 4 && this.starterMoveset.length < availableStarterMoves.length) { @@ -2680,7 +2680,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { for (let em = 0; em < 4; em++) { const eggMove = hasEggMoves ? allMoves[speciesEggMoves[species.speciesId][em]] : null; - const eggMoveUnlocked = eggMove && this.scene.gameData.starterData[species.speciesId].eggMoves & Math.pow(2, em); + const eggMoveUnlocked = eggMove && this.scene.gameData.starterData[species.speciesId].eggMoves & (1 << em); this.pokemonEggMoveBgs[em].setFrame(Type[eggMove ? eggMove.type : Type.UNKNOWN].toString().toLowerCase()); this.pokemonEggMoveLabels[em].setText(eggMove && eggMoveUnlocked ? eggMove.name : "???"); } diff --git a/src/utils.ts b/src/utils.ts index 467d7601d38..ede0a4bd78a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -165,40 +165,20 @@ export function getPlayTimeString(totalSeconds: integer): string { return `${days.padStart(2, "0")}:${hours.padStart(2, "0")}:${minutes.padStart(2, "0")}:${seconds.padStart(2, "0")}`; } -export function binToDec(input: string): integer { - const place: integer[] = []; - const binary: string[] = []; - - let decimalNum = 0; - - for (let i = 0; i < input.length; i++) { - binary.push(input[i]); - place.push(Math.pow(2, i)); - decimalNum += place[i] * parseInt(binary[i]); - } - - return decimalNum; -} - -export function decToBin(input: integer): string { - let bin = ""; - let intNum = input; - while (intNum > 0) { - bin = intNum % 2 ? `1${bin}` : `0${bin}`; - intNum = Math.floor(intNum * 0.5); - } - - return bin; -} - -export function getIvsFromId(id: integer): integer[] { +/** + * Generates IVs from a given {@linkcode id} by extracting 5 bits at a time + * starting from the least significant bit up to the 30th most significant bit. + * @param id 32-bit number + * @returns An array of six numbers corresponding to 5-bit chunks from {@linkcode id} + */ +export function getIvsFromId(id: number): number[] { return [ - binToDec(decToBin(id).substring(0, 5)), - binToDec(decToBin(id).substring(5, 10)), - binToDec(decToBin(id).substring(10, 15)), - binToDec(decToBin(id).substring(15, 20)), - binToDec(decToBin(id).substring(20, 25)), - binToDec(decToBin(id).substring(25, 30)) + (id & 0x3E000000) >>> 25, + (id & 0x01F00000) >>> 20, + (id & 0x000F8000) >>> 15, + (id & 0x00007C00) >>> 10, + (id & 0x000003E0) >>> 5, + (id & 0x0000001F) ]; } From 5d3458b38ee3a2373f309ab2f2fa5a26c76ec88f Mon Sep 17 00:00:00 2001 From: Enoch Date: Fri, 2 Aug 2024 11:45:02 +0900 Subject: [PATCH 18/23] [Localization] Localize pokemon form change message and translate (#3275) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * localize form change message (test required) * Update src/locales/fr/pokemon-form.ts Co-authored-by: Lugiad' * Update src/locales/pt_BR/pokemon-form.ts Co-authored-by: José Ricardo Fleury Oliveira * Update src/locales/zh_CN/pokemon-form.ts Co-authored-by: mercurius-00 <80205689+mercurius-00@users.noreply.github.com> * Update src/locales/zh_TW/pokemon-form.ts Co-authored-by: mercurius-00 <80205689+mercurius-00@users.noreply.github.com> * Update src/locales/de/pokemon-form.ts Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> * Update src/locales/it/pokemon-form.ts Co-authored-by: Niccolò <123510358+NicusPulcis@users.noreply.github.com> --------- Co-authored-by: Lugiad' Co-authored-by: José Ricardo Fleury Oliveira Co-authored-by: mercurius-00 <80205689+mercurius-00@users.noreply.github.com> Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> Co-authored-by: Niccolò <123510358+NicusPulcis@users.noreply.github.com> --- src/data/pokemon-forms.ts | 14 +++++++------- src/data/pokemon-species.ts | 2 +- src/locales/de/config.ts | 3 ++- src/locales/de/pokemon-form.ts | 10 +++++++++- src/locales/en/config.ts | 3 ++- src/locales/en/pokemon-form.ts | 11 +++++++++-- src/locales/es/config.ts | 3 ++- src/locales/es/pokemon-form.ts | 11 +++++++++-- src/locales/fr/config.ts | 3 ++- src/locales/fr/pokemon-form.ts | 11 +++++++++-- src/locales/it/config.ts | 3 ++- src/locales/it/pokemon-form.ts | 11 +++++++++-- src/locales/ko/config.ts | 3 ++- src/locales/ko/pokemon-form.ts | 11 +++++++++-- src/locales/pt_BR/config.ts | 3 ++- src/locales/pt_BR/pokemon-form.ts | 12 ++++++++++-- src/locales/zh_CN/config.ts | 3 ++- src/locales/zh_CN/pokemon-form.ts | 11 +++++++++-- src/locales/zh_TW/config.ts | 3 ++- src/locales/zh_TW/pokemon-form.ts | 11 +++++++++-- 20 files changed, 108 insertions(+), 34 deletions(-) diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index 7391a92005f..93781063061 100644 --- a/src/data/pokemon-forms.ts +++ b/src/data/pokemon-forms.ts @@ -9,6 +9,7 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { TimeOfDay } from "#enums/time-of-day"; import { getPokemonNameWithAffix } from "#app/messages.js"; +import i18next from "i18next"; export enum FormChangeItem { NONE, @@ -357,22 +358,21 @@ export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger { export function getSpeciesFormChangeMessage(pokemon: Pokemon, formChange: SpeciesFormChange, preName: string): string { const isMega = formChange.formKey.indexOf(SpeciesFormKey.MEGA) > -1; const isGmax = formChange.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1; - const isEmax = formChange.formKey.indexOf("eternamax") > -1; + const isEmax = formChange.formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1; const isRevert = !isMega && formChange.formKey === pokemon.species.forms[0].formKey; - const prefix = !pokemon.isPlayer() ? pokemon.hasTrainer() ? "Foe " : "Wild " : "Your "; if (isMega) { - return `${prefix}${preName} Mega Evolved\ninto ${pokemon.name}!`; + return i18next.t("battlePokemonForm:megaChange", { preName, pokemonName: pokemon.name }); } if (isGmax) { - return `${prefix}${preName} Gigantamaxed\ninto ${pokemon.name}!`; + return i18next.t("battlePokemonForm:gigantamaxChange", { preName, pokemonName: pokemon.name }); } if (isEmax) { - return `${prefix}${preName} Eternamaxed\ninto ${pokemon.name}!`; + return i18next.t("battlePokemonForm:eternamaxChange", { preName, pokemonName: pokemon.name }); } if (isRevert) { - return `${prefix}${getPokemonNameWithAffix(pokemon)} reverted\nto its original form!`; + return i18next.t("battlePokemonForm:revertChange", { pokemonName: getPokemonNameWithAffix(pokemon) }); } - return `${prefix}${preName} changed form!`; + return i18next.t("battlePokemonForm:formChange", { preName }); } /** diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index bd13dd75473..77134cca190 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -630,7 +630,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali } if (key) { - return i18next.t(`pokemonForm:${key}`, {pokemonName: this.name}); + return i18next.t(`battlePokemonForm:${key}`, {pokemonName: this.name}); } } return this.name; diff --git a/src/locales/de/config.ts b/src/locales/de/config.ts index b01f8343b29..d0779c9eec4 100644 --- a/src/locales/de/config.ts +++ b/src/locales/de/config.ts @@ -36,7 +36,7 @@ import { move } from "./move"; import { nature } from "./nature"; import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; -import { pokemonForm } from "./pokemon-form"; +import { pokemonForm, battlePokemonForm } from "./pokemon-form"; import { pokemonInfo } from "./pokemon-info"; import { pokemonInfoContainer } from "./pokemon-info-container"; import { pokemonSummary } from "./pokemon-summary"; @@ -62,6 +62,7 @@ export const deConfig = { battle: battle, battleInfo: battleInfo, battleMessageUiHandler: battleMessageUiHandler, + battlePokemonForm: battlePokemonForm, battlerTags: battlerTags, berry: berry, bgmName: bgmName, diff --git a/src/locales/de/pokemon-form.ts b/src/locales/de/pokemon-form.ts index ee4e3a8f9be..53ecc310411 100644 --- a/src/locales/de/pokemon-form.ts +++ b/src/locales/de/pokemon-form.ts @@ -1,6 +1,6 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales"; -export const pokemonForm: SimpleTranslationEntries = { +export const battlePokemonForm: SimpleTranslationEntries = { // Battle forms "mega": "Mega-{{pokemonName}}", "mega-x": "Mega-{{pokemonName}} X", @@ -9,6 +9,14 @@ export const pokemonForm: SimpleTranslationEntries = { "gigantamax": "G-Dyna-{{pokemonName}}", "eternamax": "U-Dyna-{{pokemonName}}", + "megaChange": "{{preName}} hat sich zu {{pokemonName}} mega-entwickelt!", + "gigantamaxChange": "{{preName}} hat sich zu {{pokemonName}} gigadynamaximiert!", + "eternamaxChange": "{{preName}} hat sich zu {{pokemonName}} unendynamaximiert!", + "revertChange": "{{pokemonName}} hat seine ursprüngliche Form zurückerlangt!", + "formChange": "{{preName}} hat seine Form geändert!", +} as const; + +export const pokemonForm: SimpleTranslationEntries = { // Starters forms // 1G "pikachuCosplay": "Cosplay", diff --git a/src/locales/en/config.ts b/src/locales/en/config.ts index ee180163eb1..a98dd750fbe 100644 --- a/src/locales/en/config.ts +++ b/src/locales/en/config.ts @@ -39,7 +39,7 @@ import { nature } from "./nature"; import { partyUiHandler } from "./party-ui-handler"; import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; -import { pokemonForm } from "./pokemon-form"; +import { pokemonForm, battlePokemonForm } from "./pokemon-form"; import { pokemonInfo } from "./pokemon-info"; import { pokemonInfoContainer } from "./pokemon-info-container"; import { pokemonSummary } from "./pokemon-summary"; @@ -62,6 +62,7 @@ export const enConfig = { battle: battle, battleInfo: battleInfo, battleMessageUiHandler: battleMessageUiHandler, + battlePokemonForm: battlePokemonForm, battlerTags: battlerTags, berry: berry, bgmName: bgmName, diff --git a/src/locales/en/pokemon-form.ts b/src/locales/en/pokemon-form.ts index b80819d5c64..e8d6fb8df4a 100644 --- a/src/locales/en/pokemon-form.ts +++ b/src/locales/en/pokemon-form.ts @@ -1,7 +1,6 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales"; -export const pokemonForm: SimpleTranslationEntries = { - // Battle forms +export const battlePokemonForm: SimpleTranslationEntries = { "mega": "Mega {{pokemonName}}", "mega-x": "Mega {{pokemonName}} X", "mega-y": "Mega {{pokemonName}} Y", @@ -9,6 +8,14 @@ export const pokemonForm: SimpleTranslationEntries = { "gigantamax": "G-Max {{pokemonName}}", "eternamax": "E-Max {{pokemonName}}", + "megaChange": "{{preName}} Mega Evolved\ninto {{pokemonName}}!", + "gigantamaxChange": "{{preName}} Gigantamaxed\ninto {{pokemonName}}!", + "eternamaxChange": "{{preName}} Eternamaxed\ninto {{pokemonName}}!", + "revertChange": "{{pokemonName}} reverted\nto its original form!", + "formChange": "{{preName}} changed form!", +} as const; + +export const pokemonForm: SimpleTranslationEntries = { // Starters forms // 1G "pikachuCosplay": "Cosplay", diff --git a/src/locales/es/config.ts b/src/locales/es/config.ts index ed2477aa14d..ce9ad19aac3 100644 --- a/src/locales/es/config.ts +++ b/src/locales/es/config.ts @@ -36,7 +36,7 @@ import { move } from "./move"; import { nature } from "./nature"; import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; -import { pokemonForm } from "./pokemon-form"; +import { pokemonForm, battlePokemonForm } from "./pokemon-form"; import { pokemonInfo } from "./pokemon-info"; import { pokemonInfoContainer } from "./pokemon-info-container"; import { pokemonSummary } from "./pokemon-summary"; @@ -62,6 +62,7 @@ export const esConfig = { battle: battle, battleInfo: battleInfo, battleMessageUiHandler: battleMessageUiHandler, + battlePokemonForm: battlePokemonForm, battlerTags: battlerTags, berry: berry, bgmName: bgmName, diff --git a/src/locales/es/pokemon-form.ts b/src/locales/es/pokemon-form.ts index 415593b0446..96e40bcfbbd 100644 --- a/src/locales/es/pokemon-form.ts +++ b/src/locales/es/pokemon-form.ts @@ -1,7 +1,6 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales"; -export const pokemonForm: SimpleTranslationEntries = { - // Battle forms +export const battlePokemonForm: SimpleTranslationEntries = { "mega": "Mega {{pokemonName}}", "mega-x": "Mega {{pokemonName}} X", "mega-y": "Mega {{pokemonName}} Y", @@ -9,6 +8,14 @@ export const pokemonForm: SimpleTranslationEntries = { "gigantamax": "G-Max {{pokemonName}}", "eternamax": "E-Max {{pokemonName}}", + "megaChange": "{{preName}} Mega Evolved\ninto {{pokemonName}}!", + "gigantamaxChange": "{{preName}} Gigantamaxed\ninto {{pokemonName}}!", + "eternamaxChange": "{{preName}} Eternamaxed\ninto {{pokemonName}}!", + "revertChange": "{{pokemonName}} reverted\nto its original form!", + "formChange": "{{preName}} changed form!", +} as const; + +export const pokemonForm: SimpleTranslationEntries = { // Starters forms // 1G "pikachuCosplay": "Coqueta", diff --git a/src/locales/fr/config.ts b/src/locales/fr/config.ts index 153e9edcf1d..246ae9a073f 100644 --- a/src/locales/fr/config.ts +++ b/src/locales/fr/config.ts @@ -36,7 +36,7 @@ import { move } from "./move"; import { nature } from "./nature"; import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; -import { pokemonForm } from "./pokemon-form"; +import { pokemonForm, battlePokemonForm } from "./pokemon-form"; import { pokemonInfo } from "./pokemon-info"; import { pokemonInfoContainer } from "./pokemon-info-container"; import { pokemonSummary } from "./pokemon-summary"; @@ -62,6 +62,7 @@ export const frConfig = { battle: battle, battleInfo: battleInfo, battleMessageUiHandler: battleMessageUiHandler, + battlePokemonForm: battlePokemonForm, battlerTags: battlerTags, berry: berry, bgmName: bgmName, diff --git a/src/locales/fr/pokemon-form.ts b/src/locales/fr/pokemon-form.ts index 09714f80f3b..f96931fd0e9 100644 --- a/src/locales/fr/pokemon-form.ts +++ b/src/locales/fr/pokemon-form.ts @@ -1,7 +1,6 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales"; -export const pokemonForm: SimpleTranslationEntries = { - // Battle forms +export const battlePokemonForm: SimpleTranslationEntries = { "mega": "Méga-{{pokemonName}}", "mega-x": "Méga-{{pokemonName}} X", "mega-y": "Méga-{{pokemonName}} Y", @@ -9,6 +8,14 @@ export const pokemonForm: SimpleTranslationEntries = { "gigantamax": "{{pokemonName}} Gigamax", "eternamax": "{{pokemonName}} Infinimax", + "megaChange": "{{preName}} méga-évolue\nen {{pokemonName}} !", + "gigantamaxChange": "{{preName}} se gigamaxe\nen {{pokemonName}} !", + "eternamaxChange": "{{preName}} devient\n{{pokemonName}} !", + "revertChange": "{{pokemonName}} retourne\nà sa forme initiale !", + "formChange": "{{preName}} change de forme !", +} as const; + +export const pokemonForm: SimpleTranslationEntries = { // Starters forms // 1G "pikachuCosplay": "Cosplayeur", diff --git a/src/locales/it/config.ts b/src/locales/it/config.ts index a6cb95c20eb..ceb52665796 100644 --- a/src/locales/it/config.ts +++ b/src/locales/it/config.ts @@ -36,7 +36,7 @@ import { move } from "./move"; import { nature } from "./nature"; import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; -import { pokemonForm } from "./pokemon-form"; +import { pokemonForm, battlePokemonForm } from "./pokemon-form"; import { pokemonInfo } from "./pokemon-info"; import { pokemonInfoContainer } from "./pokemon-info-container"; import { pokemonSummary } from "./pokemon-summary"; @@ -62,6 +62,7 @@ export const itConfig = { battle: battle, battleInfo: battleInfo, battleMessageUiHandler: battleMessageUiHandler, + battlePokemonForm: battlePokemonForm, battlerTags: battlerTags, berry: berry, bgmName: bgmName, diff --git a/src/locales/it/pokemon-form.ts b/src/locales/it/pokemon-form.ts index 1831e1f600e..eb5d132bacd 100644 --- a/src/locales/it/pokemon-form.ts +++ b/src/locales/it/pokemon-form.ts @@ -1,7 +1,6 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales"; -export const pokemonForm: SimpleTranslationEntries = { - // Battle forms +export const battlePokemonForm: SimpleTranslationEntries = { "mega": "Mega {{pokemonName}}", "mega-x": "Mega {{pokemonName}} X", "mega-y": "Mega {{pokemonName}} Y", @@ -9,6 +8,14 @@ export const pokemonForm: SimpleTranslationEntries = { "gigantamax": "GigaMax {{pokemonName}}", "eternamax": "EternaMax {{pokemonName}}", + "megaChange": "{{preName}} si evolve\nin {{pokemonName}}!", + "gigantamaxChange": "{{preName}} si Gigamaxxizza\nin {{pokemonName}}!", + "eternamaxChange": "{{preName}} si Dynamaxxa infinitamente\nin {{pokemonName}}!", + "revertChange": "{{pokemonName}} è tornato\nalla sua forma originaria!", + "formChange": "{{preName}} ha cambiato forma!", +} as const; + +export const pokemonForm: SimpleTranslationEntries = { // Starters forms // 1G "pikachuCosplay": "Cosplay", diff --git a/src/locales/ko/config.ts b/src/locales/ko/config.ts index f3017dd350e..114950a4d35 100644 --- a/src/locales/ko/config.ts +++ b/src/locales/ko/config.ts @@ -1,4 +1,3 @@ -import { pokemonForm } from "./pokemon-form"; import { ability } from "./ability"; import { abilityTriggers } from "./ability-trigger"; import { arenaFlyout } from "./arena-flyout"; @@ -37,6 +36,7 @@ import { move } from "./move"; import { nature } from "./nature"; import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; +import { pokemonForm, battlePokemonForm } from "./pokemon-form"; import { pokemonInfo } from "./pokemon-info"; import { pokemonInfoContainer } from "./pokemon-info-container"; import { pokemonSummary } from "./pokemon-summary"; @@ -62,6 +62,7 @@ export const koConfig = { battle: battle, battleInfo: battleInfo, battleMessageUiHandler: battleMessageUiHandler, + battlePokemonForm: battlePokemonForm, battlerTags: battlerTags, berry: berry, bgmName: bgmName, diff --git a/src/locales/ko/pokemon-form.ts b/src/locales/ko/pokemon-form.ts index 06bc743f1a1..78c9a762233 100644 --- a/src/locales/ko/pokemon-form.ts +++ b/src/locales/ko/pokemon-form.ts @@ -1,7 +1,6 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales"; -export const pokemonForm: SimpleTranslationEntries = { - // Battle forms +export const battlePokemonForm: SimpleTranslationEntries = { "mega": "메가{{pokemonName}}", "mega-x": "메가{{pokemonName}}X", "mega-y": "메가{{pokemonName}}Y", @@ -9,6 +8,14 @@ export const pokemonForm: SimpleTranslationEntries = { "gigantamax": "거다이맥스 {{pokemonName}}", "eternamax": "무한다이맥스 {{pokemonName}}", + "megaChange": "{{preName}}[[는]]\n{{pokemonName}}[[로]] 메가진화했다!", + "gigantamaxChange": "{{preName}}[[는]]\n{{pokemonName}}가 되었다!", + "eternamaxChange": "{{preName}}[[는]]\n{{pokemonName}}가 되었다!", + "revertChange": "{{pokemonName}}[[는]]\n원래 모습으로 되돌아왔다!", + "formChange": "{{preName}}[[는]]\n다른 모습으로 변화했다!", +} as const; + +export const pokemonForm: SimpleTranslationEntries = { // Starters forms // 1G "pikachuCosplay": "옷갈아입기", diff --git a/src/locales/pt_BR/config.ts b/src/locales/pt_BR/config.ts index ce4576646c2..5f7582dca63 100644 --- a/src/locales/pt_BR/config.ts +++ b/src/locales/pt_BR/config.ts @@ -39,7 +39,7 @@ import { nature } from "./nature"; import { partyUiHandler } from "./party-ui-handler"; import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; -import { pokemonForm } from "./pokemon-form"; +import { pokemonForm, battlePokemonForm } from "./pokemon-form"; import { pokemonInfo } from "./pokemon-info"; import { pokemonInfoContainer } from "./pokemon-info-container"; import { pokemonSummary } from "./pokemon-summary"; @@ -62,6 +62,7 @@ export const ptBrConfig = { battle: battle, battleInfo: battleInfo, battleMessageUiHandler: battleMessageUiHandler, + battlePokemonForm: battlePokemonForm, battlerTags: battlerTags, berry: berry, bgmName: bgmName, diff --git a/src/locales/pt_BR/pokemon-form.ts b/src/locales/pt_BR/pokemon-form.ts index 04d362e7538..6c7c649862a 100644 --- a/src/locales/pt_BR/pokemon-form.ts +++ b/src/locales/pt_BR/pokemon-form.ts @@ -1,7 +1,6 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales"; -export const pokemonForm: SimpleTranslationEntries = { - // Battle forms +export const battlePokemonForm: SimpleTranslationEntries = { "mega": "Mega {{pokemonName}}", "mega-x": "Mega {{pokemonName}} X", "mega-y": "Mega {{pokemonName}} Y", @@ -9,6 +8,15 @@ export const pokemonForm: SimpleTranslationEntries = { "gigantamax": "G-Max {{pokemonName}}", "eternamax": "E-Max {{pokemonName}}", + "megaChange": "{{preName}} Mega Evoluiu\npara {{pokemonName}}!", + "gigantamaxChange": "{{preName}} Gigantamaxou\npara {{pokemonName}}!", + "eternamaxChange": "{{preName}} Eternamaxou\npara {{pokemonName}}!", + "revertChange": "{{pokemonName}} voltou\npara sua forma original!", + "formChange": "{{preName}} mudou de forma!", +} as const; + +export const pokemonForm: SimpleTranslationEntries = { + // Starters forms // 1G "pikachuCosplay": "Cosplay", diff --git a/src/locales/zh_CN/config.ts b/src/locales/zh_CN/config.ts index 8f00168c58c..24c8b870ffa 100644 --- a/src/locales/zh_CN/config.ts +++ b/src/locales/zh_CN/config.ts @@ -36,7 +36,7 @@ import { move } from "./move"; import { nature } from "./nature"; import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; -import { pokemonForm } from "./pokemon-form"; +import { pokemonForm, battlePokemonForm } from "./pokemon-form"; import { pokemonInfo } from "./pokemon-info"; import { pokemonInfoContainer } from "./pokemon-info-container"; import { pokemonSummary } from "./pokemon-summary"; @@ -62,6 +62,7 @@ export const zhCnConfig = { battle: battle, battleInfo: battleInfo, battleMessageUiHandler: battleMessageUiHandler, + battlePokemonForm: battlePokemonForm, battlerTags: battlerTags, berry: berry, bgmName: bgmName, diff --git a/src/locales/zh_CN/pokemon-form.ts b/src/locales/zh_CN/pokemon-form.ts index 9cba58838b6..8fb82712e64 100644 --- a/src/locales/zh_CN/pokemon-form.ts +++ b/src/locales/zh_CN/pokemon-form.ts @@ -1,7 +1,6 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales"; -export const pokemonForm: SimpleTranslationEntries = { - // Battle forms +export const battlePokemonForm: SimpleTranslationEntries = { "mega": "Mega {{pokemonName}}", "mega-x": "Mega {{pokemonName}} X", "mega-y": "Mega {{pokemonName}} Y", @@ -9,6 +8,14 @@ export const pokemonForm: SimpleTranslationEntries = { "gigantamax": "超极巨{{pokemonName}}", "eternamax": "无极巨{{pokemonName}}", + "megaChange": "{{preName}}超级进化成了\n{{pokemonName}}!", + "gigantamaxChange": "{{preName}}超极巨化成了\n{{pokemonName}}!", + "eternamaxChange": "{{preName}}无极巨化成了\n{{pokemonName}}!", + "revertChange": "{{pokemonName}}变回了\n原本的样子!", + "formChange": "{{preName}}变成其他样子了。", +} as const; + +export const pokemonForm: SimpleTranslationEntries = { // Starters forms // 1G "pikachuCosplay": "服装", diff --git a/src/locales/zh_TW/config.ts b/src/locales/zh_TW/config.ts index 7a974d58fc3..004ed1da1ab 100644 --- a/src/locales/zh_TW/config.ts +++ b/src/locales/zh_TW/config.ts @@ -36,7 +36,7 @@ import { move } from "./move"; import { nature } from "./nature"; import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; -import { pokemonForm } from "./pokemon-form"; +import { pokemonForm, battlePokemonForm } from "./pokemon-form"; import { pokemonInfo } from "./pokemon-info"; import { pokemonInfoContainer } from "./pokemon-info-container"; import { pokemonSummary } from "./pokemon-summary"; @@ -62,6 +62,7 @@ export const zhTwConfig = { battle: battle, battleInfo: battleInfo, battleMessageUiHandler: battleMessageUiHandler, + battlePokemonForm: battlePokemonForm, battlerTags: battlerTags, berry: berry, bgmName: bgmName, diff --git a/src/locales/zh_TW/pokemon-form.ts b/src/locales/zh_TW/pokemon-form.ts index 2ab936b36da..aa30840fcc0 100644 --- a/src/locales/zh_TW/pokemon-form.ts +++ b/src/locales/zh_TW/pokemon-form.ts @@ -1,7 +1,6 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales"; -export const pokemonForm: SimpleTranslationEntries = { - // Battle forms +export const battlePokemonForm: SimpleTranslationEntries = { "mega": "Mega {{pokemonName}}", "mega-x": "Mega {{pokemonName}} X", "mega-y": "Mega {{pokemonName}} Y", @@ -9,6 +8,14 @@ export const pokemonForm: SimpleTranslationEntries = { "gigantamax": "G-Max {{pokemonName}}", "eternamax": "E-Max {{pokemonName}}", + "megaChange": "{{preName}}超級進化成了\n{{pokemonName}}!", + "gigantamaxChange": "{{preName}}超極巨化成了\n{{pokemonName}}!", + "eternamaxChange": "{{preName}}無極巨化成了\n{{pokemonName}}!", + "revertChange": "{{pokemonName}}變回了\n原本的樣子!", + "formChange": "{{preName}}變為其他樣子了。", +} as const; + +export const pokemonForm: SimpleTranslationEntries = { // Starters forms // 1G "pikachuCosplay": "Cosplay", From e042dafcec5813b79ca0c0abfda8a0984617c13b Mon Sep 17 00:00:00 2001 From: Leo Kim <47556641+KimJeongSun@users.noreply.github.com> Date: Fri, 2 Aug 2024 11:46:04 +0900 Subject: [PATCH 19/23] [Bug] fix sort by cost bug in starter select (#3294) --- src/ui/starter-select-ui-handler.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 08111908dda..4b1da435d52 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -1966,6 +1966,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.starterContainer.forEach(container => { container.setVisible(false); + container.cost = this.scene.gameData.getSpeciesStarterValue(container.species.speciesId); + // First, ensure you have the caught attributes for the species else default to bigint 0 const caughtVariants = this.scene.gameData.dexData[container.species.speciesId]?.caughtAttr || BigInt(0); From 9655ddc11792cc3f6d958007385d2f65db71d1cb Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Thu, 1 Aug 2024 21:50:09 -0700 Subject: [PATCH 20/23] [Test] Remove held items and abilities from test (#3295) --- src/test/moves/octolock.test.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/test/moves/octolock.test.ts b/src/test/moves/octolock.test.ts index 6d5f404d2f4..8ef161d2131 100644 --- a/src/test/moves/octolock.test.ts +++ b/src/test/moves/octolock.test.ts @@ -8,7 +8,7 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { SPLASH_ONLY } from "../utils/testUtils"; +import { removeEnemyHeldItems, SPLASH_ONLY } from "../utils/testUtils"; describe("Moves - Octolock", () => { describe("integration tests", () => { @@ -32,15 +32,16 @@ describe("Moves - Octolock", () => { game.override.enemySpecies(Species.RATTATA); game.override.enemyMoveset(SPLASH_ONLY); - game.override.enemyAbility(Abilities.NONE); + game.override.enemyAbility(Abilities.BALL_FETCH); game.override.startingLevel(2000); game.override.moveset([Moves.OCTOLOCK, Moves.SPLASH]); - game.override.ability(Abilities.NONE); + game.override.ability(Abilities.BALL_FETCH); }); it("Reduces DEf and SPDEF by 1 each turn", { timeout: 10000 }, async () => { await game.startBattle([Species.GRAPPLOCT]); + removeEnemyHeldItems(game.scene); const enemyPokemon = game.scene.getEnemyField(); @@ -62,6 +63,7 @@ describe("Moves - Octolock", () => { it("Traps the target pokemon", { timeout: 10000 }, async () => { await game.startBattle([Species.GRAPPLOCT]); + removeEnemyHeldItems(game.scene); const enemyPokemon = game.scene.getEnemyField(); From ffb7f17be1303b7c4f07df5ca3c222b9d3bda547 Mon Sep 17 00:00:00 2001 From: innerthunder <168692175+innerthunder@users.noreply.github.com> Date: Thu, 1 Aug 2024 21:51:18 -0700 Subject: [PATCH 21/23] [Documentation] Document `EnemyCommandPhase` and Enemy AI functions (#3092) * Create enemy-ai.md * Finish enemy-ai doc * Fix outspeed logic + document enemy command functions * Fix errors in target score formula * Add example section and implementation guidelines * Add info on matchup score range * Fix GitHub rendering issues (maybe) * Add types to function comments * Trying to fix GitHub rendering issues again * Use images for "cases" equations instead --- docs/enemy-ai.md | 224 +++++++++++++++++++++++++++++++++++++++++++ src/field/pokemon.ts | 88 +++++++++++++++-- src/phases.ts | 19 ++++ 3 files changed, 321 insertions(+), 10 deletions(-) create mode 100644 docs/enemy-ai.md diff --git a/docs/enemy-ai.md b/docs/enemy-ai.md new file mode 100644 index 00000000000..f53a8511893 --- /dev/null +++ b/docs/enemy-ai.md @@ -0,0 +1,224 @@ +# EnemyCommandPhase: How Enemy Pokémon Decide What to Do + +## Step 1: Should the Enemy Pokémon Switch? + +When battling an enemy Trainer, the first decision the enemy needs to make is whether or not to switch an active Pokémon with another Pokémon in their party. This decision is primarily made by comparing **matchup scores** between each Pokémon in the enemy's party. + +### Calculating Matchup Scores + +The core function for matchup score calculation can be found in `src/field/pokemon.ts`, within the `Pokemon` class: + +```ts +getMatchupScore(pokemon: Pokemon): number; +``` + +This computes the source Pokémon's matchup score against the Pokémon passed by argument using the formula + +$$\text{MUScore} = (\text{atkScore}+\text{defScore}) * \text{hpDiffRatio} $$ + +where +- $\text{atkScore}$ is the combined effectiveness of the source Pokémon's types against the opposing Pokémon's defensive typing: $\prod_{\text{types}} \text{typeEffectiveness}(\text{type}, \text{oppPokemon})$. $\text{typeEffectiveness}$ is 1 when the type deals neutral damage to the opposing Pokémon's defensive typing, 2 when the type deals super effective damage, and so on. $atkScore$ is also increased by 25 percent if the source Pokémon has a higher Speed stat than the opposing Pokémon. +- $\text{defScore}$ is the inverse of the opposing Pokémon's $\text{atkScore}$ against the source Pokémon's defensive typing, or $(\prod_{\text{types}} \text{typeEffectiveness}(\text{type}, \text{sourcePokemon}))^{-1}$. Unlike $\text{atkScore}$, $\text{defScore}$ is capped at a maximum score of 4. +- $\text{hpDiffRatio}= \text{sourceHpRatio}-\text{oppHpRatio}+1$. This is further multiplied by 1.5 if the source Pokémon has a higher Speed stat than the opposing Pokémon; however, $\text{hpDiffRatio}$ cannot be higher than 1. + +The maximum possible matchup score a Pokémon could have against a single opponent is $(16+16)\times 2=64$, which occurs when +- the Pokémon hits its opponent for 4x super effective damage with both of its types. +- the Pokémon is immune to or resists both of the opponent's types by 4x. +- the Pokémon is at max HP while the opponent's HP ratio is near zero. + +In most situations, though, a Pokémon's matchup score against an opponent will be at most 16, which is equivalent to having two super effective types and resisting both of the opponent's types with the same HP ratios as before. + +The minimum possible matchup score a Pokémon could have against a single opponent is near zero, which occurs when the Pokémon's HP ratio is near zero while the opponent is at max HP. However, a Pokémon's matchup score can also be very low when its type(s) are 4x weak to and/or resisted by its opponent's types. + +### Determining Switches in EnemyCommandPhase + +The `EnemyCommandPhase` follows this process to determine whether or not an enemy Pokémon should switch on each turn during a Trainer battle. + +1. If the Pokémon has a move already queued (e.g. they are recharging after using Hyper Beam), or they are trapped (e.g. by Bind or Arena Trap), skip to resolving a `FIGHT` command (see next section). +2. For each Pokémon in the enemy's party, [compute their matchup scores](#calculating-matchup-scores) against the active player Pokémon. If there are two active player Pokémon in the battle, add their matchup scores together. +3. Take the party member with the highest matchup score and apply a multiplier to the score that reduces the score based on how frequently the enemy trainer has switched Pokémon in the current battle. + - The multiplier scales off of a counter that increments when the enemy trainer chooses to switch a Pokémon and decrements when they choose to use a move. +4. Compare the result of Step 3 with the active enemy Pokémon's matchup score. If the party member's matchup score is at least three times that of the active Pokémon, switch to that party member. + - "Boss" trainers only require the party member's matchup score to be at least two times that of the active Pokémon, so they are more likely to switch than other trainers. The full list of boss trainers in the game is as follows: + - All gym leaders, Elite 4 members, and Champions + - All Evil Team leaders + - The last three Rival Fights (on waves 95, 145, and 195) +5. If the enemy decided to switch, send a switch `turnCommand` and end this `EnemyCommandPhase`; otherwise, move on to resolving a `FIGHT` enemy command. + +## Step 2: Selecting a Move + +At this point, the enemy (a wild or trainer Pokémon) has decided against switching and instead will use a move from its moveset. However, it still needs to figure out which move to use and, if applicable, which target to use the move against. The logic for determining an enemy's next move and target is contained within two methods: `EnemyPokemon.getNextMove()` and `EnemyPokemon.getNextTargets()` in `src/field/pokemon.ts`. + +### Choosing a Move with `getNextMove()` + +In `getNextMove()`, the enemy Pokémon chooses a move to use in the following steps: +1. If the Pokémon has a move in its Move Queue (e.g. the second turn of a charging move), and the queued move is still usable, use that move against the given target. +2. Filter out any moves it can't use within its moveset. The remaining moves make up the enemy's **move pool** for the turn. + 1. A move can be unusable if it has no PP left or it has been disabled by another move or effect + 2. If the enemy's move pool is empty, use Struggle. +3. Calculate the **move score** of each move in the enemy's move pool. + 1. A move's move score is equivalent to the move's maximum **target score** among all of the move's possible targets on the field ([more on this later](#calculating-move-and-target-scores)). + 2. A move's move score is set to -20 if at least one of these conditions are met: + - The move is unimplemented (or, more precisely, the move's name ends with " (N)"). + - Conditions for the move to succeed are not met (unless the move is Sucker Punch, Upper Hand, or Thunderclap, as those moves' conditions can't be resolved before the turn starts). + - The move's target scores are 0 or `NaN` for each target. In this case, the game assumes the target score calculation for that move is unimplemented. +4. Sort the move pool in descending order of move scores. +5. From here, the enemy's move selection varies based on its `aiType`. If the enemy is a Boss Pokémon or has a Trainer, it uses the `SMART` AI type; otherwise, it uses the `SMART_RANDOM` AI type. + 1. Let $m_i$ be the *i*-th move in the sorted move pool $M$: + - If `aiType === SMART_RANDOM`, the enemy has a 5/8 chance of selecting $m_0$ and a 3/8 chance of advancing to the next best move $m_1$, where it then repeats this roll. This process stops when a move is selected or the last move in the move pool is reached. + - If `aiType === SMART`, a similar loop is used to decide between selecting the move $m_i$ and advancing to the next iteration with the move $m_{i+1}$. However, instead of using a flat probability, the following conditions need to be met to advance from selecting $m_i$ to $m_{i+1}$: + - $\text{sign}(s_i) = \text{sign}(s_{i+1})$, where $s_i$ is the move score of $m_i$. + - $\text{randInt}(0, 100) < \text{round}(\frac{s_{i+1}}{s_i}\times 50)$. In other words: if the scores of $m_i$ and $m_{i+1}$ have the same sign, the chance to advance to the next iteration with $m_{i+1}$ is proportional to how close the scores are to each other. The probability to advance to the next iteration is at most 50 percent (when $s_i$ and $s_{i+1}$ are equal). +6. The enemy will use the move selected in Step 5 against the target(s) with the highest [**target selection score (TSS)**](#choosing-targets-with-getnexttargets) + +### Calculating Move and Target Scores + +As part of the move selection process, the enemy Pokémon must compute a **target score (TS)** for each legal target for each move in its move pool. The base target score for all moves is a combination of the move's **user benefit score (UBS)** and **target benefit score (TBS)**. + +![equation](https://latex.codecogs.com/png.image?%5Cinline%20%5Cdpi%7B100%7D%5Cbg%7Bwhite%7D%5Ctext%7BTS%7D=%5Ctext%7BUBS%7D+%5Ctext%7BTBS%7D%5Ctimes%5Cleft%5C%7B%5Cbegin%7Bmatrix%7D-1&%5Ctext%7Bif%20target%20is%20an%20opponent%7D%5C%5C1&%5Ctext%7Botherwise%7D%5C%5C%5Cend%7Bmatrix%7D%5Cright.) + +A move's UBS and TBS are computed with the respective functions in the `Move` class: + +```ts +getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer; +getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer; +``` + +Logically, these functions are very similar – they add up their respective benefit scores from each of the move's attributes (as determined by `attr.getUserBenefitScore`, and `attr.getTargetBenefitScore`, respectively) and return the total benefit score. However, there are two key functional differences in how the UBS and TBS of a move are handled: +1. In addition to influencing move selection, a move's TBS also influences target selection for that move, whereas UBS has no influence. +2. When evaluating the target score of a move against an opposing Pokémon, the move's TBS is multiplied by -1, whereas the move's UBS does not change. For this reason, move attributes return negative values for their TBS to reward using the move against an enemy. + +#### Calculating Target Benefit Score (TBS) for Attack Moves + +In addition to the base score from `Move.getTargetBenefitScore()`, attack moves calculate an `attackScore` which influences the move's TBS based on the following properties: +- The move's power (after the move's `VariablePowerAttrs` are applied) +- The move's type effectiveness against the target (note that this also accounts for type immunities from abilities such as Levitate and field effects such as Strong Winds). +- The move's category (Physical/Special), and whether the user has a higher Attack or Special Attack stat. + +More specifically, the following steps are taken to compute the move's `attackScore`: +1. Compute a multiplier based on the move's type effectiveness: + + ![typeMultEqn](https://latex.codecogs.com/png.image?%5Cdpi%7B110%7D%5Cbg%7Bwhite%7D%5Ctext%7BtypeMult%7D=%5Cleft%5C%7B%5Cbegin%7Bmatrix%7D2&&%5Ctext%7Bif%20move%20is%20super%20effective(or%20better)%7D%5C%5C-2&&%5Ctext%7Botherwise%7D%5C%5C%5Cend%7Bmatrix%7D%5Cright.) +2. Compute a multiplier based on the move's category and the user's offensive stats: + 1. Compute the user's offensive stat ratio: + + ![statRatioEqn](https://latex.codecogs.com/png.image?%5Cinline%20%5Cdpi%7B100%7D%5Cbg%7Bwhite%7D%5Ctext%7BstatRatio%7D=%5Cleft%5C%7B%5Cbegin%7Bmatrix%7D%5Cfrac%7B%5Ctext%7BuserSpAtk%7D%7D%7B%5Ctext%7BuserAtk%7D%7D&%5Ctext%7Bif%20move%20is%20physical%7D%5C%5C%5Cfrac%7B%5Ctext%7BuserAtk%7D%7D%7B%5Ctext%7BuserSpAtk%7D%7D&%5Ctext%7Botherwise%7D%5C%5C%5Cend%7Bmatrix%7D%5Cright.) + 2. Compute the stat-based multiplier: + + ![statMultEqn](https://latex.codecogs.com/png.image?%5Cinline%20%5Cdpi%7B100%7D%5Cbg%7Bwhite%7D%5Ctext%7BstatMult%7D=%5Cleft%5C%7B%5Cbegin%7Bmatrix%7D2&%5Ctext%7Bif%20statRatio%7D%5Cle%200.75%5C%5C1.5&%5Ctext%7Bif%5C;%7D0.75%5Cle%5Ctext%7BstatRatio%7D%5Cle%200.875%5C%5C1&%5Ctext%7Botherwise%7D%5C%5C%5Cend%7Bmatrix%7D%5Cright.) +3. Calculate the move's `attackScore`: + + $\text{attackScore} = (\text{typeMult}\times \text{statMult})+\lfloor \frac{\text{power}}{5} \rfloor$ + +The maximum total multiplier in `attackScore` ($\text{typeMult}\times \text{statMult}$) is 4, which occurs for attacks that are super effective against the target and are categorically aligned with the user's offensive stats (e.g. the move is physical, and the user has much higher Attack than Sp. Atk). The minimum total multiplier of -4 occurs (somewhat confusingly) for attacks that are not super effective but are categorically aligned with the user's offensive stats. + +The attack move's total TBS, then, is $\text{TBS}=\text{baseScore}-\text{attackScore}$, where $\text{baseScore}$ is the result of `Move.getTargetBenefitScore()`. + +#### Calculating Target Score (TS) for Attack Moves + +The final step to calculate an attack move's target score (TS) is to multiply the base target score by the move's type effectiveness and STAB (if it applies): +- If the target is an enemy, the corresponding TS is multiplied by the move's type effectiveness against the enemy (e.g. 2 if the move is super effective), then by 1.5 if the move shares a type with the user. +- If the target is an ally, the TS is divided by these factors instead. +- If $\text{TS}=0$ after these multipliers are applied, the TS is set to -20 for the current target. + +### Choosing Targets with `getNextTargets()` + +The enemy's target selection for single-target moves works in a very similar way to its move selection. Each potential target is given a **target selection score (TSS)** which is based on the move's [target benefit score](#calculating-move-and-target-scores) for that target: + +![TSSEqn](https://latex.codecogs.com/png.image?%5Cinline%20%5Cdpi%7B100%7D%5Cbg%7Bwhite%7D%5Ctext%7BTSS%7D=%5Ctext%7BTBS%7D%5Ctimes%5Cleft%5C%7B%5Cbegin%7Bmatrix%7D-1&%5Ctext%7Bif%20target%20is%20an%20opponent%7D%5C%5C1&%5Ctext%7Botherwise%7D%5C%5C%5Cend%7Bmatrix%7D%5Cright.) + +Once the TSS is calculated for each target, the target is selected as follows: +1. Sort the targets (indexes) in decreasing order of their target selection scores (or weights). Let $t_i$ be the index of the *i*-th target in the sorted list, and let $w_i$ be that target's corresponding TSS. +2. Normalize the weights. Let $w_n$ be the lowest-weighted target in the sorted list, then: + + ![normWeightEqn](https://latex.codecogs.com/png.image?%5Cinline%20%5Cdpi%7B100%7D%5Cbg%7Bwhite%7DW_i=%5Cleft%5C%7B%5Cbegin%7Bmatrix%7Dw_i+%7Cw_n%7C&%5Ctext%7Bif%5C;%7Dw_n%5C;%5Ctext%7Bis%20negative%7D%5C%5Cw_i&%5Ctext%7Botherwise%7D%5C%5C%5Cend%7Bmatrix%7D%5Cright.) +3. Remove all weights from the list such that $W_i < \frac{W_0}{2}$ +4. Generate a random integer $R=\text{rand}(0, W_{\text{total}})$ where $W_{\text{total}}$ is the sum of all the remaining weights after Step 3. +5. For each target $(t_i, W_i)$, + 1. if $R \le \sum_{j=0}^{i} W_i$, or if $t_i$ is the last target in the list, **return** $t_i$ + 2. otherwise, advance to the next target $t_{i+1}$ and repeat this check. + +Once the target is selected, the enemy has successfully determined its next action for the turn, and its corresponding `EnemyCommandPhase` ends. From here, the `TurnStartPhase` processes the enemy's commands alongside the player's commands and begins to resolve the turn. + +## An Example in Battle + +Suppose you enter a single battle against an enemy trainer with the following Pokémon in their party: + +1. An [Excadrill](https://bulbapedia.bulbagarden.net/wiki/Excadrill_(Pok%C3%A9mon)) with the Ability Sand Force and the following moveset + 1. Earthquake + 2. Iron Head + 3. Crush Claw + 4. Swords Dance +2. A [Heatmor](https://bulbapedia.bulbagarden.net/wiki/Heatmor_(Pok%C3%A9mon)) with the Ability Flash Fire and the following moveset + 1. Fire Lash + 2. Inferno + 3. Hone Claws + 4. Shadow Claw + +The enemy trainer leads with their Heatmor, and you lead with a [Dachsbun](https://bulbapedia.bulbagarden.net/wiki/Dachsbun_(Pok%C3%A9mon)) with the Ability Well-Baked Body. We'll cover the enemy's behavior over the next two turns. + +### Turn 1 + +To determine whether the enemy should switch Pokémon, it first calculates each party member's matchup scores against the player's Dachsbun: + +$$\text{MUScore} = (\text{atkScore}+\text{defScore}) * \text{hpDiffRatio} $$ +- Defensively, Heatmor's Fire typing resists Dachsbun's Fairy typing, so its `defScore` is 2. However, because of Dachsbun's Fire immunity granted by Well-Baked Body, Heatmor's `atkScore` against Dachsbun is 0. With both Pokémon at maximum HP, Heatmor's total matchup score is 2. +- Excadrill's Steel typing also resists Fairy, so its `defScore` is also 2. In this case, though, Steel is also super effective against Fairy, so Excadrill's base `atkScore` is 2. If Excadrill outspeeds Dachsbun (possibly due to it having a +Spd nature or holding a Carbos), its `atkScore` is further increased to 2.5. Since both Pokémon are at maximum HP, Excadrill's total matchup score is 4 (or 4.5 if it outspeeds). + +Based on the enemy party's matchup scores, whether or not the trainer switches out Heatmor for Excadrill depends on the trainer's type. The difference in matchup scores is enough to cause a switch to Excadrill for boss trainers (e.g. gym leaders) but not for regular trainers. For this example, we'll assume the trainer is a boss and, therefore, decides to switch to Excadrill on this turn. + +### Turn 2 + +Now that the enemy Pokémon with the best matchup score is on the field (assuming it survives Dachsbun's attack on the last turn), the enemy will now decide to have Excadrill use one of its moves. Assuming all of its moves are usable, we'll go through the target score calculations for each move: + +- **Earthquake**: In a single battle, this move is just a 100-power Ground-type physical attack with no additional effects. With no additional benefit score from attributes, the move's base target score against the player's Dachsbun is just the `attackScore` from `AttackMove.getTargetBenefitScore()`. In this case, Earthquake's `attackScore` is given by + + $\text{attackScore}=(\text{typeMult}\times \text{statMult}) + \lfloor \frac{\text{power}}{5} \rfloor = -2\times 2 + 20 = 16$ + + Here, `typeMult` is -2 because the move is not super effective, and `statMult` is 2 because Excadrill's Attack is significantly higher than its Sp. Atk. Accounting for STAB thanks to Excadrill's typing, the final target score for this move is **24** + +- **Iron Head**: This move is an 80-power Steel-type physical attack with an additional chance to cause the target to flinch. With these properties, Iron Head has a user benefit score of 0 and a target benefit score given by + + $\text{TBS}=\text{getTargetBenefitScore(FlinchAttr)}-\text{attackScore}$ + + Under its current implementation, the target benefit score of `FlinchAttr` is -5. Calculating the move's `attackScore`, we get: + + $\text{attackScore}=(\text{typeMult}\times \text{statMult}) + \lfloor \frac{\text{power}}{5} \rfloor = 2\times 2 + 16 = 20$ + + Note that `typeMult` in this case is 2 because Iron Head is super effective (or better) against Dachsbun. With the move's UBS at 0, the base target score calculation against Dachsbun simplifies to + + $\text{TS}=-\text{TBS}=-(-5-20)=25$ + + We then need to apply a 2x multiplier for the move's type effectiveness and a 1.5x multiplier since STAB applies. After applying these multipliers, the final score for this move is **75**. + +- **Swords Dance**: As a non-attacking move, this move's benefit score is derived entirely from the sum of its attributes' benefit scores. Swords Dance's `StatChangeAttr` has a user benefit score of 0 and a target benefit score that, in this case, simplifies to + + $\text{TBS}=4\times \text{levels} + (-2\times \text{sign(levels)})$ + + where `levels` is the number of stat stages added by the attribute (in this case, +2). The final score for this move is **6** (Note: because this move is self-targeted, we don't flip the sign of TBS when computing the target score). + +- **Crush Claw**: This move is a 75-power Normal-type physical attack with a 50 percent chance to lower the target's Defense by one stage. The additional effect is implemented by the same `StatChangeAttr` as Swords Dance, so we can use the same formulas from before to compute the total TBS and base target score. + + $\text{TBS}=\text{getTargetBenefitScore(StatChangeAttr)}-\text{attackScore}$ + + $\text{TBS}=(-4 + 2)-(-2\times 2 + \lfloor \frac{75}{5} \rfloor)=-2-11=-13$ + + $\text{TS}=-\text{TBS}=13$ + + This move is neutral against Dachsbun and isn't boosted by STAB from Excadrill, so we don't need to apply any extra multipliers. The final score for this move is **13**. + +We now have a sorted move pool in decreasing order of move scores: +1. Iron Head (**75**) +2. Earthquake (**24**) +3. Crush Claw (**13**) +4. Swords Dance (**6**) + +Since no other score is at least half that of Iron Head's score, the enemy AI automatically chooses to use Iron Head against Dachsbun at this point. + +## Guidelines for Implementing Benefit Scores + +When implementing a new move attribute, it's important to override `MoveAttr`'s `getUserBenefitScore` and `getTargetBenefitScore` functions to ensure that the enemy AI can accurately determine when and how to use moves with that attribute. Here are a few basic specifications you should adhere to when implementing benefit scores for a new attribute: +- A move's **user benefit score (UBS)** incentivizes (or discourages) the move's usage in general. A positive UBS gives the move more incentive to be used, while a negative UBS gives the move less incentive. +- A move's **target benefit score (TBS)** incentivizes (or discourages) the move's usage on a specific target. A positive TBS indicates the move is better used on the user or its allies, while a negative TBS indicates the move is better used on enemies. +- **The total benefit score (UBS + TBS) of a move should never be 0.** The move selection algorithm assumes the move's benefit score is unimplemented if the total score is 0 and penalizes the move's usage as a result. With status moves especially, it's important to have some form of implementation among the move's attributes to avoid this scenario. +- **Score functions that use formulas should include comments.** If your attribute requires complex logic or formulas to calculate benefit scores, please add comments to explain how the logic works and its intended effect on the enemy's decision making. \ No newline at end of file diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 2ed1b7e7355..22d0410233f 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1251,19 +1251,39 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return multiplier; } - getMatchupScore(pokemon: Pokemon): number { + /** + * Computes the given Pokemon's matchup score against this Pokemon. + * In most cases, this score ranges from near-zero to 16, but the maximum possible matchup score is 64. + * @param opponent {@linkcode Pokemon} The Pokemon to compare this Pokemon against + * @returns A score value based on how favorable this Pokemon is when fighting the given Pokemon + */ + getMatchupScore(opponent: Pokemon): number { const types = this.getTypes(true); - const enemyTypes = pokemon.getTypes(true, true); - const outspeed = (this.isActive(true) ? this.getBattleStat(Stat.SPD, pokemon) : this.getStat(Stat.SPD)) <= pokemon.getBattleStat(Stat.SPD, this); - let atkScore = pokemon.getAttackTypeEffectiveness(types[0], this) * (outspeed ? 1.25 : 1); - let defScore = 1 / Math.max(this.getAttackTypeEffectiveness(enemyTypes[0], pokemon), 0.25); + const enemyTypes = opponent.getTypes(true, true); + /** Is this Pokemon faster than the opponent? */ + const outspeed = (this.isActive(true) ? this.getBattleStat(Stat.SPD, opponent) : this.getStat(Stat.SPD)) >= opponent.getBattleStat(Stat.SPD, this); + /** + * Based on how effective this Pokemon's types are offensively against the opponent's types. + * This score is increased by 25 percent if this Pokemon is faster than the opponent. + */ + let atkScore = opponent.getAttackTypeEffectiveness(types[0], this) * (outspeed ? 1.25 : 1); + /** + * Based on how effectively this Pokemon defends against the opponent's types. + * This score cannot be higher than 4. + */ + let defScore = 1 / Math.max(this.getAttackTypeEffectiveness(enemyTypes[0], opponent), 0.25); if (types.length > 1) { - atkScore *= pokemon.getAttackTypeEffectiveness(types[1], this); + atkScore *= opponent.getAttackTypeEffectiveness(types[1], this); } if (enemyTypes.length > 1) { - defScore *= (1 / Math.max(this.getAttackTypeEffectiveness(enemyTypes[1], pokemon), 0.25)); + defScore *= (1 / Math.max(this.getAttackTypeEffectiveness(enemyTypes[1], opponent), 0.25)); } - let hpDiffRatio = this.getHpRatio() + (1 - pokemon.getHpRatio()); + /** + * Based on this Pokemon's HP ratio compared to that of the opponent. + * This ratio is multiplied by 1.5 if this Pokemon outspeeds the opponent; + * however, the final ratio cannot be higher than 1. + */ + let hpDiffRatio = this.getHpRatio() + (1 - opponent.getHpRatio()); if (outspeed) { hpDiffRatio = Math.min(hpDiffRatio * 1.5, 1); } @@ -3665,7 +3685,13 @@ export class EnemyPokemon extends Pokemon { } } + /** + * Determines the move this Pokemon will use on the next turn, as well as + * the Pokemon the move will target. + * @returns this Pokemon's next move in the format {move, moveTargets} + */ getNextMove(): QueuedMove { + // If this Pokemon has a move already queued, return it. const queuedMove = this.getMoveQueue().length ? this.getMoveset().find(m => m.moveId === this.getMoveQueue()[0].move) : null; @@ -3678,11 +3704,15 @@ export class EnemyPokemon extends Pokemon { } } + // Filter out any moves this Pokemon cannot use const movePool = this.getMoveset().filter(m => m.isUsable(this)); + // If no moves are left, use Struggle. Otherwise, continue with move selection if (movePool.length) { + // If there's only 1 move in the move pool, use it. if (movePool.length === 1) { return { move: movePool[0].moveId, targets: this.getNextTargets(movePool[0].moveId) }; } + // If a move is forced because of Encore, use it. const encoreTag = this.getTag(EncoreTag) as EncoreTag; if (encoreTag) { const encoreMove = movePool.find(m => m.moveId === encoreTag.moveId); @@ -3691,11 +3721,16 @@ export class EnemyPokemon extends Pokemon { } } switch (this.aiType) { - case AiType.RANDOM: + case AiType.RANDOM: // No enemy should spawn with this AI type in-game const moveId = movePool[this.scene.randBattleSeedInt(movePool.length)].moveId; return { move: moveId, targets: this.getNextTargets(moveId) }; case AiType.SMART_RANDOM: case AiType.SMART: + /** + * Move selection is based on the move's calculated "benefit score" against the + * best possible target(s) (as determined by {@linkcode getNextTargets}). + * For more information on how benefit scores are calculated, see `docs/enemy-ai.md`. + */ const moveScores = movePool.map(() => 0); const moveTargets = Object.fromEntries(movePool.map(m => [ m.moveId, this.getNextTargets(m.moveId) ])); for (const m in movePool) { @@ -3712,14 +3747,26 @@ export class EnemyPokemon extends Pokemon { } const target = this.scene.getField()[mt]; + /** + * The "target score" of a move is given by the move's user benefit score + the move's target benefit score. + * If the target is an ally, the target benefit score is multiplied by -1. + */ let targetScore = move.getUserBenefitScore(this, target, move) + move.getTargetBenefitScore(this, target, move) * (mt < BattlerIndex.ENEMY === this.isPlayer() ? 1 : -1); if (Number.isNaN(targetScore)) { console.error(`Move ${move.name} returned score of NaN`); targetScore = 0; } + /** + * If this move is unimplemented, or the move is known to fail when used, set its + * target score to -20 + */ if ((move.name.endsWith(" (N)") || !move.applyConditions(this, target, move)) && ![Moves.SUCKER_PUNCH, Moves.UPPER_HAND, Moves.THUNDERCLAP].includes(move.id)) { targetScore = -20; } else if (move instanceof AttackMove) { + /** + * Attack moves are given extra multipliers to their base benefit score based on + * the move's type effectiveness against the target and whether the move is a STAB move. + */ const effectiveness = target.getAttackMoveEffectiveness(this, pokemonMove); if (target.isPlayer() !== this.isPlayer()) { targetScore *= effectiveness; @@ -3732,13 +3779,14 @@ export class EnemyPokemon extends Pokemon { targetScore /= 1.5; } } + /** If a move has a base benefit score of 0, its benefit score is assumed to be unimplemented at this point */ if (!targetScore) { targetScore = -20; } } targetScores.push(targetScore); } - + // When a move has multiple targets, its score is equal to the maximum target score across all targets moveScore += Math.max(...targetScores); // could make smarter by checking opponent def/spdef @@ -3747,6 +3795,7 @@ export class EnemyPokemon extends Pokemon { console.log(moveScores); + // Sort the move pool in decreasing order of move score const sortedMovePool = movePool.slice(0); sortedMovePool.sort((a, b) => { const scoreA = moveScores[movePool.indexOf(a)]; @@ -3755,10 +3804,12 @@ export class EnemyPokemon extends Pokemon { }); let r = 0; if (this.aiType === AiType.SMART_RANDOM) { + // Has a 5/8 chance to select the best move, and a 3/8 chance to advance to the next best move (and repeat this roll) while (r < sortedMovePool.length - 1 && this.scene.randBattleSeedInt(8) >= 5) { r++; } } else if (this.aiType === AiType.SMART) { + // The chance to advance to the next best move increases when the compared moves' scores are closer to each other. while (r < sortedMovePool.length - 1 && (moveScores[movePool.indexOf(sortedMovePool[r + 1])] / moveScores[movePool.indexOf(sortedMovePool[r])]) >= 0 && this.scene.randBattleSeedInt(100) < Math.round((moveScores[movePool.indexOf(sortedMovePool[r + 1])] / moveScores[movePool.indexOf(sortedMovePool[r])]) * 50)) { r++; @@ -3772,15 +3823,25 @@ export class EnemyPokemon extends Pokemon { return { move: Moves.STRUGGLE, targets: this.getNextTargets(Moves.STRUGGLE) }; } + /** + * Determines the Pokemon the given move would target if used by this Pokemon + * @param moveId {@linkcode Moves} The move to be used + * @returns The indexes of the Pokemon the given move would target + */ getNextTargets(moveId: Moves): BattlerIndex[] { const moveTargets = getMoveTargets(this, moveId); const targets = this.scene.getField(true).filter(p => moveTargets.targets.indexOf(p.getBattlerIndex()) > -1); + // If the move is multi-target, return all targets' indexes if (moveTargets.multiple) { return targets.map(p => p.getBattlerIndex()); } const move = allMoves[moveId]; + /** + * Get the move's target benefit score against each potential target. + * For allies, this score is multiplied by -1. + */ const benefitScores = targets .map(p => [ p.getBattlerIndex(), move.getTargetBenefitScore(this, p, move) * (p.isPlayer() === this.isPlayer() ? 1 : -1) ]); @@ -3804,12 +3865,14 @@ export class EnemyPokemon extends Pokemon { let targetWeights = sortedBenefitScores.map(s => s[1]); const lowestWeight = targetWeights[targetWeights.length - 1]; + // If the lowest target weight (i.e. benefit score) is negative, add abs(lowestWeight) to all target weights if (lowestWeight < 1) { for (let w = 0; w < targetWeights.length; w++) { targetWeights[w] += Math.abs(lowestWeight - 1); } } + // Remove any targets whose weights are less than half the max of the target weights from consideration const benefitCutoffIndex = targetWeights.findIndex(s => s < targetWeights[0] / 2); if (benefitCutoffIndex > -1) { targetWeights = targetWeights.slice(0, benefitCutoffIndex); @@ -3824,6 +3887,11 @@ export class EnemyPokemon extends Pokemon { return total; }, 0); + /** + * Generate a random number from 0 to (totalWeight-1), + * then select the first target whose cumulative weight (with all previous targets' weights) + * is greater than that random number. + */ const randValue = this.scene.randBattleSeedInt(totalWeight); let targetIndex: integer; diff --git a/src/phases.ts b/src/phases.ts index dfadcaf2767..a7ab9ad6e51 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -2168,6 +2168,15 @@ export class CommandPhase extends FieldPhase { } } +/** + * Phase for determining an enemy AI's action for the next turn. + * During this phase, the enemy decides whether to switch (if it has a trainer) + * or to use a move from its moveset. + * + * For more information on how the Enemy AI works, see docs/enemy-ai.md + * @see {@linkcode Pokemon.getMatchupScore} + * @see {@linkcode EnemyPokemon.getNextMove} + */ export class EnemyCommandPhase extends FieldPhase { protected fieldIndex: integer; @@ -2186,6 +2195,15 @@ export class EnemyCommandPhase extends FieldPhase { const trainer = battle.trainer; + /** + * If the enemy has a trainer, decide whether or not the enemy should switch + * to another member in its party. + * + * This block compares the active enemy Pokemon's {@linkcode Pokemon.getMatchupScore | matchup score} + * against the active player Pokemon with the enemy party's other non-fainted Pokemon. If a party + * member's matchup score is 3x the active enemy's score (or 2x for "boss" trainers), + * the enemy will switch to that Pokemon. + */ if (trainer && !enemyPokemon.getMoveQueue().length) { const opponents = enemyPokemon.getOpponents(); @@ -2217,6 +2235,7 @@ export class EnemyCommandPhase extends FieldPhase { } } + /** Select a move to use (and a target to use it against, if applicable) */ const nextMove = enemyPokemon.getNextMove(); this.scene.currentBattle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] = From 8f9ffabeef038748d8eade28d079e1b907f704ec Mon Sep 17 00:00:00 2001 From: Leo Kim <47556641+KimJeongSun@users.noreply.github.com> Date: Fri, 2 Aug 2024 14:35:13 +0900 Subject: [PATCH 22/23] [Bug] Fix sprites of Pokemon without variants being weird when removed from party in starter select (#3296) --- src/ui/starter-select-ui-handler.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 4b1da435d52..ab28da206d1 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -2723,6 +2723,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const props = this.scene.gameData.getSpeciesDexAttrProps(species, currentDexAttr); this.starterIcons[s].setTexture(species.getIconAtlasKey(props.formIndex, props.shiny, props.variant)); this.starterIcons[s].setFrame(species.getIconId(props.female, props.formIndex, props.shiny, props.variant)); + this.checkIconId(this.starterIcons[s], species, props.female, props.formIndex, props.shiny, props.variant); if (s >= index) { this.starterCursorObjs[s].setPosition(this.starterCursorObjs[s + 1].x, this.starterCursorObjs[s + 1].y); this.starterCursorObjs[s].setVisible(this.starterCursorObjs[s + 1].visible); From abbf4974fdc209390e587d5076c1d3631dfb39ee Mon Sep 17 00:00:00 2001 From: chaosgrimmon <31082757+chaosgrimmon@users.noreply.github.com> Date: Fri, 2 Aug 2024 01:50:08 -0400 Subject: [PATCH 23/23] [Sprite] Shiny animated Clawitzer using wrong palette (#3297) * [Sprite] Recolour shiny Clawitzer Palette taken from back counterpart * [Sprite] Recolour shiny Clawitzer Palette taken from back counterpart --- public/images/pokemon/exp/shiny/693.png | Bin 5958 -> 6269 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/public/images/pokemon/exp/shiny/693.png b/public/images/pokemon/exp/shiny/693.png index 2212a4a5158ac7da653a2f655c8c1157b22c83ea..328c909b19696098011e2c8886cfb4f5ea768631 100644 GIT binary patch literal 6269 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 literal 5958 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