From 13c988890269d5eaa9e7a8316b5e27d2e61481c8 Mon Sep 17 00:00:00 2001 From: Dakurei Date: Sat, 1 Jun 2024 02:30:55 +0200 Subject: [PATCH 001/129] Trigger the github-pages workflow even during pull requests (#1655) + Allows upstream identification of problems such as those that occurred during merge of PR #1567 --- .github/workflows/github-pages.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index a648f792b2b..aa63792bbea 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -4,6 +4,9 @@ on: push: branches: - main + pull_request: + branches: + - main jobs: pages: @@ -33,6 +36,7 @@ jobs: node-version: 20 - name: Checkout repository for Github Pages + if: github.event_name == 'push' uses: actions/checkout@v3 with: path: pokerogue_gh @@ -52,6 +56,7 @@ jobs: npx typedoc --out /tmp/docs --githubPages false --entryPoints ./src/ - name: Commit & Push docs + if: github.event_name == 'push' run: | cd pokerogue_gh git config user.email "github-actions[bot]@users.noreply.github.com" From d052d444b6e812dd5f01de0c0c85d1af73a057ff Mon Sep 17 00:00:00 2001 From: Dmitriy K Date: Fri, 31 May 2024 20:50:30 -0400 Subject: [PATCH 002/129] [QoL] Add type inference to getAttrs methods and refactor accordingly (#1633) * add type inference to getAttrs methods and refactor accordingly * Tests/infer types for get attrs methods (#1) * #1633: add spec/tests for coverage * move ability/move tests into /src/tests and rename to *.test.ts to match common naming patterns * use None in test cases to remove ambiguity * revert back to before test cases were merged --------- Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> --- src/data/ability.ts | 34 ++++++++++++++++++++++------------ src/data/battle-anims.ts | 6 +++--- src/data/battler-tags.ts | 2 +- src/data/move.ts | 26 ++++++++++++++++++++------ src/data/terrain.ts | 2 +- src/data/weather.ts | 4 ++-- src/field/pokemon.ts | 38 +++++++++++++++++++------------------- src/phases.ts | 22 +++++++++++----------- 8 files changed, 79 insertions(+), 55 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 42787132b21..9cd8c61d47f 100755 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -55,8 +55,22 @@ export class Ability implements Localizable { this.description = this.id ? i18next.t(`ability:${i18nKey}.description`) as string : ""; } - getAttrs(attrType: { new(...args: any[]): AbAttr }): AbAttr[] { - return this.attrs.filter(a => a instanceof attrType); + /** + * Get all ability attributes that match `attrType` + * @param attrType any attribute that extends {@linkcode AbAttr} + * @returns Array of attributes that match `attrType`, Empty Array if none match. + */ + getAttrs(attrType: new(...args: any[]) => T ): T[] { + return this.attrs.filter((a): a is T => a instanceof attrType); + } + + /** + * Check if an ability has an attribute that matches `attrType` + * @param attrType any attribute that extends {@linkcode AbAttr} + * @returns true if the ability has attribute `attrType` + */ + hasAttr(attrType: new(...args: any[]) => T): boolean { + return this.attrs.some((attr) => attr instanceof attrType); } attr AbAttr>(AttrType: T, ...args: ConstructorParameters): Ability { @@ -74,10 +88,6 @@ export class Ability implements Localizable { return this; } - hasAttr(attrType: { new(...args: any[]): AbAttr }): boolean { - return !!this.getAttrs(attrType).length; - } - bypassFaint(): Ability { this.isBypassFaint = true; return this; @@ -341,7 +351,7 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr { } applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if ((move.getMove() instanceof AttackMove || move.getMove().getAttrs(StatusMoveTypeImmunityAttr).find(attr => (attr as StatusMoveTypeImmunityAttr).immuneType === this.immuneType)) && move.getMove().type === this.immuneType) { + if ((move.getMove() instanceof AttackMove || move.getMove().getAttrs(StatusMoveTypeImmunityAttr).find(attr => attr.immuneType === this.immuneType)) && move.getMove().type === this.immuneType) { (args[0] as Utils.NumberHolder).value = 0; return true; } @@ -568,7 +578,7 @@ export class MoveImmunityStatChangeAbAttr extends MoveImmunityAbAttr { export class ReverseDrainAbAttr extends PostDefendAbAttr { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { - if (!!move.getMove().getAttrs(HitHealAttr).length || !!move.getMove().getAttrs(StrengthSapHealAttr).length ) { + if (move.getMove().hasAttr(HitHealAttr) || move.getMove().hasAttr(StrengthSapHealAttr) ) { pokemon.scene.queueMessage(getPokemonMessage(attacker, " sucked up the liquid ooze!")); return true; } @@ -2194,7 +2204,7 @@ function getAnticipationCondition(): AbAttrCondition { return true; } // move is a OHKO - if (move.getMove().findAttr(attr => attr instanceof OneHitKOAttr)) { + if (move.getMove().hasAttr(OneHitKOAttr)) { return true; } // edge case for hidden power, type is computed @@ -2248,7 +2258,7 @@ export class ForewarnAbAttr extends PostSummonAbAttr { for (const move of opponent.moveset) { if (move.getMove() instanceof StatusMove) { movePower = 1; - } else if (move.getMove().findAttr(attr => attr instanceof OneHitKOAttr)) { + } else if (move.getMove().hasAttr(OneHitKOAttr)) { movePower = 150; } else if (move.getMove().id === Moves.COUNTER || move.getMove().id === Moves.MIRROR_COAT || move.getMove().id === Moves.METAL_BURST) { movePower = 120; @@ -3325,7 +3335,7 @@ function applyAbAttrsInternal(attrType: { new(...args: any } const ability = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()); - const attrs = ability.getAttrs(attrType) as TAttr[]; + const attrs = ability.getAttrs(attrType); const clearSpliceQueueAndResolve = () => { pokemon.scene.clearPhaseQueueSplice(); @@ -3524,7 +3534,7 @@ export const allAbilities = [ new Ability(Abilities.NONE, 3) ]; export function initAbilities() { allAbilities.push( new Ability(Abilities.STENCH, 3) - .attr(PostAttackApplyBattlerTagAbAttr, false, (user, target, move) => (move.getMove().category !== MoveCategory.STATUS && !move.getMove().findAttr(attr => attr instanceof FlinchAttr)) ? 10 : 0, BattlerTagType.FLINCHED), + .attr(PostAttackApplyBattlerTagAbAttr, false, (user, target, move) => (move.getMove().category !== MoveCategory.STATUS && !move.getMove().hasAttr(FlinchAttr)) ? 10 : 0, BattlerTagType.FLINCHED), new Ability(Abilities.DRIZZLE, 3) .attr(PostSummonWeatherChangeAbAttr, WeatherType.RAIN) .attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.RAIN), diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index efda3ebcb0e..75c883ec1e7 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -468,7 +468,7 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise { } else { const loadedCheckTimer = setInterval(() => { if (moveAnims.get(move) !== null) { - const chargeAttr = allMoves[move].getAttrs(ChargeAttr).find(() => true) as ChargeAttr || allMoves[move].getAttrs(DelayedAttackAttr).find(() => true) as DelayedAttackAttr; + const chargeAttr = allMoves[move].getAttrs(ChargeAttr)[0] || allMoves[move].getAttrs(DelayedAttackAttr)[0]; if (chargeAttr && chargeAnims.get(chargeAttr.chargeAnim) === null) { return; } @@ -498,7 +498,7 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise { } else { populateMoveAnim(move, ba); } - const chargeAttr = allMoves[move].getAttrs(ChargeAttr).find(() => true) as ChargeAttr || allMoves[move].getAttrs(DelayedAttackAttr).find(() => true) as DelayedAttackAttr; + const chargeAttr = allMoves[move].getAttrs(ChargeAttr)[0] || allMoves[move].getAttrs(DelayedAttackAttr)[0]; if (chargeAttr) { initMoveChargeAnim(scene, chargeAttr.chargeAnim).then(() => resolve()); } else { @@ -569,7 +569,7 @@ export function loadMoveAnimAssets(scene: BattleScene, moveIds: Moves[], startLo return new Promise(resolve => { const moveAnimations = moveIds.map(m => moveAnims.get(m) as AnimConfig).flat(); for (const moveId of moveIds) { - const chargeAttr = allMoves[moveId].getAttrs(ChargeAttr).find(() => true) as ChargeAttr || allMoves[moveId].getAttrs(DelayedAttackAttr).find(() => true) as DelayedAttackAttr; + const chargeAttr = allMoves[moveId].getAttrs(ChargeAttr)[0] || allMoves[moveId].getAttrs(DelayedAttackAttr)[0]; if (chargeAttr) { const moveChargeAnims = chargeAnims.get(chargeAttr.chargeAnim); moveAnimations.push(moveChargeAnims instanceof AnimConfig ? moveChargeAnims : moveChargeAnims[0]); diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 2cf1c6eff64..13c3954fd01 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -509,7 +509,7 @@ export class EncoreTag extends BattlerTag { return false; } - if (allMoves[repeatableMove.move].getAttrs(ChargeAttr).length && repeatableMove.result === MoveResult.OTHER) { + if (allMoves[repeatableMove.move].hasAttr(ChargeAttr) && repeatableMove.result === MoveResult.OTHER) { return false; } diff --git a/src/data/move.ts b/src/data/move.ts index 5594a45c195..5b7bd5f6c82 100755 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -148,8 +148,22 @@ export default class Move implements Localizable { this.effect = this.id ? `${i18next.t(`move:${i18nKey}.effect`)}${this.nameAppend}` : ""; } - getAttrs(attrType: { new(...args: any[]): MoveAttr }): MoveAttr[] { - return this.attrs.filter(a => a instanceof attrType); + /** + * Get all move attributes that match `attrType` + * @param attrType any attribute that extends {@linkcode MoveAttr} + * @returns Array of attributes that match `attrType`, Empty Array if none match. + */ + getAttrs(attrType: new(...args: any[]) => T): T[] { + return this.attrs.filter((a): a is T => a instanceof attrType); + } + + /** + * Check if a move has an attribute that matches `attrType` + * @param attrType any attribute that extends {@linkcode MoveAttr} + * @returns true if the move has attribute `attrType` + */ + hasAttr(attrType: new(...args: any[]) => T): boolean { + return this.attrs.some((attr) => attr instanceof attrType); } findAttr(attrPredicate: (attr: MoveAttr) => boolean): MoveAttr { @@ -3698,7 +3712,7 @@ export class ProtectAttr extends AddBattlerTagAttr { while (moveHistory.length) { turnMove = moveHistory.shift(); - if (!allMoves[turnMove.move].getAttrs(ProtectAttr).length || turnMove.result !== MoveResult.SUCCESS) { + if (!allMoves[turnMove.move].hasAttr(ProtectAttr) || turnMove.result !== MoveResult.SUCCESS) { break; } timesUsed++; @@ -4480,7 +4494,7 @@ const lastMoveCopiableCondition: MoveConditionFunc = (user, target, move) => { return false; } - if (allMoves[copiableMove].getAttrs(ChargeAttr).length) { + if (allMoves[copiableMove].hasAttr(ChargeAttr)) { return false; } @@ -4570,7 +4584,7 @@ const targetMoveCopiableCondition: MoveConditionFunc = (user, target, move) => { return false; } - if (allMoves[copiableMove.move].getAttrs(ChargeAttr).length && copiableMove.result === MoveResult.OTHER) { + if (allMoves[copiableMove.move].hasAttr(ChargeAttr) && copiableMove.result === MoveResult.OTHER) { return false; } @@ -4987,7 +5001,7 @@ export function getMoveTargets(user: Pokemon, move: Moves): MoveTargetSet { const variableTarget = new Utils.NumberHolder(0); user.getOpponents().forEach(p => applyMoveAttrs(VariableTargetAttr, user, p, allMoves[move], variableTarget)); - const moveTarget = allMoves[move].getAttrs(VariableTargetAttr).length ? variableTarget.value : move ? allMoves[move].moveTarget : move === undefined ? MoveTarget.NEAR_ENEMY : []; + const moveTarget = allMoves[move].hasAttr(VariableTargetAttr) ? variableTarget.value : move ? allMoves[move].moveTarget : move === undefined ? MoveTarget.NEAR_ENEMY : []; const opponents = user.getOpponents(); let set: Pokemon[] = []; diff --git a/src/data/terrain.ts b/src/data/terrain.ts index 6a77f876239..e396c693c4e 100644 --- a/src/data/terrain.ts +++ b/src/data/terrain.ts @@ -56,7 +56,7 @@ export class Terrain { isMoveTerrainCancelled(user: Pokemon, targets: BattlerIndex[], move: Move): boolean { switch (this.terrainType) { case TerrainType.PSYCHIC: - if (!move.getAttrs(ProtectAttr).length) { + if (!move.hasAttr(ProtectAttr)) { const priority = new Utils.IntegerHolder(move.priority); applyAbAttrs(IncrementMovePriorityAbAttr, user, null, move, priority); return priority.value > 0 && user.getOpponents().filter(o => targets.includes(o.getBattlerIndex())).length > 0; diff --git a/src/data/weather.ts b/src/data/weather.ts index 87500e69697..f2b4136f51c 100644 --- a/src/data/weather.ts +++ b/src/data/weather.ts @@ -114,9 +114,9 @@ export class Weather { const field = scene.getField(true); for (const pokemon of field) { - let suppressWeatherEffectAbAttr = pokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr).find(() => true) as SuppressWeatherEffectAbAttr; + let suppressWeatherEffectAbAttr = pokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr)[0]; if (!suppressWeatherEffectAbAttr) { - suppressWeatherEffectAbAttr = pokemon.hasPassive() ? pokemon.getPassiveAbility().getAttrs(SuppressWeatherEffectAbAttr).find(() => true) as SuppressWeatherEffectAbAttr : null; + suppressWeatherEffectAbAttr = pokemon.hasPassive() ? pokemon.getPassiveAbility().getAttrs(SuppressWeatherEffectAbAttr)[0] : null; } if (suppressWeatherEffectAbAttr && (!this.isImmutable() || suppressWeatherEffectAbAttr.affectsImmutable)) { return true; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 02565e799b7..8065b4633b7 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1058,7 +1058,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getAttackMoveEffectiveness(source: Pokemon, move: PokemonMove): TypeDamageMultiplier { - const typeless = !!move.getMove().getAttrs(TypelessAttr).length; + const typeless = move.getMove().hasAttr(TypelessAttr); const typeMultiplier = new Utils.NumberHolder(this.getAttackTypeEffectiveness(move.getMove().type, source)); const cancelled = new Utils.BooleanHolder(false); if (!typeless) { @@ -1424,19 +1424,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if (this.isBoss()) { // Bosses never get self ko moves - movePool = movePool.filter(m => !allMoves[m[0]].getAttrs(SacrificialAttr).length); + movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(SacrificialAttr)); } - movePool = movePool.filter(m => !allMoves[m[0]].getAttrs(SacrificialAttrOnHit).length); + movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(SacrificialAttrOnHit)); if (this.hasTrainer()) { // Trainers never get OHKO moves - movePool = movePool.filter(m => !allMoves[m[0]].getAttrs(OneHitKOAttr).length); + movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(OneHitKOAttr)); // Half the weight of self KO moves - movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].getAttrs(SacrificialAttr).length ? 0.5 : 1)]); - movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].getAttrs(SacrificialAttrOnHit).length ? 0.5 : 1)]); + movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].hasAttr(SacrificialAttr) ? 0.5 : 1)]); + movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].hasAttr(SacrificialAttrOnHit) ? 0.5 : 1)]); // Trainers get a weight bump to stat buffing moves - movePool = movePool.map(m => [m[0], m[1] * (allMoves[m[0]].getAttrs(StatChangeAttr).some(a => (a as StatChangeAttr).levels > 1 && (a as StatChangeAttr).selfTarget) ? 1.25 : 1)]); + movePool = movePool.map(m => [m[0], m[1] * (allMoves[m[0]].getAttrs(StatChangeAttr).some(a => a.levels > 1 && a.selfTarget) ? 1.25 : 1)]); // Trainers get a weight decrease to multiturn moves - movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].getAttrs(ChargeAttr).length || !!allMoves[m[0]].getAttrs(RechargeAttr).length ? 0.7 : 1)]); + movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].hasAttr(ChargeAttr) || !!allMoves[m[0]].hasAttr(RechargeAttr) ? 0.7 : 1)]); } // Weight towards higher power moves, by reducing the power of moves below the highest power. @@ -1627,8 +1627,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const types = this.getTypes(true, true); const cancelled = new Utils.BooleanHolder(false); - const typeless = !!move.getAttrs(TypelessAttr).length; - const typeMultiplier = new Utils.NumberHolder(!typeless && (moveCategory !== MoveCategory.STATUS || move.getAttrs(StatusMoveTypeImmunityAttr).find(attr => types.includes((attr as StatusMoveTypeImmunityAttr).immuneType))) + const typeless = move.hasAttr(TypelessAttr); + const typeMultiplier = new Utils.NumberHolder(!typeless && (moveCategory !== MoveCategory.STATUS || move.getAttrs(StatusMoveTypeImmunityAttr).find(attr => types.includes(attr.immuneType))) ? this.getAttackTypeEffectiveness(type, source) : 1); applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier); @@ -1654,7 +1654,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const isPhysical = moveCategory === MoveCategory.PHYSICAL; const power = new Utils.NumberHolder(move.power); const sourceTeraType = source.getTeraType(); - if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === type && power.value < 60 && move.priority <= 0 && !move.getAttrs(MultiHitAttr).length && !this.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { + if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === type && power.value < 60 && move.priority <= 0 && !move.hasAttr(MultiHitAttr) && !this.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { power.value = 60; } applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, this, battlerMove, power); @@ -1756,7 +1756,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!isTypeImmune) { damage.value = Math.ceil(((((2 * source.level / 5 + 2) * power.value * sourceAtk.value / targetDef.value) / 50) + 2) * stabMultiplier.value * typeMultiplier.value * arenaAttackTypeMultiplier.value * screenMultiplier.value * ((this.scene.randBattleSeedInt(15) + 85) / 100) * criticalMultiplier.value); if (isPhysical && source.status && source.status.effect === StatusEffect.BURN) { - if (!move.getAttrs(BypassBurnDamageReductionAttr).length) { + if (!move.hasAttr(BypassBurnDamageReductionAttr)) { const burnDamageReductionCancelled = new Utils.BooleanHolder(false); applyAbAttrs(BypassBurnDamageReductionAbAttr, source, burnDamageReductionCancelled); if (!burnDamageReductionCancelled.value) { @@ -1768,12 +1768,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyPreAttackAbAttrs(DamageBoostAbAttr, source, this, battlerMove, damage); /** - * For each {@link HitsTagAttr} the move has, doubles the damage of the move if: - * The target has a {@link BattlerTagType} that this move interacts with - * AND - * The move doubles damage when used against that tag - * */ - move.getAttrs(HitsTagAttr).map(hta => hta as HitsTagAttr).filter(hta => hta.doubleDamage).forEach(hta => { + * For each {@link HitsTagAttr} the move has, doubles the damage of the move if: + * The target has a {@link BattlerTagType} that this move interacts with + * AND + * The move doubles damage when used against that tag + */ + move.getAttrs(HitsTagAttr).filter(hta => hta.doubleDamage).forEach(hta => { if (this.getTag(hta.tagType)) { damage.value *= 2; } @@ -3454,7 +3454,7 @@ export class EnemyPokemon extends Pokemon { if (!sortedBenefitScores.length) { // Set target to BattlerIndex.ATTACKER when using a counter move // This is the same as when the player does so - if (!!move.findAttr(attr => attr instanceof CounterDamageAttr)) { + if (move.hasAttr(CounterDamageAttr)) { return [BattlerIndex.ATTACKER]; } diff --git a/src/phases.ts b/src/phases.ts index b23f848397d..8b5e8731faf 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -1553,7 +1553,7 @@ export class SwitchSummonPhase extends SummonPhase { const lastUsedMove = moveId ? allMoves[moveId] : undefined; const currentCommand = pokemon.scene.currentBattle.turnCommands[this.fieldIndex]?.command; - const lastPokemonIsForceSwitchedAndNotFainted = !!lastUsedMove?.findAttr(attr => attr instanceof ForceSwitchOutAttr) && !this.lastPokemon.isFainted(); + const lastPokemonIsForceSwitchedAndNotFainted = lastUsedMove?.hasAttr(ForceSwitchOutAttr) && !this.lastPokemon.isFainted(); // Compensate for turn spent summoning // Or compensate for force switch move if switched out pokemon is not fainted @@ -2459,9 +2459,9 @@ export class MovePhase extends BattlePhase { const oldTarget = moveTarget.value; this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, this.move.moveId, moveTarget)); //Check if this move is immune to being redirected, and restore its target to the intended target if it is. - if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) || this.move.getMove().getAttrs(BypassRedirectAttr).length)) { + if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) || this.move.getMove().hasAttr(BypassRedirectAttr))) { //If an ability prevented this move from being redirected, display its ability pop up. - if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) && !this.move.getMove().getAttrs(BypassRedirectAttr).length) && oldTarget !== moveTarget.value) { + if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) && !this.move.getMove().hasAttr(BypassRedirectAttr)) && oldTarget !== moveTarget.value) { this.scene.unshiftPhase(new ShowAbilityPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr))); } moveTarget.value = oldTarget; @@ -2551,7 +2551,7 @@ export class MovePhase extends BattlePhase { this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), ppUsed)); } - if (!allMoves[this.move.moveId].getAttrs(CopyMoveAttr).length) { + if (!allMoves[this.move.moveId].hasAttr(CopyMoveAttr)) { this.scene.currentBattle.lastMove = this.move.moveId; } @@ -2635,7 +2635,7 @@ export class MovePhase extends BattlePhase { } showMoveText(): void { - if (this.move.getMove().getAttrs(ChargeAttr).length) { + if (this.move.getMove().hasAttr(ChargeAttr)) { const lastMove = this.pokemon.getLastXMoves() as TurnMove[]; if (!lastMove.length || lastMove[0].move !== this.move.getMove().id || lastMove[0].result !== MoveResult.OTHER) { this.scene.queueMessage(getPokemonMessage(this.pokemon, ` used\n${this.move.getName()}!`), 500); @@ -2706,7 +2706,7 @@ export class MoveEffectPhase extends PokemonPhase { const hitCount = new Utils.IntegerHolder(1); // Assume single target for multi hit applyMoveAttrs(MultiHitAttr, user, this.getTarget(), this.move.getMove(), hitCount); - if (this.move.getMove() instanceof AttackMove && !this.move.getMove().getAttrs(FixedDamageAttr).length) { + if (this.move.getMove() instanceof AttackMove && !this.move.getMove().hasAttr(FixedDamageAttr)) { this.scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, hitCount, new Utils.IntegerHolder(0)); } user.turnData.hitsLeft = user.turnData.hitCount = hitCount.value; @@ -2717,7 +2717,7 @@ export class MoveEffectPhase extends PokemonPhase { const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ])); const activeTargets = targets.map(t => t.isActive(true)); - if (!activeTargets.length || (!this.move.getMove().getAttrs(VariableTargetAttr).length && !this.move.getMove().isMultiTarget() && !targetHitChecks[this.targets[0]])) { + if (!activeTargets.length || (!this.move.getMove().hasAttr(VariableTargetAttr) && !this.move.getMove().isMultiTarget() && !targetHitChecks[this.targets[0]])) { user.turnData.hitCount = 1; user.turnData.hitsLeft = 1; if (activeTargets.length) { @@ -2761,7 +2761,7 @@ export class MoveEffectPhase extends PokemonPhase { applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit), user, target, this.move.getMove()).then(() => { if (hitResult !== HitResult.FAIL) { - const chargeEffect = !!this.move.getMove().getAttrs(ChargeAttr).find(ca => (ca as ChargeAttr).usedChargeEffect(user, this.getTarget(), this.move.getMove())); + const chargeEffect = !!this.move.getMove().getAttrs(ChargeAttr).find(ca => ca.usedChargeEffect(user, this.getTarget(), this.move.getMove())); // Charge attribute with charge effect takes all effect attributes and applies them to charge stage, so ignore them if this is present Utils.executeIf(!chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY && (attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit), user, target, this.move.getMove())).then(() => { @@ -2861,7 +2861,7 @@ export class MoveEffectPhase extends PokemonPhase { } const hiddenTag = target.getTag(HiddenTag); - if (hiddenTag && !this.move.getMove().getAttrs(HitsTagAttr).filter(hta => (hta as HitsTagAttr).tagType === hiddenTag.tagType).length) { + if (hiddenTag && !this.move.getMove().getAttrs(HitsTagAttr).some(hta => hta.tagType === hiddenTag.tagType)) { return false; } @@ -2873,7 +2873,7 @@ export class MoveEffectPhase extends PokemonPhase { return true; } - const isOhko = !!this.move.getMove().getAttrs(OneHitKOAccuracyAttr).length; + const isOhko = this.move.getMove().hasAttr(OneHitKOAccuracyAttr); if (!isOhko) { user.scene.applyModifiers(PokemonMoveAccuracyBoosterModifier, user.isPlayer(), user, moveAccuracy); @@ -3534,7 +3534,7 @@ export class FaintPhase extends PokemonPhase { if (defeatSource?.isOnField()) { applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource); const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move]; - const pvattrs = pvmove.getAttrs(PostVictoryStatChangeAttr) as PostVictoryStatChangeAttr[]; + const pvattrs = pvmove.getAttrs(PostVictoryStatChangeAttr); if (pvattrs.length) { for (const pvattr of pvattrs) { pvattr.applyPostVictory(defeatSource, defeatSource, pvmove); From da771daee676a21fd992fdaed8a5724688cb50e2 Mon Sep 17 00:00:00 2001 From: Benjamin Odom Date: Fri, 31 May 2024 20:26:00 -0500 Subject: [PATCH 003/129] [QoL] Remove VS Code Settings (#1672) * Remove VS Code Settings * Update .gitignore --- .gitignore | 5 +++-- .vscode/settings.json | 4 ---- 2 files changed, 3 insertions(+), 6 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 548f44d3693..1b1bc0743c3 100644 --- a/.gitignore +++ b/.gitignore @@ -14,8 +14,6 @@ dist-ssr # Editor directories and files .vscode/* -!.vscode/extensions.json -!.vscode/settings.json .idea .DS_Store *.suo @@ -36,4 +34,7 @@ src/data/battle-anim-data.ts src/overrides.ts coverage + +# Local Documentation /typedoc + diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 08dd484675e..00000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "javascript.preferences.importModuleSpecifierEnding": "minimal", - "typescript.preferences.importModuleSpecifierEnding": "minimal" -} \ No newline at end of file From e7fed48f8e9e99ce1454ca916fe412ac8e9c2e70 Mon Sep 17 00:00:00 2001 From: Xavion3 Date: Sat, 1 Jun 2024 12:10:10 +1000 Subject: [PATCH 004/129] [Balance] Simplify and fix flame/toxic orb weight (#1670) --- src/modifier/modifier-type.ts | 42 +++++++++-------------------------- 1 file changed, 10 insertions(+), 32 deletions(-) diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index dac99bb2376..395c7f4cd8b 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1336,39 +1336,17 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.RARE_EVOLUTION_ITEM, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15) * 4, 32), 32), new WeightedModifierType(modifierTypes.AMULET_COIN, 3), new WeightedModifierType(modifierTypes.TOXIC_ORB, (party: Pokemon[]) => { - let weight = 0; - const filteredParty = party.filter(p => (p.status?.effect === StatusEffect.TOXIC || p.canSetStatus(StatusEffect.TOXIC, true, true)) - && !p.hasAbility(Abilities.FLARE_BOOST) - && !p.getHeldItems().some(i => i instanceof Modifiers.TurnStatusEffectModifier)); - if (filteredParty.some(p => p.hasAbility(Abilities.TOXIC_BOOST) || p.hasAbility(Abilities.POISON_HEAL))) { - weight = 4; - } else if (filteredParty.some(p => p.hasAbility(Abilities.GUTS) || p.hasAbility(Abilities.QUICK_FEET) || p.hasAbility(Abilities.MARVEL_SCALE))) { - weight = 2; - } else { - const moveList = [Moves.FACADE, Moves.TRICK, Moves.FLING, Moves.SWITCHEROO, Moves.PSYCHO_SHIFT]; - if (filteredParty.some(p => p.getMoveset().some(m => moveList.includes(m.moveId)))) { - weight = 1; - } - } - return Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15) * weight, 8 * weight); - }, 32), + const checkedAbilities = [Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.TOXIC_BOOST, Abilities.POISON_HEAL]; + const checkedMoves = [Moves.FACADE, Moves.TRICK, Moves.FLING, Moves.SWITCHEROO, Moves.PSYCHO_SHIFT]; + // If a party member doesn't already have one of these two orbs and has one of the above moves or abilities, the orb can appear + return party.some(p => !p.getHeldItems().some(i => i instanceof Modifiers.TurnStatusEffectModifier) && (checkedAbilities.some(a => p.hasAbility(a, false, true)) || p.getMoveset(true).some(m => checkedMoves.includes(m.moveId)))) ? 10 : 0; + }, 10), new WeightedModifierType(modifierTypes.FLAME_ORB, (party: Pokemon[]) => { - let weight = 0; - const filteredParty = party.filter(p => (p.status?.effect === StatusEffect.BURN || p.canSetStatus(StatusEffect.BURN, true, true)) - && !p.hasAbility(Abilities.TOXIC_BOOST) && !p.hasAbility(Abilities.POISON_HEAL) - && !p.getHeldItems().some(i => i instanceof Modifiers.TurnStatusEffectModifier)); - if (filteredParty.some(p => p.hasAbility(Abilities.FLARE_BOOST))) { - weight = 4; - } else if (filteredParty.some(p => p.hasAbility(Abilities.GUTS) || p.hasAbility(Abilities.QUICK_FEET) || p.hasAbility(Abilities.MARVEL_SCALE))) { - weight = 2; - } else { - const moveList = [Moves.FACADE, Moves.TRICK, Moves.FLING, Moves.SWITCHEROO, Moves.PSYCHO_SHIFT]; - if (filteredParty.some(p => p.getMoveset().some(m => moveList.includes(m.moveId)))) { - weight = 1; - } - } - return Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15) * weight, 8 * weight); - }, 32), + const checkedAbilities = [Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.FLARE_BOOST]; + const checkedMoves = [Moves.FACADE, Moves.TRICK, Moves.FLING, Moves.SWITCHEROO, Moves.PSYCHO_SHIFT]; + // If a party member doesn't already have one of these two orbs and has one of the above moves or abilities, the orb can appear + return party.some(p => !p.getHeldItems().some(i => i instanceof Modifiers.TurnStatusEffectModifier) && (checkedAbilities.some(a => p.hasAbility(a, false, true)) || p.getMoveset(true).some(m => checkedMoves.includes(m.moveId)))) ? 10 : 0; + }, 10), new WeightedModifierType(modifierTypes.REVIVER_SEED, 4), new WeightedModifierType(modifierTypes.CANDY_JAR, 5), new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10), From 5cf9a98ee0d466f07f787ce8da19368349817126 Mon Sep 17 00:00:00 2001 From: returntoice Date: Sat, 1 Jun 2024 14:27:21 +0900 Subject: [PATCH 005/129] [Localization] Minor Korean modifications (#1665) --- src/locales/ko/biome.ts | 6 +++--- src/locales/ko/command-ui-handler.ts | 4 ++-- src/locales/ko/modifier-type.ts | 9 ++++++--- src/locales/ko/voucher.ts | 8 ++++---- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/locales/ko/biome.ts b/src/locales/ko/biome.ts index 944ae4773b9..60fb016df64 100644 --- a/src/locales/ko/biome.ts +++ b/src/locales/ko/biome.ts @@ -24,13 +24,13 @@ export const biome: SimpleTranslationEntries = { "GRAVEYARD": "묘지", "DOJO": "도장", "FACTORY": "공장", - "RUINS": "고대 폐허", + "RUINS": "고대 유적", "WASTELAND": "황무지", "ABYSS": "심연", - "SPACE": "성층권", + "SPACE": "우주", "CONSTRUCTION_SITE": "공사장", "JUNGLE": "정글", - "FAIRY_CAVE": "요정 동굴", + "FAIRY_CAVE": "페어리 동굴", "TEMPLE": "사원", "SLUM": "슬럼", "SNOWY_FOREST": "눈덮인 숲", diff --git a/src/locales/ko/command-ui-handler.ts b/src/locales/ko/command-ui-handler.ts index ea1bf43cf78..c1d3d4b680f 100644 --- a/src/locales/ko/command-ui-handler.ts +++ b/src/locales/ko/command-ui-handler.ts @@ -1,9 +1,9 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const commandUiHandler: SimpleTranslationEntries = { - "fight": "싸우다", + "fight": "싸운다", "ball": "볼", "pokemon": "포켓몬", - "run": "도망치다", + "run": "도망간다", "actionMessage": "{{pokemonName}}(는)은 무엇을 할까?", } as const; diff --git a/src/locales/ko/modifier-type.ts b/src/locales/ko/modifier-type.ts index 1fab6609b00..3694a2ef523 100644 --- a/src/locales/ko/modifier-type.ts +++ b/src/locales/ko/modifier-type.ts @@ -167,7 +167,7 @@ export const modifierType: ModifierTypeTranslationEntries = { "EXP_SHARE": { name: "학습장치", description: "배틀에 참여하지 않아도 20%의 경험치를 받을 수 있는 장치" }, "EXP_BALANCE": { name: "균형학습장치", description: "레벨이 낮은 포켓몬이 받는 경험치를 가중" }, - "OVAL_CHARM": { name: "Oval Charm", description: "여러 마리의 포켓몬이 배틀에 참여할 경우, 전체 경험치의 10%씩을 추가로 획득" }, + "OVAL_CHARM": { name: "둥근부적", description: "여러 마리의 포켓몬이 배틀에 참여할 경우, 전체 경험치의 10%씩을 추가로 획득" }, "EXP_CHARM": { name: "경험부적" }, "SUPER_EXP_CHARM": { name: "좋은경험부적" }, @@ -209,6 +209,9 @@ export const modifierType: ModifierTypeTranslationEntries = { "LEFTOVERS": { name: "먹다남은음식", description: "포켓몬의 HP가 매 턴 최대 체력의 1/16씩 회복" }, "SHELL_BELL": { name: "조개껍질방울", description: "포켓몬이 준 데미지의 1/8씩 회복" }, + "TOXIC_ORB": { name: "맹독구슬", description: "이 도구를 지닌 포켓몬은 턴이 끝나는 시점에 상태이상에 걸리지 않았다면 맹독 상태가 된다." }, + "FLAME_ORB": { name: "화염구슬", description: "이 도구를 지닌 포켓몬은 턴이 끝나는 시점에 상태이상에 걸리지 않았다면 화상 상태가 된다." }, + "BATON": { name: "바톤", description: "포켓몬을 교체할 때 효과를 넘겨줄 수 있으며, 함정의 영향을 받지 않게 함" }, "SHINY_CHARM": { name: "빛나는부적", description: "야생 포켓몬이 색이 다른 포켓몬으로 등장할 확률을 급격히 증가" }, @@ -218,7 +221,7 @@ export const modifierType: ModifierTypeTranslationEntries = { "DNA_SPLICERS": { name: "유전자쐐기" }, - "MINI_BLACK_HOLE": { name: "미니 블랙 홀" }, + "MINI_BLACK_HOLE": { name: "미니 블랙홀" }, "GOLDEN_POKEBALL": { name: "황금몬스터볼", description: "전투 후 획득하는 아이템의 선택지를 하나 더 추가" }, @@ -315,7 +318,7 @@ export const modifierType: ModifierTypeTranslationEntries = { "ALTARIANITE": "파비코리나이트", "AMPHAROSITE": "전룡나이트", "AUDINITE": "다부니나이트", - "BANETTITE": "깜까미나이트", + "BANETTITE": "다크펫나이트", "BEEDRILLITE": "독침붕나이트", "BLASTOISINITE": "거북왕나이트", "BLAZIKENITE": "번치코나이트", diff --git a/src/locales/ko/voucher.ts b/src/locales/ko/voucher.ts index aebf29037df..df9e6b31728 100644 --- a/src/locales/ko/voucher.ts +++ b/src/locales/ko/voucher.ts @@ -2,10 +2,10 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const voucher: SimpleTranslationEntries = { "vouchers": "바우처", - "eggVoucher": "에그 바우처", - "eggVoucherPlus": "에그 바우처 플러스", - "eggVoucherPremium": "에그 바우처 프리미엄", - "eggVoucherGold": "에그 바우처 골드", + "eggVoucher": "알 바우처", + "eggVoucherPlus": "알 바우처 플러스", + "eggVoucherPremium": "알 바우처 프리미엄", + "eggVoucherGold": "알 바우처 골드", "locked": "미획득", "defeatTrainer" : "{{trainerName}}에게 승리", } as const; From 060b1b2cccf7a8b2f7451c684f8a9e0dadcffde2 Mon Sep 17 00:00:00 2001 From: Greenlamp2 <44787002+Greenlamp2@users.noreply.github.com> Date: Sat, 1 Jun 2024 14:56:32 +0200 Subject: [PATCH 006/129] Menu - Controls Rebind - Gamepad & Keyboard (Cleaner git log) (#1666) * squased merge rebind_menu * azerty to qwerty * add a check to preven a crash in firefox * reset navigation menu on quit * removed dual lock mekanism * navigation display update icons on new bind * added submit binding * removed attribute no longer used * change protected to abstract * remove last bind protection since action and cancel are protected + renamed default controller to controller * removed default alt qwerty keys in config * fix some errors for doc * fix tests * fix some more errors for docs * fix some more errors for docs final ? * added alt bind for menu navigation + update icons on delete/home --- index.css | 70 +- public/images/inputs/dualshock.json | 148 ++++ public/images/inputs/dualshock.png | Bin 0 -> 1085 bytes public/images/inputs/keyboard.json | 596 ++++++++++++++++ public/images/inputs/keyboard.png | Bin 0 -> 1973 bytes public/images/inputs/xbox.json | 140 ++++ public/images/inputs/xbox.png | Bin 0 -> 1318 bytes src/battle-scene.ts | 4 +- src/configs/inputs/cfg_keyboard_qwerty.ts | 293 ++++++++ src/configs/inputs/configHandler.ts | 208 ++++++ src/configs/inputs/pad_dualshock.ts | 88 +++ src/configs/inputs/pad_generic.ts | 90 +++ src/configs/inputs/pad_procon.ts | 85 +++ src/configs/inputs/pad_unlicensedSNES.ts | 76 ++ src/configs/inputs/pad_xbox360.ts | 84 +++ src/configs/pad_dualshock.ts | 29 - src/configs/pad_generic.ts | 27 - src/configs/pad_procon.ts | 28 - src/configs/pad_unlicensedSNES.ts | 23 - src/configs/pad_xbox360.ts | 28 - src/enums/devices.ts | 4 + src/inputs-controller.ts | 632 ++++++++++------- src/loading-scene.ts | 4 + src/system/game-data.ts | 136 ++++ src/system/settings-gamepad.ts | 147 ++++ src/system/settings-keyboard.ts | 208 ++++++ src/system/settings.ts | 16 +- src/test/helpers/inGameManip.ts | 79 +++ src/test/helpers/menuManip.ts | 131 ++++ src/test/rebinding_setting.test.ts | 417 +++++++++++ src/touch-controls.ts | 164 ++--- src/ui-inputs.ts | 8 +- .../settings/abstract-binding-ui-handler.ts | 259 +++++++ .../settings/abstract-settings-ui-handler.ts | 647 ++++++++++++++++++ src/ui/settings/gamepad-binding-ui-handler.ts | 83 +++ .../settings/keyboard-binding-ui-handler.ts | 73 ++ src/ui/settings/navigationMenu.ts | 217 ++++++ .../option-select-ui-handler.ts | 6 +- .../settings/settings-gamepad-ui-handler.ts | 168 +++++ .../settings/settings-keyboard-ui-handler.ts | 224 ++++++ src/ui/{ => settings}/settings-ui-handler.ts | 103 ++- src/ui/summary-ui-handler.ts | 2 +- src/ui/text.ts | 12 + src/ui/title-ui-handler.ts | 2 +- src/ui/ui.ts | 24 +- src/utils.ts | 46 ++ 46 files changed, 5318 insertions(+), 511 deletions(-) create mode 100644 public/images/inputs/dualshock.json create mode 100644 public/images/inputs/dualshock.png create mode 100644 public/images/inputs/keyboard.json create mode 100644 public/images/inputs/keyboard.png create mode 100644 public/images/inputs/xbox.json create mode 100644 public/images/inputs/xbox.png create mode 100644 src/configs/inputs/cfg_keyboard_qwerty.ts create mode 100644 src/configs/inputs/configHandler.ts create mode 100644 src/configs/inputs/pad_dualshock.ts create mode 100644 src/configs/inputs/pad_generic.ts create mode 100644 src/configs/inputs/pad_procon.ts create mode 100644 src/configs/inputs/pad_unlicensedSNES.ts create mode 100644 src/configs/inputs/pad_xbox360.ts delete mode 100644 src/configs/pad_dualshock.ts delete mode 100644 src/configs/pad_generic.ts delete mode 100644 src/configs/pad_procon.ts delete mode 100644 src/configs/pad_unlicensedSNES.ts delete mode 100644 src/configs/pad_xbox360.ts create mode 100644 src/enums/devices.ts create mode 100644 src/system/settings-gamepad.ts create mode 100644 src/system/settings-keyboard.ts create mode 100644 src/test/helpers/inGameManip.ts create mode 100644 src/test/helpers/menuManip.ts create mode 100644 src/test/rebinding_setting.test.ts create mode 100644 src/ui/settings/abstract-binding-ui-handler.ts create mode 100644 src/ui/settings/abstract-settings-ui-handler.ts create mode 100644 src/ui/settings/gamepad-binding-ui-handler.ts create mode 100644 src/ui/settings/keyboard-binding-ui-handler.ts create mode 100644 src/ui/settings/navigationMenu.ts rename src/ui/{ => settings}/option-select-ui-handler.ts (59%) create mode 100644 src/ui/settings/settings-gamepad-ui-handler.ts create mode 100644 src/ui/settings/settings-keyboard-ui-handler.ts rename src/ui/{ => settings}/settings-ui-handler.ts (69%) diff --git a/index.css b/index.css index df305781646..1a695fa0c4f 100644 --- a/index.css +++ b/index.css @@ -146,7 +146,9 @@ body { margin-left: 10%; } -#touchControls:not([data-ui-mode='STARTER_SELECT']) #apad .apadRectBtnContainer > .apadSqBtn, #touchControls:not([data-ui-mode='STARTER_SELECT']) #apad .apadSqBtnContainer > .apadSqBtn { +#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadRectBtnContainer > .apadSqBtn, +#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadSqBtnContainer +{ display: none; } @@ -175,4 +177,70 @@ body { input:-internal-autofill-selected { -webkit-background-clip: text; background-clip: text; +} +#banner { + display: none; + position: absolute; + top: 33.2%; + left: 0; + text-align: center; + z-index: 1000; /* Ensures the banner is on top of other elements */ + & > img { + opacity: 50%; + } +} + +/* Firefox old*/ +@-moz-keyframes blink { + 0% { + opacity:1; + } + 50% { + opacity:0; + } + 100% { + opacity:1; + } +} + +@-webkit-keyframes blink { + 0% { + opacity:1; + } + 50% { + opacity:0; + } + 100% { + opacity:1; + } +} +/* IE */ +@-ms-keyframes blink { + 0% { + opacity:1; + } + 50% { + opacity:0; + } + 100% { + opacity:1; + } +} +/* Opera and prob css3 final iteration */ +@keyframes blink { + 0% { + opacity:1; + } + 50% { + opacity:0; + } + 100% { + opacity:1; + } +} +.blink-image { + -moz-animation: blink normal 4s infinite ease-in-out; /* Firefox */ + -webkit-animation: blink normal 4s infinite ease-in-out; /* Webkit */ + -ms-animation: blink normal 4s infinite ease-in-out; /* IE */ + animation: blink normal 4s infinite ease-in-out; /* Opera and prob css3 final iteration */ } \ No newline at end of file diff --git a/public/images/inputs/dualshock.json b/public/images/inputs/dualshock.json new file mode 100644 index 00000000000..9f782061289 --- /dev/null +++ b/public/images/inputs/dualshock.json @@ -0,0 +1,148 @@ +{"frames": [ + +{ + "filename": "CIRCLE.png", + "frame": {"x":0,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "CROSS.png", + "frame": {"x":12,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "DOWN.png", + "frame": {"x":24,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "L1.png", + "frame": {"x":36,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "L2.png", + "frame": {"x":48,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "L3.png", + "frame": {"x":60,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "LEFT.png", + "frame": {"x":72,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "R1.png", + "frame": {"x":84,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "R2.png", + "frame": {"x":96,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "R3.png", + "frame": {"x":108,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "RIGHT.png", + "frame": {"x":120,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "SELECT.png", + "frame": {"x":132,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "SQUARE.png", + "frame": {"x":144,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "START.png", + "frame": {"x":156,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "TOUCH.png", + "frame": {"x":168,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "TRIANGLE.png", + "frame": {"x":180,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "UP.png", + "frame": {"x":192,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}], +"meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "1.0", + "image": "dualshock.png", + "format": "RGBA8888", + "size": {"w":204,"h":12}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:47df68ade4299adf7d334f25cb833ece:039b9ac469e3616fb9635a6a19cca50e:adc25708364be3d9e70074e95305c745$" +} +} diff --git a/public/images/inputs/dualshock.png b/public/images/inputs/dualshock.png new file mode 100644 index 0000000000000000000000000000000000000000..264f03a298ed078338a983c4374de2173c905bf2 GIT binary patch literal 1085 zcmV-D1j74?P)S-#O=gB*3xcbEQD_?DwCaiO2Hk%9qO+%^I_aYub2R+qcD{!!aL++@|`OlsEDG z{-N(Q0K89?uct&*F-9iwafu&ozkE@K(FowKe+BtmDbU%b@W;l!t74tcl>$Eg`5Y*5 zBvb?7<*t7vbz-6~g5e2a8~mt=kdZHH!gOP$;4u~5gqg(16u(Uz@tp;)41-Y@005pR z3(D9~`9rZ!jl1ZB!p~;&6nwv`js3i6{MVohH|-WvSYFzM1+N^c0KERmvjZp&Dop=& z@eE$W9@Dq244IS{8h|(;Srt|hPc^xh7uU>Qvv~>t06VqDM71-ui+B(_ifjzJD#mvm znO;6u3NA0MC+zb+*Ed$!duA~S`^kD``1<{GNC#u1OO`k1ZzrA~uTFLlihUFOeEdZ| z0{{g3El7OW1WZ(spBOulW1`IQ0m5WiUfN`zn|2EZqb@9X<Qj?N6rK#zkgR|YgCt@XD-Z>T?^nV1tD(sVCqulhCECZ|roxX$g~!w;@&N$g5or<$HK2Ho?q8%NcKz(~FM9}q(6(jP*d9Hg0 z$)Fp<(cuaEF2OJDMxuS&q7qeAT?&tRr`DL+0f*`M=ceHdlLb4?QxOUYqI7KtC3b)) zsc&yVeS3=~4;7PbrJhx?xauyv@Jlhi%h(F*FvC$7(#Zl_hVgrtbh5xUXQKX(>;da6 zm^5p>y$OBQnx0yvZL60k!!bUruBa=H_1~Tif=tSDPAp6mvBQ{jUP@xh=0UZ|HMNO+ z004O3{uFv9tB)YRUY&5QipvO_<7qnZ#>DUm)J5sQ0>PVgANb%4$OEG z;@tF$x-Q6QL!hT}(@!l{kAdIDwk;oA#oh!DmGgfA9B6W0n|z?x00000NkvXXu0mjf D>AC^o literal 0 HcmV?d00001 diff --git a/public/images/inputs/keyboard.json b/public/images/inputs/keyboard.json new file mode 100644 index 00000000000..b1902df10d6 --- /dev/null +++ b/public/images/inputs/keyboard.json @@ -0,0 +1,596 @@ +{"frames": [ + +{ + "filename": "0.png", + "frame": {"x":0,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "1.png", + "frame": {"x":12,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "2.png", + "frame": {"x":24,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "3.png", + "frame": {"x":36,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "4.png", + "frame": {"x":48,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "5.png", + "frame": {"x":60,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "6.png", + "frame": {"x":72,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "7.png", + "frame": {"x":84,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "8.png", + "frame": {"x":96,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "9.png", + "frame": {"x":108,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "A.png", + "frame": {"x":120,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "ALT.png", + "frame": {"x":132,"y":0,"w":16,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":16,"h":12}, + "sourceSize": {"w":16,"h":12} +}, +{ + "filename": "B.png", + "frame": {"x":148,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "BACK.png", + "frame": {"x":160,"y":0,"w":24,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":24,"h":12}, + "sourceSize": {"w":24,"h":12} +}, +{ + "filename": "C.png", + "frame": {"x":184,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "CTRL.png", + "frame": {"x":196,"y":0,"w":22,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":22,"h":12}, + "sourceSize": {"w":22,"h":12} +}, +{ + "filename": "D.png", + "frame": {"x":218,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "DEL.png", + "frame": {"x":230,"y":0,"w":17,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":17,"h":12}, + "sourceSize": {"w":17,"h":12} +}, +{ + "filename": "E.png", + "frame": {"x":247,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "END.png", + "frame": {"x":259,"y":0,"w":18,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":18,"h":12}, + "sourceSize": {"w":18,"h":12} +}, +{ + "filename": "ENTER.png", + "frame": {"x":277,"y":0,"w":27,"h":11}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":27,"h":11}, + "sourceSize": {"w":27,"h":11} +}, +{ + "filename": "ESC.png", + "frame": {"x":304,"y":0,"w":17,"h":11}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":17,"h":11}, + "sourceSize": {"w":17,"h":11} +}, +{ + "filename": "F.png", + "frame": {"x":321,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "F1.png", + "frame": {"x":333,"y":0,"w":13,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":13,"h":12}, + "sourceSize": {"w":13,"h":12} +}, +{ + "filename": "F2.png", + "frame": {"x":346,"y":0,"w":13,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":13,"h":12}, + "sourceSize": {"w":13,"h":12} +}, +{ + "filename": "F3.png", + "frame": {"x":359,"y":0,"w":13,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":13,"h":12}, + "sourceSize": {"w":13,"h":12} +}, +{ + "filename": "F4.png", + "frame": {"x":372,"y":0,"w":13,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":13,"h":12}, + "sourceSize": {"w":13,"h":12} +}, +{ + "filename": "F5.png", + "frame": {"x":385,"y":0,"w":13,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":13,"h":12}, + "sourceSize": {"w":13,"h":12} +}, +{ + "filename": "F6.png", + "frame": {"x":398,"y":0,"w":13,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":13,"h":12}, + "sourceSize": {"w":13,"h":12} +}, +{ + "filename": "F7.png", + "frame": {"x":411,"y":0,"w":13,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":13,"h":12}, + "sourceSize": {"w":13,"h":12} +}, +{ + "filename": "F8.png", + "frame": {"x":424,"y":0,"w":13,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":13,"h":12}, + "sourceSize": {"w":13,"h":12} +}, +{ + "filename": "F9.png", + "frame": {"x":437,"y":0,"w":13,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":13,"h":12}, + "sourceSize": {"w":13,"h":12} +}, +{ + "filename": "F10.png", + "frame": {"x":450,"y":0,"w":16,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":16,"h":12}, + "sourceSize": {"w":16,"h":12} +}, +{ + "filename": "F11.png", + "frame": {"x":466,"y":0,"w":15,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":15,"h":12}, + "sourceSize": {"w":15,"h":12} +}, +{ + "filename": "F12.png", + "frame": {"x":481,"y":0,"w":16,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":16,"h":12}, + "sourceSize": {"w":16,"h":12} +}, +{ + "filename": "G.png", + "frame": {"x":497,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "H.png", + "frame": {"x":509,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "HOME.png", + "frame": {"x":521,"y":0,"w":23,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":23,"h":12}, + "sourceSize": {"w":23,"h":12} +}, +{ + "filename": "I.png", + "frame": {"x":544,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "INS.png", + "frame": {"x":556,"y":0,"w":16,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":16,"h":12}, + "sourceSize": {"w":16,"h":12} +}, +{ + "filename": "J.png", + "frame": {"x":572,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "K.png", + "frame": {"x":584,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "KEY_ARROW_DOWN.png", + "frame": {"x":596,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "KEY_ARROW_LEFT.png", + "frame": {"x":608,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "KEY_ARROW_RIGHT.png", + "frame": {"x":620,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "KEY_ARROW_UP.png", + "frame": {"x":632,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "L.png", + "frame": {"x":644,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "LEFT_BRACKET.png", + "frame": {"x":656,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "M.png", + "frame": {"x":668,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "MINUS.png", + "frame": {"x":680,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "N.png", + "frame": {"x":692,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "O.png", + "frame": {"x":704,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "P.png", + "frame": {"x":716,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "PAGE_DOWN.png", + "frame": {"x":728,"y":0,"w":20,"h":11}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":20,"h":11}, + "sourceSize": {"w":20,"h":11} +}, +{ + "filename": "PAGE_UP.png", + "frame": {"x":748,"y":0,"w":20,"h":11}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":20,"h":11}, + "sourceSize": {"w":20,"h":11} +}, +{ + "filename": "PLUS.png", + "frame": {"x":768,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "Q.png", + "frame": {"x":780,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "QUOTE.png", + "frame": {"x":792,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "R.png", + "frame": {"x":804,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "RIGHT_BRACKET.png", + "frame": {"x":816,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "S.png", + "frame": {"x":828,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "SEMICOLON.png", + "frame": {"x":840,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "SHIFT.png", + "frame": {"x":852,"y":0,"w":23,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":23,"h":12}, + "sourceSize": {"w":23,"h":12} +}, +{ + "filename": "SPACE.png", + "frame": {"x":875,"y":0,"w":25,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":25,"h":12}, + "sourceSize": {"w":25,"h":12} +}, +{ + "filename": "T.png", + "frame": {"x":900,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "TAB.png", + "frame": {"x":912,"y":0,"w":19,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":19,"h":12}, + "sourceSize": {"w":19,"h":12} +}, +{ + "filename": "TILDE.png", + "frame": {"x":931,"y":0,"w":15,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":15,"h":12}, + "sourceSize": {"w":15,"h":12} +}, +{ + "filename": "U.png", + "frame": {"x":946,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "V.png", + "frame": {"x":958,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "W.png", + "frame": {"x":970,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "X.png", + "frame": {"x":982,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "Y.png", + "frame": {"x":994,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "Z.png", + "frame": {"x":1006,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}], +"meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "1.0", + "image": "keyboard.png", + "format": "RGBA8888", + "size": {"w":1018,"h":12}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:085d4353a5c4d18c90f82f8926710d72:45908b22b446cf7f4904d4e0b658b16a:bad03abb89ad027d879c383c13fd51bc$" +} +} diff --git a/public/images/inputs/keyboard.png b/public/images/inputs/keyboard.png new file mode 100644 index 0000000000000000000000000000000000000000..67b26af12de5b3c938d6422361e3d7972fa287fe GIT binary patch literal 1973 zcmZvddpy$%AIEt>|k^VlX z0bm38?bqA1@!J-+wz+;gn-l%dB?G_~$oE+Xl+bnpfX*ePkN25W$sFd@m2kgp^eukF z0Y4)j*Jl|XZQfjYm0_9h_ZH%x!Pu9f(IRR6n0+St^v2j5kl13b;|oHr)()GD_rhnL z^Xl17H+9NJJW%bu+1X5I}{8-=3f{EIy=uB`HBY01#n4(a}-b7DJ>(fV4Ko z2_9m>!(U#NiA_x&VJ^(oEu5sng+({kKLm88EiY#r(EOrXu+ei^EyQB6C55?cnWs{> zG63My=N>Qk12);Q`R12_#H(u~heD%&AKo)AKLzdDkQLwaBDYX;pH)5l-=f5K5C|Le z<`}PXL^?f-n1mRm9(beT3fFORUktgXH)UQkus%eTgsn}x)n{4DdE#}lnMHO4fO?&o zT7Il1%<|c-OKIDeIxl5=Jd)(N&ZiSM)nOSet1SSE?)gzp+r{|fUNvQ8e8mWeP$3=# zfo^}M6S>-K46LI&oj4ux{{lIK-e1&wen@~28WGz(Fz5nsd-?42HqaXWS{`hey>M{V zRjH_5kgDp8f2WQ853{)KsWT?w99cQ6I-fA=HQqcbP{s6~iWR)0wAdo@LqM5tq?`~Y zrp`KCL*}ftwWCby3KG=rvE6Pn)$%Hy>lM*D4xA^!(-7_22&L03MxdPfX~yqa!dd-f zmHR9qD>OS8bX(!Bdfe}`&8?Ezp-NX`VtDK;LFqQuZ0d66!52njm9*mJZ5h52X-|$W z)FjRWR@+CofqaX=f7L7FTX|SAse3ItT{xc11mKrJ@@57?F$GdP`9_o|%q^w;_4uCE zuFv7^i~`kT#PgR9{>v11Xd$?1#!Elsm`4R_MWQrhz&3f^f_<5KDslNNy>AH3fIG1 zEf!c=y2BhF8#++AG=FDcb!aN%*|h3Fm1;paSA(3j(@6_AHWW=omn zemjk1C+#=eJ_{#u4Dp1+g3f3K(wM<~RrIa-wV82@;im(zh7U_gR6z=GJTp=_h@|rUHO)FMb zRa2DUmR1NS2Km>9a@p@@1qDvP%`GgU@y!KC@?f#6o2RcPjS*~Xke6$A+FabZk>`w0 zEMK~iJ@x+W>j~0mvcXZ}Xq#d_$FpGMu#JA}X(DX9K|WH4xK$%X*NL&s_ zKiGGJZl29Vzd#1fTfhn1t|{Q_Q|*s~K7!ef%jqO1YYvw63(v*sVFS zsas>eLg80uRxM3Mq@*NtFWo*%e@fF=CbsW$iKB@HKTuw#)xC*{lMEW(Hp_v)aGWQ` zDGc^vpiKY76IA#MM`APsw{lkT>6kFce9-MtlTwWyJ=*R|6Qr1u*-~h0iD5Uo%w8NZ|_gC(`cbedgRlqg3_)@UDC?u z#)my7-D?>iKDeXvr$G*yEKf~t|Bk~jy!;Is7MV7{{0r9I*3opWQ~7$j>hnDxuj$OS z15K3#vA5H*{-+5(-Qg7-i}^xLZjNs_{tvwee|4xQ zicz<_u`_WsbUNm;jDmLfx8(UbqOwQ-TW3|}+`$k)ymK|dedBeR_<}Aib~D~%aWie0 t_px8@rUNhN6*e{{FYw{vVTEh!P)AbS?%usw{jG-qq;H^4?a_!|{|1Nm93}t& literal 0 HcmV?d00001 diff --git a/public/images/inputs/xbox.json b/public/images/inputs/xbox.json new file mode 100644 index 00000000000..32c00003687 --- /dev/null +++ b/public/images/inputs/xbox.json @@ -0,0 +1,140 @@ +{"frames": [ + +{ + "filename": "Bumper_L.png", + "frame": {"x":0,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "Bumper_R.png", + "frame": {"x":12,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "DOWN.png", + "frame": {"x":24,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "LEFT.png", + "frame": {"x":36,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "LS.png", + "frame": {"x":48,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "RIGHT.png", + "frame": {"x":60,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "RS.png", + "frame": {"x":72,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "SELECT.png", + "frame": {"x":84,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "START.png", + "frame": {"x":96,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "Trigger_L.png", + "frame": {"x":108,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "Trigger_R.png", + "frame": {"x":120,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "UP.png", + "frame": {"x":132,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "XB_Letter_A_OL.png", + "frame": {"x":144,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "XB_Letter_B_OL.png", + "frame": {"x":156,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "XB_Letter_X_OL.png", + "frame": {"x":168,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}, +{ + "filename": "XB_Letter_Y_OL.png", + "frame": {"x":180,"y":0,"w":12,"h":12}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, + "sourceSize": {"w":12,"h":12} +}], +"meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "1.0", + "image": "xbox.png", + "format": "RGBA8888", + "size": {"w":192,"h":12}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:dda9e220b2ea223723253388c465ea25:8ab4a5ecdc22848a8718a1285590a78c:7ad6008cd8fa3f9f4bfb17e0cfcbbb64$" +} +} diff --git a/public/images/inputs/xbox.png b/public/images/inputs/xbox.png new file mode 100644 index 0000000000000000000000000000000000000000..037fd8515aeb4862d73ca123329e17a37014f797 GIT binary patch literal 1318 zcmV+>1=;$EP)r zAO~$hP^fDpVn~}qau6X8qfA*Pw^E&l)W03wp$kg|If46JF)~*F~tH0BDD;tY!%ddf*{UhPAI*X5lQJ3M6 zn0rOYe|_#p;j>G_!$SP!tt}z`^;}Vi|1zxz@x{V*AjTP&_aQ4&(?MV$ZGgSdhJixVYb z5D@3zJUwZ^PyP0lFbIhA2S2SC@PAIngh4=@|FhLm1OBadzBdFxFFWaSse=AMh+h1E zHk*`76?%#AtppU|<8<-PQ!Z7oqp8qz6$>j_EUaWP92z$Tl#Ff`9|@1u$y{$PWTENJ zF-Sj;C(caxkW0kJE#Aqo-R#U(>r^bR8-8Un|FH*g>}V=3tW|MgtqM(7@q0OL+8tQu zp9|J7|5D@9bQK#XCQZ8o>-;a=x=L+x^8Ksm%~wpj1MB=Bt!+~L=Np??T^u!F`dTp} z$qsC$Fd7$iug=dJpVuvtmyaAp#BVq>PBW>Uu;UT_Y}OOaS5Xoe^hbq!b~F{a55J&& z>Di0asd+vG&pVu=B&7NOo$_lrj+(CG(nb&NPBoUj(N-~7j?a+;ii!GUK+Y!wV0q=j`S?5sz7C+LM1s^O5CC62)?^fK)nZu$PF-a;`wz0=(%R zzm@g7wBhcPt8iuj0B9iC$q#Q|#>l-T+IEuCcRqZ(`7>?v(kq+gt?nsG!eGo+9lu?A zT6x~;S-zjlzny%F#dX8wmb2YBsdN&lbP~(Cf)w7X+?}e^T|h>X+j~fE@6kZ8laYH% zsNJuicE3Uc!A{Z6b7<^x~EC+LqNJ$upM%vO8|o}aTD{!T)6fmAx#cBd!&fvS*y#1daK>;O*o z0f46~A2)*GT#VvRAtuE0uR*;2KGF#QshNxc$p0rtXXGCHoE_fF36k6Ihv)mdP?Q9M z{-}XVO;=4kWO(t;KM{|iKM*p^JvEaN>SxE`?IMHc7qVSP#>DZ-Yg0RgaQx(rD1v?* zwVmfX`z*@%Ot6XfiSbXb+-U~Fl{*xF%Eq3rO+}B|ET2F#0G_z~zVTb{oTZ~nY|NP-Hn``KE$KEnE%=7LsS1X-vQ<==6lU|VCo;bk-<;5vO@gO zjf|;3dpm&4b+P{J?Ep-5fKCd3(&Tie>~)NG2UIKiyMY&-e31mj*8!}sJl<>Fh8%M~ z${#lchFu+C{=jDn47)l29|Xktt?B@o>-(m_VCw)~D{*(DwEkbJ1iJ|snVO7Dq0q^W c|HMl0Uyu)?)k config.deviceMapping[key] === keycode); +} + +/** + * Retrieves the setting name associated with the specified keycode. + * + * @param config - The configuration object containing custom settings. + * @param keycode - The keycode to search for. + * @returns The setting name associated with the specified keycode. + */ +export function getSettingNameWithKeycode(config, keycode) { + const key = getKeyWithKeycode(config, keycode); + return config.custom[key]; +} + +/** + * Retrieves the icon associated with the specified keycode. + * + * @param config - The configuration object containing icons. + * @param keycode - The keycode to search for. + * @returns The icon associated with the specified keycode. + */ +export function getIconWithKeycode(config, keycode) { + const key = getKeyWithKeycode(config, keycode); + return config.icons[key]; +} + +/** + * Retrieves the button associated with the specified keycode. + * + * @param config - The configuration object containing settings. + * @param keycode - The keycode to search for. + * @returns The button associated with the specified keycode. + */ +export function getButtonWithKeycode(config, keycode) { + const settingName = getSettingNameWithKeycode(config, keycode); + return config.settings[settingName]; +} + +/** + * Retrieves the key associated with the specified setting name. + * + * @param config - The configuration object containing custom settings. + * @param settingName - The setting name to search for. + * @returns The key associated with the specified setting name. + */ +export function getKeyWithSettingName(config, settingName) { + return Object.keys(config.custom).find(key => config.custom[key] === settingName); +} + +/** + * Retrieves the setting name associated with the specified key. + * + * @param config - The configuration object containing custom settings. + * @param key - The key to search for. + * @returns The setting name associated with the specified key. + */ +export function getSettingNameWithKey(config, key) { + return config.custom[key]; +} + +/** + * Retrieves the icon associated with the specified key. + * + * @param config - The configuration object containing icons. + * @param key - The key to search for. + * @returns The icon associated with the specified key. + */ +export function getIconWithKey(config, key) { + return config.icons[key]; +} + +/** + * Retrieves the icon associated with the specified setting name. + * + * @param config - The configuration object containing icons. + * @param settingName - The setting name to search for. + * @returns The icon associated with the specified setting name. + */ +export function getIconWithSettingName(config, settingName) { + const key = getKeyWithSettingName(config, settingName); + return getIconWithKey(config, key); +} + +export function getIconForLatestInput(configs, source, devices, settingName) { + let config; + if (source === "gamepad") { + config = configs[devices[Device.GAMEPAD]]; + } else { + config = configs[devices[Device.KEYBOARD]]; + } + const icon = getIconWithSettingName(config, settingName); + if (!icon) { + const isAlt = settingName.includes("ALT_"); + let altSettingName; + if (isAlt) { + altSettingName = settingName.split("ALT_").splice(1)[0]; + } else { + altSettingName = `ALT_${settingName}`; + } + return getIconWithSettingName(config, altSettingName); + } + return icon; +} + +export function assign(config, settingNameTarget, keycode): boolean { + // first, we need to check if this keycode is already used on another settingName + if (!canIAssignThisKey(config, getKeyWithKeycode(config, keycode)) || !canIOverrideThisSetting(config, settingNameTarget)) { + return false; + } + const previousSettingName = getSettingNameWithKeycode(config, keycode); + // if it was already bound, we delete the bind + if (previousSettingName) { + const previousKey = getKeyWithSettingName(config, previousSettingName); + config.custom[previousKey] = -1; + } + // then, we need to delete the current key for this settingName + const currentKey = getKeyWithSettingName(config, settingNameTarget); + config.custom[currentKey] = -1; + + // then, the new key is assigned to the new settingName + const newKey = getKeyWithKeycode(config, keycode); + config.custom[newKey] = settingNameTarget; + return true; +} + +export function swap(config, settingNameTarget, keycode) { + // only for gamepad + if (config.padType === "keyboard") { + return false; + } + const prev_key = getKeyWithSettingName(config, settingNameTarget); + const prev_settingName = getSettingNameWithKey(config, prev_key); + + const new_key = getKeyWithKeycode(config, keycode); + const new_settingName = getSettingNameWithKey(config, new_key); + + config.custom[prev_key] = new_settingName; + config.custom[new_key] = prev_settingName; + return true; +} + +/** + * Deletes the binding of the specified setting name. + * + * @param config - The configuration object containing custom settings. + * @param settingName - The setting name to delete. + */ +export function deleteBind(config, settingName) { + const key = getKeyWithSettingName(config, settingName); + if (config.blacklist.includes(key)) { + return false; + } + config.custom[key] = -1; + return true; +} + +export function canIAssignThisKey(config, key) { + const settingName = getSettingNameWithKey(config, key); + if (config.blacklist?.includes(key)) { + return false; + } + if (settingName === -1) { + return true; + } + // if (isTheLatestBind(config, settingName)) { + // return false; + // } + return true; +} + +export function canIOverrideThisSetting(config, settingName) { + const key = getKeyWithSettingName(config, settingName); + // || isTheLatestBind(config, settingName) no longer needed since action and cancel are protected + if (config.blacklist?.includes(key)) { + return false; + } + return true; +} + +export function canIDeleteThisKey(config, key) { + return canIAssignThisKey(config, key); +} + +// export function isTheLatestBind(config, settingName) { +// if (config.padType !== "keyboard") { +// return false; +// } +// const isAlt = settingName.includes("ALT_"); +// let altSettingName; +// if (isAlt) { +// altSettingName = settingName.split("ALT_").splice(1)[0]; +// } else { +// altSettingName = `ALT_${settingName}`; +// } +// const secondButton = getKeyWithSettingName(config, altSettingName); +// return secondButton === undefined; +// } diff --git a/src/configs/inputs/pad_dualshock.ts b/src/configs/inputs/pad_dualshock.ts new file mode 100644 index 00000000000..b0aaedf0ab8 --- /dev/null +++ b/src/configs/inputs/pad_dualshock.ts @@ -0,0 +1,88 @@ +import {SettingGamepad} from "../../system/settings-gamepad"; +import {Button} from "../../enums/buttons"; + +/** + * Dualshock mapping + */ +const pad_dualshock = { + padID: "Dualshock", + padType: "dualshock", + deviceMapping: { + RC_S: 0, + RC_E: 1, + RC_W: 2, + RC_N: 3, + START: 9, // Options + SELECT: 8, // Share + LB: 4, + RB: 5, + LT: 6, + RT: 7, + LS: 10, + RS: 11, + LC_N: 12, + LC_S: 13, + LC_W: 14, + LC_E: 15, + TOUCH: 17 + }, + icons: { + RC_S: "CROSS.png", + RC_E: "CIRCLE.png", + RC_W: "SQUARE.png", + RC_N: "TRIANGLE.png", + START: "START.png", + SELECT: "SELECT.png", + LB: "L1.png", + RB: "R1.png", + LT: "L2.png", + RT: "R2.png", + LS: "L3.png", + RS: "R3.png", + LC_N: "UP.png", + LC_S: "DOWN.png", + LC_W: "LEFT.png", + LC_E: "RIGHT.png", + TOUCH: "TOUCH.png" + }, + settings: { + [SettingGamepad.Button_Up]: Button.UP, + [SettingGamepad.Button_Down]: Button.DOWN, + [SettingGamepad.Button_Left]: Button.LEFT, + [SettingGamepad.Button_Right]: Button.RIGHT, + [SettingGamepad.Button_Action]: Button.ACTION, + [SettingGamepad.Button_Cancel]: Button.CANCEL, + [SettingGamepad.Button_Cycle_Nature]: Button.CYCLE_NATURE, + [SettingGamepad.Button_Cycle_Variant]: Button.V, + [SettingGamepad.Button_Menu]: Button.MENU, + [SettingGamepad.Button_Stats]: Button.STATS, + [SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM, + [SettingGamepad.Button_Cycle_Shiny]: Button.CYCLE_SHINY, + [SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER, + [SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY, + [SettingGamepad.Button_Speed_Up]: Button.SPEED_UP, + [SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN, + [SettingGamepad.Button_Submit]: Button.SUBMIT + }, + default: { + LC_N: SettingGamepad.Button_Up, + LC_S: SettingGamepad.Button_Down, + LC_W: SettingGamepad.Button_Left, + LC_E: SettingGamepad.Button_Right, + RC_S: SettingGamepad.Button_Action, + RC_E: SettingGamepad.Button_Cancel, + RC_W: SettingGamepad.Button_Cycle_Nature, + RC_N: SettingGamepad.Button_Cycle_Variant, + START: SettingGamepad.Button_Menu, + SELECT: SettingGamepad.Button_Stats, + LB: SettingGamepad.Button_Cycle_Form, + RB: SettingGamepad.Button_Cycle_Shiny, + LT: SettingGamepad.Button_Cycle_Gender, + RT: SettingGamepad.Button_Cycle_Ability, + LS: SettingGamepad.Button_Speed_Up, + RS: SettingGamepad.Button_Slow_Down, + TOUCH: SettingGamepad.Button_Submit, + }, +}; + +export default pad_dualshock; diff --git a/src/configs/inputs/pad_generic.ts b/src/configs/inputs/pad_generic.ts new file mode 100644 index 00000000000..f209f6858bd --- /dev/null +++ b/src/configs/inputs/pad_generic.ts @@ -0,0 +1,90 @@ +import {SettingGamepad} from "../../system/settings-gamepad"; +import {Button} from "../../enums/buttons"; + +/** + * Generic pad mapping + */ +const pad_generic = { + padID: "Generic", + padType: "xbox", + deviceMapping: { + RC_S: 0, + RC_E: 1, + RC_W: 2, + RC_N: 3, + START: 9, + SELECT: 8, + LB: 4, + RB: 5, + LT: 6, + RT: 7, + LS: 10, + RS: 11, + LC_N: 12, + LC_S: 13, + LC_W: 14, + LC_E: 15 + }, + icons: { + RC_S: "XB_Letter_A_OL.png", + RC_E: "XB_Letter_B_OL.png", + RC_W: "XB_Letter_X_OL.png", + RC_N: "XB_Letter_Y_OL.png", + START: "START.png", + SELECT: "SELECT.png", + LB: "Bumper_L.png", + RB: "Bumper_R.png", + LT: "Trigger_L.png", + RT: "Trigger_R.png", + LS: "LS.png", + RS: "RS.png", + LC_N: "UP.png", + LC_S: "DOWN.png", + LC_W: "LEFT.png", + LC_E: "RIGHT.png", + }, + settings: { + [SettingGamepad.Button_Up]: Button.UP, + [SettingGamepad.Button_Down]: Button.DOWN, + [SettingGamepad.Button_Left]: Button.LEFT, + [SettingGamepad.Button_Right]: Button.RIGHT, + [SettingGamepad.Button_Action]: Button.ACTION, + [SettingGamepad.Button_Cancel]: Button.CANCEL, + [SettingGamepad.Button_Cycle_Nature]: Button.CYCLE_NATURE, + [SettingGamepad.Button_Cycle_Variant]: Button.V, + [SettingGamepad.Button_Menu]: Button.MENU, + [SettingGamepad.Button_Stats]: Button.STATS, + [SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM, + [SettingGamepad.Button_Cycle_Shiny]: Button.CYCLE_SHINY, + [SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER, + [SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY, + [SettingGamepad.Button_Speed_Up]: Button.SPEED_UP, + [SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN + }, + default: { + LC_N: SettingGamepad.Button_Up, + LC_S: SettingGamepad.Button_Down, + LC_W: SettingGamepad.Button_Left, + LC_E: SettingGamepad.Button_Right, + RC_S: SettingGamepad.Button_Action, + RC_E: SettingGamepad.Button_Cancel, + RC_W: SettingGamepad.Button_Cycle_Nature, + RC_N: SettingGamepad.Button_Cycle_Variant, + START: SettingGamepad.Button_Menu, + SELECT: SettingGamepad.Button_Stats, + LB: SettingGamepad.Button_Cycle_Form, + RB: SettingGamepad.Button_Cycle_Shiny, + LT: SettingGamepad.Button_Cycle_Gender, + RT: SettingGamepad.Button_Cycle_Ability, + LS: SettingGamepad.Button_Speed_Up, + RS: SettingGamepad.Button_Slow_Down + }, + blacklist: [ + "LC_N", + "LC_S", + "LC_W", + "LC_E", + ] +}; + +export default pad_generic; diff --git a/src/configs/inputs/pad_procon.ts b/src/configs/inputs/pad_procon.ts new file mode 100644 index 00000000000..ccaaf5fc635 --- /dev/null +++ b/src/configs/inputs/pad_procon.ts @@ -0,0 +1,85 @@ +import {SettingGamepad} from "#app/system/settings-gamepad"; +import {Button} from "#app/enums/buttons"; + +/** + * Nintendo Pro Controller mapping + */ +const pad_procon = { + padID: "Pro Controller", + padType: "xbox", + deviceMapping: { + RC_S: 1, + RC_E: 0, + RC_W: 3, + RC_N: 2, + START: 9, // + + SELECT: 8, // - + LB: 4, + RB: 5, + LT: 6, + RT: 7, + LS: 10, + RS: 11, + LC_N: 12, + LC_S: 13, + LC_W: 14, + LC_E: 15, + MENU: 16, // Home + }, + icons: { + RC_S: "XB_Letter_B_OL.png", + RC_E: "XB_Letter_A_OL.png", + RC_W: "XB_Letter_Y_OL.png", + RC_N: "XB_Letter_X_OL.png", + START: "START.png", + SELECT: "SELECT.png", + LB: "Bumper_L.png", + RB: "Bumper_R.png", + LT: "Trigger_L.png", + RT: "Trigger_R.png", + LS: "LS.png", + RS: "RS.png", + LC_N: "UP.png", + LC_S: "DOWN.png", + LC_W: "LEFT.png", + LC_E: "RIGHT.png", + }, + settings: { + [SettingGamepad.Button_Up]: Button.UP, + [SettingGamepad.Button_Down]: Button.DOWN, + [SettingGamepad.Button_Left]: Button.LEFT, + [SettingGamepad.Button_Right]: Button.RIGHT, + [SettingGamepad.Button_Action]: Button.ACTION, + [SettingGamepad.Button_Cancel]: Button.CANCEL, + [SettingGamepad.Button_Cycle_Nature]: Button.CYCLE_NATURE, + [SettingGamepad.Button_Cycle_Variant]: Button.V, + [SettingGamepad.Button_Menu]: Button.MENU, + [SettingGamepad.Button_Stats]: Button.STATS, + [SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM, + [SettingGamepad.Button_Cycle_Shiny]: Button.CYCLE_SHINY, + [SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER, + [SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY, + [SettingGamepad.Button_Speed_Up]: Button.SPEED_UP, + [SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN + }, + default: { + LC_N: SettingGamepad.Button_Up, + LC_S: SettingGamepad.Button_Down, + LC_W: SettingGamepad.Button_Left, + LC_E: SettingGamepad.Button_Right, + RC_S: SettingGamepad.Button_Action, + RC_E: SettingGamepad.Button_Cancel, + RC_W: SettingGamepad.Button_Cycle_Nature, + RC_N: SettingGamepad.Button_Cycle_Variant, + START: SettingGamepad.Button_Menu, + SELECT: SettingGamepad.Button_Stats, + LB: SettingGamepad.Button_Cycle_Form, + RB: SettingGamepad.Button_Cycle_Shiny, + LT: SettingGamepad.Button_Cycle_Gender, + RT: SettingGamepad.Button_Cycle_Ability, + LS: SettingGamepad.Button_Speed_Up, + RS: SettingGamepad.Button_Slow_Down + }, +}; + +export default pad_procon; diff --git a/src/configs/inputs/pad_unlicensedSNES.ts b/src/configs/inputs/pad_unlicensedSNES.ts new file mode 100644 index 00000000000..803d30442b5 --- /dev/null +++ b/src/configs/inputs/pad_unlicensedSNES.ts @@ -0,0 +1,76 @@ +import {SettingGamepad} from "../../system/settings-gamepad"; +import {Button} from "../../enums/buttons"; + +/** + * 081f-e401 - UnlicensedSNES + */ +const pad_unlicensedSNES = { + padID: "081f-e401", + padType: "xbox", + deviceMapping : { + RC_S: 2, + RC_E: 1, + RC_W: 3, + RC_N: 0, + START: 9, + SELECT: 8, + LB: 4, + RB: 5, + LC_N: 12, + LC_S: 13, + LC_W: 14, + LC_E: 15 + }, + icons: { + RC_S: "XB_Letter_A_OL.png", + RC_E: "XB_Letter_B_OL.png", + RC_W: "XB_Letter_X_OL.png", + RC_N: "XB_Letter_Y_OL.png", + START: "START.png", + SELECT: "SELECT.png", + LB: "Bumper_L.png", + RB: "Bumper_R.png", + LC_N: "UP.png", + LC_S: "DOWN.png", + LC_W: "LEFT.png", + LC_E: "RIGHT.png", + }, + settings: { + [SettingGamepad.Button_Up]: Button.UP, + [SettingGamepad.Button_Down]: Button.DOWN, + [SettingGamepad.Button_Left]: Button.LEFT, + [SettingGamepad.Button_Right]: Button.RIGHT, + [SettingGamepad.Button_Action]: Button.ACTION, + [SettingGamepad.Button_Cancel]: Button.CANCEL, + [SettingGamepad.Button_Cycle_Nature]: Button.CYCLE_NATURE, + [SettingGamepad.Button_Cycle_Variant]: Button.V, + [SettingGamepad.Button_Menu]: Button.MENU, + [SettingGamepad.Button_Stats]: Button.STATS, + [SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM, + [SettingGamepad.Button_Cycle_Shiny]: Button.CYCLE_SHINY, + [SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER, + [SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY, + [SettingGamepad.Button_Speed_Up]: Button.SPEED_UP, + [SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN + }, + default: { + LC_N: SettingGamepad.Button_Up, + LC_S: SettingGamepad.Button_Down, + LC_W: SettingGamepad.Button_Left, + LC_E: SettingGamepad.Button_Right, + RC_S: SettingGamepad.Button_Action, + RC_E: SettingGamepad.Button_Cancel, + RC_W: SettingGamepad.Button_Cycle_Nature, + RC_N: SettingGamepad.Button_Cycle_Variant, + START: SettingGamepad.Button_Menu, + SELECT: SettingGamepad.Button_Stats, + LB: SettingGamepad.Button_Cycle_Form, + RB: SettingGamepad.Button_Cycle_Shiny, + LT: -1, + RT: -1, + LS: -1, + RS: -1 + }, +}; + +export default pad_unlicensedSNES; diff --git a/src/configs/inputs/pad_xbox360.ts b/src/configs/inputs/pad_xbox360.ts new file mode 100644 index 00000000000..213ed7f89fb --- /dev/null +++ b/src/configs/inputs/pad_xbox360.ts @@ -0,0 +1,84 @@ +import {SettingGamepad} from "../../system/settings-gamepad"; +import {Button} from "#app/enums/buttons"; + +/** + * Generic pad mapping + */ +const pad_xbox360 = { + padID: "Xbox 360 controller (XInput STANDARD GAMEPAD)", + padType: "xbox", + deviceMapping: { + RC_S: 0, + RC_E: 1, + RC_W: 2, + RC_N: 3, + START: 9, + SELECT: 8, + LB: 4, + RB: 5, + LT: 6, + RT: 7, + LS: 10, + RS: 11, + LC_N: 12, + LC_S: 13, + LC_W: 14, + LC_E: 15 + }, + icons: { + RC_S: "XB_Letter_A_OL.png", + RC_E: "XB_Letter_B_OL.png", + RC_W: "XB_Letter_X_OL.png", + RC_N: "XB_Letter_Y_OL.png", + START: "START.png", + SELECT: "SELECT.png", + LB: "Bumper_L.png", + RB: "Bumper_R.png", + LT: "Trigger_L.png", + RT: "Trigger_R.png", + LS: "LS.png", + RS: "RS.png", + LC_N: "UP.png", + LC_S: "DOWN.png", + LC_W: "LEFT.png", + LC_E: "RIGHT.png", + }, + settings: { + [SettingGamepad.Button_Up]: Button.UP, + [SettingGamepad.Button_Down]: Button.DOWN, + [SettingGamepad.Button_Left]: Button.LEFT, + [SettingGamepad.Button_Right]: Button.RIGHT, + [SettingGamepad.Button_Action]: Button.ACTION, + [SettingGamepad.Button_Cancel]: Button.CANCEL, + [SettingGamepad.Button_Cycle_Nature]: Button.CYCLE_NATURE, + [SettingGamepad.Button_Cycle_Variant]: Button.V, + [SettingGamepad.Button_Menu]: Button.MENU, + [SettingGamepad.Button_Stats]: Button.STATS, + [SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM, + [SettingGamepad.Button_Cycle_Shiny]: Button.CYCLE_SHINY, + [SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER, + [SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY, + [SettingGamepad.Button_Speed_Up]: Button.SPEED_UP, + [SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN + }, + default: { + LC_N: SettingGamepad.Button_Up, + LC_S: SettingGamepad.Button_Down, + LC_W: SettingGamepad.Button_Left, + LC_E: SettingGamepad.Button_Right, + RC_S: SettingGamepad.Button_Action, + RC_E: SettingGamepad.Button_Cancel, + RC_W: SettingGamepad.Button_Cycle_Nature, + RC_N: SettingGamepad.Button_Cycle_Variant, + START: SettingGamepad.Button_Menu, + SELECT: SettingGamepad.Button_Stats, + LB: SettingGamepad.Button_Cycle_Form, + RB: SettingGamepad.Button_Cycle_Shiny, + LT: SettingGamepad.Button_Cycle_Gender, + RT: SettingGamepad.Button_Cycle_Ability, + LS: SettingGamepad.Button_Speed_Up, + RS: SettingGamepad.Button_Slow_Down + }, +}; + +export default pad_xbox360; diff --git a/src/configs/pad_dualshock.ts b/src/configs/pad_dualshock.ts deleted file mode 100644 index 4700e8e6c00..00000000000 --- a/src/configs/pad_dualshock.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Dualshock mapping - */ -const pad_dualshock = { - padID: "Dualshock", - padType: "Sony", - gamepadMapping: { - RC_S: 0, - RC_E: 1, - RC_W: 2, - RC_N: 3, - START: 9, // Options - SELECT: 8, // Share - LB: 4, - RB: 5, - LT: 6, - RT: 7, - LS: 10, - RS: 11, - LC_N: 12, - LC_S: 13, - LC_W: 14, - LC_E: 15, - MENU: 16, - TOUCH: 17 - }, -}; - -export default pad_dualshock; diff --git a/src/configs/pad_generic.ts b/src/configs/pad_generic.ts deleted file mode 100644 index 22e8c6e0579..00000000000 --- a/src/configs/pad_generic.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Generic pad mapping - */ -const pad_generic = { - padID: "Generic", - padType: "generic", - gamepadMapping: { - RC_S: 0, - RC_E: 1, - RC_W: 2, - RC_N: 3, - START: 9, - SELECT: 8, - LB: 4, - RB: 5, - LT: 6, - RT: 7, - LS: 10, - RS: 11, - LC_N: 12, - LC_S: 13, - LC_W: 14, - LC_E: 15 - }, -}; - -export default pad_generic; diff --git a/src/configs/pad_procon.ts b/src/configs/pad_procon.ts deleted file mode 100644 index c9efe6fb262..00000000000 --- a/src/configs/pad_procon.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Nintendo Pro Controller mapping - */ -const pad_procon = { - padID: "Pro Controller", - padType: "Nintendo", - gamepadMapping: { - RC_S: 1, - RC_E: 0, - RC_W: 3, - RC_N: 2, - START: 9, // + - SELECT: 8, // - - LB: 4, - RB: 5, - LT: 6, - RT: 7, - LS: 10, - RS: 11, - LC_N: 12, - LC_S: 13, - LC_W: 14, - LC_E: 15, - MENU: 16, // Home - }, -}; - -export default pad_procon; diff --git a/src/configs/pad_unlicensedSNES.ts b/src/configs/pad_unlicensedSNES.ts deleted file mode 100644 index 808e30cb6b4..00000000000 --- a/src/configs/pad_unlicensedSNES.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * 081f-e401 - UnlicensedSNES - */ -const pad_unlicensedSNES = { - padID: "081f-e401", - padType: "snes", - gamepadMapping : { - RC_S: 2, - RC_E: 1, - RC_W: 3, - RC_N: 0, - START: 9, - SELECT: 8, - LB: 4, - RB: 5, - LC_N: 12, - LC_S: 13, - LC_W: 14, - LC_E: 15 - } -}; - -export default pad_unlicensedSNES; diff --git a/src/configs/pad_xbox360.ts b/src/configs/pad_xbox360.ts deleted file mode 100644 index 50ec4f71c67..00000000000 --- a/src/configs/pad_xbox360.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Generic pad mapping - */ -const pad_xbox360 = { - padID: "Xbox 360 controller (XInput STANDARD GAMEPAD)", - padType: "xbox", - gamepadMapping: { - RC_S: 0, - RC_E: 1, - RC_W: 2, - RC_N: 3, - START: 9, - SELECT: 8, - LB: 4, - RB: 5, - LT: 6, - RT: 7, - LS: 10, - RS: 11, - LC_N: 12, - LC_S: 13, - LC_W: 14, - LC_E: 15, - MENU: 16 - }, -}; - -export default pad_xbox360; diff --git a/src/enums/devices.ts b/src/enums/devices.ts new file mode 100644 index 00000000000..b085dfbada3 --- /dev/null +++ b/src/enums/devices.ts @@ -0,0 +1,4 @@ +export enum Device { + GAMEPAD, + KEYBOARD, +} diff --git a/src/inputs-controller.ts b/src/inputs-controller.ts index cb7b828f57c..2791f3b5b85 100644 --- a/src/inputs-controller.ts +++ b/src/inputs-controller.ts @@ -1,30 +1,70 @@ import Phaser from "phaser"; import * as Utils from "./utils"; -import {ButtonKey, initTouchControls} from "./touch-controls"; -import pad_generic from "./configs/pad_generic"; -import pad_unlicensedSNES from "./configs/pad_unlicensedSNES"; -import pad_xbox360 from "./configs/pad_xbox360"; -import pad_dualshock from "./configs/pad_dualshock"; -import pad_procon from "./configs/pad_procon"; +import {deepCopy} from "./utils"; +import {initTouchControls} from "./touch-controls"; +import pad_generic from "./configs/inputs/pad_generic"; +import pad_unlicensedSNES from "./configs/inputs/pad_unlicensedSNES"; +import pad_xbox360 from "./configs/inputs/pad_xbox360"; +import pad_dualshock from "./configs/inputs/pad_dualshock"; +import pad_procon from "./configs/inputs/pad_procon"; import {Button} from "./enums/buttons"; +import {Mode} from "./ui/ui"; +import SettingsGamepadUiHandler from "./ui/settings/settings-gamepad-ui-handler"; +import SettingsKeyboardUiHandler from "./ui/settings/settings-keyboard-ui-handler"; +import cfg_keyboard_qwerty from "./configs/inputs/cfg_keyboard_qwerty"; +import {Device} from "#app/enums/devices"; +import { + assign, + getButtonWithKeycode, + getIconForLatestInput, swap, +} from "#app/configs/inputs/configHandler"; import BattleScene from "./battle-scene"; +import {SettingGamepad} from "#app/system/settings-gamepad"; +import {SettingKeyboard} from "#app/system/settings-keyboard"; -export interface GamepadMapping { +export interface DeviceMapping { [key: string]: number; } -export interface GamepadConfig { - padID: string; - padType: string; - gamepadMapping: GamepadMapping; +export interface IconsMapping { + [key: string]: string; } -export interface ActionGamepadMapping { +export interface SettingMapping { [key: string]: Button; } +export interface MappingLayout { + [key: string]: SettingGamepad | SettingKeyboard | number; +} + +export interface InterfaceConfig { + padID: string; + padType: string; + deviceMapping: DeviceMapping; + icons: IconsMapping; + settings: SettingMapping; + default: MappingLayout; + custom?: MappingLayout; +} + const repeatInputDelayMillis = 250; +// Phaser.Input.Gamepad.GamepadPlugin#refreshPads +declare module "phaser" { + namespace Input { + namespace Gamepad { + interface GamepadPlugin { + /** + * Refreshes the list of connected Gamepads. + * This is called automatically when a gamepad is connected or disconnected, and during the update loop. + */ + refreshPads(): void; + } + } + } +} + /** * Manages and handles all input controls for the game, including keyboard and gamepad interactions. * @@ -49,17 +89,24 @@ const repeatInputDelayMillis = 250; */ export class InputsController { private buttonKeys: Phaser.Input.Keyboard.Key[][]; - private gamepads: Phaser.Input.Gamepad.Gamepad[] = new Array(); + private gamepads: Array = new Array(); private scene: BattleScene; + public events: Phaser.Events.EventEmitter; private buttonLock: Button; - private buttonLock2: Button; private interactions: Map> = new Map(); private time: Phaser.Time.Clock; - private player: GamepadMapping; + private configs: Map = new Map(); - private gamepadSupport: boolean = true; - public events: Phaser.Events.EventEmitter; + public gamepadSupport: boolean = true; + public selectedDevice; + + private disconnectedGamepads: Array = new Array(); + + private pauseUpdate: boolean = false; + + public lastSource: string = "keyboard"; + private keys: Array = []; /** * Initializes a new instance of the game control system, setting up initial state and configurations. @@ -72,10 +119,15 @@ export class InputsController { * Specific buttons like MENU and STATS are set not to repeat their actions. * It concludes by calling the `init` method to complete the setup. */ + constructor(scene: BattleScene) { this.scene = scene; this.time = this.scene.time; this.buttonKeys = []; + this.selectedDevice = { + [Device.GAMEPAD]: null, + [Device.KEYBOARD]: "default" + }; for (const b of Utils.getEnumValues(Button)) { this.interactions[b] = { @@ -99,18 +151,28 @@ export class InputsController { * Additionally, it manages the game's behavior when it loses focus to prevent unwanted game actions during this state. */ init(): void { - this.events = new Phaser.Events.EventEmitter(); + this.events = this.scene.game.events; + this.scene.game.events.on(Phaser.Core.Events.BLUR, () => { this.loseFocus(); }); if (typeof this.scene.input.gamepad !== "undefined") { this.scene.input.gamepad.on("connected", function (thisGamepad) { + if (!thisGamepad) { + return; + } this.refreshGamepads(); this.setupGamepad(thisGamepad); + this.onReconnect(thisGamepad); + }, this); + + this.scene.input.gamepad.on("disconnected", function (thisGamepad) { + this.onDisconnect(thisGamepad); // when a gamepad is disconnected }, this); // Check to see if the gamepad has already been setup by the browser + this.scene.input.gamepad.refreshPads(); if (this.scene.input.gamepad.total) { this.refreshGamepads(); for (const thisGamepad of this.gamepads) { @@ -120,10 +182,10 @@ export class InputsController { this.scene.input.gamepad.on("down", this.gamepadButtonDown, this); this.scene.input.gamepad.on("up", this.gamepadButtonUp, this); + this.scene.input.keyboard.on("keydown", this.keyboardKeyDown, this); + this.scene.input.keyboard.on("keyup", this.keyboardKeyUp, this); } - - // Keyboard - this.setupKeyboardControls(); + initTouchControls(this.events); } /** @@ -149,35 +211,58 @@ export class InputsController { this.gamepadSupport = true; } else { this.gamepadSupport = false; - // if we disable the gamepad, we want to release every key pressed this.deactivatePressedKey(); } } + /** + * Sets the currently chosen gamepad and initializes related settings. + * This method first deactivates any active key presses and then initializes the gamepad settings. + * + * @param gamepad - The identifier of the gamepad to set as chosen. + */ + setChosenGamepad(gamepad: String): void { + this.deactivatePressedKey(); + this.initChosenGamepad(gamepad); + } + + /** + * Sets the currently chosen keyboard layout and initializes related settings. + * + * @param layoutKeyboard - The identifier of the keyboard layout to set as chosen. + */ + setChosenKeyboardLayout(layoutKeyboard: String): void { + this.deactivatePressedKey(); + this.initChosenLayoutKeyboard(layoutKeyboard); + } + /** * Updates the interaction handling by processing input states. * This method gives priority to certain buttons by reversing the order in which they are checked. + * This method loops through all button values, checks for valid and timely interactions, and conditionally processes + * or ignores them based on the current state of gamepad support and other criteria. * - * @remarks - * The method iterates over all possible buttons, checking for specific conditions such as: - * - If the button is registered in the `interactions` dictionary. - * - If the button has been held down long enough. - * - If the button is currently pressed. + * It handles special conditions such as the absence of gamepad support or mismatches between the source of the input and + * the currently chosen gamepad. It also respects the paused state of updates to prevent unwanted input processing. * - * Special handling is applied if gamepad support is disabled but a gamepad source is still triggering inputs, - * preventing potential infinite loops by removing the last processed movement time for the button. + * If an interaction is valid and should be processed, it emits an 'input_down' event with details of the interaction. */ update(): void { for (const b of Utils.getEnumValues(Button).reverse()) { if ( this.interactions.hasOwnProperty(b) && - this.repeatInputDurationJustPassed(b) && + this.repeatInputDurationJustPassed(b as Button) && this.interactions[b].isPressed ) { // Prevents repeating button interactions when gamepad support is disabled. - if (!this.gamepadSupport && this.interactions[b].source === "gamepad") { + if ( + (!this.gamepadSupport && this.interactions[b].source === "gamepad") || + (this.interactions[b].source === "gamepad" && this.interactions[b].sourceName && this.interactions[b].sourceName !== this.selectedDevice[Device.GAMEPAD]) || + (this.interactions[b].source === "keyboard" && this.interactions[b].sourceName && this.interactions[b].sourceName !== this.selectedDevice[Device.KEYBOARD]) || + this.pauseUpdate + ) { // Deletes the last interaction for a button if gamepad is disabled. - this.delLastProcessedMovementTime(b); + this.delLastProcessedMovementTime(b as Button); return; } // Emits an event for the button press. @@ -185,25 +270,104 @@ export class InputsController { controller_type: this.interactions[b].source, button: b, }); - this.setLastProcessedMovementTime(b, this.interactions[b].source); + this.setLastProcessedMovementTime(b as Button, this.interactions[b].source, this.interactions[b].sourceName); } } } /** - * Configures a gamepad for use based on its device ID. + * Retrieves the identifiers of all connected gamepads, excluding any that are currently marked as disconnected. + * @returns Array An array of strings representing the IDs of the connected gamepads. + */ + getGamepadsName(): Array { + return this.gamepads.filter(g => !this.disconnectedGamepads.includes(g.id)).map(g => g.id); + } + + /** + * Initializes the chosen gamepad by setting its identifier in the local storage and updating the UI to reflect the chosen gamepad. + * If a gamepad name is provided, it uses that as the chosen gamepad; otherwise, it defaults to the currently chosen gamepad. + * @param gamepadName Optional parameter to specify the name of the gamepad to initialize as chosen. + */ + initChosenGamepad(gamepadName?: String): void { + if (gamepadName) { + this.selectedDevice[Device.GAMEPAD] = gamepadName.toLowerCase(); + } + const handler = this.scene.ui?.handlers[Mode.SETTINGS_GAMEPAD] as SettingsGamepadUiHandler; + handler && handler.updateChosenGamepadDisplay(); + } + + /** + * Initializes the chosen keyboard layout by setting its identifier in the local storage and updating the UI to reflect the chosen layout. + * If a layout name is provided, it uses that as the chosen layout; otherwise, it defaults to the currently chosen layout. + * @param layoutKeyboard Optional parameter to specify the name of the keyboard layout to initialize as chosen. + */ + initChosenLayoutKeyboard(layoutKeyboard?: String): void { + if (layoutKeyboard) { + this.selectedDevice[Device.KEYBOARD] = layoutKeyboard.toLowerCase(); + } + const handler = this.scene.ui?.handlers[Mode.SETTINGS_KEYBOARD] as SettingsKeyboardUiHandler; + handler && handler.updateChosenKeyboardDisplay(); + } + + /** + * Handles the disconnection of a gamepad by adding its identifier to a list of disconnected gamepads. + * This is necessary because Phaser retains memory of previously connected gamepads, and without tracking + * disconnections, it would be impossible to determine the connection status of gamepads. This method ensures + * that disconnected gamepads are recognized and can be appropriately hidden in the gamepad selection menu. * - * @param thisGamepad - The gamepad to set up. + * @param thisGamepad The gamepad that has been disconnected. + */ + onDisconnect(thisGamepad: Phaser.Input.Gamepad.Gamepad): void { + this.disconnectedGamepads.push(thisGamepad.id); + } + + /** + * Updates the tracking of disconnected gamepads when a gamepad is reconnected. + * It removes the reconnected gamepad's identifier from the `disconnectedGamepads` array, + * effectively updating its status to connected. * - * @remarks - * This method initializes a gamepad by mapping its ID to a predefined configuration. - * It updates the player's gamepad mapping based on the identified configuration, ensuring - * that the gamepad controls are correctly mapped to in-game actions. + * @param thisGamepad The gamepad that has been reconnected. + */ + onReconnect(thisGamepad: Phaser.Input.Gamepad.Gamepad): void { + this.disconnectedGamepads = this.disconnectedGamepads.filter(g => g !== thisGamepad.id); + } + + /** + * Initializes or updates configurations for connected gamepads. + * It retrieves the names of all connected gamepads, sets up their configurations according to stored or default settings, + * and ensures these configurations are saved. If the connected gamepad is the currently chosen one, + * it reinitializes the chosen gamepad settings. + * + * @param thisGamepad The gamepad that is being set up. */ setupGamepad(thisGamepad: Phaser.Input.Gamepad.Gamepad): void { - const gamepadID = thisGamepad.id.toLowerCase(); - const mappedPad = this.mapGamepad(gamepadID); - this.player = mappedPad.gamepadMapping; + const allGamepads = this.getGamepadsName(); + for (const gamepad of allGamepads) { + const gamepadID = gamepad.toLowerCase(); + if (!this.selectedDevice[Device.GAMEPAD]) { + this.setChosenGamepad(gamepadID); + } + const config = deepCopy(this.getConfig(gamepadID)) as InterfaceConfig; + config.custom = this.configs[gamepadID]?.custom || {...config.default}; + this.configs[gamepadID] = config; + this.scene.gameData?.saveMappingConfigs(gamepadID, this.configs[gamepadID]); + } + this.lastSource = "gamepad"; + const handler = this.scene.ui?.handlers[Mode.SETTINGS_GAMEPAD] as SettingsGamepadUiHandler; + handler && handler.updateChosenGamepadDisplay(); + } + + /** + * Initializes or updates configurations for connected keyboards. + */ + setupKeyboard(): void { + for (const layout of ["default"]) { + const config = deepCopy(this.getConfigKeyboard(layout)) as InterfaceConfig; + config.custom = this.configs[layout]?.custom || {...config.default}; + this.configs[layout] = config; + this.scene.gameData?.saveMappingConfigs(this.selectedDevice[Device.KEYBOARD], this.configs[layout]); + } + this.initChosenLayoutKeyboard(this.selectedDevice[Device.KEYBOARD]); } /** @@ -226,89 +390,110 @@ export class InputsController { } /** - * Retrieves the current gamepad mapping for in-game actions. - * - * @returns An object mapping gamepad buttons to in-game actions based on the player's current gamepad configuration. - * - * @remarks - * This method constructs a mapping of gamepad buttons to in-game action buttons according to the player's - * current gamepad configuration. If no configuration is available, it returns an empty mapping. - * The mapping includes directional controls, action buttons, and system commands among others, - * adjusted for any custom settings such as swapped action buttons. + * Ensures the keyboard is initialized by checking if there is an active configuration for the keyboard. + * If not, it sets up the keyboard with default configurations. */ - getActionGamepadMapping(): ActionGamepadMapping { - const gamepadMapping = {}; - if (!this?.player) { - return gamepadMapping; + ensureKeyboardIsInit(): void { + if (!this.getActiveConfig(Device.KEYBOARD)?.padID) { + this.setupKeyboard(); } - gamepadMapping[this.player.LC_N] = Button.UP; - gamepadMapping[this.player.LC_S] = Button.DOWN; - gamepadMapping[this.player.LC_W] = Button.LEFT; - gamepadMapping[this.player.LC_E] = Button.RIGHT; - gamepadMapping[this.player.TOUCH] = Button.SUBMIT; - gamepadMapping[this.player.RC_S] = this.scene.abSwapped ? Button.CANCEL : Button.ACTION; - gamepadMapping[this.player.RC_E] = this.scene.abSwapped ? Button.ACTION : Button.CANCEL; - gamepadMapping[this.player.SELECT] = Button.STATS; - gamepadMapping[this.player.START] = Button.MENU; - gamepadMapping[this.player.RB] = Button.CYCLE_SHINY; - gamepadMapping[this.player.LB] = Button.CYCLE_FORM; - gamepadMapping[this.player.LT] = Button.CYCLE_GENDER; - gamepadMapping[this.player.RT] = Button.CYCLE_ABILITY; - gamepadMapping[this.player.RC_W] = Button.CYCLE_NATURE; - gamepadMapping[this.player.RC_N] = Button.V; - gamepadMapping[this.player.LS] = Button.SPEED_UP; - gamepadMapping[this.player.RS] = Button.SLOW_DOWN; - - return gamepadMapping; } /** - * Handles the 'down' event for gamepad buttons, emitting appropriate events and updating the interaction state. + * Handles the keydown event for the keyboard. * - * @param pad - The gamepad on which the button press occurred. - * @param button - The button that was pressed. - * @param value - The value associated with the button press, typically indicating pressure or degree of activation. - * - * @remarks - * This method is triggered when a gamepad button is pressed. If gamepad support is enabled, it: - * - Retrieves the current gamepad action mapping. - * - Checks if the pressed button is mapped to a game action. - * - If mapped, emits an 'input_down' event with the controller type and button action, and updates the interaction of this button. + * @param event The keyboard event. */ - gamepadButtonDown(pad: Phaser.Input.Gamepad.Gamepad, button: Phaser.Input.Gamepad.Button, value: number): void { - if (!this.gamepadSupport) { + keyboardKeyDown(event): void { + this.lastSource = "keyboard"; + const keyDown = event.keyCode; + this.ensureKeyboardIsInit(); + if (this.keys.includes(keyDown)) { return; } - const actionMapping = this.getActionGamepadMapping(); - const buttonDown = actionMapping.hasOwnProperty(button.index) && actionMapping[button.index]; + this.keys.push(keyDown); + const buttonDown = getButtonWithKeycode(this.getActiveConfig(Device.KEYBOARD), keyDown); + if (buttonDown !== undefined) { + this.events.emit("input_down", { + controller_type: "keyboard", + button: buttonDown, + }); + this.setLastProcessedMovementTime(buttonDown, "keyboard", this.selectedDevice[Device.KEYBOARD]); + } + } + + /** + * Handles the keyup event for the keyboard. + * + * @param event The keyboard event. + */ + keyboardKeyUp(event): void { + this.lastSource = "keyboard"; + const keyDown = event.keyCode; + this.keys = this.keys.filter(k => k !== keyDown); + this.ensureKeyboardIsInit(); + const buttonUp = getButtonWithKeycode(this.getActiveConfig(Device.KEYBOARD), keyDown); + if (buttonUp !== undefined) { + this.events.emit("input_up", { + controller_type: "keyboard", + button: buttonUp, + }); + this.delLastProcessedMovementTime(buttonUp); + } + } + + /** + * Handles button press events on a gamepad. This method sets the gamepad as chosen on the first input if no gamepad is currently chosen. + * It checks if gamepad support is enabled and if the event comes from the chosen gamepad. If so, it maps the button press to a specific + * action using a custom configuration, emits an event for the button press, and records the time of the action. + * + * @param pad The gamepad on which the button was pressed. + * @param button The specific button that was pressed. + * @param value The intensity or value of the button press, if applicable. + */ + gamepadButtonDown(pad: Phaser.Input.Gamepad.Gamepad, button: Phaser.Input.Gamepad.Button, value: number): void { + if (!this.configs[this.selectedDevice[Device.KEYBOARD]]?.padID) { + this.setupKeyboard(); + } + if (!pad) { + return; + } + this.lastSource = "gamepad"; + if (!this.selectedDevice[Device.GAMEPAD] || (this.scene.ui.getMode() !== Mode.GAMEPAD_BINDING && this.selectedDevice[Device.GAMEPAD] !== pad.id.toLowerCase())) { + this.setChosenGamepad(pad.id); + } + if (!this.gamepadSupport || pad.id.toLowerCase() !== this.selectedDevice[Device.GAMEPAD].toLowerCase()) { + return; + } + const activeConfig = this.getActiveConfig(Device.GAMEPAD); + const buttonDown = activeConfig && getButtonWithKeycode(activeConfig, button.index); if (buttonDown !== undefined) { this.events.emit("input_down", { controller_type: "gamepad", button: buttonDown, }); - this.setLastProcessedMovementTime(buttonDown, "gamepad"); + this.setLastProcessedMovementTime(buttonDown, "gamepad", pad.id); } } /** - * Handles the 'up' event for gamepad buttons, emitting appropriate events and clearing the interaction state. + * Responds to a button release event on a gamepad by checking if the gamepad is supported and currently chosen. + * If conditions are met, it identifies the configured action for the button, emits an event signaling the button release, + * and clears the record of the button. * - * @param pad - The gamepad on which the button release occurred. - * @param button - The button that was released. - * @param value - The value associated with the button release, typically indicating pressure or degree of deactivation. - * - * @remarks - * This method is triggered when a gamepad button is released. If gamepad support is enabled, it: - * - Retrieves the current gamepad action mapping. - * - Checks if the released button is mapped to a game action. - * - If mapped, emits an 'input_up' event with the controller type and button action, and clears the interaction for this button. + * @param pad The gamepad from which the button was released. + * @param button The specific button that was released. + * @param value The intensity or value of the button release, if applicable. */ gamepadButtonUp(pad: Phaser.Input.Gamepad.Gamepad, button: Phaser.Input.Gamepad.Button, value: number): void { - if (!this.gamepadSupport) { + if (!pad) { return; } - const actionMapping = this.getActionGamepadMapping(); - const buttonUp = actionMapping.hasOwnProperty(button.index) && actionMapping[button.index]; + this.lastSource = "gamepad"; + if (!this.gamepadSupport || pad.id.toLowerCase() !== this.selectedDevice[Device.GAMEPAD]) { + return; + } + const buttonUp = getButtonWithKeycode(this.getActiveConfig(Device.GAMEPAD), button.index); if (buttonUp !== undefined) { this.events.emit("input_up", { controller_type: "gamepad", @@ -319,114 +504,14 @@ export class InputsController { } /** - * Configures keyboard controls for the game, mapping physical keys to game actions. + * Retrieves the configuration object for a gamepad based on its identifier. The method identifies specific gamepad models + * based on substrings in the identifier and returns predefined configurations for recognized models. + * If no specific configuration matches, it defaults to a generic gamepad configuration. * - * @remarks - * This method sets up keyboard bindings for game controls using Phaser's `KeyCodes`. Each game action, represented - * by a button in the `Button` enum, is associated with one or more physical keys. For example, movement actions - * (up, down, left, right) are mapped to both arrow keys and WASD keys. Actions such as submit, cancel, and other - * game-specific functions are mapped to appropriate keys like Enter, Space, etc. - * - * The method does the following: - * - Defines a `keyConfig` object that associates each `Button` enum value with an array of `KeyCodes`. - * - Iterates over all values of the `Button` enum to set up these key bindings within the Phaser game scene. - * - For each button, it adds the respective keys to the game's input system and stores them in `this.buttonKeys`. - * - Additional configurations for mobile or alternative input schemes are stored in `mobileKeyConfig`. - * - * Post-setup, it initializes touch controls (if applicable) and starts listening for keyboard inputs using - * `listenInputKeyboard`, ensuring that all configured keys are actively monitored for player interactions. + * @param id The identifier string of the gamepad. + * @returns InterfaceConfig The configuration object corresponding to the identified gamepad type. */ - setupKeyboardControls(): void { - const keyCodes = Phaser.Input.Keyboard.KeyCodes; - const keyConfig = { - [Button.UP]: [keyCodes.UP, keyCodes.W], - [Button.DOWN]: [keyCodes.DOWN, keyCodes.S], - [Button.LEFT]: [keyCodes.LEFT, keyCodes.A], - [Button.RIGHT]: [keyCodes.RIGHT, keyCodes.D], - [Button.SUBMIT]: [keyCodes.ENTER], - [Button.ACTION]: [keyCodes.SPACE, keyCodes.Z], - [Button.CANCEL]: [keyCodes.BACKSPACE, keyCodes.X], - [Button.MENU]: [keyCodes.ESC, keyCodes.M], - [Button.STATS]: [keyCodes.SHIFT, keyCodes.C], - [Button.CYCLE_SHINY]: [keyCodes.R], - [Button.CYCLE_FORM]: [keyCodes.F], - [Button.CYCLE_GENDER]: [keyCodes.G], - [Button.CYCLE_ABILITY]: [keyCodes.E], - [Button.CYCLE_NATURE]: [keyCodes.N], - [Button.V]: [keyCodes.V], - [Button.SPEED_UP]: [keyCodes.PLUS], - [Button.SLOW_DOWN]: [keyCodes.MINUS] - }; - const mobileKeyConfig = new Map(); - for (const b of Utils.getEnumValues(Button)) { - const keys: Phaser.Input.Keyboard.Key[] = []; - if (keyConfig.hasOwnProperty(b)) { - for (const k of keyConfig[b]) { - keys.push(this.scene.input.keyboard.addKey(k, false)); - } - mobileKeyConfig[Button[b]] = keys[0]; - } - this.buttonKeys[b] = keys; - } - - initTouchControls(mobileKeyConfig); - this.listenInputKeyboard(); - } - - /** - * Sets up event listeners for keyboard inputs on all registered keys. - * - * @remarks - * This method iterates over an array of keyboard button rows (`this.buttonKeys`), adding 'down' and 'up' - * event listeners for each key. These listeners handle key press and release actions respectively. - * - * - **Key Down Event**: When a key is pressed down, the method emits an 'input_down' event with the button - * and the source ('keyboard'). It also records the time and state of the key press by calling - * `setLastProcessedMovementTime`. - * - * - **Key Up Event**: When a key is released, the method emits an 'input_up' event similarly, specifying the button - * and source. It then clears the recorded press time and state by calling - * `delLastProcessedMovementTime`. - * - * This setup ensures that each key on the keyboard is monitored for press and release events, - * and that these events are properly communicated within the system. - */ - listenInputKeyboard(): void { - this.buttonKeys.forEach((row, index) => { - for (const key of row) { - key.on("down", () => { - this.events.emit("input_down", { - controller_type: "keyboard", - button: index, - }); - this.setLastProcessedMovementTime(index, "keyboard"); - }); - key.on("up", () => { - this.events.emit("input_up", { - controller_type: "keyboard", - button: index, - }); - this.delLastProcessedMovementTime(index); - }); - } - }); - } - - /** - * Maps a gamepad ID to a specific gamepad configuration based on the ID's characteristics. - * - * @param id - The gamepad ID string, typically representing a unique identifier for a gamepad model or make. - * @returns A `GamepadConfig` object corresponding to the identified gamepad model. - * - * @remarks - * This function analyzes the provided gamepad ID and matches it to a predefined configuration based on known identifiers: - * - If the ID includes both '081f' and 'e401', it is identified as an unlicensed SNES gamepad. - * - If the ID contains 'xbox' and '360', it is identified as an Xbox 360 gamepad. - * - If the ID contains '054c', it is identified as a DualShock gamepad. - * - If the ID includes both '057e' and '2009', it is identified as a Pro controller gamepad. - * If no specific identifiers are recognized, a generic gamepad configuration is returned. - */ - mapGamepad(id: string): GamepadConfig { + getConfig(id: string): InterfaceConfig { id = id.toLowerCase(); if (id.includes("081f") && id.includes("e401")) { @@ -442,6 +527,20 @@ export class InputsController { return pad_generic; } + /** + * Retrieves the configuration object for a keyboard layout based on its identifier. + * + * @param id The identifier string of the keyboard layout. + * @returns InterfaceConfig The configuration object corresponding to the identified keyboard layout. + */ + getConfigKeyboard(id: string): InterfaceConfig { + if (id === "default") { + return cfg_keyboard_qwerty; + } + + return cfg_keyboard_qwerty; + } + /** * repeatInputDurationJustPassed returns true if @param button has been held down long * enough to fire a repeated input. A button must claim the buttonLock before @@ -471,7 +570,7 @@ export class InputsController { * * Additionally, this method locks the button (by calling `setButtonLock`) to prevent it from being re-processed until it is released, ensuring that each press is handled distinctly. */ - setLastProcessedMovementTime(button: Button, source: String = "keyboard"): void { + setLastProcessedMovementTime(button: Button, source: String = "keyboard", sourceName?: String): void { if (!this.interactions.hasOwnProperty(button)) { return; } @@ -479,6 +578,7 @@ export class InputsController { this.interactions[button].pressTime = this.time.now; this.interactions[button].isPressed = true; this.interactions[button].source = source; + this.interactions[button].sourceName = sourceName.toLowerCase(); } /** @@ -503,6 +603,7 @@ export class InputsController { this.interactions[button].pressTime = null; this.interactions[button].isPressed = false; this.interactions[button].source = null; + this.interactions[button].sourceName = null; } /** @@ -512,7 +613,7 @@ export class InputsController { * This method is used to reset the state of all buttons within the `interactions` dictionary, * effectively deactivating any currently pressed keys. It performs the following actions: * - * - Releases button locks for predefined buttons (`buttonLock` and `buttonLock2`), allowing them + * - Releases button lock for predefined buttons, allowing them * to be pressed again or properly re-initialized in future interactions. * - Iterates over all possible button values obtained via `Utils.getEnumValues(Button)`, and for * each button: @@ -524,55 +625,44 @@ export class InputsController { * This method is typically called when needing to ensure that all inputs are neutralized. */ deactivatePressedKey(): void { + this.pauseUpdate = true; this.releaseButtonLock(this.buttonLock); - this.releaseButtonLock(this.buttonLock2); for (const b of Utils.getEnumValues(Button)) { if (this.interactions.hasOwnProperty(b)) { this.interactions[b].pressTime = null; this.interactions[b].isPressed = false; this.interactions[b].source = null; + this.interactions[b].sourceName = null; } } + setTimeout(() => this.pauseUpdate = false, 500); } /** * Checks if a specific button is currently locked. * * @param button - The button to check for a lock status. - * @returns `true` if the button is either of the two potentially locked buttons (`buttonLock` or `buttonLock2`), otherwise `false`. + * @returns `true` if the button is locked, otherwise `false`. * * @remarks * This method is used to determine if a given button is currently prevented from being processed due to a lock. * It checks against two separate lock variables, allowing for up to two buttons to be locked simultaneously. */ isButtonLocked(button: Button): boolean { - return (this.buttonLock === button || this.buttonLock2 === button); + return this.buttonLock === button; } /** - * Sets a lock on a given button if it is not already locked. + * Sets a lock on a given button. * * @param button - The button to lock. * * @remarks * This method ensures that a button is not processed multiple times inadvertently. - * It checks if the button is already locked by either of the two lock variables (`buttonLock` or `buttonLock2`). - * If not, it locks the button using the first available lock variable. - * This mechanism allows for up to two buttons to be locked at the same time. + * It checks if the button is already locked. */ setButtonLock(button: Button): void { - if (this.buttonLock === button || this.buttonLock2 === button) { - return; - } - if (this.buttonLock === button) { - this.buttonLock2 = button; - } else if (this.buttonLock2 === button) { - this.buttonLock = button; - } else if (!!this.buttonLock) { - this.buttonLock2 = button; - } else { - this.buttonLock = button; - } + this.buttonLock = button; } /** @@ -581,15 +671,93 @@ export class InputsController { * @param button - The button whose lock is to be released. * * @remarks - * This method checks both lock variables (`buttonLock` and `buttonLock2`). + * This method checks lock variable. * If either lock matches the specified button, that lock is cleared. * This action frees the button to be processed again, ensuring it can respond to new inputs. */ releaseButtonLock(button: Button): void { if (this.buttonLock === button) { this.buttonLock = null; - } else if (this.buttonLock2 === button) { - this.buttonLock2 = null; + } + } + + /** + * Retrieves the active configuration for the currently chosen device. + * It checks if a specific device ID is stored in configurations and returns it. + * + * @returns InterfaceConfig The configuration object for the active gamepad, or null if not set. + */ + getActiveConfig(device: Device) { + if (this.configs[this.selectedDevice[device]]?.padID) { + return this.configs[this.selectedDevice[device]]; + } + return null; + } + + getIconForLatestInputRecorded(settingName) { + if (this.lastSource === "keyboard") { + this.ensureKeyboardIsInit(); + } + return getIconForLatestInput(this.configs, this.lastSource, this.selectedDevice, settingName); + } + + getLastSourceDevice(): Device { + if (this.lastSource === "gamepad") { + return Device.GAMEPAD; + } else { + return Device.KEYBOARD; + } + } + + getLastSourceConfig() { + const sourceDevice = this.getLastSourceDevice(); + if (sourceDevice === Device.KEYBOARD) { + this.ensureKeyboardIsInit(); + } + return this.getActiveConfig(sourceDevice); + } + + getLastSourceType() { + const config = this.getLastSourceConfig(); + return config?.padType; + } + + /** + * Injects a custom mapping configuration into the configuration for a specific gamepad. + * If the device does not have an existing configuration, it initializes one first. + * + * @param selectedDevice The identifier of the device to configure. + * @param mappingConfigs The mapping configuration to apply to the device. + */ + injectConfig(selectedDevice: string, mappingConfigs): void { + if (!this.configs[selectedDevice]) { + this.configs[selectedDevice] = {}; + } + this.configs[selectedDevice].custom = mappingConfigs.custom; + } + + resetConfigs(): void { + this.configs = new Map(); + if (this.getGamepadsName()?.length) { + this.setupGamepad(this.selectedDevice[Device.GAMEPAD]); + } + this.setupKeyboard(); + } + + /** + * Swaps a binding in the configuration. + * + * @param config The configuration object. + * @param settingName The name of the setting to swap. + * @param pressedButton The button that was pressed. + */ + assignBinding(config, settingName, pressedButton): boolean { + this.pauseUpdate = true; + setTimeout(() => this.pauseUpdate = false, 500); + if (config.padType === "keyboard") { + return assign(config, settingName, pressedButton); + } else { + return swap(config, settingName, pressedButton); } } } diff --git a/src/loading-scene.ts b/src/loading-scene.ts index 51adaf6c746..fe63b39f805 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -251,6 +251,10 @@ export class LoadingScene extends SceneBase { } } + this.loadAtlas("dualshock", "inputs"); + this.loadAtlas("xbox", "inputs"); + this.loadAtlas("keyboard", "inputs"); + this.loadSe("select"); this.loadSe("menu_open"); this.loadSe("hit"); diff --git a/src/system/game-data.ts b/src/system/game-data.ts index b1cbf77f4c6..dc73634e70b 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -30,6 +30,8 @@ import { allMoves } from "../data/move"; import { TrainerVariant } from "../field/trainer"; import { OutdatedPhase, ReloadSessionPhase } from "#app/phases"; import { Variant, variantData } from "#app/data/variant"; +import {setSettingGamepad, SettingGamepad, settingGamepadDefaults} from "./settings-gamepad"; +import {setSettingKeyboard, SettingKeyboard, settingKeyboardDefaults} from "#app/system/settings-keyboard"; import { TerrainChangedEvent, WeatherChangedEvent } from "#app/field/arena-events.js"; const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet necessary @@ -243,6 +245,8 @@ export class GameData { constructor(scene: BattleScene) { this.scene = scene; this.loadSettings(); + this.loadGamepadSettings(); + this.loadMappingConfigs(); this.trainerId = Utils.randInt(65536); this.secretId = Utils.randInt(65536); this.starterData = {}; @@ -565,6 +569,125 @@ export class GameData { return true; } + /** + * Saves the mapping configurations for a specified device. + * + * @param deviceName - The name of the device for which the configurations are being saved. + * @param config - The configuration object containing custom mapping details. + * @returns `true` if the configurations are successfully saved. + */ + public saveMappingConfigs(deviceName: string, config): boolean { + const key = deviceName.toLowerCase(); // Convert the gamepad name to lowercase to use as a key + let mappingConfigs: object = {}; // Initialize an empty object to hold the mapping configurations + if (localStorage.hasOwnProperty("mappingConfigs")) {// Check if 'mappingConfigs' exists in localStorage + mappingConfigs = JSON.parse(localStorage.getItem("mappingConfigs")); + } // Parse the existing 'mappingConfigs' from localStorage + if (!mappingConfigs[key]) { + mappingConfigs[key] = {}; + } // If there is no configuration for the given key, create an empty object for it + mappingConfigs[key].custom = config.custom; // Assign the custom configuration to the mapping configuration for the given key + localStorage.setItem("mappingConfigs", JSON.stringify(mappingConfigs)); // Save the updated mapping configurations back to localStorage + return true; // Return true to indicate the operation was successful + } + + /** + * Loads the mapping configurations from localStorage and injects them into the input controller. + * + * @returns `true` if the configurations are successfully loaded and injected; `false` if no configurations are found in localStorage. + * + * @remarks + * This method checks if the 'mappingConfigs' entry exists in localStorage. If it does not exist, the method returns `false`. + * If 'mappingConfigs' exists, it parses the configurations and injects each configuration into the input controller + * for the corresponding gamepad or device key. The method then returns `true` to indicate success. + */ + public loadMappingConfigs(): boolean { + if (!localStorage.hasOwnProperty("mappingConfigs")) {// Check if 'mappingConfigs' exists in localStorage + return false; + } // If 'mappingConfigs' does not exist, return false + + const mappingConfigs = JSON.parse(localStorage.getItem("mappingConfigs")); // Parse the existing 'mappingConfigs' from localStorage + + for (const key of Object.keys(mappingConfigs)) {// Iterate over the keys of the mapping configurations + this.scene.inputController.injectConfig(key, mappingConfigs[key]); + } // Inject each configuration into the input controller for the corresponding key + + return true; // Return true to indicate the operation was successful + } + + public resetMappingToFactory(): boolean { + if (!localStorage.hasOwnProperty("mappingConfigs")) {// Check if 'mappingConfigs' exists in localStorage + return false; + } // If 'mappingConfigs' does not exist, return false + localStorage.removeItem("mappingConfigs"); + this.scene.inputController.resetConfigs(); + } + + /** + * Saves a gamepad setting to localStorage. + * + * @param setting - The gamepad setting to save. + * @param valueIndex - The index of the value to set for the gamepad setting. + * @returns `true` if the setting is successfully saved. + * + * @remarks + * This method initializes an empty object for gamepad settings if none exist in localStorage. + * It then updates the setting in the current scene and iterates over the default gamepad settings + * to update the specified setting with the new value. Finally, it saves the updated settings back + * to localStorage and returns `true` to indicate success. + */ + public saveGamepadSetting(setting: SettingGamepad, valueIndex: integer): boolean { + let settingsGamepad: object = {}; // Initialize an empty object to hold the gamepad settings + + if (localStorage.hasOwnProperty("settingsGamepad")) { // Check if 'settingsGamepad' exists in localStorage + settingsGamepad = JSON.parse(localStorage.getItem("settingsGamepad")); // Parse the existing 'settingsGamepad' from localStorage + } + + setSettingGamepad(this.scene, setting as SettingGamepad, valueIndex); // Set the gamepad setting in the current scene + + Object.keys(settingGamepadDefaults).forEach(s => { // Iterate over the default gamepad settings + if (s === setting) {// If the current setting matches, update its value + settingsGamepad[s] = valueIndex; + } + }); + + localStorage.setItem("settingsGamepad", JSON.stringify(settingsGamepad)); // Save the updated gamepad settings back to localStorage + + return true; // Return true to indicate the operation was successful + } + + /** + * Saves a keyboard setting to localStorage. + * + * @param setting - The keyboard setting to save. + * @param valueIndex - The index of the value to set for the keyboard setting. + * @returns `true` if the setting is successfully saved. + * + * @remarks + * This method initializes an empty object for keyboard settings if none exist in localStorage. + * It then updates the setting in the current scene and iterates over the default keyboard settings + * to update the specified setting with the new value. Finally, it saves the updated settings back + * to localStorage and returns `true` to indicate success. + */ + public saveKeyboardSetting(setting: SettingKeyboard, valueIndex: integer): boolean { + let settingsKeyboard: object = {}; // Initialize an empty object to hold the keyboard settings + + if (localStorage.hasOwnProperty("settingsKeyboard")) { // Check if 'settingsKeyboard' exists in localStorage + settingsKeyboard = JSON.parse(localStorage.getItem("settingsKeyboard")); // Parse the existing 'settingsKeyboard' from localStorage + } + + setSettingKeyboard(this.scene, setting as SettingKeyboard, valueIndex); // Set the keyboard setting in the current scene + + Object.keys(settingKeyboardDefaults).forEach(s => { // Iterate over the default keyboard settings + if (s === setting) {// If the current setting matches, update its value + settingsKeyboard[s] = valueIndex; + } + }); + + localStorage.setItem("settingsKeyboard", JSON.stringify(settingsKeyboard)); // Save the updated keyboard settings back to localStorage + + return true; // Return true to indicate the operation was successful + } + private loadSettings(): boolean { Object.values(Setting).map(setting => setting as Setting).forEach(setting => setSetting(this.scene, setting, settingDefaults[setting])); @@ -579,6 +702,19 @@ export class GameData { } } + private loadGamepadSettings(): boolean { + Object.values(SettingGamepad).map(setting => setting as SettingGamepad).forEach(setting => setSettingGamepad(this.scene, setting, settingGamepadDefaults[setting])); + + if (!localStorage.hasOwnProperty("settingsGamepad")) { + return false; + } + const settingsGamepad = JSON.parse(localStorage.getItem("settingsGamepad")); + + for (const setting of Object.keys(settingsGamepad)) { + setSettingGamepad(this.scene, setting as SettingGamepad, settingsGamepad[setting]); + } + } + public saveTutorialFlag(tutorial: Tutorial, flag: boolean): boolean { let tutorials: object = {}; if (localStorage.hasOwnProperty("tutorials")) { diff --git a/src/system/settings-gamepad.ts b/src/system/settings-gamepad.ts new file mode 100644 index 00000000000..22cc07efce1 --- /dev/null +++ b/src/system/settings-gamepad.ts @@ -0,0 +1,147 @@ +import BattleScene from "../battle-scene"; +import {SettingDefaults, SettingOptions} from "./settings"; +import SettingsGamepadUiHandler from "../ui/settings/settings-gamepad-ui-handler"; +import {Mode} from "../ui/ui"; +import {truncateString} from "../utils"; +import {Button} from "../enums/buttons"; +import {SettingKeyboard} from "#app/system/settings-keyboard"; + +export enum SettingGamepad { + Controller = "CONTROLLER", + Gamepad_Support = "GAMEPAD_SUPPORT", + Button_Up = "BUTTON_UP", + Button_Down = "BUTTON_DOWN", + Button_Left = "BUTTON_LEFT", + Button_Right = "BUTTON_RIGHT", + Button_Action = "BUTTON_ACTION", + Button_Cancel = "BUTTON_CANCEL", + Button_Menu = "BUTTON_MENU", + Button_Stats = "BUTTON_STATS", + Button_Cycle_Form = "BUTTON_CYCLE_FORM", + Button_Cycle_Shiny = "BUTTON_CYCLE_SHINY", + Button_Cycle_Gender = "BUTTON_CYCLE_GENDER", + Button_Cycle_Ability = "BUTTON_CYCLE_ABILITY", + Button_Cycle_Nature = "BUTTON_CYCLE_NATURE", + Button_Cycle_Variant = "BUTTON_CYCLE_VARIANT", + Button_Speed_Up = "BUTTON_SPEED_UP", + Button_Slow_Down = "BUTTON_SLOW_DOWN", + Button_Submit = "BUTTON_SUBMIT", +} + +export const settingGamepadOptions: SettingOptions = { + [SettingGamepad.Controller]: ["Default", "Change"], + [SettingGamepad.Gamepad_Support]: ["Auto", "Disabled"], + [SettingGamepad.Button_Up]: [`KEY ${Button.UP.toString()}`, "Press action to assign"], + [SettingGamepad.Button_Down]: [`KEY ${Button.DOWN.toString()}`, "Press action to assign"], + [SettingGamepad.Button_Left]: [`KEY ${Button.LEFT.toString()}`, "Press action to assign"], + [SettingGamepad.Button_Right]: [`KEY ${Button.RIGHT.toString()}`, "Press action to assign"], + [SettingGamepad.Button_Action]: [`KEY ${Button.ACTION.toString()}`, "Press action to assign"], + [SettingGamepad.Button_Cancel]: [`KEY ${Button.CANCEL.toString()}`, "Press action to assign"], + [SettingGamepad.Button_Menu]: [`KEY ${Button.MENU.toString()}`, "Press action to assign"], + [SettingGamepad.Button_Stats]: [`KEY ${Button.STATS.toString()}`, "Press action to assign"], + [SettingGamepad.Button_Cycle_Form]: [`KEY ${Button.CYCLE_FORM.toString()}`, "Press action to assign"], + [SettingGamepad.Button_Cycle_Shiny]: [`KEY ${Button.CYCLE_SHINY.toString()}`, "Press action to assign"], + [SettingGamepad.Button_Cycle_Gender]: [`KEY ${Button.CYCLE_GENDER.toString()}`, "Press action to assign"], + [SettingGamepad.Button_Cycle_Ability]: [`KEY ${Button.CYCLE_ABILITY.toString()}`, "Press action to assign"], + [SettingGamepad.Button_Cycle_Nature]: [`KEY ${Button.CYCLE_NATURE.toString()}`, "Press action to assign"], + [SettingGamepad.Button_Cycle_Variant]: [`KEY ${Button.V.toString()}`, "Press action to assign"], + [SettingGamepad.Button_Speed_Up]: [`KEY ${Button.SPEED_UP.toString()}`, "Press action to assign"], + [SettingGamepad.Button_Slow_Down]: [`KEY ${Button.SLOW_DOWN.toString()}`, "Press action to assign"], + [SettingGamepad.Button_Submit]: [`KEY ${Button.SUBMIT.toString()}`, "Press action to assign"], +}; + +export const settingGamepadDefaults: SettingDefaults = { + [SettingGamepad.Controller]: 0, + [SettingGamepad.Gamepad_Support]: 0, + [SettingGamepad.Button_Up]: 0, + [SettingGamepad.Button_Down]: 0, + [SettingGamepad.Button_Left]: 0, + [SettingGamepad.Button_Right]: 0, + [SettingGamepad.Button_Action]: 0, + [SettingGamepad.Button_Cancel]: 0, + [SettingGamepad.Button_Menu]: 0, + [SettingGamepad.Button_Stats]: 0, + [SettingGamepad.Button_Cycle_Form]: 0, + [SettingGamepad.Button_Cycle_Shiny]: 0, + [SettingGamepad.Button_Cycle_Gender]: 0, + [SettingGamepad.Button_Cycle_Ability]: 0, + [SettingGamepad.Button_Cycle_Nature]: 0, + [SettingGamepad.Button_Cycle_Variant]: 0, + [SettingGamepad.Button_Speed_Up]: 0, + [SettingGamepad.Button_Slow_Down]: 0, + [SettingGamepad.Button_Submit]: 0, +}; + +export const settingGamepadBlackList = [ + SettingKeyboard.Button_Up, + SettingKeyboard.Button_Down, + SettingKeyboard.Button_Left, + SettingKeyboard.Button_Right, +]; + +export function setSettingGamepad(scene: BattleScene, setting: SettingGamepad, value: integer): boolean { + switch (setting) { + case SettingGamepad.Gamepad_Support: + // if we change the value of the gamepad support, we call a method in the inputController to + // activate or deactivate the controller listener + scene.inputController.setGamepadSupport(settingGamepadOptions[setting][value] !== "Disabled"); + break; + case SettingGamepad.Button_Action: + case SettingGamepad.Button_Cancel: + case SettingGamepad.Button_Menu: + case SettingGamepad.Button_Stats: + case SettingGamepad.Button_Cycle_Shiny: + case SettingGamepad.Button_Cycle_Form: + case SettingGamepad.Button_Cycle_Gender: + case SettingGamepad.Button_Cycle_Ability: + case SettingGamepad.Button_Cycle_Nature: + case SettingGamepad.Button_Cycle_Variant: + case SettingGamepad.Button_Speed_Up: + case SettingGamepad.Button_Slow_Down: + case SettingGamepad.Button_Submit: + if (value) { + if (scene.ui) { + const cancelHandler = (success: boolean = false) : boolean => { + scene.ui.revertMode(); + (scene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings(); + return success; + }; + scene.ui.setOverlayMode(Mode.GAMEPAD_BINDING, { + target: setting, + cancelHandler: cancelHandler, + }); + } + } + break; + case SettingGamepad.Controller: + if (value) { + const gp = scene.inputController.getGamepadsName(); + if (scene.ui && gp) { + const cancelHandler = () => { + scene.ui.revertMode(); + (scene.ui.getHandler() as SettingsGamepadUiHandler).setOptionCursor(Object.values(SettingGamepad).indexOf(SettingGamepad.Controller), 0, true); + (scene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings(); + return false; + }; + const changeGamepadHandler = (gamepad: string) => { + scene.inputController.setChosenGamepad(gamepad); + cancelHandler(); + return true; + }; + scene.ui.setOverlayMode(Mode.OPTION_SELECT, { + options: [...gp.map((g: string) => ({ + label: truncateString(g, 30), // Truncate the gamepad name for display + handler: () => changeGamepadHandler(g) + })), { + label: "Cancel", + handler: cancelHandler, + }] + }); + return false; + } + } + break; + } + + return true; +} diff --git a/src/system/settings-keyboard.ts b/src/system/settings-keyboard.ts new file mode 100644 index 00000000000..4ffe6ad3e70 --- /dev/null +++ b/src/system/settings-keyboard.ts @@ -0,0 +1,208 @@ +import {SettingDefaults, SettingOptions} from "#app/system/settings"; +import {Button} from "#app/enums/buttons"; +import BattleScene from "#app/battle-scene"; +import {Mode} from "#app/ui/ui"; +import SettingsKeyboardUiHandler from "#app/ui/settings/settings-keyboard-ui-handler"; + +export enum SettingKeyboard { + // Default_Layout = "DEFAULT_LAYOUT", + Button_Up = "BUTTON_UP", + Alt_Button_Up = "ALT_BUTTON_UP", + Button_Down = "BUTTON_DOWN", + Alt_Button_Down = "ALT_BUTTON_DOWN", + Button_Left = "BUTTON_LEFT", + Alt_Button_Left = "ALT_BUTTON_LEFT", + Button_Right = "BUTTON_RIGHT", + Alt_Button_Right = "ALT_BUTTON_RIGHT", + Button_Action = "BUTTON_ACTION", + Alt_Button_Action = "ALT_BUTTON_ACTION", + Button_Cancel = "BUTTON_CANCEL", + Alt_Button_Cancel = "ALT_BUTTON_CANCEL", + Button_Menu = "BUTTON_MENU", + Alt_Button_Menu = "ALT_BUTTON_MENU", + Button_Stats = "BUTTON_STATS", + Alt_Button_Stats = "ALT_BUTTON_STATS", + Button_Cycle_Form = "BUTTON_CYCLE_FORM", + Alt_Button_Cycle_Form = "ALT_BUTTON_CYCLE_FORM", + Button_Cycle_Shiny = "BUTTON_CYCLE_SHINY", + Alt_Button_Cycle_Shiny = "ALT_BUTTON_CYCLE_SHINY", + Button_Cycle_Gender = "BUTTON_CYCLE_GENDER", + Alt_Button_Cycle_Gender = "ALT_BUTTON_CYCLE_GENDER", + Button_Cycle_Ability = "BUTTON_CYCLE_ABILITY", + Alt_Button_Cycle_Ability = "ALT_BUTTON_CYCLE_ABILITY", + Button_Cycle_Nature = "BUTTON_CYCLE_NATURE", + Alt_Button_Cycle_Nature = "ALT_BUTTON_CYCLE_NATURE", + Button_Cycle_Variant = "BUTTON_CYCLE_VARIANT", + Alt_Button_Cycle_Variant = "ALT_BUTTON_CYCLE_VARIANT", + Button_Speed_Up = "BUTTON_SPEED_UP", + Alt_Button_Speed_Up = "ALT_BUTTON_SPEED_UP", + Button_Slow_Down = "BUTTON_SLOW_DOWN", + Alt_Button_Slow_Down = "ALT_BUTTON_SLOW_DOWN", + Button_Submit = "BUTTON_SUBMIT", + Alt_Button_Submit = "ALT_BUTTON_SUBMIT", +} + +export const settingKeyboardOptions: SettingOptions = { + // [SettingKeyboard.Default_Layout]: ['Default'], + [SettingKeyboard.Button_Up]: [`KEY ${Button.UP.toString()}`, "Press action to assign"], + [SettingKeyboard.Button_Down]: [`KEY ${Button.DOWN.toString()}`, "Press action to assign"], + [SettingKeyboard.Alt_Button_Up]: [`KEY ${Button.UP.toString()}`, "Press action to assign"], + [SettingKeyboard.Button_Left]: [`KEY ${Button.LEFT.toString()}`, "Press action to assign"], + [SettingKeyboard.Button_Right]: [`KEY ${Button.RIGHT.toString()}`, "Press action to assign"], + [SettingKeyboard.Button_Action]: [`KEY ${Button.ACTION.toString()}`, "Press action to assign"], + [SettingKeyboard.Button_Menu]: [`KEY ${Button.MENU.toString()}`, "Press action to assign"], + [SettingKeyboard.Button_Submit]: [`KEY ${Button.SUBMIT.toString()}`, "Press action to assign"], + + [SettingKeyboard.Alt_Button_Down]: [`KEY ${Button.DOWN.toString()}`, "Press action to assign"], + [SettingKeyboard.Alt_Button_Left]: [`KEY ${Button.LEFT.toString()}`, "Press action to assign"], + [SettingKeyboard.Alt_Button_Right]: [`KEY ${Button.RIGHT.toString()}`, "Press action to assign"], + [SettingKeyboard.Alt_Button_Action]: [`KEY ${Button.ACTION.toString()}`, "Press action to assign"], + [SettingKeyboard.Button_Cancel]: [`KEY ${Button.CANCEL.toString()}`, "Press action to assign"], + [SettingKeyboard.Alt_Button_Cancel]: [`KEY ${Button.CANCEL.toString()}`, "Press action to assign"], + [SettingKeyboard.Alt_Button_Menu]: [`KEY ${Button.MENU.toString()}`, "Press action to assign"], + [SettingKeyboard.Button_Stats]: [`KEY ${Button.STATS.toString()}`, "Press action to assign"], + [SettingKeyboard.Alt_Button_Stats]: [`KEY ${Button.STATS.toString()}`, "Press action to assign"], + [SettingKeyboard.Button_Cycle_Form]: [`KEY ${Button.CYCLE_FORM.toString()}`, "Press action to assign"], + [SettingKeyboard.Alt_Button_Cycle_Form]: [`KEY ${Button.CYCLE_FORM.toString()}`, "Press action to assign"], + [SettingKeyboard.Button_Cycle_Shiny]: [`KEY ${Button.CYCLE_SHINY.toString()}`, "Press action to assign"], + [SettingKeyboard.Alt_Button_Cycle_Shiny]: [`KEY ${Button.CYCLE_SHINY.toString()}`, "Press action to assign"], + [SettingKeyboard.Button_Cycle_Gender]: [`KEY ${Button.CYCLE_GENDER.toString()}`, "Press action to assign"], + [SettingKeyboard.Alt_Button_Cycle_Gender]: [`KEY ${Button.CYCLE_GENDER.toString()}`, "Press action to assign"], + [SettingKeyboard.Button_Cycle_Ability]: [`KEY ${Button.CYCLE_ABILITY.toString()}`, "Press action to assign"], + [SettingKeyboard.Alt_Button_Cycle_Ability]: [`KEY ${Button.CYCLE_ABILITY.toString()}`, "Press action to assign"], + [SettingKeyboard.Button_Cycle_Nature]: [`KEY ${Button.CYCLE_NATURE.toString()}`, "Press action to assign"], + [SettingKeyboard.Alt_Button_Cycle_Nature]: [`KEY ${Button.CYCLE_NATURE.toString()}`, "Press action to assign"], + [SettingKeyboard.Button_Cycle_Variant]: [`KEY ${Button.V.toString()}`, "Press action to assign"], + [SettingKeyboard.Alt_Button_Cycle_Variant]: [`KEY ${Button.V.toString()}`, "Press action to assign"], + [SettingKeyboard.Button_Speed_Up]: [`KEY ${Button.SPEED_UP.toString()}`, "Press action to assign"], + [SettingKeyboard.Alt_Button_Speed_Up]: [`KEY ${Button.SPEED_UP.toString()}`, "Press action to assign"], + [SettingKeyboard.Button_Slow_Down]: [`KEY ${Button.SLOW_DOWN.toString()}`, "Press action to assign"], + [SettingKeyboard.Alt_Button_Slow_Down]: [`KEY ${Button.SLOW_DOWN.toString()}`, "Press action to assign"], + [SettingKeyboard.Alt_Button_Submit]: [`KEY ${Button.SUBMIT.toString()}`, "Press action to assign"], +}; + +export const settingKeyboardDefaults: SettingDefaults = { + // [SettingKeyboard.Default_Layout]: 0, + [SettingKeyboard.Button_Up]: 0, + [SettingKeyboard.Button_Down]: 0, + [SettingKeyboard.Button_Left]: 0, + [SettingKeyboard.Button_Right]: 0, + [SettingKeyboard.Button_Action]: 0, + [SettingKeyboard.Button_Menu]: 0, + [SettingKeyboard.Button_Submit]: 0, + + [SettingKeyboard.Alt_Button_Up]: 0, + [SettingKeyboard.Alt_Button_Down]: 0, + [SettingKeyboard.Alt_Button_Left]: 0, + [SettingKeyboard.Alt_Button_Right]: 0, + [SettingKeyboard.Alt_Button_Action]: 0, + [SettingKeyboard.Button_Cancel]: 0, + [SettingKeyboard.Alt_Button_Cancel]: 0, + [SettingKeyboard.Alt_Button_Menu]: 0, + [SettingKeyboard.Button_Stats]: 0, + [SettingKeyboard.Alt_Button_Stats]: 0, + [SettingKeyboard.Button_Cycle_Form]: 0, + [SettingKeyboard.Alt_Button_Cycle_Form]: 0, + [SettingKeyboard.Button_Cycle_Shiny]: 0, + [SettingKeyboard.Alt_Button_Cycle_Shiny]: 0, + [SettingKeyboard.Button_Cycle_Gender]: 0, + [SettingKeyboard.Alt_Button_Cycle_Gender]: 0, + [SettingKeyboard.Button_Cycle_Ability]: 0, + [SettingKeyboard.Alt_Button_Cycle_Ability]: 0, + [SettingKeyboard.Button_Cycle_Nature]: 0, + [SettingKeyboard.Alt_Button_Cycle_Nature]: 0, + [SettingKeyboard.Button_Cycle_Variant]: 0, + [SettingKeyboard.Alt_Button_Cycle_Variant]: 0, + [SettingKeyboard.Button_Speed_Up]: 0, + [SettingKeyboard.Alt_Button_Speed_Up]: 0, + [SettingKeyboard.Button_Slow_Down]: 0, + [SettingKeyboard.Alt_Button_Slow_Down]: 0, + [SettingKeyboard.Alt_Button_Submit]: 0, +}; + +export const settingKeyboardBlackList = [ + SettingKeyboard.Button_Submit, + SettingKeyboard.Button_Menu, + SettingKeyboard.Button_Action, + SettingKeyboard.Button_Cancel, + SettingKeyboard.Button_Up, + SettingKeyboard.Button_Down, + SettingKeyboard.Button_Left, + SettingKeyboard.Button_Right, +]; + + +export function setSettingKeyboard(scene: BattleScene, setting: SettingKeyboard, value: integer): boolean { + switch (setting) { + case SettingKeyboard.Button_Up: + case SettingKeyboard.Button_Down: + case SettingKeyboard.Button_Left: + case SettingKeyboard.Button_Right: + case SettingKeyboard.Button_Action: + case SettingKeyboard.Button_Cancel: + case SettingKeyboard.Button_Menu: + case SettingKeyboard.Button_Stats: + case SettingKeyboard.Button_Cycle_Shiny: + case SettingKeyboard.Button_Cycle_Form: + case SettingKeyboard.Button_Cycle_Gender: + case SettingKeyboard.Button_Cycle_Ability: + case SettingKeyboard.Button_Cycle_Nature: + case SettingKeyboard.Button_Cycle_Variant: + case SettingKeyboard.Button_Speed_Up: + case SettingKeyboard.Button_Slow_Down: + case SettingKeyboard.Alt_Button_Up: + case SettingKeyboard.Alt_Button_Down: + case SettingKeyboard.Alt_Button_Left: + case SettingKeyboard.Alt_Button_Right: + case SettingKeyboard.Alt_Button_Action: + case SettingKeyboard.Alt_Button_Cancel: + case SettingKeyboard.Alt_Button_Menu: + case SettingKeyboard.Alt_Button_Stats: + case SettingKeyboard.Alt_Button_Cycle_Shiny: + case SettingKeyboard.Alt_Button_Cycle_Form: + case SettingKeyboard.Alt_Button_Cycle_Gender: + case SettingKeyboard.Alt_Button_Cycle_Ability: + case SettingKeyboard.Alt_Button_Cycle_Nature: + case SettingKeyboard.Alt_Button_Cycle_Variant: + case SettingKeyboard.Alt_Button_Speed_Up: + case SettingKeyboard.Alt_Button_Slow_Down: + case SettingKeyboard.Alt_Button_Submit: + if (value) { + if (scene.ui) { + const cancelHandler = (success: boolean = false) : boolean => { + scene.ui.revertMode(); + (scene.ui.getHandler() as SettingsKeyboardUiHandler).updateBindings(); + return success; + }; + scene.ui.setOverlayMode(Mode.KEYBOARD_BINDING, { + target: setting, + cancelHandler: cancelHandler, + }); + } + } + break; + // case SettingKeyboard.Default_Layout: + // if (value && scene.ui) { + // const cancelHandler = () => { + // scene.ui.revertMode(); + // (scene.ui.getHandler() as SettingsKeyboardUiHandler).setOptionCursor(Object.values(SettingKeyboard).indexOf(SettingKeyboard.Default_Layout), 0, true); + // (scene.ui.getHandler() as SettingsKeyboardUiHandler).updateBindings(); + // return false; + // }; + // const changeKeyboardHandler = (keyboardLayout: string) => { + // scene.inputController.setChosenKeyboardLayout(keyboardLayout); + // cancelHandler(); + // return true; + // }; + // scene.ui.setOverlayMode(Mode.OPTION_SELECT, { + // options: [{ + // label: 'Default', + // handler: changeKeyboardHandler, + // }] + // }); + // return false; + // } + } + return true; + +} diff --git a/src/system/settings.ts b/src/system/settings.ts index 3cfc6ba9be4..f2a3548da4a 100644 --- a/src/system/settings.ts +++ b/src/system/settings.ts @@ -1,4 +1,3 @@ -import SettingsUiHandler from "#app/ui/settings-ui-handler"; import { Mode } from "#app/ui/ui"; import i18next from "i18next"; import BattleScene from "../battle-scene"; @@ -7,6 +6,7 @@ import { updateWindowType } from "../ui/ui-theme"; import { PlayerGender } from "./game-data"; import { CandyUpgradeNotificationChangedEvent } from "#app/battle-scene-events.js"; import { MoneyFormat } from "../enums/money-format"; +import SettingsUiHandler from "#app/ui/settings/settings-ui-handler"; export enum Setting { Game_Speed = "GAME_SPEED", @@ -31,8 +31,6 @@ export enum Setting { HP_Bar_Speed = "HP_BAR_SPEED", Fusion_Palette_Swaps = "FUSION_PALETTE_SWAPS", Player_Gender = "PLAYER_GENDER", - Gamepad_Support = "GAMEPAD_SUPPORT", - Swap_A_and_B = "SWAP_A_B", // Swaps which gamepad button handles ACTION and CANCEL Touch_Controls = "TOUCH_CONTROLS", Vibration = "VIBRATION" } @@ -68,8 +66,6 @@ export const settingOptions: SettingOptions = { [Setting.HP_Bar_Speed]: ["Normal", "Fast", "Faster", "Instant"], [Setting.Fusion_Palette_Swaps]: ["Off", "On"], [Setting.Player_Gender]: ["Boy", "Girl"], - [Setting.Gamepad_Support]: ["Auto", "Disabled"], - [Setting.Swap_A_and_B]: ["Enabled", "Disabled"], [Setting.Touch_Controls]: ["Auto", "Disabled"], [Setting.Vibration]: ["Auto", "Disabled"] }; @@ -97,8 +93,6 @@ export const settingDefaults: SettingDefaults = { [Setting.HP_Bar_Speed]: 0, [Setting.Fusion_Palette_Swaps]: 1, [Setting.Player_Gender]: 0, - [Setting.Gamepad_Support]: 0, - [Setting.Swap_A_and_B]: 1, // Set to 'Disabled' by default [Setting.Touch_Controls]: 0, [Setting.Vibration]: 0 }; @@ -194,14 +188,6 @@ export function setSetting(scene: BattleScene, setting: Setting, value: integer) return false; } break; - case Setting.Gamepad_Support: - // if we change the value of the gamepad support, we call a method in the inputController to - // activate or deactivate the controller listener - scene.inputController.setGamepadSupport(settingOptions[setting][value] !== "Disabled"); - break; - case Setting.Swap_A_and_B: - scene.abSwapped = settingOptions[setting][value] !== "Disabled"; - break; case Setting.Touch_Controls: scene.enableTouchControls = settingOptions[setting][value] !== "Disabled" && hasTouchscreen(); const touchControls = document.getElementById("touchControls"); diff --git a/src/test/helpers/inGameManip.ts b/src/test/helpers/inGameManip.ts new file mode 100644 index 00000000000..c81602dff6d --- /dev/null +++ b/src/test/helpers/inGameManip.ts @@ -0,0 +1,79 @@ +import { + getIconForLatestInput, + getSettingNameWithKeycode +} from "#app/configs/inputs/configHandler"; +import {expect} from "vitest"; +import {SettingKeyboard} from "#app/system/settings-keyboard"; + +export class InGameManip { + private config; + private keycode; + private settingName; + private icon; + private configs; + private latestSource; + private selectedDevice; + + constructor(configs, config, selectedDevice) { + this.config = config; + this.configs = configs; + this.selectedDevice = selectedDevice; + this.keycode = null; + this.settingName = null; + this.icon = null; + this.latestSource = null; + } + + whenWePressOnKeyboard(keycode) { + this.keycode = Phaser.Input.Keyboard.KeyCodes[keycode.toUpperCase()]; + return this; + } + + nothingShouldHappen() { + const settingName = getSettingNameWithKeycode(this.config, this.keycode); + expect(settingName).toEqual(-1); + return this; + } + + forTheWantedBind(settingName) { + if (!settingName.includes("Button_")) { + settingName = "Button_" + settingName; + } + this.settingName = SettingKeyboard[settingName]; + return this; + } + + weShouldSeeTheIcon(icon) { + if (!icon.includes("KEY_")) { + icon = "KEY_" + icon; + } + this.icon = this.config.icons[icon]; + expect(getIconForLatestInput(this.configs, this.latestSource, this.selectedDevice, this.settingName)).toEqual(this.icon); + return this; + } + + forTheSource(source) { + this.latestSource = source; + return this; + } + + normalizeSettingNameString(input) { + // Convert the input string to lower case + const lowerCasedInput = input.toLowerCase(); + + // Replace underscores with spaces, capitalize the first letter of each word, and join them back with underscores + const words = lowerCasedInput.split("_").map(word => word.charAt(0).toUpperCase() + word.slice(1)); + const result = words.join("_"); + + return result; + } + + weShouldTriggerTheButton(settingName) { + if (!settingName.includes("Button_")) { + settingName = "Button_" + settingName; + } + this.settingName = SettingKeyboard[this.normalizeSettingNameString(settingName)]; + expect(getSettingNameWithKeycode(this.config, this.keycode)).toEqual(this.settingName); + return this; + } +} diff --git a/src/test/helpers/menuManip.ts b/src/test/helpers/menuManip.ts new file mode 100644 index 00000000000..2377ab70c64 --- /dev/null +++ b/src/test/helpers/menuManip.ts @@ -0,0 +1,131 @@ +import {expect} from "vitest"; +import { + deleteBind, + getIconWithKeycode, + getIconWithSettingName, + getKeyWithKeycode, + getKeyWithSettingName, + assign, + getSettingNameWithKeycode, canIAssignThisKey, canIDeleteThisKey, canIOverrideThisSetting +} from "#app/configs/inputs/configHandler"; +import {SettingKeyboard} from "#app/system/settings-keyboard"; + +export class MenuManip { + private config; + private settingName; + private keycode; + private icon; + private iconDisplayed; + private specialCaseIcon; + + constructor(config) { + this.config = config; + this.settingName = null; + this.icon = null; + this.iconDisplayed = null; + this.specialCaseIcon = null; + } + + convertNameToButtonString(input) { + // Check if the input starts with "Alt_Button" + if (input.startsWith("Alt_Button")) { + // Return the last part in uppercase + return input.split("_").pop().toUpperCase(); + } + + // Split the input string by underscore + const parts = input.split("_"); + + // Skip the first part and join the rest with an underscore + const result = parts.slice(1).map(part => part.toUpperCase()).join("_"); + + return result; + } + + whenCursorIsOnSetting(settingName) { + if (!settingName.includes("Button_")) { + settingName = "Button_" + settingName; + } + this.settingName = SettingKeyboard[settingName]; + return this; + } + + iconDisplayedIs(icon) { + if (!(icon.toUpperCase().includes("KEY_"))) { + icon = "KEY_" + icon.toUpperCase(); + } + this.iconDisplayed = this.config.icons[icon]; + expect(getIconWithSettingName(this.config, this.settingName)).toEqual(this.iconDisplayed); + return this; + } + + thereShouldBeNoIconAnymore() { + const icon = getIconWithSettingName(this.config, this.settingName); + expect(icon === undefined).toEqual(true); + return this; + } + + thereShouldBeNoIcon() { + return this.thereShouldBeNoIconAnymore(); + } + + nothingShouldHappen() { + const settingName = getSettingNameWithKeycode(this.config, this.keycode); + expect(settingName).toEqual(-1); + return this; + } + + weWantThisBindInstead(keycode) { + this.keycode = Phaser.Input.Keyboard.KeyCodes[keycode]; + const icon = getIconWithKeycode(this.config, this.keycode); + const key = getKeyWithKeycode(this.config, this.keycode); + const _keys = key.toLowerCase().split("_"); + const iconIdentifier = _keys[_keys.length-1]; + expect(icon.toLowerCase().includes(iconIdentifier)).toEqual(true); + return this; + } + + whenWeDelete(settingName?: string) { + this.settingName = SettingKeyboard[settingName] || this.settingName; + // const key = getKeyWithSettingName(this.config, this.settingName); + deleteBind(this.config, this.settingName); + // expect(this.config.custom[key]).toEqual(-1); + return this; + } + + whenWeTryToDelete(settingName?: string) { + this.settingName = SettingKeyboard[settingName] || this.settingName; + deleteBind(this.config, this.settingName); + return this; + } + + confirmAssignment() { + assign(this.config, this.settingName, this.keycode); + } + + butLetsForceIt() { + this.confirm(); + } + + + confirm() { + assign(this.config, this.settingName, this.keycode); + } + + weCantAssignThisKey() { + const key = getKeyWithKeycode(this.config, this.keycode); + expect(canIAssignThisKey(this.config, key)).toEqual(false); + return this; + } + + weCantOverrideThisBind() { + expect(canIOverrideThisSetting(this.config, this.settingName)).toEqual(false); + return this; + } + + weCantDelete() { + const key = getKeyWithSettingName(this.config, this.settingName); + expect(canIDeleteThisKey(this.config, key)).toEqual(false); + return this; + } +} diff --git a/src/test/rebinding_setting.test.ts b/src/test/rebinding_setting.test.ts new file mode 100644 index 00000000000..92376006263 --- /dev/null +++ b/src/test/rebinding_setting.test.ts @@ -0,0 +1,417 @@ +import {beforeEach, describe, expect, it} from "vitest"; +import {Button} from "#app/enums/buttons"; +import {deepCopy} from "#app/utils"; +import { + getKeyWithKeycode, + getKeyWithSettingName, +} from "#app/configs/inputs/configHandler"; +import {MenuManip} from "#app/test/helpers/menuManip"; +import {InGameManip} from "#app/test/helpers/inGameManip"; +import {Device} from "#app/enums/devices"; +import {InterfaceConfig} from "#app/inputs-controller"; +import cfg_keyboard_qwerty from "#app/configs/inputs/cfg_keyboard_qwerty"; +import {SettingKeyboard} from "#app/system/settings-keyboard"; + + +describe("Test Rebinding", () => { + let config; + let inGame; + let inTheSettingMenu; + const configs: Map = new Map(); + const selectedDevice = { + [Device.GAMEPAD]: null, + [Device.KEYBOARD]: "default", + }; + + beforeEach(() => { + config = deepCopy(cfg_keyboard_qwerty); + config.custom = {...config.default}; + configs["default"] = config; + inGame = new InGameManip(configs, config, selectedDevice); + inTheSettingMenu = new MenuManip(config); + }); + + it("Check if config is loaded", () => { + expect(config).not.toBeNull(); + }); + it("Check button for setting name", () => { + const settingName = SettingKeyboard.Button_Left; + const button = config.settings[settingName]; + expect(button).toEqual(Button.LEFT); + }); + it("Check key for Keyboard KeyCode", () => { + const key = getKeyWithKeycode(config, Phaser.Input.Keyboard.KeyCodes.LEFT); + const settingName = config.custom[key]; + const button = config.settings[settingName]; + expect(button).toEqual(Button.LEFT); + }); + it("Check key for currenly Assigned to action not alt", () => { + const key = getKeyWithKeycode(config, Phaser.Input.Keyboard.KeyCodes.A); + const settingName = config.custom[key]; + const button = config.settings[settingName]; + expect(button).toEqual(Button.LEFT); + }); + + it("Check key for currenly Assigned to setting name", () => { + const settingName = SettingKeyboard.Button_Left; + const key = getKeyWithSettingName(config, settingName); + expect(key).toEqual("KEY_ARROW_LEFT"); + }); + it("Check key for currenly Assigned to setting name alt", () => { + const settingName = SettingKeyboard.Alt_Button_Left; + const key = getKeyWithSettingName(config, settingName); + expect(key).toEqual("KEY_A"); + }); + it("Check key from key code", () => { + const keycode = Phaser.Input.Keyboard.KeyCodes.LEFT; + const key = getKeyWithKeycode(config, keycode); + expect(key).toEqual("KEY_ARROW_LEFT"); + }); + it("Check icon for currenly Assigned to key code", () => { + const keycode = Phaser.Input.Keyboard.KeyCodes.LEFT; + const key = getKeyWithKeycode(config, keycode); + const icon = config.icons[key]; + expect(icon).toEqual("KEY_ARROW_LEFT.png"); + }); + it("Check icon for currenly Assigned to key code", () => { + const keycode = Phaser.Input.Keyboard.KeyCodes.A; + const key = getKeyWithKeycode(config, keycode); + const icon = config.icons[key]; + expect(icon).toEqual("A.png"); + }); + it("Check icon for currenly Assigned to setting name", () => { + const settingName = SettingKeyboard.Button_Left; + const key = getKeyWithSettingName(config, settingName); + const icon = config.icons[key]; + expect(icon).toEqual("KEY_ARROW_LEFT.png"); + }); + it("Check icon for currenly Assigned to setting name alt", () => { + const settingName = SettingKeyboard.Alt_Button_Left; + const key = getKeyWithSettingName(config, settingName); + const icon = config.icons[key]; + expect(icon).toEqual("A.png"); + }); + + it("Check if is working", () => { + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("A"); + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Right").iconDisplayedIs("D"); + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("A").weWantThisBindInstead("D").confirm(); + inGame.whenWePressOnKeyboard("A").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Left"); + }); + + it("Check prevent rebind indirectly the d-pad buttons", () => { + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("A"); + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Right").iconDisplayedIs("D"); + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("A").weWantThisBindInstead("LEFT").weCantAssignThisKey().butLetsForceIt(); + inGame.whenWePressOnKeyboard("LEFT").weShouldTriggerTheButton("Left"); + inGame.whenWePressOnKeyboard("A").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right"); + }); + + it("Swap alt with a d-pad main", () => { + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Alt_Button_Up"); + inTheSettingMenu.whenCursorIsOnSetting("Button_Up").iconDisplayedIs("KEY_ARROW_UP").weWantThisBindInstead("W").weCantOverrideThisBind().butLetsForceIt(); + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Alt_Button_Up"); + }); + + it("Check if double assign d-pad is blocked", () => { + inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right"); + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + + inTheSettingMenu.whenCursorIsOnSetting("Button_Left").iconDisplayedIs("KEY_ARROW_LEFT").weWantThisBindInstead("RIGHT").weCantOverrideThisBind().weCantAssignThisKey().butLetsForceIt(); + + inGame.whenWePressOnKeyboard("LEFT").weShouldTriggerTheButton("Button_Left"); + inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right"); + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + + inTheSettingMenu.whenCursorIsOnSetting("Button_Left").iconDisplayedIs("KEY_ARROW_LEFT").weWantThisBindInstead("UP").weCantOverrideThisBind().weCantAssignThisKey().butLetsForceIt(); + + inGame.whenWePressOnKeyboard("LEFT").weShouldTriggerTheButton("Button_Left"); + inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right"); + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + + inTheSettingMenu.whenCursorIsOnSetting("Button_Left").iconDisplayedIs("KEY_ARROW_LEFT").weWantThisBindInstead("RIGHT").weCantOverrideThisBind().weCantAssignThisKey().butLetsForceIt(); + + inGame.whenWePressOnKeyboard("LEFT").weShouldTriggerTheButton("Button_Left"); + inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right"); + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + }); + + it("Check if double assign is working", () => { + inGame.whenWePressOnKeyboard("A").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right"); + inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Alt_Button_Up"); + + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("KEY_A").weWantThisBindInstead("D").confirm(); + + inGame.whenWePressOnKeyboard("A").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Alt_Button_Up"); + + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("KEY_D").weWantThisBindInstead("W").confirm(); + + inGame.whenWePressOnKeyboard("A").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("D").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Alt_Button_Left"); + + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("KEY_W").weWantThisBindInstead("D").confirm(); + + inGame.whenWePressOnKeyboard("A").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("W").nothingShouldHappen(); + }); + + it("Check if triple swap d-pad is prevented", () => { + inTheSettingMenu.whenCursorIsOnSetting("Button_Left").iconDisplayedIs("KEY_ARROW_LEFT").weWantThisBindInstead("RIGHT").weCantOverrideThisBind().weCantAssignThisKey().butLetsForceIt(); + + inGame.whenWePressOnKeyboard("LEFT").weShouldTriggerTheButton("Button_Left"); + inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right"); + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + + inTheSettingMenu.whenCursorIsOnSetting("Button_Right").iconDisplayedIs("KEY_ARROW_RIGHT").weWantThisBindInstead("UP").weCantOverrideThisBind().weCantAssignThisKey().butLetsForceIt(); + inGame.whenWePressOnKeyboard("LEFT").weShouldTriggerTheButton("Button_Left"); + inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right"); + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + + inTheSettingMenu.whenCursorIsOnSetting("Button_Left").iconDisplayedIs("KEY_ARROW_LEFT").weWantThisBindInstead("LEFT").weCantOverrideThisBind().weCantAssignThisKey().butLetsForceIt(); + inGame.whenWePressOnKeyboard("LEFT").weShouldTriggerTheButton("Button_Left"); + inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right"); + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + }); + + it("Check if triple swap is working", () => { + inGame.whenWePressOnKeyboard("A").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right"); + inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Alt_Button_Up"); + + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("KEY_A").weWantThisBindInstead("D").confirm(); + + inGame.whenWePressOnKeyboard("A").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Alt_Button_Up"); + + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Right").thereShouldBeNoIcon().weWantThisBindInstead("W").confirm(); + + inGame.whenWePressOnKeyboard("A").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Alt_Button_Right"); + + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("KEY_D").weWantThisBindInstead("A").confirm(); + + inGame.whenWePressOnKeyboard("A").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("D").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Alt_Button_Right"); + }); + + it("Swap alt with a main", () => { + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right"); + inGame.whenWePressOnKeyboard("R").weShouldTriggerTheButton("Cycle_Shiny"); + inTheSettingMenu.whenCursorIsOnSetting("Cycle_Shiny").iconDisplayedIs("KEY_R").weWantThisBindInstead("D").confirm(); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Cycle_Shiny"); + inGame.whenWePressOnKeyboard("R").nothingShouldHappen(); + }); + + it("multiple Swap alt with another main", () => { + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right"); + inGame.whenWePressOnKeyboard("R").weShouldTriggerTheButton("Button_Cycle_Shiny"); + inGame.whenWePressOnKeyboard("A").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("F").weShouldTriggerTheButton("Button_Cycle_Form"); + inTheSettingMenu.whenCursorIsOnSetting("Button_Cycle_Shiny").iconDisplayedIs("KEY_R").weWantThisBindInstead("D").confirm(); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Button_Cycle_Shiny"); + inGame.whenWePressOnKeyboard("R").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("A").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("F").weShouldTriggerTheButton("Button_Cycle_Form"); + inTheSettingMenu.whenCursorIsOnSetting("Button_Cycle_Form").iconDisplayedIs("KEY_F").weWantThisBindInstead("R").confirm(); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Button_Cycle_Shiny"); + inGame.whenWePressOnKeyboard("R").weShouldTriggerTheButton("Button_Cycle_Form"); + inGame.whenWePressOnKeyboard("A").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("F").nothingShouldHappen(); + }); + + it("Swap alt with a key not binded yet", () => { + inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Alt_Button_Up"); + inGame.whenWePressOnKeyboard("B").nothingShouldHappen(); + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Up").iconDisplayedIs("KEY_W").weWantThisBindInstead("B").confirm(); + inGame.whenWePressOnKeyboard("W").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("B").weShouldTriggerTheButton("Alt_Button_Up"); + }); + + it("Delete blacklisted bind", () => { + inGame.whenWePressOnKeyboard("LEFT").weShouldTriggerTheButton("Button_Left"); + inTheSettingMenu.whenWeDelete("Button_Left").weCantDelete().iconDisplayedIs("KEY_ARROW_LEFT"); + inGame.whenWePressOnKeyboard("LEFT").weShouldTriggerTheButton("Button_Left"); + }); + + it("Delete bind", () => { + inGame.whenWePressOnKeyboard("A").weShouldTriggerTheButton("Alt_Button_Left"); + inTheSettingMenu.whenWeDelete("Alt_Button_Left").thereShouldBeNoIconAnymore(); + inGame.whenWePressOnKeyboard("A").nothingShouldHappen(); + }); + + it("Delete bind then assign a not yet binded button", () => { + inTheSettingMenu.whenWeDelete("Alt_Button_Left").thereShouldBeNoIconAnymore(); + inGame.whenWePressOnKeyboard("A").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("B").nothingShouldHappen(); + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").thereShouldBeNoIcon().weWantThisBindInstead("B").confirm(); + inGame.whenWePressOnKeyboard("A").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("B").weShouldTriggerTheButton("Alt_Button_Left"); + }); + it("swap 2 bind, than delete 1 bind than assign another bind", () => { + inGame.whenWePressOnKeyboard("R").weShouldTriggerTheButton("Button_Cycle_Shiny"); + inGame.whenWePressOnKeyboard("F").weShouldTriggerTheButton("Button_Cycle_Form"); + inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Alt_Button_Up"); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right"); + inTheSettingMenu.whenCursorIsOnSetting("Button_Cycle_Shiny").iconDisplayedIs("KEY_R").weWantThisBindInstead("D").confirm(); + inGame.whenWePressOnKeyboard("R").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("F").weShouldTriggerTheButton("Button_Cycle_Form"); + inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Alt_Button_Up"); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Button_Cycle_Shiny"); + + inTheSettingMenu.whenCursorIsOnSetting("Button_Cycle_Form").iconDisplayedIs("KEY_F").weWantThisBindInstead("W").confirm(); + inGame.whenWePressOnKeyboard("R").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("F").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Button_Cycle_Form"); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Button_Cycle_Shiny"); + inGame.whenWePressOnKeyboard("A").weShouldTriggerTheButton("Alt_Button_Left"); + + inTheSettingMenu.whenWeDelete("Alt_Button_Left").thereShouldBeNoIconAnymore(); + inGame.whenWePressOnKeyboard("R").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("F").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Button_Cycle_Form"); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Button_Cycle_Shiny"); + inGame.whenWePressOnKeyboard("S").weShouldTriggerTheButton("Alt_Button_Down"); + inGame.whenWePressOnKeyboard("A").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("B").nothingShouldHappen(); + + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Down").iconDisplayedIs("KEY_S").weWantThisBindInstead("B").confirm(); + inGame.whenWePressOnKeyboard("R").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("F").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Button_Cycle_Form"); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Button_Cycle_Shiny"); + inGame.whenWePressOnKeyboard("S").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("A").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("B").weShouldTriggerTheButton("Alt_Button_Down"); + }); + + + it("Delete bind then assign not already existing button", () => { + + inGame.whenWePressOnKeyboard("A").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("B").nothingShouldHappen(); + + inTheSettingMenu.whenWeDelete("Alt_Button_Left").thereShouldBeNoIconAnymore(); + inGame.whenWePressOnKeyboard("A").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("B").nothingShouldHappen(); + + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").thereShouldBeNoIcon().weWantThisBindInstead("B").confirm(); + inGame.whenWePressOnKeyboard("A").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("B").weShouldTriggerTheButton("Alt_Button_Left"); + }); + + + it("change alt bind to not already existing button, than another one alt bind with another not already existing button", () => { + inGame.whenWePressOnKeyboard("A").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right"); + inGame.whenWePressOnKeyboard("B").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("U").nothingShouldHappen(); + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("KEY_A").weWantThisBindInstead("B").confirm(); + inGame.whenWePressOnKeyboard("A").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right"); + inGame.whenWePressOnKeyboard("B").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("U").nothingShouldHappen(); + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Right").iconDisplayedIs("KEY_D").weWantThisBindInstead("U").confirm(); + inGame.whenWePressOnKeyboard("A").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("D").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("B").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("U").weShouldTriggerTheButton("Alt_Button_Right"); + }); + + it("Swap multiple touch alt and main", () => { + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right"); + inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Alt_Button_Up"); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right"); + inTheSettingMenu.whenCursorIsOnSetting("Button_Up").iconDisplayedIs("KEY_ARROW_UP").weWantThisBindInstead("RIGHT").weCantOverrideThisBind().weCantAssignThisKey().butLetsForceIt(); + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right"); + inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Alt_Button_Up"); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right"); + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Up").iconDisplayedIs("KEY_W").weWantThisBindInstead("D").confirm(); + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right"); + inGame.whenWePressOnKeyboard("W").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Up"); + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Up").iconDisplayedIs("KEY_D").weWantThisBindInstead("W").confirm(); + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right"); + inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Alt_Button_Up"); + inGame.whenWePressOnKeyboard("D").nothingShouldHappen(); + }); + + + it("Delete 2 bind then reassign one of them", () => { + + inGame.whenWePressOnKeyboard("A").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right"); + + inTheSettingMenu.whenWeDelete("Alt_Button_Left").thereShouldBeNoIconAnymore(); + inGame.whenWePressOnKeyboard("A").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right"); + + inTheSettingMenu.whenWeDelete("Alt_Button_Right").thereShouldBeNoIconAnymore(); + inGame.whenWePressOnKeyboard("A").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("D").nothingShouldHappen(); + + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").thereShouldBeNoIcon().weWantThisBindInstead("A").confirm(); + inGame.whenWePressOnKeyboard("A").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("D").nothingShouldHappen(); + }); + + it("test keyboard listener", () => { + const keyDown = Phaser.Input.Keyboard.KeyCodes.S; + const key = getKeyWithKeycode(config, keyDown); + const settingName = config.custom[key]; + const buttonDown = config.settings[settingName]; + expect(buttonDown).toEqual(Button.DOWN); + }); + + it("retrieve the correct icon for a given source", () => { + inTheSettingMenu.whenCursorIsOnSetting("Cycle_Shiny").iconDisplayedIs("KEY_R"); + inTheSettingMenu.whenCursorIsOnSetting("Cycle_Form").iconDisplayedIs("KEY_F"); + inGame.forTheSource("keyboard").forTheWantedBind("Cycle_Shiny").weShouldSeeTheIcon("R"); + inGame.forTheSource("keyboard").forTheWantedBind("Cycle_Form").weShouldSeeTheIcon("F"); + }); + + it("check the key displayed on confirm", () => { + inGame.whenWePressOnKeyboard("ENTER").weShouldTriggerTheButton("Button_Submit"); + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + inGame.whenWePressOnKeyboard("DOWN").weShouldTriggerTheButton("Button_Down"); + inGame.whenWePressOnKeyboard("LEFT").weShouldTriggerTheButton("Button_Left"); + inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right"); + inGame.whenWePressOnKeyboard("ESC").weShouldTriggerTheButton("Button_Menu"); + inGame.whenWePressOnKeyboard("HOME").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("DELETE").nothingShouldHappen(); + inTheSettingMenu.whenCursorIsOnSetting("Button_Submit").iconDisplayedIs("KEY_ENTER").whenWeDelete().iconDisplayedIs("KEY_ENTER"); + inTheSettingMenu.whenCursorIsOnSetting("Button_Up").iconDisplayedIs("KEY_ARROW_UP").whenWeDelete().iconDisplayedIs("KEY_ARROW_UP"); + inTheSettingMenu.whenCursorIsOnSetting("Button_Down").iconDisplayedIs("KEY_ARROW_DOWN").whenWeDelete().iconDisplayedIs("KEY_ARROW_DOWN"); + inTheSettingMenu.whenCursorIsOnSetting("Button_Left").iconDisplayedIs("KEY_ARROW_LEFT").whenWeDelete().iconDisplayedIs("KEY_ARROW_LEFT"); + inTheSettingMenu.whenCursorIsOnSetting("Button_Right").iconDisplayedIs("KEY_ARROW_RIGHT").whenWeDelete().iconDisplayedIs("KEY_ARROW_RIGHT"); + inTheSettingMenu.whenCursorIsOnSetting("Button_Menu").iconDisplayedIs("KEY_ESC").whenWeDelete().iconDisplayedIs("KEY_ESC"); + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Up").iconDisplayedIs("KEY_W").whenWeDelete().thereShouldBeNoIconAnymore(); + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Up").thereShouldBeNoIcon().weWantThisBindInstead("DELETE").weCantAssignThisKey().butLetsForceIt(); + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Up").thereShouldBeNoIcon().weWantThisBindInstead("HOME").weCantAssignThisKey().butLetsForceIt(); + inGame.whenWePressOnKeyboard("DELETE").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("HOME").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("W").nothingShouldHappen(); + }); + + it("check to delete all the binds of an action", () => { + inGame.whenWePressOnKeyboard("V").weShouldTriggerTheButton("Button_Cycle_Variant"); + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Cycle_Variant").thereShouldBeNoIcon().weWantThisBindInstead("K").confirm(); + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Cycle_Variant").iconDisplayedIs("KEY_K").whenWeDelete().thereShouldBeNoIconAnymore(); + inTheSettingMenu.whenCursorIsOnSetting("Button_Cycle_Variant").iconDisplayedIs("KEY_V").whenWeDelete().thereShouldBeNoIconAnymore(); + }); +}); diff --git a/src/touch-controls.ts b/src/touch-controls.ts index d828e709f31..3b734e01467 100644 --- a/src/touch-controls.ts +++ b/src/touch-controls.ts @@ -1,153 +1,137 @@ -export interface ButtonKey { - onDown: (opt: object) => void; - onUp: (opt: object) => void; -} +import {Button} from "./enums/buttons"; +import EventEmitter = Phaser.Events.EventEmitter; -export type ButtonMap = Map; - -export const keys = new Map(); -export const keysDown = new Map(); +// Create a map to store key bindings +export const keys = new Map(); +// Create a map to store keys that are currently pressed +export const keysDown = new Map(); +// Variable to store the ID of the last touched element let lastTouchedId: string; /** - * Initializes all touch controls + * Initialize touch controls by binding keys to buttons. * - * @param buttonMap Map of buttons to key objects + * @param events - The event emitter for handling input events. */ -export function initTouchControls(buttonMap: ButtonMap) { +export function initTouchControls(events: EventEmitter): void { preventElementZoom(document.querySelector("#dpad")); preventElementZoom(document.querySelector("#apad")); - - for (const button of document.querySelectorAll("[data-key]")) { - bindKey(button, button.dataset.key, buttonMap); + // Select all elements with the 'data-key' attribute and bind keys to them + for (const button of document.querySelectorAll("[data-key]")) { + // @ts-ignore - Bind the key to the button using the dataset key + bindKey(button, button.dataset.key, events); } } /** - * Check if the device has a touchscreen + * Check if the device has a touchscreen. + * + * @returns `true` if the device has a touchscreen, otherwise `false`. */ -export function hasTouchscreen() { +export function hasTouchscreen(): boolean { return window.matchMedia("(hover: none), (pointer: coarse)").matches; } /** - * Check if it's a mobile device through the user-agent + * Check if the device is a mobile device. + * + * @returns `true` if the device is a mobile device, otherwise `false`. */ -export function isMobile() { - const userAgent = navigator.userAgent || navigator.vendor || window["opera"]; - return (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(userAgent)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(userAgent.substr(0, 4))); +export function isMobile(): boolean { + let ret = false; + (function (a) { + // Check the user agent string against a regex for mobile devices + if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) { + ret = true; + } + })(navigator.userAgent || navigator.vendor || window["opera"]); + return ret; } /** - * Simulate a keyboard event on the canvas + * Simulates a keyboard event on the canvas. * - * @param eventType Type of the keyboard event - * @param button Button to simulate - * @param buttonMap Map of buttons to key objects + * @param eventType - The type of the keyboard event ('keydown' or 'keyup'). + * @param key - The key to simulate. + * @param events - The event emitter for handling input events. + * + * @remarks + * This function checks if the key exists in the Button enum. If it does, it retrieves the corresponding button + * and emits the appropriate event ('input_down' or 'input_up') based on the event type. */ -function simulateKeyboardEvent(eventType: string, button: string, buttonMap: ButtonMap) { - const key = buttonMap[button]; +function simulateKeyboardEvent(eventType: string, key: string, events: EventEmitter) { + if (!Button.hasOwnProperty(key)) { + return; + } + const button = Button[key]; switch (eventType) { case "keydown": - key.onDown({}); + events.emit("input_down", { + controller_type: "keyboard", + button: button, + }); break; case "keyup": - key.onUp({}); + events.emit("input_up", { + controller_type: "keyboard", + button: button, + }); break; } } /** - * Simulate a keyboard input from 'keydown' to 'keyup' + * Binds a node to a specific key to simulate keyboard events on touch. * - * @param {string} key Key to simulate - * @param {object} buttonMap Map of buttons to key objects - */ -// function simulateKeyboardInput(key, buttonMap) { -// simulateKeyboardEvent('keydown', key, buttonMap); -// window.setTimeout(() => { -// simulateKeyboardEvent('keyup', key, buttonMap); -// }, 100); -// } - -/** - * Bind a node by a specific key to simulate on touch + * @param node - The DOM element to bind the key to. + * @param key - The key to simulate. + * @param events - The event emitter for handling input events. * - * @param node The node to bind a key to - * @param key Key to simulate - * @param buttonMap Map of buttons to key objects + * @remarks + * This function binds touch events to a node to simulate 'keydown' and 'keyup' keyboard events. + * It adds the key to the keys map and tracks the keydown state. When a touch starts, it simulates + * a 'keydown' event and adds an 'active' class to the node. When the touch ends, it simulates a 'keyup' + * event, removes the keydown state, and removes the 'active' class from the node and the last touched element. */ -function bindKey(node: Element, key: string, buttonMap: ButtonMap) { +function bindKey(node: HTMLElement, key: string, events) { keys.set(node.id, key); - node.addEventListener("touchstart", (event: TouchEvent) => { + node.addEventListener("touchstart", event => { event.preventDefault(); - simulateKeyboardEvent("keydown", key, buttonMap); - if (!(event.target instanceof Element)) { - return; - } - keysDown.set(event.target.id, node.id); + simulateKeyboardEvent("keydown", key, events); + keysDown.set(event.target["id"], node.id); node.classList.add("active"); }); - node.addEventListener("touchend", (event: TouchEvent) => { + node.addEventListener("touchend", event => { event.preventDefault(); - if (!(event.target instanceof Element)) { - return; - } - const pressedKey = keysDown.get(event.target.id); + const pressedKey = keysDown.get(event.target["id"]); if (pressedKey && keys.has(pressedKey)) { const key = keys.get(pressedKey); - simulateKeyboardEvent("keyup", key, buttonMap); + simulateKeyboardEvent("keyup", key, events); } - keysDown.delete(event.target.id); + keysDown.delete(event.target["id"]); node.classList.remove("active"); if (lastTouchedId) { document.getElementById(lastTouchedId).classList.remove("active"); } }); - - // Inspired by https://github.com/pulsejet/mkxp-web/blob/262a2254b684567311c9f0e135ee29f6e8c3613e/extra/js/dpad.js - node.addEventListener("touchmove", (event : TouchEvent) => { - if (!(event.changedTouches[0].target instanceof Element)) { - return; - } - const { target, clientX, clientY } = event.changedTouches[0]; - const origTargetId = keysDown.get(target.id); - const nextTargetId = document.elementFromPoint(clientX, clientY).id; - if (origTargetId === nextTargetId) { - return; - } - - if (origTargetId) { - const key = keys.get(origTargetId); - simulateKeyboardEvent("keyup", key, buttonMap); - keysDown.delete(target.id); - document.getElementById(origTargetId).classList.remove("active"); - } - - if (keys.has(nextTargetId)) { - const key = keys.get(nextTargetId); - simulateKeyboardEvent("keydown", key, buttonMap); - keysDown.set(target.id, nextTargetId); - lastTouchedId = nextTargetId; - document.getElementById(nextTargetId).classList.add("active"); - } - }); } /** - * Prevent zoom on specified element - * * {@link https://stackoverflow.com/a/39778831/4622620|Source} * - * @param element The element to prevent zoom on + * Prevent zoom on specified element + * @param {HTMLElement} element */ -function preventElementZoom(element: HTMLElement) { +function preventElementZoom(element: HTMLElement): void { + if (!element) { + return; + } element.addEventListener("touchstart", (event: TouchEvent) => { if (!(event.currentTarget instanceof HTMLElement)) { diff --git a/src/ui-inputs.ts b/src/ui-inputs.ts index d443e4d85b7..a5fb631644a 100644 --- a/src/ui-inputs.ts +++ b/src/ui-inputs.ts @@ -4,8 +4,10 @@ import {InputsController} from "./inputs-controller"; import MessageUiHandler from "./ui/message-ui-handler"; import StarterSelectUiHandler from "./ui/starter-select-ui-handler"; import {Setting, settingOptions} from "./system/settings"; -import SettingsUiHandler from "./ui/settings-ui-handler"; +import SettingsUiHandler from "./ui/settings/settings-ui-handler"; import {Button} from "./enums/buttons"; +import SettingsGamepadUiHandler from "./ui/settings/settings-gamepad-ui-handler"; +import SettingsKeyboardUiHandler from "#app/ui/settings/settings-keyboard-ui-handler"; import BattleScene from "./battle-scene"; type ActionKeys = Record void>; @@ -159,7 +161,9 @@ export class UiInputs { } buttonCycleOption(button: Button): void { - if (this.scene.ui?.getHandler() instanceof StarterSelectUiHandler) { + const whitelist = [StarterSelectUiHandler, SettingsUiHandler, SettingsGamepadUiHandler, SettingsKeyboardUiHandler]; + const uiHandler = this.scene.ui?.getHandler(); + if (whitelist.some(handler => uiHandler instanceof handler)) { this.scene.ui.processInput(button); } else if (button === Button.V) { this.buttonInfo(true); diff --git a/src/ui/settings/abstract-binding-ui-handler.ts b/src/ui/settings/abstract-binding-ui-handler.ts new file mode 100644 index 00000000000..fe0736ce101 --- /dev/null +++ b/src/ui/settings/abstract-binding-ui-handler.ts @@ -0,0 +1,259 @@ +import UiHandler from "../ui-handler"; +import BattleScene from "../../battle-scene"; +import {Mode} from "../ui"; +import {addWindow} from "../ui-theme"; +import {addTextObject, TextStyle} from "../text"; +import {Button} from "../../enums/buttons"; +import {NavigationManager} from "#app/ui/settings/navigationMenu"; + +/** + * Abstract class for handling UI elements related to button bindings. + */ +export default abstract class AbstractBindingUiHandler extends UiHandler { + // Containers for different segments of the UI. + protected optionSelectContainer: Phaser.GameObjects.Container; + protected actionsContainer: Phaser.GameObjects.Container; + + // Background elements for titles and action areas. + protected titleBg: Phaser.GameObjects.NineSlice; + protected actionBg: Phaser.GameObjects.NineSlice; + protected optionSelectBg: Phaser.GameObjects.NineSlice; + + // Text elements for displaying instructions and actions. + protected unlockText: Phaser.GameObjects.Text; + protected timerText: Phaser.GameObjects.Text; + protected swapText: Phaser.GameObjects.Text; + protected actionLabel: Phaser.GameObjects.Text; + protected cancelLabel: Phaser.GameObjects.Text; + + protected listening: boolean = false; + protected buttonPressed: number | null = null; + + // Icons for displaying current and new button assignments. + protected newButtonIcon: Phaser.GameObjects.Sprite; + protected targetButtonIcon: Phaser.GameObjects.Sprite; + + // Function to call on cancel or completion of binding. + protected cancelFn: (boolean?) => boolean; + abstract swapAction(): boolean; + + protected timeLeftAutoClose: number = 5; + protected countdownTimer; + + // The specific setting being modified. + protected target; + + /** + * Constructor for the AbstractBindingUiHandler. + * + * @param scene - The BattleScene instance. + * @param mode - The UI mode. + */ + constructor(scene: BattleScene, mode?: Mode) { + super(scene, mode); + } + + /** + * Setup UI elements. + */ + setup() { + const ui = this.getUi(); + this.optionSelectContainer = this.scene.add.container(0, 0); + this.actionsContainer = this.scene.add.container(0, 0); + // Initially, containers are not visible. + this.optionSelectContainer.setVisible(false); + this.actionsContainer.setVisible(false); + + // Add containers to the UI. + ui.add(this.optionSelectContainer); + ui.add(this.actionsContainer); + + // Setup backgrounds and text objects for UI. + this.titleBg = addWindow(this.scene, (this.scene.game.canvas.width / 6) - this.getWindowWidth(), -(this.scene.game.canvas.height / 6) + 28 + 21, this.getWindowWidth(), 24); + this.titleBg.setOrigin(0.5); + this.optionSelectContainer.add(this.titleBg); + + this.actionBg = addWindow(this.scene, (this.scene.game.canvas.width / 6) - this.getWindowWidth(), -(this.scene.game.canvas.height / 6) + this.getWindowHeight() + 28 + 21 + 21, this.getWindowWidth(), 24); + this.actionBg.setOrigin(0.5); + this.actionsContainer.add(this.actionBg); + + // Text prompts and instructions for the user. + this.unlockText = addTextObject(this.scene, 0, 0, "Press a button...", TextStyle.WINDOW); + this.unlockText.setOrigin(0, 0); + this.unlockText.setPositionRelative(this.titleBg, 36, 4); + this.optionSelectContainer.add(this.unlockText); + + this.timerText = addTextObject(this.scene, 0, 0, "(5)", TextStyle.WINDOW); + this.timerText.setOrigin(0, 0); + this.timerText.setPositionRelative(this.unlockText, (this.unlockText.width/6) + 5, 0); + this.optionSelectContainer.add(this.timerText); + + this.optionSelectBg = addWindow(this.scene, (this.scene.game.canvas.width / 6) - this.getWindowWidth(), -(this.scene.game.canvas.height / 6) + this.getWindowHeight() + 28, this.getWindowWidth(), this.getWindowHeight()); + this.optionSelectBg.setOrigin(0.5); + this.optionSelectContainer.add(this.optionSelectBg); + + this.cancelLabel = addTextObject(this.scene, 0, 0, "Cancel", TextStyle.SETTINGS_LABEL); + this.cancelLabel.setOrigin(0, 0.5); + this.cancelLabel.setPositionRelative(this.actionBg, 10, this.actionBg.height / 2); + this.actionsContainer.add(this.cancelLabel); + } + + manageAutoCloseTimer() { + clearTimeout(this.countdownTimer); + this.countdownTimer = setTimeout(() => { + this.timeLeftAutoClose -= 1; + this.timerText.setText(`(${this.timeLeftAutoClose})`); + if (this.timeLeftAutoClose >= 0) { + this.manageAutoCloseTimer(); + } else { + this.cancelFn(); + } + }, 1000); + } + + /** + * Show the UI with the provided arguments. + * + * @param args - Arguments to be passed to the show method. + * @returns `true` if successful. + */ + show(args: any[]): boolean { + super.show(args); + this.buttonPressed = null; + this.timeLeftAutoClose = 5; + this.cancelFn = args[0].cancelHandler; + this.target = args[0].target; + + // Bring the option and action containers to the front of the UI. + this.getUi().bringToTop(this.optionSelectContainer); + this.getUi().bringToTop(this.actionsContainer); + + this.optionSelectContainer.setVisible(true); + setTimeout(() => { + this.listening = true; + this.manageAutoCloseTimer(); + }, 100); + return true; + } + + /** + * Get the width of the window. + * + * @returns The window width. + */ + getWindowWidth(): number { + return 160; + } + + /** + * Get the height of the window. + * + * @returns The window height. + */ + getWindowHeight(): number { + return 64; + } + + /** + * Process the input for the given button. + * + * @param button - The button to process. + * @returns `true` if the input was processed successfully. + */ + processInput(button: Button): boolean { + if (this.buttonPressed === null) { + return; + } + const ui = this.getUi(); + let success = false; + switch (button) { + case Button.LEFT: + case Button.RIGHT: + // Toggle between action and cancel options. + const cursor = this.cursor ? 0 : 1; + success = this.setCursor(cursor); + break; + case Button.ACTION: + // Process actions based on current cursor position. + if (this.cursor === 0) { + this.cancelFn(); + } else { + success = this.swapAction(); + NavigationManager.getInstance().updateIcons(); + this.cancelFn(success); + } + break; + } + + // Plays a select sound effect if an action was successfully processed. + if (success) { + ui.playSelect(); + } else { + ui.playError(); + } + + return success; + } + + /** + * Set the cursor to the specified position. + * + * @param cursor - The cursor position to set. + * @returns `true` if the cursor was set successfully. + */ + setCursor(cursor: integer): boolean { + this.cursor = cursor; + if (cursor === 1) { + this.actionLabel.setColor(this.getTextColor(TextStyle.SETTINGS_SELECTED)); + this.actionLabel.setShadowColor(this.getTextColor(TextStyle.SETTINGS_SELECTED, true)); + this.cancelLabel.setColor(this.getTextColor(TextStyle.WINDOW)); + this.cancelLabel.setShadowColor(this.getTextColor(TextStyle.WINDOW, true)); + return true; + } + this.actionLabel.setColor(this.getTextColor(TextStyle.WINDOW)); + this.actionLabel.setShadowColor(this.getTextColor(TextStyle.WINDOW, true)); + this.cancelLabel.setColor(this.getTextColor(TextStyle.SETTINGS_SELECTED)); + this.cancelLabel.setShadowColor(this.getTextColor(TextStyle.SETTINGS_SELECTED, true)); + return true; + } + + /** + * Clear the UI elements and state. + */ + clear() { + super.clear(); + clearTimeout(this.countdownTimer); + this.timerText.setText("(5)"); + this.timeLeftAutoClose = 5; + this.listening = false; + this.target = null; + this.cancelFn = null; + this.optionSelectContainer.setVisible(false); + this.actionsContainer.setVisible(false); + this.newButtonIcon.setVisible(false); + this.buttonPressed = null; + } + + /** + * Handle input down events. + * + * @param buttonIcon - The icon of the button that was pressed. + * @param assignedButtonIcon - The icon of the button that is assigned. + * @param type - The type of button press. + */ + onInputDown(buttonIcon: string, assignedButtonIcon: string, type: string): void { + clearTimeout(this.countdownTimer); + this.timerText.setText(""); + this.newButtonIcon.setTexture(type); + this.newButtonIcon.setFrame(buttonIcon); + if (assignedButtonIcon) { + this.targetButtonIcon.setTexture(type); + this.targetButtonIcon.setFrame(assignedButtonIcon); + this.targetButtonIcon.setVisible(true); + this.swapText.setVisible(true); + } + this.newButtonIcon.setVisible(true); + this.setCursor(0); + this.actionsContainer.setVisible(true); + } +} diff --git a/src/ui/settings/abstract-settings-ui-handler.ts b/src/ui/settings/abstract-settings-ui-handler.ts new file mode 100644 index 00000000000..f37ce05b69f --- /dev/null +++ b/src/ui/settings/abstract-settings-ui-handler.ts @@ -0,0 +1,647 @@ +import UiHandler from "../ui-handler"; +import BattleScene from "../../battle-scene"; +import {Mode} from "../ui"; +import {InterfaceConfig} from "../../inputs-controller"; +import {addWindow} from "../ui-theme"; +import {addTextObject, TextStyle} from "../text"; +import {Button} from "../../enums/buttons"; +import {getIconWithSettingName} from "#app/configs/inputs/configHandler"; +import NavigationMenu, {NavigationManager} from "#app/ui/settings/navigationMenu"; + +export interface InputsIcons { + [key: string]: Phaser.GameObjects.Sprite; +} + +export interface LayoutConfig { + optionsContainer: Phaser.GameObjects.Container; + inputsIcons: InputsIcons; + settingLabels: Phaser.GameObjects.Text[]; + optionValueLabels: Phaser.GameObjects.Text[][]; + optionCursors: integer[]; + keys: string[]; + bindingSettings: Array; +} +/** + * Abstract class for handling UI elements related to settings. + */ +export default abstract class AbstractSettingsUiUiHandler extends UiHandler { + protected settingsContainer: Phaser.GameObjects.Container; + protected optionsContainer: Phaser.GameObjects.Container; + protected navigationContainer: NavigationMenu; + + protected scrollCursor: integer; + protected optionCursors: integer[]; + protected cursorObj: Phaser.GameObjects.NineSlice; + + protected optionsBg: Phaser.GameObjects.NineSlice; + protected actionsBg: Phaser.GameObjects.NineSlice; + + protected settingLabels: Phaser.GameObjects.Text[]; + protected optionValueLabels: Phaser.GameObjects.Text[][]; + + // layout will contain the 3 Gamepad tab for each config - dualshock, xbox, snes + protected layout: Map = new Map(); + // Will contain the input icons from the selected layout + protected inputsIcons: InputsIcons; + protected navigationIcons: InputsIcons; + // list all the setting keys used in the selected layout (because dualshock has more buttons than xbox) + protected keys: Array; + + // Store the specific settings related to key bindings for the current gamepad configuration. + protected bindingSettings: Array; + + protected settingDevice; + protected settingBlacklisted; + protected settingDeviceDefaults; + protected settingDeviceOptions; + protected configs; + protected commonSettingsCount; + protected textureOverride; + protected titleSelected; + protected localStoragePropertyName; + protected rowsToDisplay: number; + + abstract getLocalStorageSetting(): object; + abstract navigateMenuLeft(): boolean; + abstract navigateMenuRight(): boolean; + abstract saveSettingToLocalStorage(setting, cursor): void; + abstract getActiveConfig(): InterfaceConfig; + abstract setSetting(scene: BattleScene, setting, value: integer): boolean; + + /** + * Constructor for the AbstractSettingsUiUiHandler. + * + * @param scene - The BattleScene instance. + * @param mode - The UI mode. + */ + constructor(scene: BattleScene, mode?: Mode) { + super(scene, mode); + this.rowsToDisplay = 8; + } + + /** + * Setup UI elements. + */ + setup() { + const ui = this.getUi(); + this.navigationIcons = {}; + + this.settingsContainer = this.scene.add.container(1, -(this.scene.game.canvas.height / 6) + 1); + + this.settingsContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains); + + this.navigationContainer = new NavigationMenu(this.scene, 0, 0); + + this.optionsBg = addWindow(this.scene, 0, this.navigationContainer.height, (this.scene.game.canvas.width / 6) - 2, (this.scene.game.canvas.height / 6) - 16 - this.navigationContainer.height - 2); + this.optionsBg.setOrigin(0, 0); + + + this.actionsBg = addWindow(this.scene, 0, (this.scene.game.canvas.height / 6) - this.navigationContainer.height, (this.scene.game.canvas.width / 6) - 2, 22); + this.actionsBg.setOrigin(0, 0); + + const iconAction = this.scene.add.sprite(0, 0, "keyboard"); + iconAction.setOrigin(0, -0.1); + iconAction.setPositionRelative(this.actionsBg, this.navigationContainer.width - 32, 4); + this.navigationIcons["BUTTON_ACTION"] = iconAction; + + const actionText = addTextObject(this.scene, 0, 0, "Action", TextStyle.SETTINGS_LABEL); + actionText.setOrigin(0, 0.15); + actionText.setPositionRelative(iconAction, -actionText.width/6-2, 0); + + const iconCancel = this.scene.add.sprite(0, 0, "keyboard"); + iconCancel.setOrigin(0, -0.1); + iconCancel.setPositionRelative(this.actionsBg, this.navigationContainer.width - 100, 4); + this.navigationIcons["BUTTON_CANCEL"] = iconCancel; + + const cancelText = addTextObject(this.scene, 0, 0, "Cancel", TextStyle.SETTINGS_LABEL); + cancelText.setOrigin(0, 0.15); + cancelText.setPositionRelative(iconCancel, -cancelText.width/6-2, 0); + + const iconReset = this.scene.add.sprite(0, 0, "keyboard"); + iconReset.setOrigin(0, -0.1); + iconReset.setPositionRelative(this.actionsBg, this.navigationContainer.width - 180, 4); + this.navigationIcons["BUTTON_HOME"] = iconReset; + + const resetText = addTextObject(this.scene, 0, 0, "Reset all", TextStyle.SETTINGS_LABEL); + resetText.setOrigin(0, 0.15); + resetText.setPositionRelative(iconReset, -resetText.width/6-2, 0); + + this.settingsContainer.add(this.optionsBg); + this.settingsContainer.add(this.actionsBg); + this.settingsContainer.add(this.navigationContainer); + this.settingsContainer.add(iconAction); + this.settingsContainer.add(iconCancel); + this.settingsContainer.add(iconReset); + this.settingsContainer.add(actionText); + this.settingsContainer.add(cancelText); + this.settingsContainer.add(resetText); + + /// Initialize a new configuration "screen" for each type of gamepad. + for (const config of this.configs) { + // Create a map to store layout settings based on the pad type. + this.layout[config.padType] = new Map(); + // Create a container for gamepad options in the scene, initially hidden. + + const optionsContainer = this.scene.add.container(0, 0); + optionsContainer.setVisible(false); + + // Gather all binding settings from the configuration. + const bindingSettings = Object.keys(config.settings); + + // Array to hold labels for different settings such as 'Controller', 'Gamepad Support', etc. + const settingLabels: Phaser.GameObjects.Text[] = []; + + // Array to hold options for each setting, e.g., 'Auto', 'Disabled'. + const optionValueLabels: Phaser.GameObjects.GameObject[][] = []; + + // Object to store sprites for each button configuration. + const inputsIcons: InputsIcons = {}; + + // Fetch common setting keys such as 'Controller' and 'Gamepad Support' from gamepad settings. + const commonSettingKeys = Object.keys(this.settingDevice).slice(0, this.commonSettingsCount).map(key => this.settingDevice[key]); + // Combine common and specific bindings into a single array. + const specificBindingKeys = [...commonSettingKeys, ...Object.keys(config.settings)]; + // Fetch default values for these settings and prepare to highlight selected options. + const optionCursors = Object.values(Object.keys(this.settingDeviceDefaults).filter(s => specificBindingKeys.includes(s)).map(k => this.settingDeviceDefaults[k])); + // Filter out settings that are not relevant to the current gamepad configuration. + const settingFiltered = Object.keys(this.settingDevice).filter(_key => specificBindingKeys.includes(this.settingDevice[_key])); + // Loop through the filtered settings to manage display and options. + + settingFiltered.forEach((setting, s) => { + // Convert the setting key from format 'Key_Name' to 'Key name' for display. + const settingName = setting.replace(/\_/g, " "); + + // Create and add a text object for the setting name to the scene. + const isLock = this.settingBlacklisted.includes(this.settingDevice[setting]); + const labelStyle = isLock ? TextStyle.SETTINGS_LOCKED : TextStyle.SETTINGS_LABEL; + settingLabels[s] = addTextObject(this.scene, 8, 28 + s * 16, settingName, labelStyle); + settingLabels[s].setOrigin(0, 0); + optionsContainer.add(settingLabels[s]); + + // Initialize an array to store the option labels for this setting. + const valueLabels: Phaser.GameObjects.GameObject[] = []; + + // Process each option for the current setting. + for (const [o, option] of this.settingDeviceOptions[this.settingDevice[setting]].entries()) { + // Check if the current setting is for binding keys. + if (bindingSettings.includes(this.settingDevice[setting])) { + // Create a label for non-null options, typically indicating actionable options like 'change'. + if (o) { + const valueLabel = addTextObject(this.scene, 0, 0, isLock ? "" : option, TextStyle.WINDOW); + valueLabel.setOrigin(0, 0); + optionsContainer.add(valueLabel); + valueLabels.push(valueLabel); + continue; + } + // For null options, add an icon for the key. + const icon = this.scene.add.sprite(0, 0, this.textureOverride ? this.textureOverride : config.padType); + icon.setOrigin(0, -0.15); + inputsIcons[this.settingDevice[setting]] = icon; + optionsContainer.add(icon); + valueLabels.push(icon); + continue; + } + // For regular settings like 'Gamepad support', create a label and determine if it is selected. + const valueLabel = addTextObject(this.scene, 0, 0, option, this.settingDeviceDefaults[this.settingDevice[setting]] === o ? TextStyle.SETTINGS_SELECTED : TextStyle.WINDOW); + valueLabel.setOrigin(0, 0); + + optionsContainer.add(valueLabel); + + //if a setting has 2 options, valueLabels will be an array of 2 elements + valueLabels.push(valueLabel); + } + // Collect all option labels for this setting into the main array. + optionValueLabels.push(valueLabels); + + // Calculate the total width of all option labels within a specific setting + // This is achieved by summing the width of each option label + const totalWidth = optionValueLabels[s].map((o) => (o as Phaser.GameObjects.Text).width).reduce((total, width) => total += width, 0); + + // Define the minimum width for a label, ensuring it's at least 78 pixels wide or the width of the setting label plus some padding + const labelWidth = Math.max(130, settingLabels[s].displayWidth + 8); + + // Calculate the total available space for placing option labels next to their setting label + // We reserve space for the setting label and then distribute the remaining space evenly + const totalSpace = (300 - labelWidth) - totalWidth / 6; + // Calculate the spacing between options based on the available space divided by the number of gaps between labels + const optionSpacing = Math.floor(totalSpace / (optionValueLabels[s].length - 1)); + + // Initialize xOffset to zero, which will be used to position each option label horizontally + let xOffset = 0; + + // Start positioning each option label one by one + for (const value of optionValueLabels[s]) { + // Set the option label's position right next to the setting label, adjusted by xOffset + (value as Phaser.GameObjects.Text).setPositionRelative(settingLabels[s], labelWidth + xOffset, 0); + // Move the xOffset to the right for the next label, ensuring each label is spaced evenly + xOffset += (value as Phaser.GameObjects.Text).width / 6 + optionSpacing; + } + }); + + // Assigning the newly created components to the layout map under the specific gamepad type. + this.layout[config.padType].optionsContainer = optionsContainer; // Container for this pad's options. + this.layout[config.padType].inputsIcons = inputsIcons; // Icons for each input specific to this pad. + this.layout[config.padType].settingLabels = settingLabels; // Text labels for each setting available on this pad. + this.layout[config.padType].optionValueLabels = optionValueLabels; // Labels for values corresponding to each setting. + this.layout[config.padType].optionCursors = optionCursors; // Cursors to navigate through the options. + this.layout[config.padType].keys = specificBindingKeys; // Keys that identify each setting specifically bound to this pad. + this.layout[config.padType].bindingSettings = bindingSettings; // Settings that define how the keys are bound. + + // Add the options container to the overall settings container to be displayed in the UI. + this.settingsContainer.add(optionsContainer); + } + // Add the settings container to the UI. + ui.add(this.settingsContainer); + + // Initially hide the settings container until needed (e.g., when a gamepad is connected or a button is pressed). + this.settingsContainer.setVisible(false); + } + + /** + * Update the bindings for the current active device configuration. + */ + updateBindings(): void { + // Hide the options container for all layouts to reset the UI visibility. + Object.keys(this.layout).forEach((key) => this.layout[key].optionsContainer.setVisible(false)); + // Fetch the active gamepad configuration from the input controller. + const activeConfig = this.getActiveConfig(); + + // Set the UI layout for the active configuration. If unsuccessful, exit the function early. + if (!this.setLayout(activeConfig)) { + return; + } + + // Retrieve the gamepad settings from local storage or use an empty object if none exist. + const settings: object = this.getLocalStorageSetting(); + + // Update the cursor for each key based on the stored settings or default cursors. + this.keys.forEach((key, index) => { + this.setOptionCursor(index, settings.hasOwnProperty(key as string) ? settings[key as string] : this.optionCursors[index]); + }); + + // If the active configuration has no custom bindings set, exit the function early. + // by default, if custom does not exists, a default is assigned to it + // it only means the gamepad is not yet initalized + if (!activeConfig.custom) { + return; + } + + // For each element in the binding settings, update the icon according to the current assignment. + for (const elm of this.bindingSettings) { + const icon = getIconWithSettingName(activeConfig, elm); + if (icon) { + this.inputsIcons[elm as string].setFrame(icon); + this.inputsIcons[elm as string].alpha = 1; + } else { + this.inputsIcons[elm as string].alpha = 0; + } + } + + // Set the cursor and scroll cursor to their initial positions. + this.setCursor(this.cursor); + this.setScrollCursor(this.scrollCursor); + } + + updateNavigationDisplay() { + const specialIcons = { + "BUTTON_HOME": "HOME.png", + "BUTTON_DELETE": "DEL.png", + }; + for (const settingName of Object.keys(this.navigationIcons)) { + if (Object.keys(specialIcons).includes(settingName)) { + this.navigationIcons[settingName].setTexture("keyboard"); + this.navigationIcons[settingName].setFrame(specialIcons[settingName]); + this.navigationIcons[settingName].alpha = 1; + continue; + } + const icon = this.scene.inputController?.getIconForLatestInputRecorded(settingName); + if (icon) { + const type = this.scene.inputController?.getLastSourceType(); + this.navigationIcons[settingName].setTexture(type); + this.navigationIcons[settingName].setFrame(icon); + this.navigationIcons[settingName].alpha = 1; + } else { + this.navigationIcons[settingName].alpha = 0; + } + } + } + + /** + * Show the UI with the provided arguments. + * + * @param args - Arguments to be passed to the show method. + * @returns `true` if successful. + */ + show(args: any[]): boolean { + super.show(args); + + this.updateNavigationDisplay(); + NavigationManager.getInstance().updateIcons(); + // Update the bindings for the current active gamepad configuration. + this.updateBindings(); + + // Make the settings container visible to the user. + this.settingsContainer.setVisible(true); + // Reset the scroll cursor to the top of the settings container. + this.resetScroll(); + + // Move the settings container to the end of the UI stack to ensure it is displayed on top. + this.getUi().moveTo(this.settingsContainer, this.getUi().length - 1); + + // Hide any tooltips that might be visible before showing the settings container. + this.getUi().hideTooltip(); + + // Return true to indicate the UI was successfully shown. + return true; + } + + /** + * Set the UI layout for the active device configuration. + * + * @param activeConfig - The active device configuration. + * @returns `true` if the layout was successfully applied, otherwise `false`. + */ + setLayout(activeConfig: InterfaceConfig): boolean { + // Check if there is no active configuration (e.g., no gamepad connected). + if (!activeConfig) { + // Retrieve the layout for when no gamepads are connected. + const layout = this.layout["noGamepads"]; + // Make the options container visible to show message. + layout.optionsContainer.setVisible(true); + // Return false indicating the layout application was not successful due to lack of gamepad. + return false; + } + // Extract the type of the gamepad from the active configuration. + const configType = activeConfig.padType; + + // Retrieve the layout settings based on the type of the gamepad. + const layout = this.layout[configType]; + // Update the main controller with configuration details from the selected layout. + this.keys = layout.keys; + this.optionsContainer = layout.optionsContainer; + this.optionsContainer.setVisible(true); + this.settingLabels = layout.settingLabels; + this.optionValueLabels = layout.optionValueLabels; + this.optionCursors = layout.optionCursors; + this.inputsIcons = layout.inputsIcons; + this.bindingSettings = layout.bindingSettings; + + // Return true indicating the layout was successfully applied. + return true; + } + + /** + * Process the input for the given button. + * + * @param button - The button to process. + * @returns `true` if the input was processed successfully. + */ + processInput(button: Button): boolean { + const ui = this.getUi(); + // Defines the maximum number of rows that can be displayed on the screen. + let success = false; + this.updateNavigationDisplay(); + + // Handle the input based on the button pressed. + if (button === Button.CANCEL) { + // Handle cancel button press, reverting UI mode to previous state. + success = true; + NavigationManager.getInstance().reset(); + this.scene.ui.revertMode(); + } else { + const cursor = this.cursor + this.scrollCursor; // Calculate the absolute cursor position. + const setting = this.settingDevice[Object.keys(this.settingDevice)[cursor]]; + switch (button) { + case Button.ACTION: + if (!this.optionCursors || !this.optionValueLabels) { + return; + } + if (this.settingBlacklisted.includes(setting) || !setting.includes("BUTTON_")) { + success = false; + } else { + success = this.setSetting(this.scene, setting, 1); + } + break; + case Button.UP: // Move up in the menu. + if (!this.optionValueLabels) { + return false; + } + if (cursor) { // If not at the top, move the cursor up. + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } else {// If at the top of the visible items, scroll up. + success = this.setScrollCursor(this.scrollCursor - 1); + } + } else { + // When at the top of the menu and pressing UP, move to the bottommost item. + // First, set the cursor to the last visible element, preparing for the scroll to the end. + const successA = this.setCursor(this.rowsToDisplay - 1); + // Then, adjust the scroll to display the bottommost elements of the menu. + const successB = this.setScrollCursor(this.optionValueLabels.length - this.rowsToDisplay); + success = successA && successB; // success is just there to play the little validation sound effect + } + break; + case Button.DOWN: // Move down in the menu. + if (!this.optionValueLabels) { + return false; + } + if (cursor < this.optionValueLabels.length - 1) { + if (this.cursor < this.rowsToDisplay - 1) { + success = this.setCursor(this.cursor + 1); + } else if (this.scrollCursor < this.optionValueLabels.length - this.rowsToDisplay) { + success = this.setScrollCursor(this.scrollCursor + 1); + } + } else { + // When at the bottom of the menu and pressing DOWN, move to the topmost item. + // First, set the cursor to the first visible element, resetting the scroll to the top. + const successA = this.setCursor(0); + // Then, reset the scroll to start from the first element of the menu. + const successB = this.setScrollCursor(0); + success = successA && successB; // Indicates a successful cursor and scroll adjustment. + } + break; + case Button.LEFT: // Move selection left within the current option set. + if (!this.optionCursors || !this.optionValueLabels) { + return; + } + if (this.settingBlacklisted.includes(setting) || setting.includes("BUTTON_")) { + success = false; + } else if (this.optionCursors[cursor]) { + success = this.setOptionCursor(cursor, this.optionCursors[cursor] - 1, true); + } + break; + case Button.RIGHT: // Move selection right within the current option set. + if (!this.optionCursors || !this.optionValueLabels) { + return; + } + if (this.settingBlacklisted.includes(setting) || setting.includes("BUTTON_")) { + success = false; + } else if (this.optionCursors[cursor] < this.optionValueLabels[cursor].length - 1) { + success = this.setOptionCursor(cursor, this.optionCursors[cursor] + 1, true); + } + break; + case Button.CYCLE_FORM: + case Button.CYCLE_SHINY: + success = this.navigationContainer.navigate(button); + break; + } + } + + // If a change occurred, play the selection sound. + if (success) { + ui.playSelect(); + } + + return success; // Return whether the input resulted in a successful action. + } + + resetScroll() { + this.cursorObj?.destroy(); + this.cursorObj = null; + this.cursor = null; + this.setCursor(0); + this.setScrollCursor(0); + this.updateSettingsScroll(); + } + + /** + * Set the cursor to the specified position. + * + * @param cursor - The cursor position to set. + * @returns `true` if the cursor was set successfully. + */ + setCursor(cursor: integer): boolean { + const ret = super.setCursor(cursor); + // If the optionsContainer is not initialized, return the result from the parent class directly. + if (!this.optionsContainer) { + return ret; + } + + // Check if the cursor object exists, if not, create it. + if (!this.cursorObj) { + this.cursorObj = this.scene.add.nineslice(0, 0, "summary_moves_cursor", null, (this.scene.game.canvas.width / 6) - 10, 16, 1, 1, 1, 1); + this.cursorObj.setOrigin(0, 0); // Set the origin to the top-left corner. + this.optionsContainer.add(this.cursorObj); // Add the cursor to the options container. + } + + // Update the position of the cursor object relative to the options background based on the current cursor and scroll positions. + this.cursorObj.setPositionRelative(this.optionsBg, 4, 4 + (this.cursor + this.scrollCursor) * 16); + + return ret; // Return the result from the parent class's setCursor method. + } + + /** + * Set the scroll cursor to the specified position. + * + * @param scrollCursor - The scroll cursor position to set. + * @returns `true` if the scroll cursor was set successfully. + */ + setScrollCursor(scrollCursor: integer): boolean { + // Check if the new scroll position is the same as the current one; if so, do not update. + if (scrollCursor === this.scrollCursor) { + return false; + } + + // Update the internal scroll cursor state + this.scrollCursor = scrollCursor; + + // Apply the new scroll position to the settings UI. + this.updateSettingsScroll(); + + // Reset the cursor to its current position to adjust its visibility after scrolling. + this.setCursor(this.cursor); + + return true; // Return true to indicate the scroll cursor was successfully updated. + } + + /** + * Set the option cursor to the specified position. + * + * @param settingIndex - The index of the setting. + * @param cursor - The cursor position to set. + * @param save - Whether to save the setting to local storage. + * @returns `true` if the option cursor was set successfully. + */ + setOptionCursor(settingIndex: integer, cursor: integer, save?: boolean): boolean { + // Retrieve the specific setting using the settingIndex from the settingDevice enumeration. + const setting = this.settingDevice[Object.keys(this.settingDevice)[settingIndex]]; + + // Get the current cursor position for this setting. + const lastCursor = this.optionCursors[settingIndex]; + + // Check if the setting is not part of the bindings (i.e., it's a regular setting). + if (!this.bindingSettings.includes(setting) && !setting.includes("BUTTON_")) { + // Get the label of the last selected option and revert its color to the default. + const lastValueLabel = this.optionValueLabels[settingIndex][lastCursor]; + lastValueLabel.setColor(this.getTextColor(TextStyle.WINDOW)); + lastValueLabel.setShadowColor(this.getTextColor(TextStyle.WINDOW, true)); + + // Update the cursor for the setting to the new position. + this.optionCursors[settingIndex] = cursor; + + // Change the color of the new selected option to indicate it's selected. + const newValueLabel = this.optionValueLabels[settingIndex][cursor]; + newValueLabel.setColor(this.getTextColor(TextStyle.SETTINGS_SELECTED)); + newValueLabel.setShadowColor(this.getTextColor(TextStyle.SETTINGS_SELECTED, true)); + } + + // If the save flag is set, save the setting to local storage + if (save) { + this.saveSettingToLocalStorage(setting, cursor); + } + + return true; // Return true to indicate the cursor was successfully updated. + } + + /** + * Update the scroll position of the settings UI. + */ + updateSettingsScroll(): void { + // Return immediately if the options container is not initialized. + if (!this.optionsContainer) { + return; + } + + // Set the vertical position of the options container based on the current scroll cursor, multiplying by the item height. + this.optionsContainer.setY(-16 * this.scrollCursor); + + // Iterate over all setting labels to update their visibility. + for (let s = 0; s < this.settingLabels.length; s++) { + // Determine if the current setting should be visible based on the scroll position. + const visible = s >= this.scrollCursor && s < this.scrollCursor + this.rowsToDisplay; + + // Set the visibility of the setting label and its corresponding options. + this.settingLabels[s].setVisible(visible); + for (const option of this.optionValueLabels[s]) { + option.setVisible(visible); + } + } + } + + /** + * Clear the UI elements and state. + */ + clear(): void { + super.clear(); + + // Hide the settings container to remove it from the view. + this.settingsContainer.setVisible(false); + + // Remove the cursor from the UI. + this.eraseCursor(); + } + + /** + * Erase the cursor from the UI. + */ + eraseCursor(): void { + // Check if a cursor object exists. + if (this.cursorObj) { + this.cursorObj.destroy(); + } // Destroy the cursor object to clean up resources. + + // Set the cursor object reference to null to fully dereference it. + this.cursorObj = null; + } + +} diff --git a/src/ui/settings/gamepad-binding-ui-handler.ts b/src/ui/settings/gamepad-binding-ui-handler.ts new file mode 100644 index 00000000000..1ab705d3278 --- /dev/null +++ b/src/ui/settings/gamepad-binding-ui-handler.ts @@ -0,0 +1,83 @@ +import BattleScene from "../../battle-scene"; +import AbstractBindingUiHandler from "./abstract-binding-ui-handler"; +import {Mode} from "../ui"; +import {Device} from "#app/enums/devices"; +import {getIconWithSettingName, getKeyWithKeycode} from "#app/configs/inputs/configHandler"; +import {addTextObject, TextStyle} from "#app/ui/text"; + + +export default class GamepadBindingUiHandler extends AbstractBindingUiHandler { + + constructor(scene: BattleScene, mode?: Mode) { + super(scene, mode); + this.scene.input.gamepad.on("down", this.gamepadButtonDown, this); + } + setup() { + super.setup(); + + // New button icon setup. + this.newButtonIcon = this.scene.add.sprite(0, 0, "xbox"); + this.newButtonIcon.setPositionRelative(this.optionSelectBg, 78, 16); + this.newButtonIcon.setOrigin(0.5); + this.newButtonIcon.setVisible(false); + + this.swapText = addTextObject(this.scene, 0, 0, "will swap with", TextStyle.WINDOW); + this.swapText.setOrigin(0.5); + this.swapText.setPositionRelative(this.optionSelectBg, this.optionSelectBg.width / 2 - 2, this.optionSelectBg.height / 2 - 2); + this.swapText.setVisible(false); + + this.targetButtonIcon = this.scene.add.sprite(0, 0, "xbox"); + this.targetButtonIcon.setPositionRelative(this.optionSelectBg, 78, 48); + this.targetButtonIcon.setOrigin(0.5); + this.targetButtonIcon.setVisible(false); + + this.actionLabel = addTextObject(this.scene, 0, 0, "Confirm swap", TextStyle.SETTINGS_LABEL); + this.actionLabel.setOrigin(0, 0.5); + this.actionLabel.setPositionRelative(this.actionBg, this.actionBg.width - 75, this.actionBg.height / 2); + this.actionsContainer.add(this.actionLabel); + + this.optionSelectContainer.add(this.newButtonIcon); + this.optionSelectContainer.add(this.swapText); + this.optionSelectContainer.add(this.targetButtonIcon); + } + + getSelectedDevice() { + return this.scene.inputController?.selectedDevice[Device.GAMEPAD]; + } + + gamepadButtonDown(pad: Phaser.Input.Gamepad.Gamepad, button: Phaser.Input.Gamepad.Button, value: number): void { + const blacklist = [12, 13, 14, 15]; // d-pad buttons are blacklisted. + // Check conditions before processing the button press. + if (!this.listening || pad.id.toLowerCase() !== this.getSelectedDevice() || blacklist.includes(button.index) || this.buttonPressed !== null) { + return; + } + const activeConfig = this.scene.inputController.getActiveConfig(Device.GAMEPAD); + const type = activeConfig.padType; + const key = getKeyWithKeycode(activeConfig, button.index); + const buttonIcon = activeConfig.icons[key]; + if (!buttonIcon) { + return; + } + this.buttonPressed = button.index; + const assignedButtonIcon = getIconWithSettingName(activeConfig, this.target); + this.onInputDown(buttonIcon, assignedButtonIcon, type); + } + + swapAction(): boolean { + const activeConfig = this.scene.inputController.getActiveConfig(Device.GAMEPAD); + if (this.scene.inputController.assignBinding(activeConfig, this.target, this.buttonPressed)) { + this.scene.gameData.saveMappingConfigs(this.getSelectedDevice(), activeConfig); + return true; + } + return false; + } + + /** + * Clear the UI elements and state. + */ + clear() { + super.clear(); + this.targetButtonIcon.setVisible(false); + this.swapText.setVisible(false); + } +} diff --git a/src/ui/settings/keyboard-binding-ui-handler.ts b/src/ui/settings/keyboard-binding-ui-handler.ts new file mode 100644 index 00000000000..ca490857200 --- /dev/null +++ b/src/ui/settings/keyboard-binding-ui-handler.ts @@ -0,0 +1,73 @@ +import BattleScene from "../../battle-scene"; +import AbstractBindingUiHandler from "./abstract-binding-ui-handler"; +import {Mode} from "../ui"; +import { getKeyWithKeycode} from "#app/configs/inputs/configHandler"; +import {Device} from "#app/enums/devices"; +import {addTextObject, TextStyle} from "#app/ui/text"; + + +export default class KeyboardBindingUiHandler extends AbstractBindingUiHandler { + + constructor(scene: BattleScene, mode?: Mode) { + super(scene, mode); + // Listen to gamepad button down events to initiate binding. + scene.input.keyboard.on("keydown", this.onKeyDown, this); + } + setup() { + super.setup(); + + // New button icon setup. + this.newButtonIcon = this.scene.add.sprite(0, 0, "keyboard"); + this.newButtonIcon.setPositionRelative(this.optionSelectBg, 78, 32); + this.newButtonIcon.setOrigin(0.5); + this.newButtonIcon.setVisible(false); + + this.actionLabel = addTextObject(this.scene, 0, 0, "Assign button", TextStyle.SETTINGS_LABEL); + this.actionLabel.setOrigin(0, 0.5); + this.actionLabel.setPositionRelative(this.actionBg, this.actionBg.width - 80, this.actionBg.height / 2); + this.actionsContainer.add(this.actionLabel); + + this.optionSelectContainer.add(this.newButtonIcon); + } + + getSelectedDevice() { + return this.scene.inputController?.selectedDevice[Device.KEYBOARD]; + } + + onKeyDown(event): void { + const blacklist = [ + Phaser.Input.Keyboard.KeyCodes.UP, + Phaser.Input.Keyboard.KeyCodes.DOWN, + Phaser.Input.Keyboard.KeyCodes.LEFT, + Phaser.Input.Keyboard.KeyCodes.RIGHT, + Phaser.Input.Keyboard.KeyCodes.HOME, + Phaser.Input.Keyboard.KeyCodes.ENTER, + Phaser.Input.Keyboard.KeyCodes.ESC, + Phaser.Input.Keyboard.KeyCodes.DELETE, + ]; + const key = event.keyCode; + // // Check conditions before processing the button press. + if (!this.listening || this.buttonPressed !== null || blacklist.includes(key)) { + return; + } + const activeConfig = this.scene.inputController.getActiveConfig(Device.KEYBOARD); + const _key = getKeyWithKeycode(activeConfig, key); + const buttonIcon = activeConfig.icons[_key]; + if (!buttonIcon) { + return; + } + this.buttonPressed = key; + // const assignedButtonIcon = getIconWithSettingName(activeConfig, this.target); + this.onInputDown(buttonIcon, null, "keyboard"); + } + + swapAction(): boolean { + const activeConfig = this.scene.inputController.getActiveConfig(Device.KEYBOARD); + if (this.scene.inputController.assignBinding(activeConfig, this.target, this.buttonPressed)) { + this.scene.gameData.saveMappingConfigs(this.getSelectedDevice(), activeConfig); + return true; + } + return false; + } + +} diff --git a/src/ui/settings/navigationMenu.ts b/src/ui/settings/navigationMenu.ts new file mode 100644 index 00000000000..843e9fd1f86 --- /dev/null +++ b/src/ui/settings/navigationMenu.ts @@ -0,0 +1,217 @@ +import BattleScene from "#app/battle-scene"; +import {Mode} from "#app/ui/ui"; +import {InputsIcons} from "#app/ui/settings/abstract-settings-ui-handler"; +import {addTextObject, setTextStyle, TextStyle} from "#app/ui/text"; +import {addWindow} from "#app/ui/ui-theme"; +import {Button} from "#app/enums/buttons"; + +/** + * Manages navigation and menus tabs within the setting menu. + */ +export class NavigationManager { + private static instance: NavigationManager; + public modes: Mode[]; + public selectedMode: Mode = Mode.SETTINGS; + public navigationMenus: NavigationMenu[] = new Array(); + public labels: string[]; + + /** + * Creates an instance of NavigationManager. + * To create a new tab in the menu, add the mode to the modes array and the label to the labels array. + * and instantiate a new NavigationMenu instance in your handler + * like: this.navigationContainer = new NavigationMenu(this.scene, 0, 0); + */ + constructor() { + this.modes = [ + Mode.SETTINGS, + Mode.SETTINGS_GAMEPAD, + Mode.SETTINGS_KEYBOARD, + ]; + this.labels = ["General", "Gamepad", "Keyboard"]; + } + + public reset() { + this.selectedMode = Mode.SETTINGS; + } + + /** + * Gets the singleton instance of the NavigationManager. + * @returns The singleton instance of NavigationManager. + */ + public static getInstance(): NavigationManager { + if (!NavigationManager.instance) { + NavigationManager.instance = new NavigationManager(); + } + return NavigationManager.instance; + } + + /** + * Navigates to the previous mode in the modes array. + * @param scene The current BattleScene instance. + */ + public navigateLeft(scene) { + const pos = this.modes.indexOf(this.selectedMode); + const maxPos = this.modes.length - 1; + if (pos === 0) { + this.selectedMode = this.modes[maxPos]; + } else { + this.selectedMode = this.modes[pos - 1]; + } + scene.ui.setMode(this.selectedMode); + this.updateNavigationMenus(); + } + + /** + * Navigates to the next mode in the modes array. + * @param scene The current BattleScene instance. + */ + public navigateRight(scene) { + const pos = this.modes.indexOf(this.selectedMode); + const maxPos = this.modes.length - 1; + if (pos === maxPos) { + this.selectedMode = this.modes[0]; + } else { + this.selectedMode = this.modes[pos + 1]; + } + scene.ui.setMode(this.selectedMode); + this.updateNavigationMenus(); + } + + /** + * Updates all navigation menus. + */ + public updateNavigationMenus() { + for (const instance of this.navigationMenus) { + instance.update(); + } + } + + /** + * Updates icons for all navigation menus. + */ + public updateIcons() { + for (const instance of this.navigationMenus) { + instance.updateIcons(); + } + } + +} + +export default class NavigationMenu extends Phaser.GameObjects.Container { + private navigationIcons: InputsIcons; + public scene: BattleScene; + protected headerTitles: Phaser.GameObjects.Text[] = new Array(); + + /** + * Creates an instance of NavigationMenu. + * @param scene The current BattleScene instance. + * @param x The x position of the NavigationMenu. + * @param y The y position of the NavigationMenu. + */ + constructor(scene: BattleScene, x: number, y: number) { + super(scene, x, y); + this.scene = scene; + + this.setup(); + } + + /** + * Sets up the NavigationMenu by adding windows, icons, and labels. + */ + setup() { + const navigationManager = NavigationManager.getInstance(); + const headerBg = addWindow(this.scene, 0, 0, (this.scene.game.canvas.width / 6) - 2, 24); + headerBg.setOrigin(0, 0); + this.add(headerBg); + this.width = headerBg.width; + this.height = headerBg.height; + + this.navigationIcons = {}; + + const iconPreviousTab = this.scene.add.sprite(8, 4, "keyboard"); + iconPreviousTab.setOrigin(0, -0.1); + iconPreviousTab.setPositionRelative(headerBg, 8, 4); + this.navigationIcons["BUTTON_CYCLE_FORM"] = iconPreviousTab; + + const iconNextTab = this.scene.add.sprite(0, 0, "keyboard"); + iconNextTab.setOrigin(0, -0.1); + iconNextTab.setPositionRelative(headerBg, headerBg.width - 20, 4); + this.navigationIcons["BUTTON_CYCLE_SHINY"] = iconNextTab; + + let relative: Phaser.GameObjects.Sprite | Phaser.GameObjects.Text = iconPreviousTab; + let relativeWidth: number = iconPreviousTab.width*6; + for (const label of navigationManager.labels) { + const labelText = addTextObject(this.scene, 0, 0, label, TextStyle.SETTINGS_LABEL); + labelText.setOrigin(0, 0); + labelText.setPositionRelative(relative, 6 + relativeWidth/6, 0); + this.add(labelText); + this.headerTitles.push(labelText); + relative = labelText; + relativeWidth = labelText.width; + } + + this.add(iconPreviousTab); + this.add(iconNextTab); + navigationManager.navigationMenus.push(this); + navigationManager.updateNavigationMenus(); + } + + /** + * Updates the NavigationMenu's header titles based on the selected mode. + */ + update() { + const navigationManager = NavigationManager.getInstance(); + const posSelected = navigationManager.modes.indexOf(navigationManager.selectedMode); + + for (const [index, title] of this.headerTitles.entries()) { + setTextStyle(title, this.scene, index === posSelected ? TextStyle.SETTINGS_SELECTED : TextStyle.SETTINGS_LABEL); + } + } + + /** + * Updates the icons in the NavigationMenu based on the latest input recorded. + */ + updateIcons() { + const specialIcons = { + "BUTTON_HOME": "HOME.png", + "BUTTON_DELETE": "DEL.png", + }; + for (const settingName of Object.keys(this.navigationIcons)) { + if (Object.keys(specialIcons).includes(settingName)) { + this.navigationIcons[settingName].setTexture("keyboard"); + this.navigationIcons[settingName].setFrame(specialIcons[settingName]); + this.navigationIcons[settingName].alpha = 1; + continue; + } + const icon = this.scene.inputController?.getIconForLatestInputRecorded(settingName); + if (icon) { + const type = this.scene.inputController?.getLastSourceType(); + this.navigationIcons[settingName].setTexture(type); + this.navigationIcons[settingName].setFrame(icon); + this.navigationIcons[settingName].alpha = 1; + } else { + this.navigationIcons[settingName].alpha = 0; + } + } + } + + /** + * Handles navigation based on the button pressed. + * @param button The button pressed for navigation. + * @returns A boolean indicating if the navigation was handled. + */ + navigate(button: Button): boolean { + const navigationManager = NavigationManager.getInstance(); + switch (button) { + case Button.CYCLE_FORM: + navigationManager.navigateLeft(this.scene); + return true; + break; + case Button.CYCLE_SHINY: + navigationManager.navigateRight(this.scene); + return true; + break; + } + return false; + } +} diff --git a/src/ui/option-select-ui-handler.ts b/src/ui/settings/option-select-ui-handler.ts similarity index 59% rename from src/ui/option-select-ui-handler.ts rename to src/ui/settings/option-select-ui-handler.ts index 3253ca8d325..8d2c534476a 100644 --- a/src/ui/option-select-ui-handler.ts +++ b/src/ui/settings/option-select-ui-handler.ts @@ -1,6 +1,6 @@ -import BattleScene from "../battle-scene"; -import AbstractOptionSelectUiHandler from "./abstact-option-select-ui-handler"; -import { Mode } from "./ui"; +import BattleScene from "../../battle-scene"; +import AbstractOptionSelectUiHandler from "../abstact-option-select-ui-handler"; +import { Mode } from "../ui"; export default class OptionSelectUiHandler extends AbstractOptionSelectUiHandler { constructor(scene: BattleScene, mode: Mode = Mode.OPTION_SELECT) { diff --git a/src/ui/settings/settings-gamepad-ui-handler.ts b/src/ui/settings/settings-gamepad-ui-handler.ts new file mode 100644 index 00000000000..5389d4a1940 --- /dev/null +++ b/src/ui/settings/settings-gamepad-ui-handler.ts @@ -0,0 +1,168 @@ +import BattleScene from "../../battle-scene"; +import {addTextObject, TextStyle} from "../text"; +import {Mode} from "../ui"; +import { + setSettingGamepad, + SettingGamepad, + settingGamepadBlackList, + settingGamepadDefaults, + settingGamepadOptions +} from "../../system/settings-gamepad"; +import pad_xbox360 from "#app/configs/inputs/pad_xbox360"; +import pad_dualshock from "#app/configs/inputs/pad_dualshock"; +import pad_unlicensedSNES from "#app/configs/inputs/pad_unlicensedSNES"; +import {InterfaceConfig} from "#app/inputs-controller"; +import AbstractSettingsUiUiHandler from "#app/ui/settings/abstract-settings-ui-handler"; +import {Device} from "#app/enums/devices"; +import {truncateString} from "#app/utils"; + +/** + * Class representing the settings UI handler for gamepads. + * + * @extends AbstractSettingsUiUiHandler + */ + +export default class SettingsGamepadUiHandler extends AbstractSettingsUiUiHandler { + + /** + * Creates an instance of SettingsGamepadUiHandler. + * + * @param scene - The BattleScene instance. + * @param mode - The UI mode, optional. + */ + constructor(scene: BattleScene, mode?: Mode) { + super(scene, mode); + this.titleSelected = "Gamepad"; + this.settingDevice = SettingGamepad; + this.settingDeviceDefaults = settingGamepadDefaults; + this.settingDeviceOptions = settingGamepadOptions; + this.configs = [pad_xbox360, pad_dualshock, pad_unlicensedSNES]; + this.commonSettingsCount = 2; + this.localStoragePropertyName = "settingsGamepad"; + this.settingBlacklisted = settingGamepadBlackList; + } + + setSetting(scene: BattleScene, setting, value: integer): boolean { + return setSettingGamepad(scene, setting, value); + } + + /** + * Setup UI elements. + */ + setup() { + super.setup(); + // If no gamepads are detected, set up a default UI prompt in the settings container. + this.layout["noGamepads"] = new Map(); + const optionsContainer = this.scene.add.container(0, 0); + optionsContainer.setVisible(false); // Initially hide the container as no gamepads are connected. + const label = addTextObject(this.scene, 8, 28, "Please plug a controller or press a button", TextStyle.SETTINGS_LABEL); + label.setOrigin(0, 0); + optionsContainer.add(label); + this.settingsContainer.add(optionsContainer); + + // Map the 'noGamepads' layout options for easy access. + this.layout["noGamepads"].optionsContainer = optionsContainer; + this.layout["noGamepads"].label = label; + } + + /** + * Get the active configuration. + * + * @returns The active gamepad configuration. + */ + getActiveConfig(): InterfaceConfig { + return this.scene.inputController.getActiveConfig(Device.GAMEPAD); + } + + /** + * Get the gamepad settings from local storage. + * + * @returns The gamepad settings from local storage. + */ + getLocalStorageSetting(): object { + // Retrieve the gamepad settings from local storage or use an empty object if none exist. + const settings: object = localStorage.hasOwnProperty("settingsGamepad") ? JSON.parse(localStorage.getItem("settingsGamepad")) : {}; + return settings; + } + + /** + * Set the layout for the active configuration. + * + * @param activeConfig - The active gamepad configuration. + * @returns `true` if the layout was successfully applied, otherwise `false`. + */ + setLayout(activeConfig: InterfaceConfig): boolean { + // Check if there is no active configuration (e.g., no gamepad connected). + if (!activeConfig) { + // Retrieve the layout for when no gamepads are connected. + const layout = this.layout["noGamepads"]; + // Make the options container visible to show message. + layout.optionsContainer.setVisible(true); + // Return false indicating the layout application was not successful due to lack of gamepad. + return false; + } + + return super.setLayout(activeConfig); + } + + + /** + * Navigate to the left menu tab. + * + * @returns `true` indicating the navigation was successful. + */ + navigateMenuLeft(): boolean { + this.scene.ui.setMode(Mode.SETTINGS); + return true; + } + + /** + * Navigate to the right menu tab. + * + * @returns `true` indicating the navigation was successful. + */ + navigateMenuRight(): boolean { + this.scene.ui.setMode(Mode.SETTINGS_KEYBOARD); + return true; + } + + /** + * Update the display of the chosen gamepad. + */ + updateChosenGamepadDisplay(): void { + // Update any bindings that might have changed since the last update. + this.updateBindings(); + this.resetScroll(); + + // Iterate over the keys in the settingDevice enumeration. + for (const [index, key] of Object.keys(this.settingDevice).entries()) { + const setting = this.settingDevice[key]; // Get the actual setting value using the key. + + // Check if the current setting corresponds to the controller setting. + if (setting === this.settingDevice.Controller) { + // Iterate over all layouts excluding the 'noGamepads' special case. + for (const _key of Object.keys(this.layout)) { + if (_key === "noGamepads") { + continue; + } // Skip updating the no gamepad layout. + + // Update the text of the first option label under the current setting to the name of the chosen gamepad, + // truncating the name to 30 characters if necessary. + this.layout[_key].optionValueLabels[index][0].setText(truncateString(this.scene.inputController.selectedDevice[Device.GAMEPAD], 20)); + } + } + } + } + + /** + * Save the setting to local storage. + * + * @param setting - The setting to save. + * @param cursor - The cursor position to save. + */ + saveSettingToLocalStorage(setting, cursor): void { + if (this.settingDevice[setting] !== this.settingDevice.Controller) { + this.scene.gameData.saveGamepadSetting(setting, cursor); + } + } +} diff --git a/src/ui/settings/settings-keyboard-ui-handler.ts b/src/ui/settings/settings-keyboard-ui-handler.ts new file mode 100644 index 00000000000..3a7751c4522 --- /dev/null +++ b/src/ui/settings/settings-keyboard-ui-handler.ts @@ -0,0 +1,224 @@ +import BattleScene from "../../battle-scene"; +import {Mode} from "../ui"; +import cfg_keyboard_qwerty from "#app/configs/inputs/cfg_keyboard_qwerty"; +import { + setSettingKeyboard, + SettingKeyboard, + settingKeyboardBlackList, + settingKeyboardDefaults, + settingKeyboardOptions +} from "#app/system/settings-keyboard"; +import {reverseValueToKeySetting, truncateString} from "#app/utils"; +import AbstractSettingsUiUiHandler from "#app/ui/settings/abstract-settings-ui-handler"; +import {InterfaceConfig} from "#app/inputs-controller"; +import {addTextObject, TextStyle} from "#app/ui/text"; +import {deleteBind} from "#app/configs/inputs/configHandler"; +import {Device} from "#app/enums/devices"; +import {NavigationManager} from "#app/ui/settings/navigationMenu"; + +/** + * Class representing the settings UI handler for keyboards. + * + * @extends AbstractSettingsUiUiHandler + */ +export default class SettingsKeyboardUiHandler extends AbstractSettingsUiUiHandler { + /** + * Creates an instance of SettingsKeyboardUiHandler. + * + * @param scene - The BattleScene instance. + * @param mode - The UI mode, optional. + */ + constructor(scene: BattleScene, mode?: Mode) { + super(scene, mode); + this.titleSelected = "Keyboard"; + this.settingDevice = SettingKeyboard; + this.settingDeviceDefaults = settingKeyboardDefaults; + this.settingDeviceOptions = settingKeyboardOptions; + this.configs = [cfg_keyboard_qwerty]; + this.commonSettingsCount = 0; + this.textureOverride = "keyboard"; + this.localStoragePropertyName = "settingsKeyboard"; + this.settingBlacklisted = settingKeyboardBlackList; + + const deleteEvent = scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.DELETE); + const restoreDefaultEvent = scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.HOME); + deleteEvent.on("up", this.onDeleteDown, this); + restoreDefaultEvent.on("up", this.onHomeDown, this); + } + + setSetting(scene: BattleScene, setting, value: integer): boolean { + return setSettingKeyboard(scene, setting, value); + } + + /** + * Setup UI elements. + */ + setup() { + super.setup(); + // If no gamepads are detected, set up a default UI prompt in the settings container. + this.layout["noKeyboard"] = new Map(); + const optionsContainer = this.scene.add.container(0, 0); + optionsContainer.setVisible(false); // Initially hide the container as no gamepads are connected. + const label = addTextObject(this.scene, 8, 28, "Please press a key on your keyboard", TextStyle.SETTINGS_LABEL); + label.setOrigin(0, 0); + optionsContainer.add(label); + this.settingsContainer.add(optionsContainer); + + const iconDelete = this.scene.add.sprite(0, 0, "keyboard"); + iconDelete.setOrigin(0, -0.1); + iconDelete.setPositionRelative(this.actionsBg, this.navigationContainer.width - 260, 4); + this.navigationIcons["BUTTON_DELETE"] = iconDelete; + + const deleteText = addTextObject(this.scene, 0, 0, "Delete", TextStyle.SETTINGS_LABEL); + deleteText.setOrigin(0, 0.15); + deleteText.setPositionRelative(iconDelete, -deleteText.width/6-2, 0); + + this.settingsContainer.add(iconDelete); + this.settingsContainer.add(deleteText); + + + + // Map the 'noKeyboard' layout options for easy access. + this.layout["noKeyboard"].optionsContainer = optionsContainer; + this.layout["noKeyboard"].label = label; + } + + /** + * Handle the home key press event. + */ + onHomeDown(): void { + if (![Mode.SETTINGS_KEYBOARD, Mode.SETTINGS_GAMEPAD].includes(this.scene.ui.getMode())) { + return; + } + this.scene.gameData.resetMappingToFactory(); + NavigationManager.getInstance().updateIcons(); + } + + /** + * Handle the delete key press event. + */ + onDeleteDown(): void { + if (this.scene.ui.getMode() !== Mode.SETTINGS_KEYBOARD) { + return; + } + const cursor = this.cursor + this.scrollCursor; // Calculate the absolute cursor position. + const selection = this.settingLabels[cursor].text; + const key = reverseValueToKeySetting(selection); + const settingName = SettingKeyboard[key]; + const activeConfig = this.getActiveConfig(); + const success = deleteBind(this.getActiveConfig(), settingName); + if (success) { + this.saveCustomKeyboardMappingToLocalStorage(activeConfig); + this.updateBindings(); + NavigationManager.getInstance().updateIcons(); + } + } + + /** + * Get the active configuration. + * + * @returns The active keyboard configuration. + */ + getActiveConfig(): InterfaceConfig { + return this.scene.inputController.getActiveConfig(Device.KEYBOARD); + } + + /** + * Get the keyboard settings from local storage. + * + * @returns The keyboard settings from local storage. + */ + getLocalStorageSetting(): object { + // Retrieve the gamepad settings from local storage or use an empty object if none exist. + const settings: object = localStorage.hasOwnProperty("settingsKeyboard") ? JSON.parse(localStorage.getItem("settingsKeyboard")) : {}; + return settings; + } + + /** + * Set the layout for the active configuration. + * + * @param activeConfig - The active keyboard configuration. + * @returns `true` if the layout was successfully applied, otherwise `false`. + */ + setLayout(activeConfig: InterfaceConfig): boolean { + // Check if there is no active configuration (e.g., no gamepad connected). + if (!activeConfig) { + // Retrieve the layout for when no gamepads are connected. + const layout = this.layout["noKeyboard"]; + // Make the options container visible to show message. + layout.optionsContainer.setVisible(true); + // Return false indicating the layout application was not successful due to lack of gamepad. + return false; + } + + return super.setLayout(activeConfig); + } + + /** + * Navigate to the left menu tab. + * + * @returns `true` indicating the navigation was successful. + */ + navigateMenuLeft(): boolean { + this.scene.ui.setMode(Mode.SETTINGS_GAMEPAD); + return true; + } + + /** + * Navigate to the right menu tab. + * + * @returns `true` indicating the navigation was successful. + */ + navigateMenuRight(): boolean { + this.scene.ui.setMode(Mode.SETTINGS); + return true; + } + + /** + * Update the display of the chosen keyboard layout. + */ + updateChosenKeyboardDisplay(): void { + // Update any bindings that might have changed since the last update. + this.updateBindings(); + + // Iterate over the keys in the settingDevice enumeration. + for (const [index, key] of Object.keys(this.settingDevice).entries()) { + const setting = this.settingDevice[key]; // Get the actual setting value using the key. + + // Check if the current setting corresponds to the layout setting. + if (setting === this.settingDevice.Default_Layout) { + // Iterate over all layouts excluding the 'noGamepads' special case. + for (const _key of Object.keys(this.layout)) { + if (_key === "noKeyboard") { + continue; + } // Skip updating the no gamepad layout. + // Update the text of the first option label under the current setting to the name of the chosen gamepad, + // truncating the name to 30 characters if necessary. + this.layout[_key].optionValueLabels[index][0].setText(truncateString(this.scene.inputController.selectedDevice[Device.KEYBOARD], 22)); + } + } + } + + } + + /** + * Save the custom keyboard mapping to local storage. + * + * @param config - The configuration to save. + */ + saveCustomKeyboardMappingToLocalStorage(config): void { + this.scene.gameData.saveMappingConfigs(this.scene.inputController?.selectedDevice[Device.KEYBOARD], config); + } + + /** + * Save the setting to local storage. + * + * @param settingName - The name of the setting to save. + * @param cursor - The cursor position to save. + */ + saveSettingToLocalStorage(settingName, cursor): void { + if (this.settingDevice[settingName] !== this.settingDevice.Default_Layout) { + this.scene.gameData.saveKeyboardSetting(settingName, cursor); + } + } +} diff --git a/src/ui/settings-ui-handler.ts b/src/ui/settings/settings-ui-handler.ts similarity index 69% rename from src/ui/settings-ui-handler.ts rename to src/ui/settings/settings-ui-handler.ts index ba6515ad0c4..44dc90dff88 100644 --- a/src/ui/settings-ui-handler.ts +++ b/src/ui/settings/settings-ui-handler.ts @@ -1,15 +1,18 @@ -import BattleScene from "../battle-scene"; -import { Setting, reloadSettings, settingDefaults, settingOptions } from "../system/settings"; -import { hasTouchscreen, isMobile } from "../touch-controls"; -import { TextStyle, addTextObject } from "./text"; -import { Mode } from "./ui"; -import UiHandler from "./ui-handler"; -import { addWindow } from "./ui-theme"; -import {Button} from "../enums/buttons"; +import BattleScene from "../../battle-scene"; +import {Setting, reloadSettings, settingDefaults, settingOptions} from "../../system/settings"; +import { hasTouchscreen, isMobile } from "../../touch-controls"; +import { TextStyle, addTextObject } from "../text"; +import { Mode } from "../ui"; +import UiHandler from "../ui-handler"; +import { addWindow } from "../ui-theme"; +import {Button} from "../../enums/buttons"; +import {InputsIcons} from "#app/ui/settings/abstract-settings-ui-handler"; +import NavigationMenu, {NavigationManager} from "#app/ui/settings/navigationMenu"; export default class SettingsUiHandler extends UiHandler { private settingsContainer: Phaser.GameObjects.Container; private optionsContainer: Phaser.GameObjects.Container; + private navigationContainer: NavigationMenu; private scrollCursor: integer; @@ -20,16 +23,20 @@ export default class SettingsUiHandler extends UiHandler { private settingLabels: Phaser.GameObjects.Text[]; private optionValueLabels: Phaser.GameObjects.Text[][]; + protected navigationIcons: InputsIcons; + private cursorObj: Phaser.GameObjects.NineSlice; private reloadRequired: boolean; private reloadI18n: boolean; + private rowsToDisplay: number; constructor(scene: BattleScene, mode?: Mode) { super(scene, mode); this.reloadRequired = false; this.reloadI18n = false; + this.rowsToDisplay = 8; } setup() { @@ -37,18 +44,36 @@ export default class SettingsUiHandler extends UiHandler { this.settingsContainer = this.scene.add.container(1, -(this.scene.game.canvas.height / 6) + 1); - this.settingsContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains); + this.settingsContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6 - 20), Phaser.Geom.Rectangle.Contains); - const headerBg = addWindow(this.scene, 0, 0, (this.scene.game.canvas.width / 6) - 2, 24); - headerBg.setOrigin(0, 0); + this.navigationIcons = {}; - const headerText = addTextObject(this.scene, 0, 0, "Options", TextStyle.SETTINGS_LABEL); - headerText.setOrigin(0, 0); - headerText.setPositionRelative(headerBg, 8, 4); + this.navigationContainer = new NavigationMenu(this.scene, 0, 0); - this.optionsBg = addWindow(this.scene, 0, headerBg.height, (this.scene.game.canvas.width / 6) - 2, (this.scene.game.canvas.height / 6) - headerBg.height - 2); + this.optionsBg = addWindow(this.scene, 0, this.navigationContainer.height, (this.scene.game.canvas.width / 6) - 2, (this.scene.game.canvas.height / 6) - 16 - this.navigationContainer.height - 2); this.optionsBg.setOrigin(0, 0); + const actionsBg = addWindow(this.scene, 0, (this.scene.game.canvas.height / 6) - this.navigationContainer.height, (this.scene.game.canvas.width / 6) - 2, 22); + actionsBg.setOrigin(0, 0); + + const iconAction = this.scene.add.sprite(0, 0, "keyboard"); + iconAction.setOrigin(0, -0.1); + iconAction.setPositionRelative(actionsBg, this.navigationContainer.width - 32, 4); + this.navigationIcons["BUTTON_ACTION"] = iconAction; + + const actionText = addTextObject(this.scene, 0, 0, "Action", TextStyle.SETTINGS_LABEL); + actionText.setOrigin(0, 0.15); + actionText.setPositionRelative(iconAction, -actionText.width/6-2, 0); + + const iconCancel = this.scene.add.sprite(0, 0, "keyboard"); + iconCancel.setOrigin(0, -0.1); + iconCancel.setPositionRelative(actionsBg, this.navigationContainer.width - 100, 4); + this.navigationIcons["BUTTON_CANCEL"] = iconCancel; + + const cancelText = addTextObject(this.scene, 0, 0, "Cancel", TextStyle.SETTINGS_LABEL); + cancelText.setOrigin(0, 0.15); + cancelText.setPositionRelative(iconCancel, -cancelText.width/6-2, 0); + this.optionsContainer = this.scene.add.container(0, 0); this.settingLabels = []; @@ -91,10 +116,14 @@ export default class SettingsUiHandler extends UiHandler { this.optionCursors = Object.values(settingDefaults); - this.settingsContainer.add(headerBg); - this.settingsContainer.add(headerText); this.settingsContainer.add(this.optionsBg); + this.settingsContainer.add(this.navigationContainer); + this.settingsContainer.add(actionsBg); this.settingsContainer.add(this.optionsContainer); + this.settingsContainer.add(iconAction); + this.settingsContainer.add(iconCancel); + this.settingsContainer.add(actionText); + this.settingsContainer.add(cancelText); ui.add(this.settingsContainer); @@ -104,8 +133,30 @@ export default class SettingsUiHandler extends UiHandler { this.settingsContainer.setVisible(false); } + updateBindings(): void { + for (const settingName of Object.keys(this.navigationIcons)) { + if (settingName === "BUTTON_HOME") { + this.navigationIcons[settingName].setTexture("keyboard"); + this.navigationIcons[settingName].setFrame("HOME.png"); + this.navigationIcons[settingName].alpha = 1; + continue; + } + const icon = this.scene.inputController?.getIconForLatestInputRecorded(settingName); + if (icon) { + const type = this.scene.inputController?.getLastSourceType(); + this.navigationIcons[settingName].setTexture(type); + this.navigationIcons[settingName].setFrame(icon); + this.navigationIcons[settingName].alpha = 1; + } else { + this.navigationIcons[settingName].alpha = 0; + } + } + NavigationManager.getInstance().updateIcons(); + } + show(args: any[]): boolean { super.show(args); + this.updateBindings(); const settings: object = localStorage.hasOwnProperty("settings") ? JSON.parse(localStorage.getItem("settings")) : {}; @@ -133,12 +184,12 @@ export default class SettingsUiHandler extends UiHandler { processInput(button: Button): boolean { const ui = this.getUi(); // Defines the maximum number of rows that can be displayed on the screen. - const rowsToDisplay = 9; let success = false; if (button === Button.CANCEL) { success = true; + NavigationManager.getInstance().reset(); // Reverts UI to its previous state on cancel. this.scene.ui.revertMode(); } else { @@ -154,17 +205,17 @@ export default class SettingsUiHandler extends UiHandler { } else { // When at the top of the menu and pressing UP, move to the bottommost item. // First, set the cursor to the last visible element, preparing for the scroll to the end. - const successA = this.setCursor(rowsToDisplay - 1); + const successA = this.setCursor(this.rowsToDisplay - 1); // Then, adjust the scroll to display the bottommost elements of the menu. - const successB = this.setScrollCursor(this.optionValueLabels.length - rowsToDisplay); + const successB = this.setScrollCursor(this.optionValueLabels.length - this.rowsToDisplay); success = successA && successB; // success is just there to play the little validation sound effect } break; case Button.DOWN: if (cursor < this.optionValueLabels.length - 1) { - if (this.cursor < rowsToDisplay - 1) { // if the visual cursor is in the frame of 0 to 8 + if (this.cursor < this.rowsToDisplay - 1) {// if the visual cursor is in the frame of 0 to 8 success = this.setCursor(this.cursor + 1); - } else if (this.scrollCursor < this.optionValueLabels.length - rowsToDisplay) { + } else if (this.scrollCursor < this.optionValueLabels.length - this.rowsToDisplay) { success = this.setScrollCursor(this.scrollCursor + 1); } } else { @@ -177,7 +228,7 @@ export default class SettingsUiHandler extends UiHandler { } break; case Button.LEFT: - if (this.optionCursors[cursor]) { // Moves the option cursor left, if possible. + if (this.optionCursors[cursor]) {// Moves the option cursor left, if possible. success = this.setOptionCursor(cursor, this.optionCursors[cursor] - 1, true); } break; @@ -187,6 +238,10 @@ export default class SettingsUiHandler extends UiHandler { success = this.setOptionCursor(cursor, this.optionCursors[cursor] + 1, true); } break; + case Button.CYCLE_FORM: + case Button.CYCLE_SHINY: + success = this.navigationContainer.navigate(button); + break; } } @@ -263,7 +318,7 @@ export default class SettingsUiHandler extends UiHandler { this.optionsContainer.setY(-16 * this.scrollCursor); for (let s = 0; s < this.settingLabels.length; s++) { - const visible = s >= this.scrollCursor && s < this.scrollCursor + 9; + const visible = s >= this.scrollCursor && s < this.scrollCursor + this.rowsToDisplay; this.settingLabels[s].setVisible(visible); for (const option of this.optionValueLabels[s]) { option.setVisible(visible); diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index a8b75fe6357..32f5bffeb1b 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -742,7 +742,7 @@ export default class SummaryUiHandler extends UiHandler { allAbilityInfo.push(this.passiveContainer); // Sets up the pixel button prompt image - this.abilityPrompt = this.scene.add.image(0, 0, !this.scene.gamepadSupport ? "summary_profile_prompt_z" : "summary_profile_prompt_a"); + this.abilityPrompt = this.scene.add.image(0, 0, !this.scene.inputController?.gamepadSupport ? "summary_profile_prompt_z" : "summary_profile_prompt_a"); this.abilityPrompt.setPosition(8, 43); this.abilityPrompt.setVisible(true); this.abilityPrompt.setOrigin(0, 0); diff --git a/src/ui/text.ts b/src/ui/text.ts index 5c11f1cfbd1..3db4c37fe67 100644 --- a/src/ui/text.ts +++ b/src/ui/text.ts @@ -26,6 +26,7 @@ export enum TextStyle { STATS_VALUE, SETTINGS_LABEL, SETTINGS_SELECTED, + SETTINGS_LOCKED, TOOLTIP_TITLE, TOOLTIP_CONTENT, MOVE_INFO_CONTENT @@ -63,6 +64,15 @@ export function addTextObject(scene: Phaser.Scene, x: number, y: number, content return ret; } +export function setTextStyle(obj: Phaser.GameObjects.Text, scene: Phaser.Scene, style: TextStyle, extraStyleOptions?: Phaser.Types.GameObjects.Text.TextStyle) { + const [ styleOptions, shadowColor, shadowXpos, shadowYpos ] = getTextStyleOptions(style, (scene as BattleScene).uiTheme, extraStyleOptions); + obj.setScale(0.1666666667); + obj.setShadow(shadowXpos, shadowYpos, shadowColor); + if (!(styleOptions as Phaser.Types.GameObjects.Text.TextStyle).lineSpacing) { + obj.setLineSpacing(5); + } +} + export function addBBCodeTextObject(scene: Phaser.Scene, x: number, y: number, content: string, style: TextStyle, extraStyleOptions?: Phaser.Types.GameObjects.Text.TextStyle): BBCodeText { const [ styleOptions, shadowColor, shadowXpos, shadowYpos ] = getTextStyleOptions(style, (scene as BattleScene).uiTheme, extraStyleOptions); @@ -143,6 +153,7 @@ function getTextStyleOptions(style: TextStyle, uiTheme: UiTheme, extraStyleOptio break; case TextStyle.MESSAGE: case TextStyle.SETTINGS_LABEL: + case TextStyle.SETTINGS_LOCKED: case TextStyle.SETTINGS_SELECTED: styleOptions.fontSize = languageSettings[lang]?.summaryFontSize || "96px"; break; @@ -226,6 +237,7 @@ export function getTextColor(textStyle: TextStyle, shadow?: boolean, uiTheme: Ui case TextStyle.SUMMARY_GOLD: case TextStyle.MONEY: return !shadow ? "#e8e8a8" : "#a0a060"; + case TextStyle.SETTINGS_LOCKED: case TextStyle.SUMMARY_GRAY: return !shadow ? "#a0a0a0" : "#636363"; case TextStyle.STATS_LABEL: diff --git a/src/ui/title-ui-handler.ts b/src/ui/title-ui-handler.ts index 033327c0582..673d8c870d9 100644 --- a/src/ui/title-ui-handler.ts +++ b/src/ui/title-ui-handler.ts @@ -1,6 +1,6 @@ import BattleScene from "../battle-scene"; import { DailyRunScoreboard } from "./daily-run-scoreboard"; -import OptionSelectUiHandler from "./option-select-ui-handler"; +import OptionSelectUiHandler from "./settings/option-select-ui-handler"; import { Mode } from "./ui"; import * as Utils from "../utils"; import { TextStyle, addTextObject } from "./text"; diff --git a/src/ui/ui.ts b/src/ui/ui.ts index e7a38e2b7c3..7092f16a57c 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -12,12 +12,13 @@ import SummaryUiHandler from "./summary-ui-handler"; import StarterSelectUiHandler from "./starter-select-ui-handler"; import EvolutionSceneHandler from "./evolution-scene-handler"; import TargetSelectUiHandler from "./target-select-ui-handler"; -import SettingsUiHandler from "./settings-ui-handler"; -import {addTextObject, TextStyle} from "./text"; +import SettingsUiHandler from "./settings/settings-ui-handler"; +import SettingsGamepadUiHandler from "./settings/settings-gamepad-ui-handler"; +import { TextStyle, addTextObject } from "./text"; import AchvBar from "./achv-bar"; import MenuUiHandler from "./menu-ui-handler"; import AchvsUiHandler from "./achvs-ui-handler"; -import OptionSelectUiHandler from "./option-select-ui-handler"; +import OptionSelectUiHandler from "./settings/option-select-ui-handler"; import EggHatchSceneHandler from "./egg-hatch-scene-handler"; import EggListUiHandler from "./egg-list-ui-handler"; import EggGachaUiHandler from "./egg-gacha-ui-handler"; @@ -38,6 +39,9 @@ import SessionReloadModalUiHandler from "./session-reload-modal-ui-handler"; import {Button} from "../enums/buttons"; import i18next, {ParseKeys} from "i18next"; import {PlayerGender} from "#app/system/game-data"; +import GamepadBindingUiHandler from "./settings/gamepad-binding-ui-handler"; +import SettingsKeyboardUiHandler from "#app/ui/settings/settings-keyboard-ui-handler"; +import KeyboardBindingUiHandler from "#app/ui/settings/keyboard-binding-ui-handler"; export enum Mode { MESSAGE, @@ -58,6 +62,10 @@ export enum Mode { MENU, MENU_OPTION_SELECT, SETTINGS, + SETTINGS_GAMEPAD, + GAMEPAD_BINDING, + SETTINGS_KEYBOARD, + KEYBOARD_BINDING, ACHIEVEMENTS, GAME_STATS, VOUCHERS, @@ -88,7 +96,11 @@ const noTransitionModes = [ Mode.OPTION_SELECT, Mode.MENU, Mode.MENU_OPTION_SELECT, + Mode.GAMEPAD_BINDING, + Mode.KEYBOARD_BINDING, Mode.SETTINGS, + Mode.SETTINGS_GAMEPAD, + Mode.SETTINGS_KEYBOARD, Mode.ACHIEVEMENTS, Mode.GAME_STATS, Mode.VOUCHERS, @@ -103,7 +115,7 @@ const noTransitionModes = [ export default class UI extends Phaser.GameObjects.Container { private mode: Mode; private modeChain: Mode[]; - private handlers: UiHandler[]; + public handlers: UiHandler[]; private overlay: Phaser.GameObjects.Rectangle; public achvBar: AchvBar; public savingIcon: SavingIconHandler; @@ -139,6 +151,10 @@ export default class UI extends Phaser.GameObjects.Container { new MenuUiHandler(scene), new OptionSelectUiHandler(scene, Mode.MENU_OPTION_SELECT), new SettingsUiHandler(scene), + new SettingsGamepadUiHandler(scene), + new GamepadBindingUiHandler(scene), + new SettingsKeyboardUiHandler(scene), + new KeyboardBindingUiHandler(scene), new AchvsUiHandler(scene), new GameStatsUiHandler(scene), new VouchersUiHandler(scene), diff --git a/src/utils.ts b/src/utils.ts index 43ce73f1ddb..adfe0b0df20 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -423,3 +423,49 @@ export function printContainerList(container: Phaser.GameObjects.Container): voi return {type: go.type, name: go.name}; })); } + + +/** + * Truncate a string to a specified maximum length and add an ellipsis if it exceeds that length. + * + * @param str - The string to be truncated. + * @param maxLength - The maximum length of the truncated string, defaults to 10. + * @returns The truncated string with an ellipsis if it was longer than maxLength. + */ +export function truncateString(str: String, maxLength: number = 10) { + // Check if the string length exceeds the maximum length + if (str.length > maxLength) { + // Truncate the string and add an ellipsis + return str.slice(0, maxLength - 3) + "..."; // Subtract 3 to accommodate the ellipsis + } + // Return the original string if it does not exceed the maximum length + return str; +} + +/** + * Perform a deep copy of an object. + * + * @param values - The object to be deep copied. + * @returns A new object that is a deep copy of the input. + */ +export function deepCopy(values: object): object { + // Convert the object to a JSON string and parse it back to an object to perform a deep copy + return JSON.parse(JSON.stringify(values)); +} + +/** + * Convert a space-separated string into a capitalized and underscored string. + * + * @param input - The string to be converted. + * @returns The converted string with words capitalized and separated by underscores. + */ +export function reverseValueToKeySetting(input) { + // Split the input string into an array of words + const words = input.split(" "); + // Capitalize the first letter of each word and convert the rest to lowercase + const capitalizedWords = words.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()); + // Join the capitalized words with underscores and return the result + return capitalizedWords.join("_"); +} + + From b7ecebbc6e0be0632780765806c08fb7cee68f17 Mon Sep 17 00:00:00 2001 From: InnocentGameDev Date: Sat, 1 Jun 2024 17:25:46 +0200 Subject: [PATCH 007/129] [Localization] Toxic and Flame Orb (en, de, es, fr, it) Localisations (only missing pt_BR, zh) (#1668) * toxic and flame orb odds tweak and localisation * Update modifier-type.ts German name, but English description * Update modifier-type.ts Spanish done, in this regard! * Update src/locales/fr/modifier-type.ts French translation, thanks @Dakurei Co-authored-by: Dakurei * Update src/locales/fr/modifier-type.ts French translation, thanks @Dakurei Co-authored-by: Dakurei * Update modifier-type.ts Missed eliminating the "." * Update modifier-type.ts * Update modifier-type.ts "it" gets proper English description, waiting on an Italian localisation * Update modifier-type.ts fixed the english desc. * Update src/locales/it/modifier-type.ts it localisation Co-authored-by: Dakurei * Update src/locales/it/modifier-type.ts GoaTed translator Co-authored-by: Dakurei * Update modifier-type.ts English in pt * Update modifier-type.ts English everywhere zzz * Update modifier-type.ts Final English, ready for translation by whoever sees it fit! * Update src/locales/pt_BR/modifier-type.ts Fixed a mixup Co-authored-by: Dakurei * Update src/locales/pt_BR/modifier-type.ts Fixed a mixup Co-authored-by: Dakurei * Update modifier-type.ts Added DerTapp translations without the "Ein Item zum Tragen." part for consistency throughout the file and with the rest of the locales, and according to what was briefly discussed in the Discord last night. * Update modifier-type.ts Missed taking out the "." in the /de, to keep it consistent with all other descriptions * Delete src/locales/ko/modifier-type.ts Korean localisation is already done * Add back ko modifier-type --------- Co-authored-by: Dakurei Co-authored-by: Benjamin Odom Co-authored-by: Temps Ray --- src/locales/de/modifier-type.ts | 3 +++ src/locales/es/modifier-type.ts | 3 +++ src/locales/fr/modifier-type.ts | 3 +++ src/locales/it/modifier-type.ts | 3 +++ src/locales/pt_BR/modifier-type.ts | 3 +++ src/locales/zh_CN/modifier-type.ts | 3 +++ src/locales/zh_TW/modifier-type.ts | 10 ++++++++++ 7 files changed, 28 insertions(+) diff --git a/src/locales/de/modifier-type.ts b/src/locales/de/modifier-type.ts index 57468a8ad1b..e32e804c3ca 100644 --- a/src/locales/de/modifier-type.ts +++ b/src/locales/de/modifier-type.ts @@ -209,6 +209,9 @@ export const modifierType: ModifierTypeTranslationEntries = { "LEFTOVERS": { name: "Überreste", description: "Heilt 1/16 der maximalen KP eines Pokémon pro Runde" }, "SHELL_BELL": { name: "Muschelglocke", description: "Heilt den Anwender um 1/8 des von ihm zugefügten Schadens" }, + "TOXIC_ORB": { name: "Toxik-Orb", description: "Dieser bizarre Orb vergiftet seinen Träger im Kampf schwer" }, + "FLAME_ORB": { name: "Heiß-Orb", description: "Dieser bizarre Orb fügt seinem Träger im Kampf Verbrennungen zu" }, + "BATON": { name: "Stab", description: "Ermöglicht das Weitergeben von Effekten beim Wechseln von Pokémon, wodurch auch Fallen umgangen werden." }, "SHINY_CHARM": { name: "Schillerpin", description: "Erhöht die Chance deutlich, dass ein wildes Pokémon ein schillernd ist" }, diff --git a/src/locales/es/modifier-type.ts b/src/locales/es/modifier-type.ts index 49f30b15683..dafdfcc11e8 100644 --- a/src/locales/es/modifier-type.ts +++ b/src/locales/es/modifier-type.ts @@ -209,6 +209,9 @@ export const modifierType: ModifierTypeTranslationEntries = { "LEFTOVERS": { name: "Restos", description: "Cura 1/16 de los PS máximo de un Pokémon cada turno" }, "SHELL_BELL": { name: "Camp Concha", description: "Cura 1/8 del daño infligido por un Pokémon" }, + "TOXIC_ORB": { name: "Toxiesfera", description: "Extraña esfera que envenena gravemente a quien la usa en combate" }, + "FLAME_ORB": { name: "Llamasfera", description: "Extraña esfera que causa quemaduras a quien la usa en combate" }, + "BATON": { name: "Baton", description: "Permite pasar los efectos al cambiar de Pokémon, también evita las trampas" }, "SHINY_CHARM": { name: "Amuleto Iris", description: "Aumenta drásticamente la posibilidad de que un Pokémon salvaje sea Shiny" }, diff --git a/src/locales/fr/modifier-type.ts b/src/locales/fr/modifier-type.ts index 1f82eb2c8cf..9307bfb155b 100644 --- a/src/locales/fr/modifier-type.ts +++ b/src/locales/fr/modifier-type.ts @@ -209,6 +209,9 @@ export const modifierType: ModifierTypeTranslationEntries = { "LEFTOVERS": { name: "Restes", description: "Soigne à chaque tour 1/16 des PV max d’un Pokémon" }, "SHELL_BELL": { name: "Grelot Coque", description: "Soigne 1/8 des dégâts infligés par un Pokémon" }, + "TOXIC_ORB": { name: "Orbe Toxique", description: "Un orbe bizarre qui empoisonne gravement son porteur durant le combat" }, + "FLAME_ORB": { name: "Orbe Flamme", description: "Un orbe bizarre qui brûle son porteur durant le combat" }, + "BATON": { name: "Bâton", description: "Permet de transmettre les effets en cas de changement de Pokémon. Ignore les pièges." }, "SHINY_CHARM": { name: "Charme Chroma", description: "Augmente énormément les chances de rencontrer un Pokémon sauvage chromatique" }, diff --git a/src/locales/it/modifier-type.ts b/src/locales/it/modifier-type.ts index c7e4cf3f56a..57d9595a224 100644 --- a/src/locales/it/modifier-type.ts +++ b/src/locales/it/modifier-type.ts @@ -209,6 +209,9 @@ export const modifierType: ModifierTypeTranslationEntries = { "LEFTOVERS": { name: "Avanzi", description: "Ripristina 1/16 dei PS massimi di un Pokémon ogni turno" }, "SHELL_BELL": { name: "Conchinella", description: "Guarisce 1/8 del danno inflitto a un Pokémon" }, + "TOXIC_ORB": { name: "Tossicsfera", description: "Sfera bizzarra che iperavvelena chi l’ha con sé in una lotta" }, + "FLAME_ORB": { name: "Fiammosfera", description: "Sfera bizzarra che procura una scottatura a chi l’ha con sé in una lotta" }, + "BATON": { name: "Staffetta", description: "Permette di trasmettere gli effetti quando si cambia Pokémon, aggirando anche le trappole" }, "SHINY_CHARM": { name: "Cromamuleto", description: "Misterioso amuleto luminoso che aumenta la probabilità di incontrare Pokémon cromatici" }, diff --git a/src/locales/pt_BR/modifier-type.ts b/src/locales/pt_BR/modifier-type.ts index df9c7c4745b..051454009f2 100644 --- a/src/locales/pt_BR/modifier-type.ts +++ b/src/locales/pt_BR/modifier-type.ts @@ -209,6 +209,9 @@ export const modifierType: ModifierTypeTranslationEntries = { "LEFTOVERS": { name: "Sobras", description: "Cura 1/16 dos PS máximos de um Pokémon a cada turno" }, "SHELL_BELL": { name: "Concha-Sino", description: "Cura 1/8 do dano causado por um Pokémon" }, + "TOXIC_ORB": { name: "Toxic Orb", description: "It's a bizarre orb that exudes toxins when touched and will badly poison the holder during battle" }, + "FLAME_ORB": { name: "Flame Orb", description: "It's a bizarre orb that gives off heat when touched and will affect the holder with a burn during battle" }, + "BATON": { name: "Bastão", description: "Permite passar mudanças de atributo ao trocar Pokémon, ignorando armadilhas" }, "SHINY_CHARM": { name: "Amuleto Brilhante", description: "Aumenta drasticamente a chance de um Pokémon selvagem ser Shiny" }, diff --git a/src/locales/zh_CN/modifier-type.ts b/src/locales/zh_CN/modifier-type.ts index 79e6106d546..c367000bf48 100644 --- a/src/locales/zh_CN/modifier-type.ts +++ b/src/locales/zh_CN/modifier-type.ts @@ -209,6 +209,9 @@ export const modifierType: ModifierTypeTranslationEntries = { "LEFTOVERS": { name: "吃剩的东西", description: "携带该道具的宝可梦在每个回合结束时恢复\n最大HP的1/16" }, "SHELL_BELL": { name: "贝壳之铃", description: "携带该道具的宝可梦在攻击对方成功造成伤\n害时,携带者的HP会恢复其所造成伤害\n的1/8" }, + "TOXIC_ORB": { name: "Toxic Orb", description: "It's a bizarre orb that exudes toxins when touched and will badly poison the holder during battle" }, + "FLAME_ORB": { name: "Flame Orb", description: "It's a bizarre orb that gives off heat when touched and will affect the holder with a burn during battle" }, + "BATON": { name: "接力棒", description: "允许在切换宝可梦时保留能力变化, 对陷阱\n同样生效" }, "SHINY_CHARM": { name: "闪耀护符", description: "显著增加野生宝可梦的闪光概率" }, diff --git a/src/locales/zh_TW/modifier-type.ts b/src/locales/zh_TW/modifier-type.ts index 7369c64b06a..253f65e84dc 100644 --- a/src/locales/zh_TW/modifier-type.ts +++ b/src/locales/zh_TW/modifier-type.ts @@ -238,6 +238,16 @@ export const modifierType: ModifierTypeTranslationEntries = { description: "攜帶該道具的寶可夢在攻擊對方成功造成傷\n害時,攜帶者的HP會恢復其所造成傷害\n的1/8", }, + TOXIC_ORB: { + name: "Toxic Orb", + description: + "It's a bizarre orb that exudes toxins when touched and will badly poison the holder during battle" + }, + FLAME_ORB: { + name: "Flame Orb", + description: + "It's a bizarre orb that gives off heat when touched and will affect the holder with a burn during battle" + }, BATON: { name: "接力棒", description: "允許在切換寶可夢時保留能力變化, 對陷阱\n同樣生效", From 06ba63dd7dda5454f842593e0f190714404d41c0 Mon Sep 17 00:00:00 2001 From: td76099 <85713900+td76099@users.noreply.github.com> Date: Sat, 1 Jun 2024 15:53:32 -0400 Subject: [PATCH 008/129] [Bug] Liquid Ooze hurts move user instead of one with ability (#1301) * Fixing Liquid Ooze to turn move user and not one with the ability as well as converted strength sap to use same logic instead of making it a separate class * Replaced "undefined" with "null" * Updated based on feedback + fix file permissions * Fixing file permission on battler-tags * Adding localization for drain message * Apparently this file is 755 unlike the others * Removed ability for custom message in exchange for getting to pass Pokemon name * Once again changing moves from 644 to 755 like it is in repo right now :) * putting ability back to 755 (why are file permissions all over the place) --- src/data/ability.ts | 22 +++++++++-- src/data/battler-tags.ts | 3 +- src/data/move.ts | 74 ++++++++++++++++++++++++------------- src/locales/de/battle.ts | 4 +- src/locales/en/battle.ts | 4 +- src/locales/es/battle.ts | 4 +- src/locales/fr/battle.ts | 4 +- src/locales/it/battle.ts | 4 +- src/locales/pt_BR/battle.ts | 4 +- src/locales/zh_CN/battle.ts | 4 +- src/locales/zh_TW/battle.ts | 4 +- src/phases.ts | 5 ++- 12 files changed, 96 insertions(+), 40 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 9cd8c61d47f..31f41492ba7 100755 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -9,7 +9,7 @@ import { BattlerTag } from "./battler-tags"; import { BattlerTagType } from "./enums/battler-tag-type"; import { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect"; import { Gender } from "./gender"; -import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, StatusMoveTypeImmunityAttr, FlinchAttr, OneHitKOAttr, HitHealAttr, StrengthSapHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr } from "./move"; +import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, StatusMoveTypeImmunityAttr, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr } from "./move"; import { ArenaTagSide, ArenaTrapTag } from "./arena-tag"; import { ArenaTagType } from "./enums/arena-tag-type"; import { Stat } from "./pokemon-stat"; @@ -575,10 +575,26 @@ export class MoveImmunityStatChangeAbAttr extends MoveImmunityAbAttr { return ret; } } - +/** + * Class for abilities that make drain moves deal damage to user instead of healing them. + * @extends PostDefendAbAttr + * @see {@linkcode applyPostDefend} + */ export class ReverseDrainAbAttr extends PostDefendAbAttr { + /** + * Determines if a damage and draining move was used to check if this ability should stop the healing. + * Examples include: Absorb, Draining Kiss, Bitter Blade, etc. + * Also displays a message to show this ability was activated. + * @param pokemon {@linkcode Pokemon} with this ability + * @param passive N/A + * @param attacker {@linkcode Pokemon} that is attacking this Pokemon + * @param move {@linkcode PokemonMove} that is being used + * @param hitResult N/A + * @args N/A + * @returns true if healing should be reversed on a healing move, false otherwise. + */ applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { - if (move.getMove().hasAttr(HitHealAttr) || move.getMove().hasAttr(StrengthSapHealAttr) ) { + if (move.getMove().hasAttr(HitHealAttr)) { pokemon.scene.queueMessage(getPokemonMessage(attacker, " sucked up the liquid ooze!")); return true; } diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 13c3954fd01..c4fd5dfb45b 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -391,7 +391,7 @@ export class SeedTag extends BattlerTag { pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, source.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.LEECH_SEED)); const damage = pokemon.damageAndUpdate(Math.max(Math.floor(pokemon.getMaxHp() / 8), 1)); - const reverseDrain = pokemon.hasAbilityWithAttr(ReverseDrainAbAttr); + const reverseDrain = pokemon.hasAbilityWithAttr(ReverseDrainAbAttr, false); pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, source.getBattlerIndex(), !reverseDrain ? damage : damage * -1, !reverseDrain ? getPokemonMessage(pokemon, "'s health is\nsapped by Leech Seed!") : getPokemonMessage(source, "'s Leech Seed\nsucked up the liquid ooze!"), @@ -1479,4 +1479,3 @@ export function loadBattlerTag(source: BattlerTag | any): BattlerTag { tag.loadTag(source); return tag; } - diff --git a/src/data/move.ts b/src/data/move.ts index 5b7bd5f6c82..594b9e73efe 100755 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1201,48 +1201,72 @@ export class BoostHealAttr extends HealAttr { } } +/** + * Heals user as a side effect of a move that hits a target. + * Healing is based on {@linkcode healRatio} * the amount of damage dealt or a stat of the target. + * @extends MoveEffectAttr + * @see {@linkcode apply} + * @see {@linkcode getUserBenefitScore} + */ export class HitHealAttr extends MoveEffectAttr { private healRatio: number; + private message: string; + private healStat: Stat; - constructor(healRatio?: number) { + constructor(healRatio?: number, healStat?: Stat) { super(true, MoveEffectTrigger.HIT); this.healRatio = healRatio || 0.5; + this.healStat = healStat || null; } - + /** + * Heals the user the determined amount and possibly displays a message about regaining health. + * If the target has the {@linkcode ReverseDrainAbAttr}, all healing is instead converted + * to damage to the user. + * @param user {@linkcode Pokemon} using this move + * @param target {@linkcode Pokemon} target of this move + * @param move {@linkcode Move} being used + * @param args N/A + * @returns true if the function succeeds + */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const healAmount = Math.max(Math.floor(user.turnData.damageDealt * this.healRatio), 1); - const reverseDrain = user.hasAbilityWithAttr(ReverseDrainAbAttr); - user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.getBattlerIndex(), - !reverseDrain ? healAmount : healAmount * -1, - !reverseDrain ? getPokemonMessage(target, " had its\nenergy drained!") : undefined, - false, true)); + let healAmount = 0; + let message = ""; + const reverseDrain = target.hasAbilityWithAttr(ReverseDrainAbAttr, false); + if (this.healStat) { + // Strength Sap formula + healAmount = target.getBattleStat(this.healStat); + message = i18next.t("battle:drainMessage", {pokemonName: target.name}); + } else { + // Default healing formula used by draining moves like Absorb, Draining Kiss, Bitter Blade, etc. + healAmount = Math.max(Math.floor(user.turnData.damageDealt * this.healRatio), 1); + message = i18next.t("battle:regainHealth", {pokemonName: user.name}); + } if (reverseDrain) { user.turnData.damageTaken += healAmount; + healAmount = healAmount * -1; + message = null; } + user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.getBattlerIndex(), healAmount, message, false, true)); return true; } + /** + * Used by the Enemy AI to rank an attack based on a given user + * @param user {@linkcode Pokemon} using this move + * @param target {@linkcode Pokemon} target of this move + * @param move {@linkcode Move} being used + * @returns an integer. Higher means enemy is more likely to use that move. + */ getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { - return Math.floor(Math.max((1 - user.getHpRatio()) - 0.33, 0) * ((move.power / 5) / 4)); + if (this.healStat) { + const healAmount = target.getBattleStat(this.healStat); + return Math.floor(Math.max(0, (Math.min(1, (healAmount+user.hp)/user.getMaxHp() - 0.33))) / user.getHpRatio()); + } + return Math.floor(Math.max((1 - user.getHpRatio()) - 0.33, 0) * (move.power / 4)); } } -export class StrengthSapHealAttr extends MoveEffectAttr { - constructor() { - super(true, MoveEffectTrigger.HIT); - } - - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const healAmount = target.stats[Stat.ATK] * (Math.max(2, 2 + target.summonData.battleStats[BattleStat.ATK]) / Math.max(2, 2 - target.summonData.battleStats[BattleStat.ATK])); - const reverseDrain = user.hasAbilityWithAttr(ReverseDrainAbAttr); - user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.getBattlerIndex(), - !reverseDrain ? healAmount : healAmount * -1, - !reverseDrain ? getPokemonMessage(user, " regained\nhealth!") : undefined, - false, true)); - return true; - } -} /** * Attribute used for moves that change priority in a turn given a condition, * e.g. Grassy Glide @@ -6918,7 +6942,7 @@ export function initMoves() { .triageMove(), new AttackMove(Moves.HIGH_HORSEPOWER, Type.GROUND, MoveCategory.PHYSICAL, 95, 95, 10, -1, 0, 7), new StatusMove(Moves.STRENGTH_SAP, Type.GRASS, 100, 10, 100, 0, 7) - .attr(StrengthSapHealAttr) + .attr(HitHealAttr, null, Stat.ATK) .attr(StatChangeAttr, BattleStat.ATK, -1) .condition((user, target, move) => target.summonData.battleStats[BattleStat.ATK] > -6) .triageMove(), diff --git a/src/locales/de/battle.ts b/src/locales/de/battle.ts index e60833cb27a..8c153aa77eb 100644 --- a/src/locales/de/battle.ts +++ b/src/locales/de/battle.ts @@ -55,5 +55,7 @@ export const battle: SimpleTranslationEntries = { "skipItemQuestion": "Bist du sicher, dass du kein Item nehmen willst?", "notDisabled": "{{pokemonName}}'s {{moveName}} ist\nnicht mehr deaktiviert!", "eggHatching": "Oh?", - "ivScannerUseQuestion": "IV-Scanner auf {{pokemonName}} benutzen?" + "ivScannerUseQuestion": "IV-Scanner auf {{pokemonName}} benutzen?", + "drainMessage": "{{pokemonName}} wurde Energie abgesaugt", + "regainHealth": "KP von {{pokemonName}} wurden wieder aufgefrischt!" } as const; diff --git a/src/locales/en/battle.ts b/src/locales/en/battle.ts index a2eb9e4a9c9..a3fa41d9b76 100644 --- a/src/locales/en/battle.ts +++ b/src/locales/en/battle.ts @@ -55,5 +55,7 @@ export const battle: SimpleTranslationEntries = { "notDisabled": "{{pokemonName}}'s {{moveName}} is disabled\nno more!", "skipItemQuestion": "Are you sure you want to skip taking an item?", "eggHatching": "Oh?", - "ivScannerUseQuestion": "Use IV Scanner on {{pokemonName}}?" + "ivScannerUseQuestion": "Use IV Scanner on {{pokemonName}}?", + "drainMessage": "{{pokemonName}} had its\nenergy drained!", + "regainHealth": "{{pokemonName}} regained\nhealth!" } as const; diff --git a/src/locales/es/battle.ts b/src/locales/es/battle.ts index 7b7b71c464d..af090153a04 100644 --- a/src/locales/es/battle.ts +++ b/src/locales/es/battle.ts @@ -55,5 +55,7 @@ export const battle: SimpleTranslationEntries = { "notDisabled": "¡El movimiento {{moveName}} de {{pokemonName}}\nya no está anulado!", "skipItemQuestion": "¿Estás seguro de que no quieres coger un objeto?", "eggHatching": "¿Y esto?", - "ivScannerUseQuestion": "¿Quieres usar el Escáner de IVs en {{pokemonName}}?" + "ivScannerUseQuestion": "¿Quieres usar el Escáner de IVs en {{pokemonName}}?", + "drainMessage": "{{pokemonName}} had its\nenergy drained!", + "regainHealth": "{{pokemonName}} regained\nhealth!" } as const; diff --git a/src/locales/fr/battle.ts b/src/locales/fr/battle.ts index b1ae6f48f35..535ed4b6ade 100644 --- a/src/locales/fr/battle.ts +++ b/src/locales/fr/battle.ts @@ -55,5 +55,7 @@ export const battle: SimpleTranslationEntries = { "notDisabled": "La capacité {{moveName}}\nde {{pokemonName}} n’est plus sous entrave !", "skipItemQuestion": "Êtes-vous sûr·e de ne pas vouloir prendre d’objet ?", "eggHatching": "Oh ?", - "ivScannerUseQuestion": "Utiliser le Scanner d’IV sur {{pokemonName}} ?" + "ivScannerUseQuestion": "Utiliser le Scanner d’IV sur {{pokemonName}} ?", + "drainMessage": "{{pokemonName}} had its\nenergy drained!", + "regainHealth": "{{pokemonName}} regained\nhealth!" } as const; diff --git a/src/locales/it/battle.ts b/src/locales/it/battle.ts index 444e1f01bf0..5b8089e6677 100644 --- a/src/locales/it/battle.ts +++ b/src/locales/it/battle.ts @@ -55,5 +55,7 @@ export const battle: SimpleTranslationEntries = { "notDisabled": "{{pokemonName}}'s {{moveName}} non è più\ndisabilitata!", "skipItemQuestion": "Sei sicuro di non voler prendere nessun oggetto?", "eggHatching": "Oh!", - "ivScannerUseQuestion": "Vuoi usare lo scanner di IV su {{pokemonName}}?" + "ivScannerUseQuestion": "Vuoi usare lo scanner di IV su {{pokemonName}}?", + "drainMessage": "{{pokemonName}} had its\nenergy drained!", + "regainHealth": "{{pokemonName}} regained\nhealth!" } as const; diff --git a/src/locales/pt_BR/battle.ts b/src/locales/pt_BR/battle.ts index b96266ac189..b3563665d9e 100644 --- a/src/locales/pt_BR/battle.ts +++ b/src/locales/pt_BR/battle.ts @@ -55,5 +55,7 @@ export const battle: SimpleTranslationEntries = { "notDisabled": "O movimento {{moveName}}\nnão está mais desabilitado!", "skipItemQuestion": "Tem certeza de que não quer escolher um item?", "eggHatching": "Opa?", - "ivScannerUseQuestion": "Quer usar o Scanner de IVs em {{pokemonName}}?" + "ivScannerUseQuestion": "Quer usar o Scanner de IVs em {{pokemonName}}?", + "drainMessage": "{{pokemonName}} had its\nenergy drained!", + "regainHealth": "{{pokemonName}} regained\nhealth!" } as const; diff --git a/src/locales/zh_CN/battle.ts b/src/locales/zh_CN/battle.ts index 6eb400f5176..cd6357608ac 100644 --- a/src/locales/zh_CN/battle.ts +++ b/src/locales/zh_CN/battle.ts @@ -55,5 +55,7 @@ export const battle: SimpleTranslationEntries = { "notDisabled": "{{moveName}} 不再被禁用!", "skipItemQuestion": "你确定要跳过拾取道具吗?", "eggHatching": "咦?", - "ivScannerUseQuestion": "对 {{pokemonName}} 使用个体值扫描仪?" + "ivScannerUseQuestion": "对 {{pokemonName}} 使用个体值扫描仪?", + "drainMessage": "{{pokemonName}} had its\nenergy drained!", + "regainHealth": "{{pokemonName}} regained\nhealth!" } as const; diff --git a/src/locales/zh_TW/battle.ts b/src/locales/zh_TW/battle.ts index c1fd9155dae..c0846e1cb18 100644 --- a/src/locales/zh_TW/battle.ts +++ b/src/locales/zh_TW/battle.ts @@ -52,5 +52,7 @@ export const battle: SimpleTranslationEntries = { "notDisabled": "{{moveName}} 不再被禁用!", "skipItemQuestion": "你要跳過拾取道具嗎?", "eggHatching": "咦?", - "ivScannerUseQuestion": "對 {{pokemonName}} 使用個體值掃描?" + "ivScannerUseQuestion": "對 {{pokemonName}} 使用個體值掃描?", + "drainMessage": "{{pokemonName}} had its\nenergy drained!", + "regainHealth": "{{pokemonName}} regained\nhealth!" } as const; diff --git a/src/phases.ts b/src/phases.ts index 8b5e8731faf..b896c6f9484 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -4483,9 +4483,10 @@ export class PokemonHealPhase extends CommonAnimPhase { const fullHp = pokemon.getHpRatio() >= 1; const hasMessage = !!this.message; + const healOrDamage = (!fullHp || this.hpHealed < 0); let lastStatusEffect = StatusEffect.NONE; - if (!fullHp || this.hpHealed < 0) { + if (healOrDamage) { const hpRestoreMultiplier = new Utils.IntegerHolder(1); if (!this.revive) { this.scene.applyModifiers(HealingBoosterModifier, this.player, hpRestoreMultiplier); @@ -4530,7 +4531,7 @@ export class PokemonHealPhase extends CommonAnimPhase { this.scene.queueMessage(getPokemonMessage(pokemon, getStatusEffectHealText(lastStatusEffect))); } - if (fullHp && !lastStatusEffect) { + if (!healOrDamage && !lastStatusEffect) { super.end(); } } From 7ee6c979cf91b141b79ba55c13bca6a0c70d4eda Mon Sep 17 00:00:00 2001 From: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> Date: Sun, 2 Jun 2024 01:15:17 +0200 Subject: [PATCH 009/129] Disables all unique double battles but tate and Liza. And those two will now always have lunatone and the other one (solrock?) as their first pokemon (#1654) --- src/battle.ts | 4 ++-- src/field/trainer.ts | 20 +++++++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/battle.ts b/src/battle.ts index 0f893fc5016..f6db308d344 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -314,8 +314,8 @@ function getRandomTrainerFunc(trainerPool: (TrainerType | TrainerType[])[]): Get : trainerPoolEntry; trainerTypes.push(trainerType); } - // If the trainer type has a double variant, there's a 33% chance of it being a double battle - if (trainerConfigs[trainerTypes[rand]].trainerTypeDouble) { + // If the trainer type has a double variant, there's a 33% chance of it being a double battle (for now we only allow tate&liza to be double) + if (trainerConfigs[trainerTypes[rand]].trainerTypeDouble && (trainerTypes[rand] === TrainerType.TATE || trainerTypes[rand] === TrainerType.LIZA)) { return new Trainer(scene, trainerTypes[rand], Utils.randSeedInt(3) ? TrainerVariant.DOUBLE : TrainerVariant.DEFAULT); } return new Trainer(scene, trainerTypes[rand], TrainerVariant.DEFAULT); diff --git a/src/field/trainer.ts b/src/field/trainer.ts index 3e59eaf7832..fb85bfbe8b7 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -20,6 +20,7 @@ import {trainerNamePools} from "../data/trainer-names"; import {ArenaTagSide, ArenaTrapTag} from "#app/data/arena-tag"; import {getIsInitialized, initI18n} from "#app/plugins/i18n"; import i18next from "i18next"; +import {Species} from "#app/data/enums/species"; export enum TrainerVariant { DEFAULT, @@ -314,12 +315,25 @@ export default class Trainer extends Phaser.GameObjects.Container { // If the index is even, use the species pool for the main trainer (that way he only uses his own pokemon in battle) if (!(index % 2)) { - newSpeciesPool = speciesPoolFiltered; + // Since the only currently allowed double battle with named trainers is Tate & Liza, we need to make sure that Solrock is the first pokemon in the party for Tate and Lunatone for Liza + if (index === 0 && (TrainerType[this.config.trainerType] === TrainerType[TrainerType.TATE])) { + newSpeciesPool = [Species.SOLROCK]; + } else if (index === 0 && (TrainerType[this.config.trainerType] === TrainerType[TrainerType.LIZA])) { + newSpeciesPool = [Species.LUNATONE]; + } else { + newSpeciesPool = speciesPoolFiltered; + } } else { // If the index is odd, use the species pool for the partner trainer (that way he only uses his own pokemon in battle) - newSpeciesPool = speciesPoolPartnerFiltered; + // Since the only currently allowed double battle with named trainers is Tate & Liza, we need to make sure that Solrock is the first pokemon in the party for Tate and Lunatone for Liza + if (index === 1 && (TrainerType[this.config.trainerTypeDouble] === TrainerType[TrainerType.TATE])) { + newSpeciesPool = [Species.SOLROCK]; + } else if (index === 1 && (TrainerType[this.config.trainerTypeDouble] === TrainerType[TrainerType.LIZA])) { + newSpeciesPool = [Species.LUNATONE]; + } else { + newSpeciesPool = speciesPoolPartnerFiltered; + } } - // Fallback for when the species pool is empty if (newSpeciesPool.length === 0) { // If all pokemon from this pool are already in the party, generate a random species From c7de17a46ebd190dda380fb59f78b7c11583fb68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Ricardo=20Fleury=20Oliveira?= Date: Sat, 1 Jun 2024 20:53:13 -0300 Subject: [PATCH 010/129] Achievements localization, organized some locales files and ptBR translations (#1150) * organizing, translations and achv localization * localized for all languages * minor fix * minor fix 2 * minor fix 3 * Fixed Achivment Localization, Added german localization and fixes some issues with german localization at other parts * fix pickup description * Update French achv.ts * French typo correction achv.ts * eslint fixes * added zhTW * minor fix * Achivment Bar is localized and gets bigger when using german * Changed some things to make it not as Big * The Achivment Bar now grows with the context * Updated Achivment Names in german * Update French achv.ts * Vouchers now will show the correct descrption again * minor fix * minor fixes * "sub" to "semi" * minor fix * fixes warning and organizes some files * forgot about english * korean translations * test fix --------- Co-authored-by: Jannik Tappert Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> Co-authored-by: Lugiad --- src/locales/de/ability.ts | 12 +- src/locales/de/achv.ts | 172 +++++++++++++++ src/locales/de/config.ts | 60 +++--- src/locales/de/pokemon-info.ts | 4 +- src/locales/de/starter-select-ui-handler.ts | 2 +- src/locales/en/achv.ts | 171 +++++++++++++++ src/locales/en/config.ts | 60 +++--- src/locales/es/achv.ts | 171 +++++++++++++++ src/locales/es/config.ts | 60 +++--- src/locales/fr/achv.ts | 171 +++++++++++++++ src/locales/fr/config.ts | 60 +++--- src/locales/it/achv.ts | 171 +++++++++++++++ src/locales/it/config.ts | 60 +++--- src/locales/ko/achv.ts | 171 +++++++++++++++ src/locales/ko/config.ts | 61 +++--- src/locales/pt_BR/ability.ts | 3 +- src/locales/pt_BR/achv.ts | 171 +++++++++++++++ src/locales/pt_BR/config.ts | 60 +++--- src/locales/pt_BR/modifier-type.ts | 8 +- src/locales/pt_BR/voucher.ts | 14 +- src/locales/zh_CN/achv.ts | 171 +++++++++++++++ src/locales/zh_CN/config.ts | 60 +++--- src/locales/zh_TW/achv.ts | 171 +++++++++++++++ src/locales/zh_TW/config.ts | 60 +++--- src/locales/zh_TW/game-stats-ui-handler.ts | 44 ++++ src/plugins/i18n.ts | 18 +- src/system/achv.ts | 223 ++++++++++++++------ src/test/achievement.test.ts | 6 +- src/ui/achv-bar.ts | 8 +- src/ui/achvs-ui-handler.ts | 12 +- src/ui/starter-select-ui-handler.ts | 11 +- src/ui/text.ts | 10 +- 32 files changed, 2095 insertions(+), 361 deletions(-) create mode 100644 src/locales/de/achv.ts create mode 100644 src/locales/en/achv.ts create mode 100644 src/locales/es/achv.ts create mode 100644 src/locales/fr/achv.ts create mode 100644 src/locales/it/achv.ts create mode 100644 src/locales/ko/achv.ts create mode 100644 src/locales/pt_BR/achv.ts create mode 100644 src/locales/zh_CN/achv.ts create mode 100644 src/locales/zh_TW/achv.ts create mode 100644 src/locales/zh_TW/game-stats-ui-handler.ts diff --git a/src/locales/de/ability.ts b/src/locales/de/ability.ts index 3d8ca7f61f5..4567976e0ef 100644 --- a/src/locales/de/ability.ts +++ b/src/locales/de/ability.ts @@ -894,27 +894,27 @@ export const ability: AbilityTranslationEntries = { description: "Wechselt seine Fähigkeit zu der eines kampfunfähig gewordenen Mitstreiters.", }, beastBoost: { - name: "BestienBoost", + name: "Bestien-Boost", description: "Erhöht in jeder Runde, in der es ein anderes Pokémon besiegt, seinen höchsten Statuswert.", }, rksSystem: { - name: "AlphaSystem", + name: "Alpha-System", description: "Das Pokémon passt seinen Typ der getragenen Disc an.", }, electricSurge: { - name: "ElektroErzeuger", + name: "Elektro-Erzeuger", description: "Erzeugt bei Kampfantritt ein Elektrofeld.", }, psychicSurge: { - name: "PsychoErzeuger", + name: "Psycho-Erzeuger", description: "Erzeugt bei Kampfantritt ein Psychofeld.", }, mistySurge: { - name: "NebelErzeuger", + name: "Nebel-Erzeuger", description: "Erzeugt bei Kampfantritt ein Nebelfeld.", }, grassySurge: { - name: "GrasErzeuger", + name: "Gras-Erzeuger", description: "Erzeugt bei Kampfantritt ein Grasfeld.", }, fullMetalBody: { diff --git a/src/locales/de/achv.ts b/src/locales/de/achv.ts new file mode 100644 index 00000000000..42f2b2cf86c --- /dev/null +++ b/src/locales/de/achv.ts @@ -0,0 +1,172 @@ +import { AchievementTranslationEntries } from "#app/plugins/i18n.js"; + +export const achv: AchievementTranslationEntries = { + "Achievements": { + name: "Errungenschaften", + }, + "Locked": { + name: "Gesperrt", + }, + + + "MoneyAchv": { + description:"Häufe eine Gesamtsumme von {{moneyAmount}} ₽ an", + }, + "10K_MONEY": { + name: "Besserverdiener", + }, + "100K_MONEY": { + name: "Reich", + }, + "1M_MONEY": { + name: "Millionär", + }, + "10M_MONEY": { + name: "Einprozenter", + }, + + "DamageAchv": { + description: "Füge mit einem Treffer {{damageAmount}} Schaden zu", + }, + "250_DMG": { + name: "Harte Treffer", + }, + "1000_DMG": { + name: "Härtere Treffer", + }, + "2500_DMG": { + name: "Das ist ne Menge Schaden!", + }, + "10000_DMG": { + name: "One Punch Man", + }, + + "HealAchv": { + description: "Heile {{healAmount}} {{HP}} auf einmal. Mit einer Attacke, Fähigkeit oder einem gehaltenen Gegenstand", + }, + "250_HEAL": { + name: "Anfänger-Heiler", + }, + "1000_HEAL": { + name: "Gesundheitsprofi", + }, + "2500_HEAL": { + name: "Kleriker", + }, + "10000_HEAL": { + name: "Wiederherstellungsmeister", + }, + + "LevelAchv": { + description: "Erhöhe das Level eines Pokémon auf {{level}}", + }, + "LV_100": { + name: "Warte, es gibt mehr!", + }, + "LV_250": { + name: "Elite", + }, + "LV_1000": { + name: "Geh noch höher hinaus!", + }, + + "RibbonAchv": { + description: "Sammle insgesamt {{ribbonAmount}} Bänder", + }, + "10_RIBBONS": { + name: "Champion der Pokémon Liga", + }, + "25_RIBBONS": { + name: "Bänder-Sammler", + }, + "50_RIBBONS": { + name: "Bänder-Experte", + }, + "75_RIBBONS": { + name: "Bänder-Guru", + }, + "100_RIBBONS": { + name: "Bänder-Meister", + }, + + "TRANSFER_MAX_BATTLE_STAT": { + name: "Teamwork", + description: "Nutze Staffette, während der Anwender mindestens eines Statuswertes maximiert hat", + }, + "MAX_FRIENDSHIP": { + name: "Freundschaftsmaximierung", + description: "Erreiche maximale Freundschaft bei einem Pokémon", + }, + "MEGA_EVOLVE": { + name: "Megaverwandlung", + description: "Megaentwickle ein Pokémon", + }, + "GIGANTAMAX": { + name: "Absolute Einheit", + description: "Gigadynamaximiere ein Pokémon", + }, + "TERASTALLIZE": { + name: "Typen-Bonus Enthusiast", + description: "Terrakristallisiere ein Pokémon", + }, + "STELLAR_TERASTALLIZE": { + name: "Der geheime Typ", + description: "Terrakristallisiere ein Pokémon zum Typen Stellar", + }, + "SPLICE": { + name: "Unendliche Fusion", + description: "Kombiniere zwei Pokémon mit einem DNS-Keil", + }, + "MINI_BLACK_HOLE": { + name: "Ein Loch voller Items", + description: "Erlange ein Mini-Schwarzes Loch", + }, + "CATCH_MYTHICAL": { + name: "Mysteriöses!", + description: "Fange ein mysteriöses Pokémon", + }, + "CATCH_SUB_LEGENDARY": { + name: "Sub-Legendär", + description: "Fange ein sub-legendäres Pokémon", + }, + "CATCH_LEGENDARY": { + name: "Legendär", + description: "Fange ein legendäres Pokémon", + }, + "SEE_SHINY": { + name: "Schillerndes Licht", + description: "Finde ein wildes schillerndes Pokémon", + }, + "SHINY_PARTY": { + name: "Das ist Hingabe", + description: "Habe ein Team aus schillernden Pokémon", + }, + "HATCH_MYTHICAL": { + name: "Mysteriöses Ei", + description: "Lass ein mysteriöses Pokémon aus einem Ei schlüpfen", + }, + "HATCH_SUB_LEGENDARY": { + name: "Sub-Legendäres Ei", + description: "Lass ein sub-legendäres Pokémon aus einem Ei schlüpfen", + }, + "HATCH_LEGENDARY": { + name: "Legendäres Ei", + description: "Lass ein legendäres Pokémon aus einem Ei schlüpfen", + }, + "HATCH_SHINY": { + name: "Schillerndes Ei", + description: "Lass ein schillerndes Pokémon aus einem Ei schlüpfen", + }, + "HIDDEN_ABILITY": { + name: "Geheimes Talent", + description: "Fang ein Pokémon mit versteckter Fähigkeit", + }, + "PERFECT_IVS": { + name: "Zertifikat der Echtheit", + description: "Erhalte ein Pokémon mit perfekten IS-Werten", + }, + "CLASSIC_VICTORY": { + name: "Ungeschlagen", + description: "Beende den klassischen Modus erfolgreich", + }, +} as const; diff --git a/src/locales/de/config.ts b/src/locales/de/config.ts index 50cf09181fa..e243aac135d 100644 --- a/src/locales/de/config.ts +++ b/src/locales/de/config.ts @@ -1,9 +1,24 @@ import { ability } from "./ability"; import { abilityTriggers } from "./ability-trigger"; +import { achv } from "./achv"; import { battle } from "./battle"; +import { battleMessageUiHandler } from "./battle-message-ui-handler"; +import { berry } from "./berry"; +import { biome } from "./biome"; import { commandUiHandler } from "./command-ui-handler"; +import { + PGFbattleSpecDialogue, + PGFdialogue, + PGFdoubleBattleDialogue, + PGFmiscDialogue, + PGMbattleSpecDialogue, + PGMdialogue, + PGMdoubleBattleDialogue, + PGMmiscDialogue +} from "./dialogue"; import { egg } from "./egg"; import { fightUiHandler } from "./fight-ui-handler"; +import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; @@ -13,33 +28,34 @@ import { nature } from "./nature"; import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; import { pokemonInfo } from "./pokemon-info"; +import { pokemonInfoContainer } from "./pokemon-info-container"; import { splashMessages } from "./splash-messages"; import { starterSelectUiHandler } from "./starter-select-ui-handler"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; -import { weather } from "./weather"; -import { battleMessageUiHandler } from "./battle-message-ui-handler"; -import { berry } from "./berry"; -import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { voucher } from "./voucher"; -import { - PGMdialogue, - PGFdialogue, - PGMbattleSpecDialogue, - PGFbattleSpecDialogue, - PGMmiscDialogue, - PGFmiscDialogue, PGMdoubleBattleDialogue, PGFdoubleBattleDialogue -} from "./dialogue"; -import { biome } from "./biome"; -import { pokemonInfoContainer } from "./pokemon-info-container"; +import { weather } from "./weather"; export const deConfig = { ability: ability, abilityTriggers: abilityTriggers, + achv: achv, battle: battle, + battleMessageUiHandler: battleMessageUiHandler, + berry: berry, + biome: biome, commandUiHandler: commandUiHandler, + PGMdialogue: PGMdialogue, + PGFdialogue: PGFdialogue, + PGMbattleSpecDialogue: PGMbattleSpecDialogue, + PGFbattleSpecDialogue: PGFbattleSpecDialogue, + PGMmiscDialogue: PGMmiscDialogue, + PGFmiscDialogue: PGFmiscDialogue, + PGMdoubleBattleDialogue: PGMdoubleBattleDialogue, + PGFdoubleBattleDialogue: PGFdoubleBattleDialogue, egg: egg, fightUiHandler: fightUiHandler, + gameStatsUiHandler: gameStatsUiHandler, growth: growth, menu: menu, menuUiHandler: menuUiHandler, @@ -49,25 +65,13 @@ export const deConfig = { pokeball: pokeball, pokemon: pokemon, pokemonInfo: pokemonInfo, + pokemonInfoContainer: pokemonInfoContainer, splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, titles: titles, trainerClasses: trainerClasses, trainerNames: trainerNames, tutorial: tutorial, - weather: weather, - battleMessageUiHandler: battleMessageUiHandler, - berry: berry, - gameStatsUiHandler: gameStatsUiHandler, voucher: voucher, - biome: biome, - pokemonInfoContainer: pokemonInfoContainer, - PGMdialogue: PGMdialogue, - PGFdialogue: PGFdialogue, - PGMbattleSpecDialogue: PGMbattleSpecDialogue, - PGFbattleSpecDialogue: PGFbattleSpecDialogue, - PGMmiscDialogue: PGMmiscDialogue, - PGFmiscDialogue: PGFmiscDialogue, - PGMdoubleBattleDialogue: PGMdoubleBattleDialogue, - PGFdoubleBattleDialogue: PGFdoubleBattleDialogue + weather: weather }; diff --git a/src/locales/de/pokemon-info.ts b/src/locales/de/pokemon-info.ts index d61e6475299..203ad9e6e1e 100644 --- a/src/locales/de/pokemon-info.ts +++ b/src/locales/de/pokemon-info.ts @@ -2,8 +2,8 @@ import { PokemonInfoTranslationEntries } from "#app/plugins/i18n"; export const pokemonInfo: PokemonInfoTranslationEntries = { Stat: { - "HP": "Max. KP", - "HPshortened": "MaxKP", + "HP": "KP", + "HPshortened": "KP", "ATK": "Angriff", "ATKshortened": "Ang", "DEF": "Verteidigung", diff --git a/src/locales/de/starter-select-ui-handler.ts b/src/locales/de/starter-select-ui-handler.ts index 12061e7ca3f..bae094563cb 100644 --- a/src/locales/de/starter-select-ui-handler.ts +++ b/src/locales/de/starter-select-ui-handler.ts @@ -17,7 +17,7 @@ export const starterSelectUiHandler: SimpleTranslationEntries = { "gen8": "VIII", "gen9": "IX", "growthRate": "Wachstum:", - "ability": "Fähgkeit:", + "ability": "Fähigkeit:", "passive": "Passiv:", "nature": "Wesen:", "eggMoves": "Ei-Attacken", diff --git a/src/locales/en/achv.ts b/src/locales/en/achv.ts new file mode 100644 index 00000000000..42b1995bcde --- /dev/null +++ b/src/locales/en/achv.ts @@ -0,0 +1,171 @@ +import { AchievementTranslationEntries } from "#app/plugins/i18n.js"; + +export const achv: AchievementTranslationEntries = { + "Achievements": { + name: "Achievements", + }, + "Locked": { + name: "Locked", + }, + + "MoneyAchv": { + description: "Accumulate a total of ₽{{moneyAmount}}", + }, + "10K_MONEY": { + name: "Money Haver", + }, + "100K_MONEY": { + name: "Rich", + }, + "1M_MONEY": { + name: "Millionaire", + }, + "10M_MONEY": { + name: "One Percenter", + }, + + "DamageAchv": { + description: "Inflict {{damageAmount}} damage in one hit", + }, + "250_DMG": { + name: "Hard Hitter", + }, + "1000_DMG": { + name: "Harder Hitter", + }, + "2500_DMG": { + name: "That's a Lotta Damage!", + }, + "10000_DMG": { + name: "One Punch Man", + }, + + "HealAchv": { + description: "Heal {{healAmount}} {{HP}} at once with a move, ability, or held item", + }, + "250_HEAL": { + name: "Novice Healer", + }, + "1000_HEAL": { + name: "Big Healer", + }, + "2500_HEAL": { + name: "Cleric", + }, + "10000_HEAL": { + name: "Recovery Master", + }, + + "LevelAchv": { + description: "Level up a Pokémon to Lv{{level}}", + }, + "LV_100": { + name: "But Wait, There's More!", + }, + "LV_250": { + name: "Elite", + }, + "LV_1000": { + name: "To Go Even Further Beyond", + }, + + "RibbonAchv": { + description: "Accumulate a total of {{ribbonAmount}} Ribbons", + }, + "10_RIBBONS": { + name: "Pokémon League Champion", + }, + "25_RIBBONS": { + name: "Great League Champion", + }, + "50_RIBBONS": { + name: "Ultra League Champion", + }, + "75_RIBBONS": { + name: "Rogue League Champion", + }, + "100_RIBBONS": { + name: "Master League Champion", + }, + + "TRANSFER_MAX_BATTLE_STAT": { + name: "Teamwork", + description: "Baton pass to another party member with at least one stat maxed out", + }, + "MAX_FRIENDSHIP": { + name: "Friendmaxxing", + description: "Reach max friendship on a Pokémon", + }, + "MEGA_EVOLVE": { + name: "Megamorph", + description: "Mega evolve a Pokémon", + }, + "GIGANTAMAX": { + name: "Absolute Unit", + description: "Gigantamax a Pokémon", + }, + "TERASTALLIZE": { + name: "STAB Enthusiast", + description: "Terastallize a Pokémon", + }, + "STELLAR_TERASTALLIZE": { + name: "The Hidden Type", + description: "Stellar Terastallize a Pokémon", + }, + "SPLICE": { + name: "Infinite Fusion", + description: "Splice two Pokémon together with DNA Splicers", + }, + "MINI_BLACK_HOLE": { + name: "A Hole Lot of Items", + description: "Acquire a Mini Black Hole", + }, + "CATCH_MYTHICAL": { + name: "Mythical", + description: "Catch a mythical Pokémon", + }, + "CATCH_SUB_LEGENDARY": { + name: "(Sub-)Legendary", + description: "Catch a sub-legendary Pokémon", + }, + "CATCH_LEGENDARY": { + name: "Legendary", + description: "Catch a legendary Pokémon", + }, + "SEE_SHINY": { + name: "Shiny", + description: "Find a shiny Pokémon in the wild", + }, + "SHINY_PARTY": { + name: "That's Dedication", + description: "Have a full party of shiny Pokémon", + }, + "HATCH_MYTHICAL": { + name: "Mythical Egg", + description: "Hatch a mythical Pokémon from an egg", + }, + "HATCH_SUB_LEGENDARY": { + name: "Sub-Legendary Egg", + description: "Hatch a sub-legendary Pokémon from an egg", + }, + "HATCH_LEGENDARY": { + name: "Legendary Egg", + description: "Hatch a legendary Pokémon from an egg", + }, + "HATCH_SHINY": { + name: "Shiny Egg", + description: "Hatch a shiny Pokémon from an egg", + }, + "HIDDEN_ABILITY": { + name: "Hidden Potential", + description: "Catch a Pokémon with a hidden ability", + }, + "PERFECT_IVS": { + name: "Certificate of Authenticity", + description: "Get perfect IVs on a Pokémon", + }, + "CLASSIC_VICTORY": { + name: "Undefeated", + description: "Beat the game in classic mode", + }, +} as const; diff --git a/src/locales/en/config.ts b/src/locales/en/config.ts index d01102bc3a4..0891a6a4c10 100644 --- a/src/locales/en/config.ts +++ b/src/locales/en/config.ts @@ -1,9 +1,24 @@ import { ability } from "./ability"; import { abilityTriggers } from "./ability-trigger"; +import { achv } from "./achv"; import { battle } from "./battle"; +import { battleMessageUiHandler } from "./battle-message-ui-handler"; +import { berry } from "./berry"; +import { biome } from "./biome"; import { commandUiHandler } from "./command-ui-handler"; +import { + PGFbattleSpecDialogue, + PGFdialogue, + PGFdoubleBattleDialogue, + PGFmiscDialogue, + PGMbattleSpecDialogue, + PGMdialogue, + PGMdoubleBattleDialogue, + PGMmiscDialogue +} from "./dialogue"; import { egg } from "./egg"; import { fightUiHandler } from "./fight-ui-handler"; +import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; @@ -13,33 +28,34 @@ import { nature } from "./nature"; import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; import { pokemonInfo } from "./pokemon-info"; +import { pokemonInfoContainer } from "./pokemon-info-container"; import { splashMessages } from "./splash-messages"; import { starterSelectUiHandler } from "./starter-select-ui-handler"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; -import { weather } from "./weather"; -import { battleMessageUiHandler } from "./battle-message-ui-handler"; -import { berry } from "./berry"; -import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { voucher } from "./voucher"; -import { - PGMdialogue, - PGFdialogue, - PGMbattleSpecDialogue, - PGFbattleSpecDialogue, - PGMmiscDialogue, - PGFmiscDialogue, PGMdoubleBattleDialogue, PGFdoubleBattleDialogue -} from "./dialogue"; -import { biome } from "./biome"; -import { pokemonInfoContainer } from "./pokemon-info-container"; +import { weather } from "./weather"; export const enConfig = { ability: ability, abilityTriggers: abilityTriggers, + achv: achv, battle: battle, + battleMessageUiHandler: battleMessageUiHandler, + berry: berry, + biome: biome, commandUiHandler: commandUiHandler, + PGMdialogue: PGMdialogue, + PGFdialogue: PGFdialogue, + PGMbattleSpecDialogue: PGMbattleSpecDialogue, + PGFbattleSpecDialogue: PGFbattleSpecDialogue, + PGMmiscDialogue: PGMmiscDialogue, + PGFmiscDialogue: PGFmiscDialogue, + PGMdoubleBattleDialogue: PGMdoubleBattleDialogue, + PGFdoubleBattleDialogue: PGFdoubleBattleDialogue, egg: egg, fightUiHandler: fightUiHandler, + gameStatsUiHandler: gameStatsUiHandler, growth: growth, menu: menu, menuUiHandler: menuUiHandler, @@ -49,25 +65,13 @@ export const enConfig = { pokeball: pokeball, pokemon: pokemon, pokemonInfo: pokemonInfo, + pokemonInfoContainer: pokemonInfoContainer, splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, titles: titles, trainerClasses: trainerClasses, trainerNames: trainerNames, tutorial: tutorial, - weather: weather, - battleMessageUiHandler: battleMessageUiHandler, - berry: berry, - gameStatsUiHandler: gameStatsUiHandler, voucher: voucher, - biome: biome, - pokemonInfoContainer: pokemonInfoContainer, - PGMdialogue: PGMdialogue, - PGFdialogue: PGFdialogue, - PGMbattleSpecDialogue: PGMbattleSpecDialogue, - PGFbattleSpecDialogue: PGFbattleSpecDialogue, - PGMmiscDialogue: PGMmiscDialogue, - PGFmiscDialogue: PGFmiscDialogue, - PGMdoubleBattleDialogue: PGMdoubleBattleDialogue, - PGFdoubleBattleDialogue: PGFdoubleBattleDialogue + weather: weather }; diff --git a/src/locales/es/achv.ts b/src/locales/es/achv.ts new file mode 100644 index 00000000000..42b1995bcde --- /dev/null +++ b/src/locales/es/achv.ts @@ -0,0 +1,171 @@ +import { AchievementTranslationEntries } from "#app/plugins/i18n.js"; + +export const achv: AchievementTranslationEntries = { + "Achievements": { + name: "Achievements", + }, + "Locked": { + name: "Locked", + }, + + "MoneyAchv": { + description: "Accumulate a total of ₽{{moneyAmount}}", + }, + "10K_MONEY": { + name: "Money Haver", + }, + "100K_MONEY": { + name: "Rich", + }, + "1M_MONEY": { + name: "Millionaire", + }, + "10M_MONEY": { + name: "One Percenter", + }, + + "DamageAchv": { + description: "Inflict {{damageAmount}} damage in one hit", + }, + "250_DMG": { + name: "Hard Hitter", + }, + "1000_DMG": { + name: "Harder Hitter", + }, + "2500_DMG": { + name: "That's a Lotta Damage!", + }, + "10000_DMG": { + name: "One Punch Man", + }, + + "HealAchv": { + description: "Heal {{healAmount}} {{HP}} at once with a move, ability, or held item", + }, + "250_HEAL": { + name: "Novice Healer", + }, + "1000_HEAL": { + name: "Big Healer", + }, + "2500_HEAL": { + name: "Cleric", + }, + "10000_HEAL": { + name: "Recovery Master", + }, + + "LevelAchv": { + description: "Level up a Pokémon to Lv{{level}}", + }, + "LV_100": { + name: "But Wait, There's More!", + }, + "LV_250": { + name: "Elite", + }, + "LV_1000": { + name: "To Go Even Further Beyond", + }, + + "RibbonAchv": { + description: "Accumulate a total of {{ribbonAmount}} Ribbons", + }, + "10_RIBBONS": { + name: "Pokémon League Champion", + }, + "25_RIBBONS": { + name: "Great League Champion", + }, + "50_RIBBONS": { + name: "Ultra League Champion", + }, + "75_RIBBONS": { + name: "Rogue League Champion", + }, + "100_RIBBONS": { + name: "Master League Champion", + }, + + "TRANSFER_MAX_BATTLE_STAT": { + name: "Teamwork", + description: "Baton pass to another party member with at least one stat maxed out", + }, + "MAX_FRIENDSHIP": { + name: "Friendmaxxing", + description: "Reach max friendship on a Pokémon", + }, + "MEGA_EVOLVE": { + name: "Megamorph", + description: "Mega evolve a Pokémon", + }, + "GIGANTAMAX": { + name: "Absolute Unit", + description: "Gigantamax a Pokémon", + }, + "TERASTALLIZE": { + name: "STAB Enthusiast", + description: "Terastallize a Pokémon", + }, + "STELLAR_TERASTALLIZE": { + name: "The Hidden Type", + description: "Stellar Terastallize a Pokémon", + }, + "SPLICE": { + name: "Infinite Fusion", + description: "Splice two Pokémon together with DNA Splicers", + }, + "MINI_BLACK_HOLE": { + name: "A Hole Lot of Items", + description: "Acquire a Mini Black Hole", + }, + "CATCH_MYTHICAL": { + name: "Mythical", + description: "Catch a mythical Pokémon", + }, + "CATCH_SUB_LEGENDARY": { + name: "(Sub-)Legendary", + description: "Catch a sub-legendary Pokémon", + }, + "CATCH_LEGENDARY": { + name: "Legendary", + description: "Catch a legendary Pokémon", + }, + "SEE_SHINY": { + name: "Shiny", + description: "Find a shiny Pokémon in the wild", + }, + "SHINY_PARTY": { + name: "That's Dedication", + description: "Have a full party of shiny Pokémon", + }, + "HATCH_MYTHICAL": { + name: "Mythical Egg", + description: "Hatch a mythical Pokémon from an egg", + }, + "HATCH_SUB_LEGENDARY": { + name: "Sub-Legendary Egg", + description: "Hatch a sub-legendary Pokémon from an egg", + }, + "HATCH_LEGENDARY": { + name: "Legendary Egg", + description: "Hatch a legendary Pokémon from an egg", + }, + "HATCH_SHINY": { + name: "Shiny Egg", + description: "Hatch a shiny Pokémon from an egg", + }, + "HIDDEN_ABILITY": { + name: "Hidden Potential", + description: "Catch a Pokémon with a hidden ability", + }, + "PERFECT_IVS": { + name: "Certificate of Authenticity", + description: "Get perfect IVs on a Pokémon", + }, + "CLASSIC_VICTORY": { + name: "Undefeated", + description: "Beat the game in classic mode", + }, +} as const; diff --git a/src/locales/es/config.ts b/src/locales/es/config.ts index 2f33cd680ae..7e1935a23a0 100644 --- a/src/locales/es/config.ts +++ b/src/locales/es/config.ts @@ -1,9 +1,24 @@ import { ability } from "./ability"; import { abilityTriggers } from "./ability-trigger"; +import { achv } from "./achv"; import { battle } from "./battle"; +import { battleMessageUiHandler } from "./battle-message-ui-handler"; +import { berry } from "./berry"; +import { biome } from "./biome"; import { commandUiHandler } from "./command-ui-handler"; +import { + PGFbattleSpecDialogue, + PGFdialogue, + PGFdoubleBattleDialogue, + PGFmiscDialogue, + PGMbattleSpecDialogue, + PGMdialogue, + PGMdoubleBattleDialogue, + PGMmiscDialogue +} from "./dialogue"; import { egg } from "./egg"; import { fightUiHandler } from "./fight-ui-handler"; +import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; @@ -13,33 +28,34 @@ import { nature } from "./nature"; import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; import { pokemonInfo } from "./pokemon-info"; +import { pokemonInfoContainer } from "./pokemon-info-container"; import { splashMessages } from "./splash-messages"; import { starterSelectUiHandler } from "./starter-select-ui-handler"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; -import { weather } from "./weather"; -import { battleMessageUiHandler } from "./battle-message-ui-handler"; -import { berry } from "./berry"; -import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { voucher } from "./voucher"; -import { - PGMdialogue, - PGFdialogue, - PGMbattleSpecDialogue, - PGFbattleSpecDialogue, - PGMmiscDialogue, - PGFmiscDialogue, PGMdoubleBattleDialogue, PGFdoubleBattleDialogue -} from "./dialogue"; -import { biome } from "./biome"; -import { pokemonInfoContainer } from "./pokemon-info-container"; +import { weather } from "./weather"; export const esConfig = { ability: ability, abilityTriggers: abilityTriggers, + achv: achv, battle: battle, + battleMessageUiHandler: battleMessageUiHandler, + berry: berry, + biome: biome, commandUiHandler: commandUiHandler, + PGMdialogue: PGMdialogue, + PGFdialogue: PGFdialogue, + PGMbattleSpecDialogue: PGMbattleSpecDialogue, + PGFbattleSpecDialogue: PGFbattleSpecDialogue, + PGMmiscDialogue: PGMmiscDialogue, + PGFmiscDialogue: PGFmiscDialogue, + PGMdoubleBattleDialogue: PGMdoubleBattleDialogue, + PGFdoubleBattleDialogue: PGFdoubleBattleDialogue, egg: egg, fightUiHandler: fightUiHandler, + gameStatsUiHandler: gameStatsUiHandler, growth: growth, menu: menu, menuUiHandler: menuUiHandler, @@ -49,25 +65,13 @@ export const esConfig = { pokeball: pokeball, pokemon: pokemon, pokemonInfo: pokemonInfo, + pokemonInfoContainer: pokemonInfoContainer, splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, titles: titles, trainerClasses: trainerClasses, trainerNames: trainerNames, tutorial: tutorial, - weather: weather, - battleMessageUiHandler: battleMessageUiHandler, - berry: berry, - gameStatsUiHandler: gameStatsUiHandler, voucher: voucher, - biome: biome, - pokemonInfoContainer: pokemonInfoContainer, - PGMdialogue: PGMdialogue, - PGFdialogue: PGFdialogue, - PGMbattleSpecDialogue: PGMbattleSpecDialogue, - PGFbattleSpecDialogue: PGFbattleSpecDialogue, - PGMmiscDialogue: PGMmiscDialogue, - PGFmiscDialogue: PGFmiscDialogue, - PGMdoubleBattleDialogue: PGMdoubleBattleDialogue, - PGFdoubleBattleDialogue: PGFdoubleBattleDialogue + weather: weather }; diff --git a/src/locales/fr/achv.ts b/src/locales/fr/achv.ts new file mode 100644 index 00000000000..6383c6817c0 --- /dev/null +++ b/src/locales/fr/achv.ts @@ -0,0 +1,171 @@ +import { AchievementTranslationEntries } from "#app/plugins/i18n.js"; + +export const achv: AchievementTranslationEntries = { + "Achievements": { + name: "Succès", + }, + "Locked": { + name: "Verrouillé", + }, + + "MoneyAchv": { + description: "Récolter un total de {{moneyAmount}} ₽", + }, + "10K_MONEY": { + name: "Épargnant", + }, + "100K_MONEY": { + name: "Je possède des thunes", + }, + "1M_MONEY": { + name: "Banquier", + }, + "10M_MONEY": { + name: "Évadé·e fiscal·e", + }, + + "DamageAchv": { + description: "Infliger {{damageAmount}} de dégâts en un coup", + }, + "250_DMG": { + name: "Caïd", + }, + "1000_DMG": { + name: "Boxeur", + }, + "2500_DMG": { + name: "Distributeur de pains", + }, + "10000_DMG": { + name: "One Punch Man", + }, + + "HealAchv": { + description: "Soigner {{healAmount}} {{HP}} en une fois avec une capacité, un talent ou un objet tenu", + }, + "250_HEAL": { + name: "Infirmier·è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: "Maitre·sse de la Ligue", + }, + "25_RIBBONS": { + name: "Super Maitre·sse de la Ligue", + }, + "50_RIBBONS": { + name: "Hyper Maitre·sse de la Ligue", + }, + "75_RIBBONS": { + name: "Rogue Maitre·sse de la Ligue", + }, + "100_RIBBONS": { + name: "Master Maitre·sse 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: "Shasseur", + 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: "Invaincu·e", + description: "Terminer le jeu en mode classique", + }, +} as const; diff --git a/src/locales/fr/config.ts b/src/locales/fr/config.ts index f7f7201ddd7..ee9e9a1e2ac 100644 --- a/src/locales/fr/config.ts +++ b/src/locales/fr/config.ts @@ -1,9 +1,24 @@ import { ability } from "./ability"; import { abilityTriggers } from "./ability-trigger"; +import { achv } from "./achv"; import { battle } from "./battle"; +import { battleMessageUiHandler } from "./battle-message-ui-handler"; +import { berry } from "./berry"; +import { biome } from "./biome"; import { commandUiHandler } from "./command-ui-handler"; +import { + PGFbattleSpecDialogue, + PGFdialogue, + PGFdoubleBattleDialogue, + PGFmiscDialogue, + PGMbattleSpecDialogue, + PGMdialogue, + PGMdoubleBattleDialogue, + PGMmiscDialogue +} from "./dialogue"; import { egg } from "./egg"; import { fightUiHandler } from "./fight-ui-handler"; +import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; @@ -13,33 +28,34 @@ import { nature } from "./nature"; import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; import { pokemonInfo } from "./pokemon-info"; +import { pokemonInfoContainer } from "./pokemon-info-container"; import { splashMessages } from "./splash-messages"; import { starterSelectUiHandler } from "./starter-select-ui-handler"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; -import { weather } from "./weather"; -import { battleMessageUiHandler } from "./battle-message-ui-handler"; -import { berry } from "./berry"; -import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { voucher } from "./voucher"; -import { - PGMdialogue, - PGFdialogue, - PGMbattleSpecDialogue, - PGFbattleSpecDialogue, - PGMmiscDialogue, - PGFmiscDialogue, PGMdoubleBattleDialogue, PGFdoubleBattleDialogue -} from "./dialogue"; -import { biome } from "./biome"; -import { pokemonInfoContainer } from "./pokemon-info-container"; +import { weather } from "./weather"; export const frConfig = { ability: ability, abilityTriggers: abilityTriggers, + achv: achv, battle: battle, + battleMessageUiHandler: battleMessageUiHandler, + berry: berry, + biome: biome, commandUiHandler: commandUiHandler, + PGMdialogue: PGMdialogue, + PGFdialogue: PGFdialogue, + PGMbattleSpecDialogue: PGMbattleSpecDialogue, + PGFbattleSpecDialogue: PGFbattleSpecDialogue, + PGMmiscDialogue: PGMmiscDialogue, + PGFmiscDialogue: PGFmiscDialogue, + PGMdoubleBattleDialogue: PGMdoubleBattleDialogue, + PGFdoubleBattleDialogue: PGFdoubleBattleDialogue, egg: egg, fightUiHandler: fightUiHandler, + gameStatsUiHandler: gameStatsUiHandler, growth: growth, menu: menu, menuUiHandler: menuUiHandler, @@ -49,25 +65,13 @@ export const frConfig = { pokeball: pokeball, pokemon: pokemon, pokemonInfo: pokemonInfo, + pokemonInfoContainer: pokemonInfoContainer, splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, titles: titles, trainerClasses: trainerClasses, trainerNames: trainerNames, tutorial: tutorial, - weather: weather, - battleMessageUiHandler: battleMessageUiHandler, - berry: berry, - gameStatsUiHandler: gameStatsUiHandler, voucher: voucher, - biome: biome, - pokemonInfoContainer: pokemonInfoContainer, - PGMdialogue: PGMdialogue, - PGFdialogue: PGFdialogue, - PGMbattleSpecDialogue: PGMbattleSpecDialogue, - PGFbattleSpecDialogue: PGFbattleSpecDialogue, - PGMmiscDialogue: PGMmiscDialogue, - PGFmiscDialogue: PGFmiscDialogue, - PGMdoubleBattleDialogue: PGMdoubleBattleDialogue, - PGFdoubleBattleDialogue: PGFdoubleBattleDialogue + weather: weather }; diff --git a/src/locales/it/achv.ts b/src/locales/it/achv.ts new file mode 100644 index 00000000000..42b1995bcde --- /dev/null +++ b/src/locales/it/achv.ts @@ -0,0 +1,171 @@ +import { AchievementTranslationEntries } from "#app/plugins/i18n.js"; + +export const achv: AchievementTranslationEntries = { + "Achievements": { + name: "Achievements", + }, + "Locked": { + name: "Locked", + }, + + "MoneyAchv": { + description: "Accumulate a total of ₽{{moneyAmount}}", + }, + "10K_MONEY": { + name: "Money Haver", + }, + "100K_MONEY": { + name: "Rich", + }, + "1M_MONEY": { + name: "Millionaire", + }, + "10M_MONEY": { + name: "One Percenter", + }, + + "DamageAchv": { + description: "Inflict {{damageAmount}} damage in one hit", + }, + "250_DMG": { + name: "Hard Hitter", + }, + "1000_DMG": { + name: "Harder Hitter", + }, + "2500_DMG": { + name: "That's a Lotta Damage!", + }, + "10000_DMG": { + name: "One Punch Man", + }, + + "HealAchv": { + description: "Heal {{healAmount}} {{HP}} at once with a move, ability, or held item", + }, + "250_HEAL": { + name: "Novice Healer", + }, + "1000_HEAL": { + name: "Big Healer", + }, + "2500_HEAL": { + name: "Cleric", + }, + "10000_HEAL": { + name: "Recovery Master", + }, + + "LevelAchv": { + description: "Level up a Pokémon to Lv{{level}}", + }, + "LV_100": { + name: "But Wait, There's More!", + }, + "LV_250": { + name: "Elite", + }, + "LV_1000": { + name: "To Go Even Further Beyond", + }, + + "RibbonAchv": { + description: "Accumulate a total of {{ribbonAmount}} Ribbons", + }, + "10_RIBBONS": { + name: "Pokémon League Champion", + }, + "25_RIBBONS": { + name: "Great League Champion", + }, + "50_RIBBONS": { + name: "Ultra League Champion", + }, + "75_RIBBONS": { + name: "Rogue League Champion", + }, + "100_RIBBONS": { + name: "Master League Champion", + }, + + "TRANSFER_MAX_BATTLE_STAT": { + name: "Teamwork", + description: "Baton pass to another party member with at least one stat maxed out", + }, + "MAX_FRIENDSHIP": { + name: "Friendmaxxing", + description: "Reach max friendship on a Pokémon", + }, + "MEGA_EVOLVE": { + name: "Megamorph", + description: "Mega evolve a Pokémon", + }, + "GIGANTAMAX": { + name: "Absolute Unit", + description: "Gigantamax a Pokémon", + }, + "TERASTALLIZE": { + name: "STAB Enthusiast", + description: "Terastallize a Pokémon", + }, + "STELLAR_TERASTALLIZE": { + name: "The Hidden Type", + description: "Stellar Terastallize a Pokémon", + }, + "SPLICE": { + name: "Infinite Fusion", + description: "Splice two Pokémon together with DNA Splicers", + }, + "MINI_BLACK_HOLE": { + name: "A Hole Lot of Items", + description: "Acquire a Mini Black Hole", + }, + "CATCH_MYTHICAL": { + name: "Mythical", + description: "Catch a mythical Pokémon", + }, + "CATCH_SUB_LEGENDARY": { + name: "(Sub-)Legendary", + description: "Catch a sub-legendary Pokémon", + }, + "CATCH_LEGENDARY": { + name: "Legendary", + description: "Catch a legendary Pokémon", + }, + "SEE_SHINY": { + name: "Shiny", + description: "Find a shiny Pokémon in the wild", + }, + "SHINY_PARTY": { + name: "That's Dedication", + description: "Have a full party of shiny Pokémon", + }, + "HATCH_MYTHICAL": { + name: "Mythical Egg", + description: "Hatch a mythical Pokémon from an egg", + }, + "HATCH_SUB_LEGENDARY": { + name: "Sub-Legendary Egg", + description: "Hatch a sub-legendary Pokémon from an egg", + }, + "HATCH_LEGENDARY": { + name: "Legendary Egg", + description: "Hatch a legendary Pokémon from an egg", + }, + "HATCH_SHINY": { + name: "Shiny Egg", + description: "Hatch a shiny Pokémon from an egg", + }, + "HIDDEN_ABILITY": { + name: "Hidden Potential", + description: "Catch a Pokémon with a hidden ability", + }, + "PERFECT_IVS": { + name: "Certificate of Authenticity", + description: "Get perfect IVs on a Pokémon", + }, + "CLASSIC_VICTORY": { + name: "Undefeated", + description: "Beat the game in classic mode", + }, +} as const; diff --git a/src/locales/it/config.ts b/src/locales/it/config.ts index 5f76e4d4205..8549afb12be 100644 --- a/src/locales/it/config.ts +++ b/src/locales/it/config.ts @@ -1,9 +1,24 @@ import { ability } from "./ability"; import { abilityTriggers } from "./ability-trigger"; +import { achv } from "./achv"; import { battle } from "./battle"; +import { battleMessageUiHandler } from "./battle-message-ui-handler"; +import { berry } from "./berry"; +import { biome } from "./biome"; import { commandUiHandler } from "./command-ui-handler"; +import { + PGFbattleSpecDialogue, + PGFdialogue, + PGFdoubleBattleDialogue, + PGFmiscDialogue, + PGMbattleSpecDialogue, + PGMdialogue, + PGMdoubleBattleDialogue, + PGMmiscDialogue +} from "./dialogue"; import { egg } from "./egg"; import { fightUiHandler } from "./fight-ui-handler"; +import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; @@ -13,33 +28,34 @@ import { nature } from "./nature"; import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; import { pokemonInfo } from "./pokemon-info"; +import { pokemonInfoContainer } from "./pokemon-info-container"; import { splashMessages } from "./splash-messages"; import { starterSelectUiHandler } from "./starter-select-ui-handler"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; -import { weather } from "./weather"; -import { battleMessageUiHandler } from "./battle-message-ui-handler"; -import { berry } from "./berry"; -import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { voucher } from "./voucher"; -import { - PGMdialogue, - PGFdialogue, - PGMbattleSpecDialogue, - PGFbattleSpecDialogue, - PGMmiscDialogue, - PGFmiscDialogue, PGMdoubleBattleDialogue, PGFdoubleBattleDialogue -} from "./dialogue"; -import { biome } from "./biome"; -import { pokemonInfoContainer } from "./pokemon-info-container"; +import { weather } from "./weather"; export const itConfig = { ability: ability, abilityTriggers: abilityTriggers, + achv: achv, battle: battle, + battleMessageUiHandler: battleMessageUiHandler, + berry: berry, + biome: biome, commandUiHandler: commandUiHandler, + PGMdialogue: PGMdialogue, + PGFdialogue: PGFdialogue, + PGMbattleSpecDialogue: PGMbattleSpecDialogue, + PGFbattleSpecDialogue: PGFbattleSpecDialogue, + PGMmiscDialogue: PGMmiscDialogue, + PGFmiscDialogue: PGFmiscDialogue, + PGMdoubleBattleDialogue: PGMdoubleBattleDialogue, + PGFdoubleBattleDialogue: PGFdoubleBattleDialogue, egg: egg, fightUiHandler: fightUiHandler, + gameStatsUiHandler: gameStatsUiHandler, growth: growth, menu: menu, menuUiHandler: menuUiHandler, @@ -49,25 +65,13 @@ export const itConfig = { pokeball: pokeball, pokemon: pokemon, pokemonInfo: pokemonInfo, + pokemonInfoContainer: pokemonInfoContainer, splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, titles: titles, trainerClasses: trainerClasses, trainerNames: trainerNames, tutorial: tutorial, - weather: weather, - battleMessageUiHandler: battleMessageUiHandler, - berry: berry, - gameStatsUiHandler: gameStatsUiHandler, voucher: voucher, - biome: biome, - pokemonInfoContainer: pokemonInfoContainer, - PGMdialogue: PGMdialogue, - PGFdialogue: PGFdialogue, - PGMbattleSpecDialogue: PGMbattleSpecDialogue, - PGFbattleSpecDialogue: PGFbattleSpecDialogue, - PGMmiscDialogue: PGMmiscDialogue, - PGFmiscDialogue: PGFmiscDialogue, - PGMdoubleBattleDialogue: PGMdoubleBattleDialogue, - PGFdoubleBattleDialogue: PGFdoubleBattleDialogue + weather: weather }; diff --git a/src/locales/ko/achv.ts b/src/locales/ko/achv.ts new file mode 100644 index 00000000000..d48a63868b1 --- /dev/null +++ b/src/locales/ko/achv.ts @@ -0,0 +1,171 @@ +import { AchievementTranslationEntries } from "#app/plugins/i18n.js"; + +export const achv: AchievementTranslationEntries = { + "Achievements": { + name: "업적", + }, + "Locked": { + name: "미완료", + }, + + "MoneyAchv": { + description: "누적 소지금 ₽{{moneyAmount}} 달성", + }, + "10K_MONEY": { + name: "돈 좀 있나?", + }, + "100K_MONEY": { + name: "부자", + }, + "1M_MONEY": { + name: "백만장자", + }, + "10M_MONEY": { + name: "상위 1프로", + }, + + "DamageAchv": { + description: "한 번의 공격만으로 {{damageAmount}} 대미지", + }, + "250_DMG": { + name: "강타자", + }, + "1000_DMG": { + name: "최강타자", + }, + "2500_DMG": { + name: "때릴 줄 아시는군요!", + }, + "10000_DMG": { + name: "원펀맨", + }, + + "HealAchv": { + description: "기술이나 특성, 지닌 도구로 한 번에 {{healAmount}} {{HP}} 회복", + }, + "250_HEAL": { + name: "견습 힐러", + }, + "1000_HEAL": { + name: "상급 힐러", + }, + "2500_HEAL": { + name: "클레릭", + }, + "10000_HEAL": { + name: "회복 마스터", + }, + + "LevelAchv": { + description: "포켓몬 Lv{{level}} 달성", + }, + "LV_100": { + name: "잠깐, 여기가 끝이 아니라구!", + }, + "LV_250": { + name: "엘리트", + }, + "LV_1000": { + name: "더 먼 곳을 향해", + }, + + "RibbonAchv": { + description: "총 {{ribbonAmount}}개의 리본 획득", + }, + "10_RIBBONS": { + name: "포켓몬 리그 챔피언", + }, + "25_RIBBONS": { + name: "슈퍼 리그 챔피언", + }, + "50_RIBBONS": { + name: "하이퍼 리그 챔피언", + }, + "75_RIBBONS": { + name: "로그 리그 챔피언", + }, + "100_RIBBONS": { + name: "마스터 리그 챔피언", + }, + + "TRANSFER_MAX_BATTLE_STAT": { + name: "팀워크", + description: "한 개 이상의 능력치가 최대 랭크일 때 배턴터치 사용", + }, + "MAX_FRIENDSHIP": { + name: "친밀 맥스", + description: "최대 친밀도 달성", + }, + "MEGA_EVOLVE": { + name: "메가변환", + description: "포켓몬을 메가진화", + }, + "GIGANTAMAX": { + name: "엄청난 것", + description: "포켓몬을 다이맥스", + }, + "TERASTALLIZE": { + name: "반짝반짝", + description: "포켓몬을 테라스탈", + }, + "STELLAR_TERASTALLIZE": { + name: "숨겨진 타입", + description: "포켓몬을 스텔라 테라스탈", + }, + "SPLICE": { + name: "끝없는 융합", + description: "유전자쐐기로 두 포켓몬을 융합", + }, + "MINI_BLACK_HOLE": { + name: "도구가 가득한 구멍", + description: "미니 블랙홀 획득", + }, + "CATCH_MYTHICAL": { + name: "환상", + description: "환상의 포켓몬 포획", + }, + "CATCH_SUB_LEGENDARY": { + name: "(준)전설", + description: "준전설 포켓몬 포획", + }, + "CATCH_LEGENDARY": { + name: "전설", + description: "전설의 포켓몬 포획", + }, + "SEE_SHINY": { + name: "다른 색", + description: "야생의 색이 다른 포켓몬 발견", + }, + "SHINY_PARTY": { + name: "찐사랑", + description: "색이 다른 포켓몬만으로 파티 구성", + }, + "HATCH_MYTHICAL": { + name: "환상의 알", + description: "알에서 환상의 포켓몬이 부화", + }, + "HATCH_SUB_LEGENDARY": { + name: "준전설 알", + description: "알에서 준전설 포켓몬이 부화", + }, + "HATCH_LEGENDARY": { + name: "전설의 알", + description: "알에서 전설의 포켓몬이 부화", + }, + "HATCH_SHINY": { + name: "빛나는 알", + description: "알에서 색이 다른 포켓몬이 부화", + }, + "HIDDEN_ABILITY": { + name: "숨은 잠재력", + description: "숨겨진 특성을 지닌 포켓몬을 포획", + }, + "PERFECT_IVS": { + name: "진짜배기 증명서", + description: "최고의 개체값을 지닌 포켓몬 획득", + }, + "CLASSIC_VICTORY": { + name: "무패", + description: "클래식 모드 클리어", + }, +} as const; diff --git a/src/locales/ko/config.ts b/src/locales/ko/config.ts index 3de0e3275c2..ad0035ede0f 100644 --- a/src/locales/ko/config.ts +++ b/src/locales/ko/config.ts @@ -1,9 +1,24 @@ import { ability } from "./ability"; import { abilityTriggers } from "./ability-trigger"; +import { achv } from "./achv"; import { battle } from "./battle"; +import { battleMessageUiHandler } from "./battle-message-ui-handler"; +import { berry } from "./berry"; +import { biome } from "./biome"; import { commandUiHandler } from "./command-ui-handler"; +import { + PGFbattleSpecDialogue, + PGFdialogue, + PGFdoubleBattleDialogue, + PGFmiscDialogue, + PGMbattleSpecDialogue, + PGMdialogue, + PGMdoubleBattleDialogue, + PGMmiscDialogue +} from "./dialogue"; import { egg } from "./egg"; import { fightUiHandler } from "./fight-ui-handler"; +import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; @@ -13,34 +28,34 @@ import { nature } from "./nature"; import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; import { pokemonInfo } from "./pokemon-info"; +import { pokemonInfoContainer } from "./pokemon-info-container"; import { splashMessages } from "./splash-messages"; import { starterSelectUiHandler } from "./starter-select-ui-handler"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; -import { weather } from "./weather"; -import { battleMessageUiHandler } from "./battle-message-ui-handler"; -import { berry } from "./berry"; -import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { voucher } from "./voucher"; -import { - PGMdialogue, - PGFdialogue, - PGMbattleSpecDialogue, - PGFbattleSpecDialogue, - PGMmiscDialogue, - PGFmiscDialogue, PGMdoubleBattleDialogue, PGFdoubleBattleDialogue -} from "./dialogue"; -import { biome } from "./biome"; -import { pokemonInfoContainer } from "./pokemon-info-container"; - +import { weather } from "./weather"; export const koConfig = { ability: ability, abilityTriggers: abilityTriggers, + achv: achv, battle: battle, + battleMessageUiHandler: battleMessageUiHandler, + berry: berry, + biome: biome, commandUiHandler: commandUiHandler, + PGMdialogue: PGMdialogue, + PGFdialogue: PGFdialogue, + PGMbattleSpecDialogue: PGMbattleSpecDialogue, + PGFbattleSpecDialogue: PGFbattleSpecDialogue, + PGMmiscDialogue: PGMmiscDialogue, + PGFmiscDialogue: PGFmiscDialogue, + PGMdoubleBattleDialogue: PGMdoubleBattleDialogue, + PGFdoubleBattleDialogue: PGFdoubleBattleDialogue, egg: egg, fightUiHandler: fightUiHandler, + gameStatsUiHandler: gameStatsUiHandler, growth: growth, menu: menu, menuUiHandler: menuUiHandler, @@ -50,25 +65,13 @@ export const koConfig = { pokeball: pokeball, pokemon: pokemon, pokemonInfo: pokemonInfo, + pokemonInfoContainer: pokemonInfoContainer, splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, titles: titles, trainerClasses: trainerClasses, trainerNames: trainerNames, tutorial: tutorial, - weather: weather, - battleMessageUiHandler: battleMessageUiHandler, - berry: berry, - gameStatsUiHandler: gameStatsUiHandler, voucher: voucher, - biome: biome, - pokemonInfoContainer: pokemonInfoContainer, - PGMdialogue: PGMdialogue, - PGFdialogue: PGFdialogue, - PGMbattleSpecDialogue: PGMbattleSpecDialogue, - PGFbattleSpecDialogue: PGFbattleSpecDialogue, - PGMmiscDialogue: PGMmiscDialogue, - PGFmiscDialogue: PGFmiscDialogue, - PGMdoubleBattleDialogue: PGMdoubleBattleDialogue, - PGFdoubleBattleDialogue: PGFdoubleBattleDialogue + weather: weather }; diff --git a/src/locales/pt_BR/ability.ts b/src/locales/pt_BR/ability.ts index 05ef735bfde..48e8790b227 100644 --- a/src/locales/pt_BR/ability.ts +++ b/src/locales/pt_BR/ability.ts @@ -211,7 +211,7 @@ export const ability: AbilityTranslationEntries = { }, pickup: { name: "Pickup", - description: "Durante a batalha, o Pokémon pode tomar o item do Pokémon adversário. Fora de batalha pode encontrar itens pelo chão.", + description: "Após uma batalha, o Pokémon pegará um item que um adversário deixou cair.", }, truant: { name: "Truant", @@ -1237,5 +1237,4 @@ export const ability: AbilityTranslationEntries = { name: "Poison Puppeteer", description: "Pokémon envenenados pelos movimentos de Pecharunt também ficarão confusos.", }, - } as const; diff --git a/src/locales/pt_BR/achv.ts b/src/locales/pt_BR/achv.ts new file mode 100644 index 00000000000..5aaccc465ac --- /dev/null +++ b/src/locales/pt_BR/achv.ts @@ -0,0 +1,171 @@ +import { AchievementTranslationEntries } from "#app/plugins/i18n.js"; + +export const achv: AchievementTranslationEntries = { + "Achievements": { + name: "Conquistas", + }, + "Locked": { + name: "Não conquistado", + }, + + "MoneyAchv": { + description: "Acumule um total de ₽{{moneyAmount}}", + }, + "10K_MONEY": { + name: "Chuva de Dinheiro", + }, + "100K_MONEY": { + name: "Tô Rica!", + }, + "1M_MONEY": { + name: "Quem Quer Ser Um Milionário?", + }, + "10M_MONEY": { + name: "Tio Patinhas", + }, + + "DamageAchv": { + description: "Inflija {{damageAmount}} de dano em um único golpe", + }, + "250_DMG": { + name: "Essa Doeu!", + }, + "1000_DMG": { + name: "Essa Doeu Mais!", + }, + "2500_DMG": { + name: "Essa Doeu Muito!", + }, + "10000_DMG": { + name: "Essa Doeu Pra Caramba!", + }, + + "HealAchv": { + description: "Cure {{healAmount}} {{HP}} de uma vez só com um movimento, habilidade ou item segurado", + }, + "250_HEAL": { + name: "Residente", + }, + "1000_HEAL": { + name: "Enfermeiro", + }, + "2500_HEAL": { + name: "Médico", + }, + "10000_HEAL": { + name: "Médico de Plantão", + }, + + "LevelAchv": { + description: "Aumente o nível de um Pokémon para o Nv{{level}}", + }, + "LV_100": { + name: "Calma Que Tem Mais!", + }, + "LV_250": { + name: "Treinador de Elite", + }, + "LV_1000": { + name: "Ao Infinito e Além!", + }, + + "RibbonAchv": { + description: "Acumule um total de {{ribbonAmount}} Fitas", + }, + "10_RIBBONS": { + name: "Fita de Bronze", + }, + "25_RIBBONS": { + name: "Fita de Prata", + }, + "50_RIBBONS": { + name: "Fita de Ouro", + }, + "75_RIBBONS": { + name: "Fita de Platina", + }, + "100_RIBBONS": { + name: "Fita de Diamante", + }, + + "TRANSFER_MAX_BATTLE_STAT": { + name: "Trabalho em Equipe", + description: "Use Baton Pass com pelo menos um atributo aumentado ao máximo", + }, + "MAX_FRIENDSHIP": { + name: "Melhores Amigos", + description: "Alcance a amizade máxima com um Pokémon", + }, + "MEGA_EVOLVE": { + name: "Megamorfose", + description: "Megaevolua um Pokémon", + }, + "GIGANTAMAX": { + name: "Ficou Gigante!", + description: "Gigantamax um Pokémon", + }, + "TERASTALLIZE": { + name: "Terastalização", + description: "Terastalize um Pokémon", + }, + "STELLAR_TERASTALLIZE": { + name: "Estrela Cadente", + description: "Terastalize um Pokémon para o tipo Estelar", + }, + "SPLICE": { + name: "Fusão!", + description: "Funda dois Pokémon com um Splicer de DNA", + }, + "MINI_BLACK_HOLE": { + name: "Buraco Sem Fundo", + description: "Adquira um Mini Buraco Negro", + }, + "CATCH_MYTHICAL": { + name: "Mítico", + description: "Capture um Pokémon Mítico", + }, + "CATCH_SUB_LEGENDARY": { + name: "Quase Lendário", + description: "Capture um Pokémon Semi-Lendário", + }, + "CATCH_LEGENDARY": { + name: "Lendário", + description: "Capture um Pokémon Lendário", + }, + "SEE_SHINY": { + name: "Ué, Tá Brilhando?", + description: "Encontre um Pokémon Shiny selvagem", + }, + "SHINY_PARTY": { + name: "Tá Todo Mundo Brilhando!", + description: "Tenha uma equipe formada por 6 Pokémon Shiny", + }, + "HATCH_MYTHICAL": { + name: "Ovo Mítico", + description: "Choque um Pokémon Mítico", + }, + "HATCH_SUB_LEGENDARY": { + name: "Ovo Semi-Lendário", + description: "Choque um Pokémon Semi-Lendário", + }, + "HATCH_LEGENDARY": { + name: "Ovo Lendário", + description: "Choque um Pokémon Lendário", + }, + "HATCH_SHINY": { + name: "Ovo Shiny", + description: "Choque um Pokémon Shiny", + }, + "HIDDEN_ABILITY": { + name: "Potencial Oculto", + description: "Capture um Pokémon com uma Habilidade Oculta", + }, + "PERFECT_IVS": { + name: "Perfeição Certificada", + description: "Obtenha IVs perfeitos em um Pokémon", + }, + "CLASSIC_VICTORY": { + name: "Invencível", + description: "Vença o jogo no modo clássico", + }, +} as const; diff --git a/src/locales/pt_BR/config.ts b/src/locales/pt_BR/config.ts index ae43e0aba75..1aadb5b20b1 100644 --- a/src/locales/pt_BR/config.ts +++ b/src/locales/pt_BR/config.ts @@ -1,9 +1,24 @@ import { ability } from "./ability"; import { abilityTriggers } from "./ability-trigger"; +import { achv } from "./achv"; import { battle } from "./battle"; +import { battleMessageUiHandler } from "./battle-message-ui-handler"; +import { berry } from "./berry"; +import { biome } from "./biome"; import { commandUiHandler } from "./command-ui-handler"; +import { + PGFbattleSpecDialogue, + PGFdialogue, + PGFdoubleBattleDialogue, + PGFmiscDialogue, + PGMbattleSpecDialogue, + PGMdialogue, + PGMdoubleBattleDialogue, + PGMmiscDialogue +} from "./dialogue"; import { egg } from "./egg"; import { fightUiHandler } from "./fight-ui-handler"; +import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; @@ -13,33 +28,34 @@ import { nature } from "./nature"; import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; import { pokemonInfo } from "./pokemon-info"; +import { pokemonInfoContainer } from "./pokemon-info-container"; import { splashMessages } from "./splash-messages"; import { starterSelectUiHandler } from "./starter-select-ui-handler"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; -import { weather } from "./weather"; -import { battleMessageUiHandler } from "./battle-message-ui-handler"; -import { berry } from "./berry"; -import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { voucher } from "./voucher"; -import { - PGMdialogue, - PGFdialogue, - PGMbattleSpecDialogue, - PGFbattleSpecDialogue, - PGMmiscDialogue, - PGFmiscDialogue, PGMdoubleBattleDialogue, PGFdoubleBattleDialogue -} from "./dialogue"; -import { biome } from "./biome"; -import { pokemonInfoContainer } from "./pokemon-info-container"; +import { weather } from "./weather"; export const ptBrConfig = { ability: ability, abilityTriggers: abilityTriggers, + achv: achv, battle: battle, + battleMessageUiHandler: battleMessageUiHandler, + berry: berry, + biome: biome, commandUiHandler: commandUiHandler, + PGMdialogue: PGMdialogue, + PGFdialogue: PGFdialogue, + PGMbattleSpecDialogue: PGMbattleSpecDialogue, + PGFbattleSpecDialogue: PGFbattleSpecDialogue, + PGMmiscDialogue: PGMmiscDialogue, + PGFmiscDialogue: PGFmiscDialogue, + PGMdoubleBattleDialogue: PGMdoubleBattleDialogue, + PGFdoubleBattleDialogue: PGFdoubleBattleDialogue, egg: egg, fightUiHandler: fightUiHandler, + gameStatsUiHandler: gameStatsUiHandler, growth: growth, menu: menu, menuUiHandler: menuUiHandler, @@ -49,25 +65,13 @@ export const ptBrConfig = { pokeball: pokeball, pokemon: pokemon, pokemonInfo: pokemonInfo, + pokemonInfoContainer: pokemonInfoContainer, splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, titles: titles, trainerClasses: trainerClasses, trainerNames: trainerNames, tutorial: tutorial, - weather: weather, - battleMessageUiHandler: battleMessageUiHandler, - berry: berry, - gameStatsUiHandler: gameStatsUiHandler, voucher: voucher, - biome: biome, - pokemonInfoContainer: pokemonInfoContainer, - PGMdialogue: PGMdialogue, - PGFdialogue: PGFdialogue, - PGMbattleSpecDialogue: PGMbattleSpecDialogue, - PGFbattleSpecDialogue: PGFbattleSpecDialogue, - PGMmiscDialogue: PGMmiscDialogue, - PGFmiscDialogue: PGFmiscDialogue, - PGMdoubleBattleDialogue: PGMdoubleBattleDialogue, - PGFdoubleBattleDialogue: PGFdoubleBattleDialogue + weather: weather }; diff --git a/src/locales/pt_BR/modifier-type.ts b/src/locales/pt_BR/modifier-type.ts index 051454009f2..fafcae9a835 100644 --- a/src/locales/pt_BR/modifier-type.ts +++ b/src/locales/pt_BR/modifier-type.ts @@ -110,7 +110,7 @@ export const modifierType: ModifierTypeTranslationEntries = { }, "TerastallizeModifierType": { name: "{{teraType}} Fragmento Tera", - description: "{{teraType}} Terastaliza um Pokémon por até 10 batalhas", + description: "{{teraType}} Terastalize um Pokémon por até 10 batalhas", }, "ContactHeldItemTransferChanceModifierType": { description: "Quando atacar, tem {{chancePercent}}% de chance de roubar um item do oponente", @@ -128,8 +128,8 @@ export const modifierType: ModifierTypeTranslationEntries = { "RARE_CANDY": { name: "Doce Raro" }, "RARER_CANDY": { name: "Doce Raríssimo" }, - "MEGA_BRACELET": { name: "Mega Bracelete", description: "Mega Stones become available" }, - "DYNAMAX_BAND": { name: "Bracelete Dynamax", description: "Max Mushrooms become available" }, + "MEGA_BRACELET": { name: "Mega Bracelete", description: "Mega Pedras ficam disponíveis" }, + "DYNAMAX_BAND": { name: "Bracelete Dynamax", description: "Cogumáximos ficam disponíveis" }, "TERA_ORB": { name: "Orbe Tera", description: "Fragmentos Tera ficam disponíveis" }, "MAP": { name: "Mapa", description: "Permite escolher a próxima rota" }, @@ -369,7 +369,7 @@ export const modifierType: ModifierTypeTranslationEntries = { "GRISEOUS_CORE": "Núcleo Platinado", "REVEAL_GLASS": "Espelho da Verdade", "GRACIDEA": "Gracídea", - "MAX_MUSHROOMS": "Cogumax", + "MAX_MUSHROOMS": "Cogumáximo", "DARK_STONE": "Pedra das Trevas", "LIGHT_STONE": "Pedra da Luz", "PRISON_BOTTLE": "Garrafa Prisão", diff --git a/src/locales/pt_BR/voucher.ts b/src/locales/pt_BR/voucher.ts index f2bcb8d723c..6ffffd48735 100644 --- a/src/locales/pt_BR/voucher.ts +++ b/src/locales/pt_BR/voucher.ts @@ -2,10 +2,10 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const voucher: SimpleTranslationEntries = { "vouchers": "Vouchers", - "eggVoucher": "Egg Voucher", - "eggVoucherPlus": "Egg Voucher Plus", - "eggVoucherPremium": "Egg Voucher Premium", - "eggVoucherGold": "Egg Voucher Gold", - "locked": "Locked", - "defeatTrainer": "Defeat {{trainerName}}" -} as const; + "eggVoucher": "Voucher de Ovo", + "eggVoucherPlus": "Voucher de Ovo Plus", + "eggVoucherPremium": "Voucher de Ovo Premium", + "eggVoucherGold": "Voucher de Ovo Dourado", + "locked": "Bloqueado", + "defeatTrainer": "Derrote {{trainerName}}" +} as const; diff --git a/src/locales/zh_CN/achv.ts b/src/locales/zh_CN/achv.ts new file mode 100644 index 00000000000..42b1995bcde --- /dev/null +++ b/src/locales/zh_CN/achv.ts @@ -0,0 +1,171 @@ +import { AchievementTranslationEntries } from "#app/plugins/i18n.js"; + +export const achv: AchievementTranslationEntries = { + "Achievements": { + name: "Achievements", + }, + "Locked": { + name: "Locked", + }, + + "MoneyAchv": { + description: "Accumulate a total of ₽{{moneyAmount}}", + }, + "10K_MONEY": { + name: "Money Haver", + }, + "100K_MONEY": { + name: "Rich", + }, + "1M_MONEY": { + name: "Millionaire", + }, + "10M_MONEY": { + name: "One Percenter", + }, + + "DamageAchv": { + description: "Inflict {{damageAmount}} damage in one hit", + }, + "250_DMG": { + name: "Hard Hitter", + }, + "1000_DMG": { + name: "Harder Hitter", + }, + "2500_DMG": { + name: "That's a Lotta Damage!", + }, + "10000_DMG": { + name: "One Punch Man", + }, + + "HealAchv": { + description: "Heal {{healAmount}} {{HP}} at once with a move, ability, or held item", + }, + "250_HEAL": { + name: "Novice Healer", + }, + "1000_HEAL": { + name: "Big Healer", + }, + "2500_HEAL": { + name: "Cleric", + }, + "10000_HEAL": { + name: "Recovery Master", + }, + + "LevelAchv": { + description: "Level up a Pokémon to Lv{{level}}", + }, + "LV_100": { + name: "But Wait, There's More!", + }, + "LV_250": { + name: "Elite", + }, + "LV_1000": { + name: "To Go Even Further Beyond", + }, + + "RibbonAchv": { + description: "Accumulate a total of {{ribbonAmount}} Ribbons", + }, + "10_RIBBONS": { + name: "Pokémon League Champion", + }, + "25_RIBBONS": { + name: "Great League Champion", + }, + "50_RIBBONS": { + name: "Ultra League Champion", + }, + "75_RIBBONS": { + name: "Rogue League Champion", + }, + "100_RIBBONS": { + name: "Master League Champion", + }, + + "TRANSFER_MAX_BATTLE_STAT": { + name: "Teamwork", + description: "Baton pass to another party member with at least one stat maxed out", + }, + "MAX_FRIENDSHIP": { + name: "Friendmaxxing", + description: "Reach max friendship on a Pokémon", + }, + "MEGA_EVOLVE": { + name: "Megamorph", + description: "Mega evolve a Pokémon", + }, + "GIGANTAMAX": { + name: "Absolute Unit", + description: "Gigantamax a Pokémon", + }, + "TERASTALLIZE": { + name: "STAB Enthusiast", + description: "Terastallize a Pokémon", + }, + "STELLAR_TERASTALLIZE": { + name: "The Hidden Type", + description: "Stellar Terastallize a Pokémon", + }, + "SPLICE": { + name: "Infinite Fusion", + description: "Splice two Pokémon together with DNA Splicers", + }, + "MINI_BLACK_HOLE": { + name: "A Hole Lot of Items", + description: "Acquire a Mini Black Hole", + }, + "CATCH_MYTHICAL": { + name: "Mythical", + description: "Catch a mythical Pokémon", + }, + "CATCH_SUB_LEGENDARY": { + name: "(Sub-)Legendary", + description: "Catch a sub-legendary Pokémon", + }, + "CATCH_LEGENDARY": { + name: "Legendary", + description: "Catch a legendary Pokémon", + }, + "SEE_SHINY": { + name: "Shiny", + description: "Find a shiny Pokémon in the wild", + }, + "SHINY_PARTY": { + name: "That's Dedication", + description: "Have a full party of shiny Pokémon", + }, + "HATCH_MYTHICAL": { + name: "Mythical Egg", + description: "Hatch a mythical Pokémon from an egg", + }, + "HATCH_SUB_LEGENDARY": { + name: "Sub-Legendary Egg", + description: "Hatch a sub-legendary Pokémon from an egg", + }, + "HATCH_LEGENDARY": { + name: "Legendary Egg", + description: "Hatch a legendary Pokémon from an egg", + }, + "HATCH_SHINY": { + name: "Shiny Egg", + description: "Hatch a shiny Pokémon from an egg", + }, + "HIDDEN_ABILITY": { + name: "Hidden Potential", + description: "Catch a Pokémon with a hidden ability", + }, + "PERFECT_IVS": { + name: "Certificate of Authenticity", + description: "Get perfect IVs on a Pokémon", + }, + "CLASSIC_VICTORY": { + name: "Undefeated", + description: "Beat the game in classic mode", + }, +} as const; diff --git a/src/locales/zh_CN/config.ts b/src/locales/zh_CN/config.ts index 245727d874b..7a8ab5595f7 100644 --- a/src/locales/zh_CN/config.ts +++ b/src/locales/zh_CN/config.ts @@ -1,9 +1,24 @@ import { ability } from "./ability"; import { abilityTriggers } from "./ability-trigger"; +import { achv } from "./achv"; import { battle } from "./battle"; +import { battleMessageUiHandler } from "./battle-message-ui-handler"; +import { berry } from "./berry"; +import { biome } from "./biome"; import { commandUiHandler } from "./command-ui-handler"; +import { + PGFbattleSpecDialogue, + PGFdialogue, + PGFdoubleBattleDialogue, + PGFmiscDialogue, + PGMbattleSpecDialogue, + PGMdialogue, + PGMdoubleBattleDialogue, + PGMmiscDialogue +} from "./dialogue"; import { egg } from "./egg"; import { fightUiHandler } from "./fight-ui-handler"; +import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; @@ -13,33 +28,34 @@ import { nature } from "./nature"; import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; import { pokemonInfo } from "./pokemon-info"; +import { pokemonInfoContainer } from "./pokemon-info-container"; import { splashMessages } from "./splash-messages"; import { starterSelectUiHandler } from "./starter-select-ui-handler"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; -import { weather } from "./weather"; -import { battleMessageUiHandler } from "./battle-message-ui-handler"; -import { berry } from "./berry"; -import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { voucher } from "./voucher"; -import { - PGMdialogue, - PGFdialogue, - PGMbattleSpecDialogue, - PGFbattleSpecDialogue, - PGMmiscDialogue, - PGFmiscDialogue, PGMdoubleBattleDialogue, PGFdoubleBattleDialogue -} from "./dialogue"; -import { biome } from "./biome"; -import { pokemonInfoContainer } from "./pokemon-info-container"; +import { weather } from "./weather"; export const zhCnConfig = { ability: ability, abilityTriggers: abilityTriggers, + achv: achv, battle: battle, + battleMessageUiHandler: battleMessageUiHandler, + berry: berry, + biome: biome, commandUiHandler: commandUiHandler, + PGMdialogue: PGMdialogue, + PGFdialogue: PGFdialogue, + PGMbattleSpecDialogue: PGMbattleSpecDialogue, + PGFbattleSpecDialogue: PGFbattleSpecDialogue, + PGMmiscDialogue: PGMmiscDialogue, + PGFmiscDialogue: PGFmiscDialogue, + PGMdoubleBattleDialogue: PGMdoubleBattleDialogue, + PGFdoubleBattleDialogue: PGFdoubleBattleDialogue, egg: egg, fightUiHandler: fightUiHandler, + gameStatsUiHandler: gameStatsUiHandler, growth: growth, menu: menu, menuUiHandler: menuUiHandler, @@ -49,25 +65,13 @@ export const zhCnConfig = { pokeball: pokeball, pokemon: pokemon, pokemonInfo: pokemonInfo, + pokemonInfoContainer: pokemonInfoContainer, splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, titles: titles, trainerClasses: trainerClasses, trainerNames: trainerNames, tutorial: tutorial, - weather: weather, - battleMessageUiHandler: battleMessageUiHandler, - berry: berry, - gameStatsUiHandler: gameStatsUiHandler, voucher: voucher, - biome: biome, - pokemonInfoContainer: pokemonInfoContainer, - PGMdialogue: PGMdialogue, - PGFdialogue: PGFdialogue, - PGMbattleSpecDialogue: PGMbattleSpecDialogue, - PGFbattleSpecDialogue: PGFbattleSpecDialogue, - PGMmiscDialogue: PGMmiscDialogue, - PGFmiscDialogue: PGFmiscDialogue, - PGMdoubleBattleDialogue: PGMdoubleBattleDialogue, - PGFdoubleBattleDialogue: PGFdoubleBattleDialogue + weather: weather }; diff --git a/src/locales/zh_TW/achv.ts b/src/locales/zh_TW/achv.ts new file mode 100644 index 00000000000..42b1995bcde --- /dev/null +++ b/src/locales/zh_TW/achv.ts @@ -0,0 +1,171 @@ +import { AchievementTranslationEntries } from "#app/plugins/i18n.js"; + +export const achv: AchievementTranslationEntries = { + "Achievements": { + name: "Achievements", + }, + "Locked": { + name: "Locked", + }, + + "MoneyAchv": { + description: "Accumulate a total of ₽{{moneyAmount}}", + }, + "10K_MONEY": { + name: "Money Haver", + }, + "100K_MONEY": { + name: "Rich", + }, + "1M_MONEY": { + name: "Millionaire", + }, + "10M_MONEY": { + name: "One Percenter", + }, + + "DamageAchv": { + description: "Inflict {{damageAmount}} damage in one hit", + }, + "250_DMG": { + name: "Hard Hitter", + }, + "1000_DMG": { + name: "Harder Hitter", + }, + "2500_DMG": { + name: "That's a Lotta Damage!", + }, + "10000_DMG": { + name: "One Punch Man", + }, + + "HealAchv": { + description: "Heal {{healAmount}} {{HP}} at once with a move, ability, or held item", + }, + "250_HEAL": { + name: "Novice Healer", + }, + "1000_HEAL": { + name: "Big Healer", + }, + "2500_HEAL": { + name: "Cleric", + }, + "10000_HEAL": { + name: "Recovery Master", + }, + + "LevelAchv": { + description: "Level up a Pokémon to Lv{{level}}", + }, + "LV_100": { + name: "But Wait, There's More!", + }, + "LV_250": { + name: "Elite", + }, + "LV_1000": { + name: "To Go Even Further Beyond", + }, + + "RibbonAchv": { + description: "Accumulate a total of {{ribbonAmount}} Ribbons", + }, + "10_RIBBONS": { + name: "Pokémon League Champion", + }, + "25_RIBBONS": { + name: "Great League Champion", + }, + "50_RIBBONS": { + name: "Ultra League Champion", + }, + "75_RIBBONS": { + name: "Rogue League Champion", + }, + "100_RIBBONS": { + name: "Master League Champion", + }, + + "TRANSFER_MAX_BATTLE_STAT": { + name: "Teamwork", + description: "Baton pass to another party member with at least one stat maxed out", + }, + "MAX_FRIENDSHIP": { + name: "Friendmaxxing", + description: "Reach max friendship on a Pokémon", + }, + "MEGA_EVOLVE": { + name: "Megamorph", + description: "Mega evolve a Pokémon", + }, + "GIGANTAMAX": { + name: "Absolute Unit", + description: "Gigantamax a Pokémon", + }, + "TERASTALLIZE": { + name: "STAB Enthusiast", + description: "Terastallize a Pokémon", + }, + "STELLAR_TERASTALLIZE": { + name: "The Hidden Type", + description: "Stellar Terastallize a Pokémon", + }, + "SPLICE": { + name: "Infinite Fusion", + description: "Splice two Pokémon together with DNA Splicers", + }, + "MINI_BLACK_HOLE": { + name: "A Hole Lot of Items", + description: "Acquire a Mini Black Hole", + }, + "CATCH_MYTHICAL": { + name: "Mythical", + description: "Catch a mythical Pokémon", + }, + "CATCH_SUB_LEGENDARY": { + name: "(Sub-)Legendary", + description: "Catch a sub-legendary Pokémon", + }, + "CATCH_LEGENDARY": { + name: "Legendary", + description: "Catch a legendary Pokémon", + }, + "SEE_SHINY": { + name: "Shiny", + description: "Find a shiny Pokémon in the wild", + }, + "SHINY_PARTY": { + name: "That's Dedication", + description: "Have a full party of shiny Pokémon", + }, + "HATCH_MYTHICAL": { + name: "Mythical Egg", + description: "Hatch a mythical Pokémon from an egg", + }, + "HATCH_SUB_LEGENDARY": { + name: "Sub-Legendary Egg", + description: "Hatch a sub-legendary Pokémon from an egg", + }, + "HATCH_LEGENDARY": { + name: "Legendary Egg", + description: "Hatch a legendary Pokémon from an egg", + }, + "HATCH_SHINY": { + name: "Shiny Egg", + description: "Hatch a shiny Pokémon from an egg", + }, + "HIDDEN_ABILITY": { + name: "Hidden Potential", + description: "Catch a Pokémon with a hidden ability", + }, + "PERFECT_IVS": { + name: "Certificate of Authenticity", + description: "Get perfect IVs on a Pokémon", + }, + "CLASSIC_VICTORY": { + name: "Undefeated", + description: "Beat the game in classic mode", + }, +} as const; diff --git a/src/locales/zh_TW/config.ts b/src/locales/zh_TW/config.ts index c213aa4b0df..1b942e0234f 100644 --- a/src/locales/zh_TW/config.ts +++ b/src/locales/zh_TW/config.ts @@ -1,9 +1,24 @@ import { ability } from "./ability"; import { abilityTriggers } from "./ability-trigger"; +import { achv } from "./achv"; import { battle } from "./battle"; +import { battleMessageUiHandler } from "./battle-message-ui-handler"; +import { berry } from "./berry"; +import { biome } from "./biome"; import { commandUiHandler } from "./command-ui-handler"; +import { + PGFbattleSpecDialogue, + PGFdialogue, + PGFdoubleBattleDialogue, + PGFmiscDialogue, + PGMbattleSpecDialogue, + PGMdialogue, + PGMdoubleBattleDialogue, + PGMmiscDialogue +} from "./dialogue"; import { egg } from "./egg"; import { fightUiHandler } from "./fight-ui-handler"; +import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; @@ -13,32 +28,34 @@ import { nature } from "./nature"; import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; import { pokemonInfo } from "./pokemon-info"; +import { pokemonInfoContainer } from "./pokemon-info-container"; import { splashMessages } from "./splash-messages"; import { starterSelectUiHandler } from "./starter-select-ui-handler"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; -import { weather } from "./weather"; -import { battleMessageUiHandler } from "./battle-message-ui-handler"; -import { berry } from "./berry"; import { voucher } from "./voucher"; -import { - PGMdialogue, - PGFdialogue, - PGMbattleSpecDialogue, - PGFbattleSpecDialogue, - PGMmiscDialogue, - PGFmiscDialogue, PGMdoubleBattleDialogue, PGFdoubleBattleDialogue -} from "./dialogue"; -import { biome } from "./biome"; -import { pokemonInfoContainer } from "./pokemon-info-container"; +import { weather } from "./weather"; -export const zhTWConfig = { +export const zhTwConfig = { ability: ability, abilityTriggers: abilityTriggers, + achv: achv, battle: battle, + battleMessageUiHandler: battleMessageUiHandler, + berry: berry, + biome: biome, commandUiHandler: commandUiHandler, + PGMdialogue: PGMdialogue, + PGFdialogue: PGFdialogue, + PGMbattleSpecDialogue: PGMbattleSpecDialogue, + PGFbattleSpecDialogue: PGFbattleSpecDialogue, + PGMmiscDialogue: PGMmiscDialogue, + PGFmiscDialogue: PGFmiscDialogue, + PGMdoubleBattleDialogue: PGMdoubleBattleDialogue, + PGFdoubleBattleDialogue: PGFdoubleBattleDialogue, egg: egg, fightUiHandler: fightUiHandler, + gameStatsUiHandler: gameStatsUiHandler, growth: growth, menu: menu, menuUiHandler: menuUiHandler, @@ -48,24 +65,13 @@ export const zhTWConfig = { pokeball: pokeball, pokemon: pokemon, pokemonInfo: pokemonInfo, + pokemonInfoContainer: pokemonInfoContainer, splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, titles: titles, trainerClasses: trainerClasses, trainerNames: trainerNames, tutorial: tutorial, - weather: weather, - battleMessageUiHandler: battleMessageUiHandler, - berry: berry, voucher: voucher, - biome: biome, - pokemonInfoContainer: pokemonInfoContainer, - PGMdialogue: PGMdialogue, - PGFdialogue: PGFdialogue, - PGMbattleSpecDialogue: PGMbattleSpecDialogue, - PGFbattleSpecDialogue: PGFbattleSpecDialogue, - PGMmiscDialogue: PGMmiscDialogue, - PGFmiscDialogue: PGFmiscDialogue, - PGMdoubleBattleDialogue: PGMdoubleBattleDialogue, - PGFdoubleBattleDialogue: PGFdoubleBattleDialogue + weather: weather }; diff --git a/src/locales/zh_TW/game-stats-ui-handler.ts b/src/locales/zh_TW/game-stats-ui-handler.ts new file mode 100644 index 00000000000..cb3228c1a4a --- /dev/null +++ b/src/locales/zh_TW/game-stats-ui-handler.ts @@ -0,0 +1,44 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const gameStatsUiHandler: SimpleTranslationEntries = { + "stats": "Stats", + "playTime": "Play Time", + "totalBattles": "Total Battles", + "starters": "Starters", + "shinyStarters": "Shiny Starters", + "speciesSeen": "Species Seen", + "speciesCaught": "Species Caught", + "ribbonsOwned": "Ribbons Owned", + "classicRuns": "Classic Runs", + "classicWins": "Classic Wins", + "dailyRunAttempts": "Daily Run Attempts", + "dailyRunWins": "Daily Run Wins", + "endlessRuns": "Endless Runs", + "highestWaveEndless": "Highest Wave (Endless)", + "highestMoney": "Highest Money", + "highestDamage": "Highest Damage", + "highestHPHealed": "Highest HP Healed", + "pokemonEncountered": "Pokémon Encountered", + "pokemonDefeated": "Pokémon Defeated", + "pokemonCaught": "Pokémon Caught", + "eggsHatched": "Eggs Hatched", + "subLegendsSeen": "Sub-Legends Seen", + "subLegendsCaught": "Sub-Legends Caught", + "subLegendsHatched": "Sub-Legends Hatched", + "legendsSeen": "Legends Seen", + "legendsCaught": "Legends Caught", + "legendsHatched": "Legends Hatched", + "mythicalsSeen": "Mythicals Seen", + "mythicalsCaught": "Mythicals Caught", + "mythicalsHatched": "Mythicals Hatched", + "shiniesSeen": "Shinies Seen", + "shiniesCaught": "Shinies Caught", + "shiniesHatched": "Shinies Hatched", + "pokemonFused": "Pokémon Fused", + "trainersDefeated": "Trainers Defeated", + "eggsPulled": "Eggs Pulled", + "rareEggsPulled": "Rare Eggs Pulled", + "epicEggsPulled": "Epic Eggs Pulled", + "legendaryEggsPulled": "Legendary Eggs Pulled", + "manaphyEggsPulled": "Manaphy Eggs Pulled", +} as const; diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts index 06c660434d5..9bd03b501f4 100644 --- a/src/plugins/i18n.ts +++ b/src/plugins/i18n.ts @@ -6,10 +6,10 @@ import { enConfig } from "#app/locales/en/config.js"; import { esConfig } from "#app/locales/es/config.js"; import { frConfig } from "#app/locales/fr/config.js"; import { itConfig } from "#app/locales/it/config.js"; +import { koConfig } from "#app/locales/ko/config.js"; import { ptBrConfig } from "#app/locales/pt_BR/config.js"; import { zhCnConfig } from "#app/locales/zh_CN/config.js"; -import { zhTWConfig } from "#app/locales/zh_TW/config.js"; -import { koConfig } from "#app/locales/ko/config.js"; +import { zhTwConfig } from "#app/locales/zh_TW/config.js"; export interface SimpleTranslationEntries { [key: string]: string @@ -54,13 +54,22 @@ export interface PokemonInfoTranslationEntries { export interface BerryTranslationEntry { name: string, - effect: string + effect: string, } export interface BerryTranslationEntries { [key: string]: BerryTranslationEntry } +export interface AchievementTranslationEntry { + name?: string, + description?: string, +} + +export interface AchievementTranslationEntries { + [key: string]: AchievementTranslationEntry; +} + export interface DialogueTranslationEntry { [key: number]: string; } @@ -171,7 +180,7 @@ export function initI18n(): void { ...zhCnConfig }, zh_TW: { - ...zhTWConfig + ...zhTwConfig }, ko: { ...koConfig @@ -209,6 +218,7 @@ declare module "i18next" { modifierType: ModifierTypeTranslationEntries; battleMessageUiHandler: SimpleTranslationEntries; berry: BerryTranslationEntries; + achv: AchievementTranslationEntries; gameStatsUiHandler: SimpleTranslationEntries; voucher: SimpleTranslationEntries; biome: SimpleTranslationEntries; diff --git a/src/system/achv.ts b/src/system/achv.ts index 9af01830b60..f52d547a53f 100644 --- a/src/system/achv.ts +++ b/src/system/achv.ts @@ -1,7 +1,8 @@ import { Modifier } from "typescript"; import BattleScene from "../battle-scene"; -import * as Utils from "../utils"; import { TurnHeldItemTransferModifier } from "../modifier/modifier"; +import i18next from "../plugins/i18n"; +import * as Utils from "../utils"; export enum AchvTier { COMMON, @@ -12,6 +13,7 @@ export enum AchvTier { } export class Achv { + public localizationKey: string; public id: string; public name: string; public description: string; @@ -24,16 +26,18 @@ export class Achv { private conditionFunc: (scene: BattleScene, args: any[]) => boolean; - constructor(name: string, description: string, iconImage: string, score: integer, conditionFunc?: (scene: BattleScene, args: any[]) => boolean) { + constructor(localizationKey:string, name: string, description: string, iconImage: string, score: integer, conditionFunc?: (scene: BattleScene, args: any[]) => boolean) { this.name = name; this.description = description; this.iconImage = iconImage; this.score = score; this.conditionFunc = conditionFunc; + this.localizationKey = localizationKey; } getName(): string { - return this.name; + // Localization key is used to get the name of the achievement + return i18next.t(`achv:${this.localizationKey}.name`); } getIconImage(): string { @@ -68,102 +72,191 @@ export class Achv { } export class MoneyAchv extends Achv { - private moneyAmount: integer; - - constructor(name: string, moneyAmount: integer, iconImage: string, score: integer) { - super(name, `Accumulate a total of ₽${moneyAmount.toLocaleString("en-US")}`, iconImage, score, (scene: BattleScene, _args: any[]) => scene.money >= this.moneyAmount); + moneyAmount: integer; + constructor(localizationKey: string, name: string, moneyAmount: integer, iconImage: string, score: integer) { + super(localizationKey, name, "", iconImage, score, (scene: BattleScene, _args: any[]) => scene.money >= this.moneyAmount); this.moneyAmount = moneyAmount; } } export class RibbonAchv extends Achv { - private ribbonAmount: integer; - - constructor(name: string, ribbonAmount: integer, iconImage: string, score: integer) { - super(name, `Accumulate a total of ${ribbonAmount.toLocaleString("en-US")} Ribbons`, iconImage, score, (scene: BattleScene, _args: any[]) => scene.gameData.gameStats.ribbonsOwned >= this.ribbonAmount); + ribbonAmount: integer; + constructor(localizationKey: string, name: string, ribbonAmount: integer, iconImage: string, score: integer) { + super(localizationKey, name, "", iconImage, score, (scene: BattleScene, _args: any[]) => scene.gameData.gameStats.ribbonsOwned >= this.ribbonAmount); this.ribbonAmount = ribbonAmount; } } export class DamageAchv extends Achv { - private damageAmount: integer; - - constructor(name: string, damageAmount: integer, iconImage: string, score: integer) { - super(name, `Inflict ${damageAmount.toLocaleString("en-US")} damage in one hit`, iconImage, score, (_scene: BattleScene, args: any[]) => (args[0] as Utils.NumberHolder).value >= this.damageAmount); + damageAmount: integer; + constructor(localizationKey: string, name: string, damageAmount: integer, iconImage: string, score: integer) { + super(localizationKey, name, "", iconImage, score, (_scene: BattleScene, args: any[]) => (args[0] as Utils.NumberHolder).value >= this.damageAmount); this.damageAmount = damageAmount; } } export class HealAchv extends Achv { - private healAmount: integer; - - constructor(name: string, healAmount: integer, iconImage: string, score: integer) { - super(name, `Heal ${healAmount.toLocaleString("en-US")} HP at once with a move, ability, or held item`, iconImage, score, (_scene: BattleScene, args: any[]) => (args[0] as Utils.NumberHolder).value >= this.healAmount); + healAmount: integer; + constructor(localizationKey: string, name: string, healAmount: integer, iconImage: string, score: integer) { + super(localizationKey, name, "", iconImage, score, (_scene: BattleScene, args: any[]) => (args[0] as Utils.NumberHolder).value >= this.healAmount); this.healAmount = healAmount; } } export class LevelAchv extends Achv { - private level: integer; - - constructor(name: string, level: integer, iconImage: string, score: integer) { - super(name, `Level up a Pokémon to Lv${level}`, iconImage, score, (scene: BattleScene, args: any[]) => (args[0] as Utils.IntegerHolder).value >= this.level); + level: integer; + constructor(localizationKey: string, name: string, level: integer, iconImage: string, score: integer) { + super(localizationKey, name, "", iconImage, score, (scene: BattleScene, args: any[]) => (args[0] as Utils.IntegerHolder).value >= this.level); this.level = level; } } export class ModifierAchv extends Achv { - constructor(name: string, description: string, iconImage: string, score: integer, modifierFunc: (modifier: Modifier) => boolean) { - super(name, description, iconImage, score, (_scene: BattleScene, args: any[]) => modifierFunc((args[0] as Modifier))); + constructor(localizationKey: string, name: string, description: string, iconImage: string, score: integer, modifierFunc: (modifier: Modifier) => boolean) { + super(localizationKey, name, description, iconImage, score, (_scene: BattleScene, args: any[]) => modifierFunc((args[0] as Modifier))); } } + +/** + * Get the description of an achievement from the localization file with all the necessary variables filled in + * @param localizationKey The localization key of the achievement + * @returns The description of the achievement + */ +export function getAchievementDescription(localizationKey: string): string { + switch (localizationKey) { + case "10K_MONEY": + return i18next.t("achv:MoneyAchv.description", {"moneyAmount": achvs._10K_MONEY.moneyAmount.toLocaleString("en-US")}); + case "100K_MONEY": + return i18next.t("achv:MoneyAchv.description", {"moneyAmount": achvs._100K_MONEY.moneyAmount.toLocaleString("en-US")}); + case "1M_MONEY": + return i18next.t("achv:MoneyAchv.description", {"moneyAmount": achvs._1M_MONEY.moneyAmount.toLocaleString("en-US")}); + case "10M_MONEY": + return i18next.t("achv:MoneyAchv.description", {"moneyAmount": achvs._10M_MONEY.moneyAmount.toLocaleString("en-US")}); + case "250_DMG": + return i18next.t("achv:DamageAchv.description", {"damageAmount": achvs._250_DMG.damageAmount.toLocaleString("en-US")}); + case "1000_DMG": + return i18next.t("achv:DamageAchv.description", {"damageAmount": achvs._1000_DMG.damageAmount.toLocaleString("en-US")}); + case "2500_DMG": + return i18next.t("achv:DamageAchv.description", {"damageAmount": achvs._2500_DMG.damageAmount.toLocaleString("en-US")}); + case "10000_DMG": + return i18next.t("achv:DamageAchv.description", {"damageAmount": achvs._10000_DMG.damageAmount.toLocaleString("en-US")}); + case "250_HEAL": + return i18next.t("achv:HealAchv.description", {"healAmount": achvs._250_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t("pokemonInfo:Stat.HPshortened")}); + case "1000_HEAL": + return i18next.t("achv:HealAchv.description", {"healAmount": achvs._1000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t("pokemonInfo:Stat.HPshortened")}); + case "2500_HEAL": + return i18next.t("achv:HealAchv.description", {"healAmount": achvs._2500_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t("pokemonInfo:Stat.HPshortened")}); + case "10000_HEAL": + return i18next.t("achv:HealAchv.description", {"healAmount": achvs._10000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t("pokemonInfo:Stat.HPshortened")}); + case "LV_100": + return i18next.t("achv:LevelAchv.description", {"level": achvs.LV_100.level}); + case "LV_250": + return i18next.t("achv:LevelAchv.description", {"level": achvs.LV_250.level}); + case "LV_1000": + return i18next.t("achv:LevelAchv.description", {"level": achvs.LV_1000.level}); + case "10_RIBBONS": + return i18next.t("achv:RibbonAchv.description", {"ribbonAmount": achvs._10_RIBBONS.ribbonAmount.toLocaleString("en-US")}); + case "25_RIBBONS": + return i18next.t("achv:RibbonAchv.description", {"ribbonAmount": achvs._25_RIBBONS.ribbonAmount.toLocaleString("en-US")}); + case "50_RIBBONS": + return i18next.t("achv:RibbonAchv.description", {"ribbonAmount": achvs._50_RIBBONS.ribbonAmount.toLocaleString("en-US")}); + case "75_RIBBONS": + return i18next.t("achv:RibbonAchv.description", {"ribbonAmount": achvs._75_RIBBONS.ribbonAmount.toLocaleString("en-US")}); + case "100_RIBBONS": + return i18next.t("achv:RibbonAchv.description", {"ribbonAmount": achvs._100_RIBBONS.ribbonAmount.toLocaleString("en-US")}); + case "TRANSFER_MAX_BATTLE_STAT": + return i18next.t("achv:TRANSFER_MAX_BATTLE_STAT.description"); + case "MAX_FRIENDSHIP": + return i18next.t("achv:MAX_FRIENDSHIP.description"); + case "MEGA_EVOLVE": + return i18next.t("achv:MEGA_EVOLVE.description"); + case "GIGANTAMAX": + return i18next.t("achv:GIGANTAMAX.description"); + case "TERASTALLIZE": + return i18next.t("achv:TERASTALLIZE.description"); + case "STELLAR_TERASTALLIZE": + return i18next.t("achv:STELLAR_TERASTALLIZE.description"); + case "SPLICE": + return i18next.t("achv:SPLICE.description"); + case "MINI_BLACK_HOLE": + return i18next.t("achv:MINI_BLACK_HOLE.description"); + case "CATCH_MYTHICAL": + return i18next.t("achv:CATCH_MYTHICAL.description"); + case "CATCH_SUB_LEGENDARY": + return i18next.t("achv:CATCH_SUB_LEGENDARY.description"); + case "CATCH_LEGENDARY": + return i18next.t("achv:CATCH_LEGENDARY.description"); + case "SEE_SHINY": + return i18next.t("achv:SEE_SHINY.description"); + case "SHINY_PARTY": + return i18next.t("achv:SHINY_PARTY.description"); + case "HATCH_MYTHICAL": + return i18next.t("achv:HATCH_MYTHICAL.description"); + case "HATCH_SUB_LEGENDARY": + return i18next.t("achv:HATCH_SUB_LEGENDARY.description"); + case "HATCH_LEGENDARY": + return i18next.t("achv:HATCH_LEGENDARY.description"); + case "HATCH_SHINY": + return i18next.t("achv:HATCH_SHINY.description"); + case "HIDDEN_ABILITY": + return i18next.t("achv:HIDDEN_ABILITY.description"); + case "PERFECT_IVS": + return i18next.t("achv:PERFECT_IVS.description"); + case "CLASSIC_VICTORY": + return i18next.t("achv:CLASSIC_VICTORY.description"); + default: + return ""; + } + +} + export const achvs = { - _10K_MONEY: new MoneyAchv("Money Haver", 10000, "nugget", 10), - _100K_MONEY: new MoneyAchv("Rich", 100000, "big_nugget", 25).setSecret(true), - _1M_MONEY: new MoneyAchv("Millionaire", 1000000, "relic_gold", 50).setSecret(true), - _10M_MONEY: new MoneyAchv("One Percenter", 10000000, "coin_case", 100).setSecret(true), - _250_DMG: new DamageAchv("Hard Hitter", 250, "lucky_punch", 10), - _1000_DMG: new DamageAchv("Harder Hitter", 1000, "lucky_punch_great", 25).setSecret(true), - _2500_DMG: new DamageAchv("That's a Lotta Damage!", 2500, "lucky_punch_ultra", 50).setSecret(true), - _10000_DMG: new DamageAchv("One Punch Man", 10000, "lucky_punch_master", 100).setSecret(true), - _250_HEAL: new HealAchv("Novice Healer", 250, "potion", 10), - _1000_HEAL: new HealAchv("Big Healer", 1000, "super_potion", 25).setSecret(true), - _2500_HEAL: new HealAchv("Cleric", 2500, "hyper_potion", 50).setSecret(true), - _10000_HEAL: new HealAchv("Recovery Master", 10000, "max_potion", 100).setSecret(true), - LV_100: new LevelAchv("But Wait, There's More!", 100, "rare_candy", 25).setSecret(), - LV_250: new LevelAchv("Elite", 250, "rarer_candy", 50).setSecret(true), - LV_1000: new LevelAchv("To Go Even Further Beyond", 1000, "candy_jar", 100).setSecret(true), - _10_RIBBONS: new RibbonAchv("Pokémon League Champion", 10, "bronze_ribbon", 10), - _25_RIBBONS: new RibbonAchv("Great League Champion", 25, "great_ribbon", 25).setSecret(true), - _50_RIBBONS: new RibbonAchv("Ultra League Champion", 50, "ultra_ribbon", 50).setSecret(true), - _75_RIBBONS: new RibbonAchv("Rogue League Champion", 75, "rogue_ribbon", 75).setSecret(true), - _100_RIBBONS: new RibbonAchv("Master League Champion", 100, "master_ribbon", 100).setSecret(true), - TRANSFER_MAX_BATTLE_STAT: new Achv("Teamwork", "Baton pass to another party member with at least one stat maxed out", "stick", 20), - MAX_FRIENDSHIP: new Achv("Friendmaxxing", "Reach max friendship on a Pokémon", "soothe_bell", 25), - MEGA_EVOLVE: new Achv("Megamorph", "Mega evolve a Pokémon", "mega_bracelet", 50), - GIGANTAMAX: new Achv("Absolute Unit", "Gigantamax a Pokémon", "dynamax_band", 50), - TERASTALLIZE: new Achv("STAB Enthusiast", "Terastallize a Pokémon", "tera_orb", 25), - STELLAR_TERASTALLIZE: new Achv("The Hidden Type", "Stellar Terastallize a Pokémon", "stellar_tera_shard", 25).setSecret(true), - SPLICE: new Achv("Infinite Fusion", "Splice two Pokémon together with DNA Splicers", "dna_splicers", 10), - MINI_BLACK_HOLE: new ModifierAchv("A Hole Lot of Items", "Acquire a Mini Black Hole", "mini_black_hole", 25, modifier => modifier instanceof TurnHeldItemTransferModifier).setSecret(), - CATCH_MYTHICAL: new Achv("Mythical", "Catch a mythical Pokémon", "strange_ball", 50).setSecret(), - CATCH_SUB_LEGENDARY: new Achv("(Sub-)Legendary", "Catch a sub-legendary Pokémon", "rb", 75).setSecret(), - CATCH_LEGENDARY: new Achv("Legendary", "Catch a legendary Pokémon", "mb", 100).setSecret(), - SEE_SHINY: new Achv("Shiny", "Find a shiny Pokémon in the wild", "pb_gold", 75), - SHINY_PARTY: new Achv("That's Dedication", "Have a full party of shiny Pokémon", "shiny_charm", 100).setSecret(true), - HATCH_MYTHICAL: new Achv("Mythical Egg", "Hatch a mythical Pokémon from an egg", "pair_of_tickets", 75).setSecret(), - HATCH_SUB_LEGENDARY: new Achv("Sub-Legendary Egg", "Hatch a sub-legendary Pokémon from an egg", "mystic_ticket", 100).setSecret(), - HATCH_LEGENDARY: new Achv("Legendary Egg", "Hatch a legendary Pokémon from an egg", "mystic_ticket", 125).setSecret(), - HATCH_SHINY: new Achv("Shiny Egg", "Hatch a shiny Pokémon from an egg", "golden_mystic_ticket", 100).setSecret(), - HIDDEN_ABILITY: new Achv("Hidden Potential", "Catch a Pokémon with a hidden ability", "ability_charm", 75), - PERFECT_IVS: new Achv("Certificate of Authenticity", "Get perfect IVs on a Pokémon", "blunder_policy", 100), - CLASSIC_VICTORY: new Achv("Undefeated", "Beat the game in classic mode", "relic_crown", 150) + _10K_MONEY: new MoneyAchv("10K_MONEY", "",10000, "nugget", 10), + _100K_MONEY: new MoneyAchv("100K_MONEY", "",100000, "big_nugget", 25).setSecret(true), + _1M_MONEY: new MoneyAchv("1M_MONEY","", 1000000, "relic_gold", 50).setSecret(true), + _10M_MONEY: new MoneyAchv("10M_MONEY","", 10000000, "coin_case", 100).setSecret(true), + _250_DMG: new DamageAchv("250_DMG","", 250, "lucky_punch", 10), + _1000_DMG: new DamageAchv("1000_DMG","", 1000, "lucky_punch_great", 25).setSecret(true), + _2500_DMG: new DamageAchv("2500_DMG","", 2500, "lucky_punch_ultra", 50).setSecret(true), + _10000_DMG: new DamageAchv("10000_DMG","", 10000, "lucky_punch_master", 100).setSecret(true), + _250_HEAL: new HealAchv("250_HEAL","", 250, "potion", 10), + _1000_HEAL: new HealAchv("1000_HEAL", "",1000, "super_potion", 25).setSecret(true), + _2500_HEAL: new HealAchv("2500_HEAL","", 2500, "hyper_potion", 50).setSecret(true), + _10000_HEAL: new HealAchv("10000_HEAL","", 10000, "max_potion", 100).setSecret(true), + LV_100: new LevelAchv("LV_100", "",100, "rare_candy", 25).setSecret(), + LV_250: new LevelAchv("LV_250", "",250, "rarer_candy", 50).setSecret(true), + LV_1000: new LevelAchv("LV_1000", "",1000, "candy_jar", 100).setSecret(true), + _10_RIBBONS: new RibbonAchv("10_RIBBONS","", 10, "bronze_ribbon", 10), + _25_RIBBONS: new RibbonAchv("25_RIBBONS", "",25, "great_ribbon", 25).setSecret(true), + _50_RIBBONS: new RibbonAchv("50_RIBBONS","", 50, "ultra_ribbon", 50).setSecret(true), + _75_RIBBONS: new RibbonAchv("75_RIBBONS","", 75, "rogue_ribbon", 75).setSecret(true), + _100_RIBBONS: new RibbonAchv("100_RIBBONS","", 100, "master_ribbon", 100).setSecret(true), + TRANSFER_MAX_BATTLE_STAT: new Achv("TRANSFER_MAX_BATTLE_STAT","", "TRANSFER_MAX_BATTLE_STAT.description", "stick", 20), + MAX_FRIENDSHIP: new Achv("MAX_FRIENDSHIP", "", "MAX_FRIENDSHIP.description", "soothe_bell", 25), + MEGA_EVOLVE: new Achv("MEGA_EVOLVE", "", "MEGA_EVOLVE.description", "mega_bracelet", 50), + GIGANTAMAX: new Achv("GIGANTAMAX", "", "GIGANTAMAX.description", "dynamax_band", 50), + TERASTALLIZE: new Achv("TERASTALLIZE","", "TERASTALLIZE.description", "tera_orb", 25), + STELLAR_TERASTALLIZE: new Achv("STELLAR_TERASTALLIZE", "", "STELLAR_TERASTALLIZE.description", "stellar_tera_shard", 25).setSecret(true), + SPLICE: new Achv("SPLICE","", "SPLICE.description", "dna_splicers", 10), + MINI_BLACK_HOLE: new ModifierAchv("MINI_BLACK_HOLE","", "MINI_BLACK_HOLE.description", "mini_black_hole", 25, modifier => modifier instanceof TurnHeldItemTransferModifier).setSecret(), + CATCH_MYTHICAL: new Achv("CATCH_MYTHICAL","", "CATCH_MYTHICAL.description", "strange_ball", 50).setSecret(), + CATCH_SUB_LEGENDARY: new Achv("CATCH_SUB_LEGENDARY","", "CATCH_SUB_LEGENDARY.description", "rb", 75).setSecret(), + CATCH_LEGENDARY: new Achv("CATCH_LEGENDARY", "", "CATCH_LEGENDARY.description", "mb", 100).setSecret(), + SEE_SHINY: new Achv("SEE_SHINY", "", "SEE_SHINY.description", "pb_gold", 75), + SHINY_PARTY: new Achv("SHINY_PARTY", "", "SHINY_PARTY.description", "shiny_charm", 100).setSecret(true), + HATCH_MYTHICAL: new Achv("HATCH_MYTHICAL", "", "HATCH_MYTHICAL.description", "pair_of_tickets", 75).setSecret(), + HATCH_SUB_LEGENDARY: new Achv("HATCH_SUB_LEGENDARY","", "HATCH_SUB_LEGENDARY.description", "mystic_ticket", 100).setSecret(), + HATCH_LEGENDARY: new Achv("HATCH_LEGENDARY","", "HATCH_LEGENDARY.description", "mystic_ticket", 125).setSecret(), + HATCH_SHINY: new Achv("HATCH_SHINY","", "HATCH_SHINY.description", "golden_mystic_ticket", 100).setSecret(), + HIDDEN_ABILITY: new Achv("HIDDEN_ABILITY","", "HIDDEN_ABILITY.description", "ability_charm", 75), + PERFECT_IVS: new Achv("PERFECT_IVS","", "PERFECT_IVS.description", "blunder_policy", 100), + CLASSIC_VICTORY: new Achv("CLASSIC_VICTORY","", "CLASSIC_VICTORY.description", "relic_crown", 150), }; { diff --git a/src/test/achievement.test.ts b/src/test/achievement.test.ts index a27c2d90154..c33707cf5b2 100644 --- a/src/test/achievement.test.ts +++ b/src/test/achievement.test.ts @@ -1,9 +1,9 @@ -import {describe, expect, it} from "vitest"; -import {MoneyAchv} from "#app/system/achv"; +import { MoneyAchv } from "#app/system/achv"; +import { describe, expect, it } from "vitest"; describe("check some Achievement related stuff", () => { it ("should check Achievement creation", () => { - const ach = new MoneyAchv("Achievement", 1000, null, 100); + const ach = new MoneyAchv("", "Achievement", 1000, null, 100); expect(ach.name).toBe("Achievement"); }); }); diff --git a/src/ui/achv-bar.ts b/src/ui/achv-bar.ts index e28de4e7869..f24fda0792b 100644 --- a/src/ui/achv-bar.ts +++ b/src/ui/achv-bar.ts @@ -1,5 +1,5 @@ import BattleScene from "../battle-scene"; -import { Achv } from "../system/achv"; +import { Achv, getAchievementDescription } from "../system/achv"; import { Voucher } from "../system/voucher"; import { TextStyle, addTextObject } from "./text"; @@ -66,7 +66,11 @@ export default class AchvBar extends Phaser.GameObjects.Container { this.icon.setFrame(achv.getIconImage()); this.titleText.setText(achv.getName()); this.scoreText.setVisible(achv instanceof Achv); - this.descriptionText.setText(achv.description); + if (achv instanceof Achv) { + this.descriptionText.setText(getAchievementDescription((achv as Achv).localizationKey)); + } else if (achv instanceof Voucher) { + this.descriptionText.setText((achv as Voucher).description); + } if (achv instanceof Achv) { this.scoreText.setText(`+${(achv as Achv).score}pt`); diff --git a/src/ui/achvs-ui-handler.ts b/src/ui/achvs-ui-handler.ts index da56024a4d6..a73f7560282 100644 --- a/src/ui/achvs-ui-handler.ts +++ b/src/ui/achvs-ui-handler.ts @@ -1,10 +1,11 @@ import BattleScene from "../battle-scene"; -import { Achv, achvs } from "../system/achv"; +import { Button } from "../enums/buttons"; +import i18next from "../plugins/i18n"; +import { Achv, achvs, getAchievementDescription } from "../system/achv"; import MessageUiHandler from "./message-ui-handler"; import { TextStyle, addTextObject } from "./text"; import { Mode } from "./ui"; import { addWindow } from "./ui-theme"; -import {Button} from "../enums/buttons"; export default class AchvsUiHandler extends MessageUiHandler { private achvsContainer: Phaser.GameObjects.Container; @@ -32,7 +33,7 @@ export default class AchvsUiHandler extends MessageUiHandler { const headerBg = addWindow(this.scene, 0, 0, (this.scene.game.canvas.width / 6) - 2, 24); headerBg.setOrigin(0, 0); - const headerText = addTextObject(this.scene, 0, 0, "Achievements", TextStyle.SETTINGS_LABEL); + const headerText = addTextObject(this.scene, 0, 0, i18next.t("achv:Achievements.name"), TextStyle.SETTINGS_LABEL); headerText.setOrigin(0, 0); headerText.setPositionRelative(headerBg, 8, 4); @@ -136,14 +137,15 @@ export default class AchvsUiHandler extends MessageUiHandler { } protected showAchv(achv: Achv) { + achv.name = i18next.t(`achv:${achv.localizationKey}.name`); + achv.description = getAchievementDescription(achv.localizationKey); const achvUnlocks = this.scene.gameData.achvUnlocks; const unlocked = achvUnlocks.hasOwnProperty(achv.id); const hidden = !unlocked && achv.secret && (!achv.parentId || !achvUnlocks.hasOwnProperty(achv.parentId)); - this.titleText.setText(unlocked ? achv.name : "???"); this.showText(!hidden ? achv.description : ""); this.scoreText.setText(`${achv.score}pt`); - this.unlockText.setText(unlocked ? new Date(achvUnlocks[achv.id]).toLocaleDateString() : "Locked"); + this.unlockText.setText(unlocked ? new Date(achvUnlocks[achv.id]).toLocaleDateString() : i18next.t("achv:Locked.name")); } processInput(button: Button): boolean { diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index c739804609a..3faec4bfcc6 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -63,13 +63,18 @@ const languageSettings: { [key: string]: LanguageSetting } = { starterInfoTextSize: "56px", instructionTextSize: "35px", }, + "fr":{ + starterInfoTextSize: "54px", + instructionTextSize: "42px", + }, "it":{ starterInfoTextSize: "56px", instructionTextSize: "38px", }, - "fr":{ - starterInfoTextSize: "54px", - instructionTextSize: "42px", + "pt_BR":{ + starterInfoTextSize: "47px", + instructionTextSize: "38px", + starterInfoXPos: 33, }, "zh":{ starterInfoTextSize: "40px", diff --git a/src/ui/text.ts b/src/ui/text.ts index 3db4c37fe67..4e76386aae7 100644 --- a/src/ui/text.ts +++ b/src/ui/text.ts @@ -1,10 +1,10 @@ +import i18next from "i18next"; import BBCodeText from "phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText"; import InputText from "phaser3-rex-plugins/plugins/inputtext"; -import { ModifierTier } from "../modifier/modifier-tier"; -import { EggTier } from "../data/enums/egg-type"; import BattleScene from "../battle-scene"; +import { EggTier } from "../data/enums/egg-type"; import { UiTheme } from "../enums/ui-theme"; -import i18next from "i18next"; +import { ModifierTier } from "../modifier/modifier-tier"; export enum TextStyle { MESSAGE, @@ -45,10 +45,10 @@ const languageSettings: { [key: string]: LanguageSetting } = { "en":{}, "de":{}, "es":{}, - "it":{}, "fr":{}, - "zh_CN":{}, + "it":{}, "pt_BR":{}, + "zh_CN":{}, }; export function addTextObject(scene: Phaser.Scene, x: number, y: number, content: string, style: TextStyle, extraStyleOptions?: Phaser.Types.GameObjects.Text.TextStyle): Phaser.GameObjects.Text { From dcbe479001cc355c25791f59965bf80e19009456 Mon Sep 17 00:00:00 2001 From: MrWaterT <87186129+MrWaterT@users.noreply.github.com> Date: Sun, 2 Jun 2024 09:33:40 +0900 Subject: [PATCH 011/129] [Localization] Fix wrong sentence in ko rival dialogue (#1704) --- src/locales/ko/dialogue.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locales/ko/dialogue.ts b/src/locales/ko/dialogue.ts index 8fb8da64206..37c4d67936e 100644 --- a/src/locales/ko/dialogue.ts +++ b/src/locales/ko/dialogue.ts @@ -2321,7 +2321,7 @@ export const PGMmiscDialogue: SimpleTranslationEntries = { $@c{smile_eclosed}물론… 언제나 느껴왔지.\n@c{smile}끝난 거, 맞지? 이 굴레를 말이야. $@c{smile_ehalf}네 꿈도 이뤘고 말이야.\n어떻게 한번도 안 졌대? $네가 한 일은 나만 기억하게 될 모양이지만.\n@c{angry_mopen}나, 안 까먹어볼 테니까! - $@c{smile_wave_wink}농담이야!@d{64} @c{smile}절대로 안 잊어버릴 거야.@d{32}\n오늘 일은 우리의 마음 속에서 살아갈 야. + $@c{smile_wave_wink}농담이야!@d{64} @c{smile}절대 안 잊어버릴 거야.@d{32}\n마음 속엔 쭉 남아있을 수 있게. $@c{smile_wave}어쨌든,@d{64} 시간이 좀 늦었어…@d{96}\n이런 곳에서 할 말은 아닌가? $집에 가자. @c{smile_wave_wink}아마 내일은,\n추억을 되짚어보기 위한 배틀을 해볼 수 있을 거야.`, "ending_endless": "끝에 도달하신 것을 축하드립니다!\n더 많은 컨텐츠를 기다려주세요.", From 902d4df1a8f8f135769647431b75112eca5023f6 Mon Sep 17 00:00:00 2001 From: damocleas Date: Sat, 1 Jun 2024 20:10:58 -0600 Subject: [PATCH 012/129] Wide Lens to Ultra item tier, Berry Count limits, and small Berry Pouch % chance reduction (#1472) * Voucher and Wide Lens item table Changes - Wide Lens moved from Rogue -> Ultra Tier, same weight - Voucher moved from Ultra -> Great Tier, same weight - Voucher Plus moved from Master -> Rogue Tier, with weight starting at 9 -> 6 and decreasing with each reroll with 3 -> 2 - Voucher Premium added to Master (based on suggestion from Madmadness) with same weight as new Voucher Plus, and disabled in Endless / Endless Spliced * Added Berry Count limiter 2 count for Lum, Leppa, Sitrus, Enigma 3 count for all else * fix test 1 * fix test 2 I blame browser coding for this * fix test 3 I BLAME BROWSER CODING * Changed Berry Pouch to 30% > 33*%, max stack 90% > 100% * fix test 4!!!! oops * english. * german. * spanish. * french. * italian * portugese. * simplified chinese. * traditional chinese. * Removed voucher changes, making separate PR for them to be discussed. * Swapped switch statement to .includes statement * Update modifier-type.ts * thank you cheek pouch pr --- src/locales/de/modifier-type.ts | 2 +- src/locales/en/modifier-type.ts | 2 +- src/locales/es/modifier-type.ts | 2 +- src/locales/fr/modifier-type.ts | 2 +- src/locales/it/modifier-type.ts | 2 +- src/locales/pt_BR/modifier-type.ts | 2 +- src/locales/zh_CN/modifier-type.ts | 2 +- src/locales/zh_TW/modifier-type.ts | 2 +- src/modifier/modifier-type.ts | 2 +- src/modifier/modifier.ts | 7 +++++-- 10 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/locales/de/modifier-type.ts b/src/locales/de/modifier-type.ts index e32e804c3ca..a03eb64d07e 100644 --- a/src/locales/de/modifier-type.ts +++ b/src/locales/de/modifier-type.ts @@ -198,7 +198,7 @@ export const modifierType: ModifierTypeTranslationEntries = { "HEALING_CHARM": { name: "Heilungspin", description: "Erhöht die Effektivität von Heilungsattacken sowie Heilitems um 10% (Beleber ausgenommen)" }, "CANDY_JAR": { name: "Bonbonglas", description: "Erhöht die Anzahl der Level die ein Sonderbonbon erhöht um 1" }, - "BERRY_POUCH": { name: "Beerentüte", description: "Fügt eine 33% Chance hinzu, dass Beeren nicht verbraucht werden" }, + "BERRY_POUCH": { name: "Beerentüte", description: "Fügt eine 30% Chance hinzu, dass Beeren nicht verbraucht werden" }, "FOCUS_BAND": { name: "Fokusband", description: "Fügt eine 10% Chance hinzu, dass Angriffe die zur Kampfunfähigkeit führen mit 1 KP überlebt werden" }, diff --git a/src/locales/en/modifier-type.ts b/src/locales/en/modifier-type.ts index 92cf45e6541..e5c37843a99 100644 --- a/src/locales/en/modifier-type.ts +++ b/src/locales/en/modifier-type.ts @@ -198,7 +198,7 @@ export const modifierType: ModifierTypeTranslationEntries = { "HEALING_CHARM": { name: "Healing Charm", description: "Increases the effectiveness of HP restoring moves and items by 10% (excludes Revives)" }, "CANDY_JAR": { name: "Candy Jar", description: "Increases the number of levels added by Rare Candy items by 1" }, - "BERRY_POUCH": { name: "Berry Pouch", description: "Adds a 33% chance that a used berry will not be consumed" }, + "BERRY_POUCH": { name: "Berry Pouch", description: "Adds a 30% chance that a used berry will not be consumed" }, "FOCUS_BAND": { name: "Focus Band", description: "Adds a 10% chance to survive with 1 HP after being damaged enough to faint" }, diff --git a/src/locales/es/modifier-type.ts b/src/locales/es/modifier-type.ts index dafdfcc11e8..8e48b8e6627 100644 --- a/src/locales/es/modifier-type.ts +++ b/src/locales/es/modifier-type.ts @@ -198,7 +198,7 @@ export const modifierType: ModifierTypeTranslationEntries = { "HEALING_CHARM": { name: "Amuleto curación", description: "Aumenta la efectividad de los movimientos y objetos de curacion de PS en un 10% (excepto revivir)" }, "CANDY_JAR": { name: "Candy Jar", description: "Aumenta en 1 el número de niveles añadidos por los carameloraros" }, - "BERRY_POUCH": { name: "Saco Bayas", description: "Agrega un 33% de posibilidades de que una baya usada no se consuma" }, + "BERRY_POUCH": { name: "Saco Bayas", description: "Agrega un 30% de posibilidades de que una baya usada no se consuma" }, "FOCUS_BAND": { name: "Cinta Focus", description: "Agrega un 10% de probabilidad de resistir un ataque que lo debilitaría" }, diff --git a/src/locales/fr/modifier-type.ts b/src/locales/fr/modifier-type.ts index 9307bfb155b..7b82b1d4ad5 100644 --- a/src/locales/fr/modifier-type.ts +++ b/src/locales/fr/modifier-type.ts @@ -198,7 +198,7 @@ export const modifierType: ModifierTypeTranslationEntries = { "HEALING_CHARM": { name: "Charme Soin", description: "Augmente de 10% l’efficacité des capacités et objets de soin de PV (hors Rappels)" }, "CANDY_JAR": { name: "Bonbonnière", description: "Augmente de 1 le nombre de niveaux gagnés à l’utilisation d’un Super Bonbon" }, - "BERRY_POUCH": { name: "Sac à Baies", description: "Ajoute 33% de chances qu’une Baie utilisée ne soit pas consommée" }, + "BERRY_POUCH": { name: "Sac à Baies", description: "Ajoute 30% de chances qu’une Baie utilisée ne soit pas consommée" }, "FOCUS_BAND": { name: "Bandeau", description: "Ajoute 10% de chances de survivre avec 1 PV si les dégâts reçus pouvaient mettre K.O." }, diff --git a/src/locales/it/modifier-type.ts b/src/locales/it/modifier-type.ts index 57d9595a224..ca703073d63 100644 --- a/src/locales/it/modifier-type.ts +++ b/src/locales/it/modifier-type.ts @@ -198,7 +198,7 @@ export const modifierType: ModifierTypeTranslationEntries = { "HEALING_CHARM": { name: "Curamuleto", description: "Aumenta del 10% l'efficacia delle mosse e degli oggetti che ripristinano i PS (escluse le rianimazioni)" }, "CANDY_JAR": { name: "Barattolo di caramelle", description: "Aumenta di 1 il numero di livelli aggiunti dalle Caramelle Rare" }, - "BERRY_POUCH": { name: "Porta Bacche", description: "Aggiunge il 33% di possibilità che una bacca usata non venga consumata" }, + "BERRY_POUCH": { name: "Porta Bacche", description: "Aggiunge il 30% di possibilità che una bacca usata non venga consumata" }, "FOCUS_BAND": { name: "Bandana", description: "Chi ce l'ha ottiene il 10% di possibilità aggiuntivo di evitare un potenziale KO e rimanere con un solo PS" }, diff --git a/src/locales/pt_BR/modifier-type.ts b/src/locales/pt_BR/modifier-type.ts index fafcae9a835..2deabc7836b 100644 --- a/src/locales/pt_BR/modifier-type.ts +++ b/src/locales/pt_BR/modifier-type.ts @@ -198,7 +198,7 @@ export const modifierType: ModifierTypeTranslationEntries = { "HEALING_CHARM": { name: "Amuleto de Cura", description: "Aumenta a eficácia dos movimentos e itens que restauram PS em 10% (exceto Reanimador)" }, "CANDY_JAR": { name: "Pote de Doces", description: "Aumenta o número de níveis adicionados pelo Doce Raro em 1" }, - "BERRY_POUCH": { name: "Bolsa de Berries", description: "Adiciona uma chance de 33% de que uma berry usada não seja consumida" }, + "BERRY_POUCH": { name: "Bolsa de Berries", description: "Adiciona uma chance de 30% de que uma berry usada não seja consumida" }, "FOCUS_BAND": { name: "Bandana", description: "Adiciona uma chance de 10% de sobreviver com 1 PS após ser danificado o suficiente para desmaiar" }, diff --git a/src/locales/zh_CN/modifier-type.ts b/src/locales/zh_CN/modifier-type.ts index c367000bf48..39e8ed9bfca 100644 --- a/src/locales/zh_CN/modifier-type.ts +++ b/src/locales/zh_CN/modifier-type.ts @@ -198,7 +198,7 @@ export const modifierType: ModifierTypeTranslationEntries = { "HEALING_CHARM": { name: "治愈护符", description: "HP回复量增加10% (含复活)" }, "CANDY_JAR": { name: "糖果罐", description: "神奇糖果提供的升级额外增加1级" }, - "BERRY_POUCH": { name: "树果袋", description: "使用树果时有33%的几率不会消耗树果" }, + "BERRY_POUCH": { name: "树果袋", description: "使用树果时有30%的几率不会消耗树果" }, "FOCUS_BAND": { name: "气势头带", description: "携带该道具的宝可梦有10%几率在受到\n攻击而将陷入濒死状态时,保留1点HP不陷入濒死状态" }, diff --git a/src/locales/zh_TW/modifier-type.ts b/src/locales/zh_TW/modifier-type.ts index 253f65e84dc..55a4e268ae5 100644 --- a/src/locales/zh_TW/modifier-type.ts +++ b/src/locales/zh_TW/modifier-type.ts @@ -213,7 +213,7 @@ export const modifierType: ModifierTypeTranslationEntries = { CANDY_JAR: { name: "糖果罐", description: "神奇糖果提供的升級提升1級" }, BERRY_POUCH: { name: "樹果袋", - description: "使用樹果時有33%的幾率不會消耗樹果", + description: "使用樹果時有30%的幾率不會消耗樹果", }, FOCUS_BAND: { name: "氣勢頭帶", diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 395c7f4cd8b..5705ee43f16 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1359,6 +1359,7 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.EXP_BALANCE, 4), new WeightedModifierType(modifierTypes.TERA_ORB, (party: Pokemon[]) => Math.min(Math.max(Math.floor(party[0].scene.currentBattle.waveIndex / 50) * 2, 1), 4), 4), new WeightedModifierType(modifierTypes.VOUCHER, (party: Pokemon[], rerollCount: integer) => !party[0].scene.gameMode.isDaily ? Math.max(3 - rerollCount, 0) : 0, 3), + new WeightedModifierType(modifierTypes.WIDE_LENS, 4), ].map(m => { m.setTier(ModifierTier.ULTRA); return m; }), @@ -1369,7 +1370,6 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.SHELL_BELL, 3), new WeightedModifierType(modifierTypes.BERRY_POUCH, 4), new WeightedModifierType(modifierTypes.GRIP_CLAW, 5), - new WeightedModifierType(modifierTypes.WIDE_LENS, 4), new WeightedModifierType(modifierTypes.BATON, 2), new WeightedModifierType(modifierTypes.SOUL_DEW, 8), //new WeightedModifierType(modifierTypes.OVAL_CHARM, 6), diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 716a48cda89..8b965cb4ddc 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1011,7 +1011,10 @@ export class BerryModifier extends PokemonHeldItemModifier { } getMaxHeldItemCount(pokemon: Pokemon): integer { - return 10; + if ([BerryType.LUM, BerryType.LEPPA, BerryType.SITRUS, BerryType.ENIGMA].includes(this.berryType)) { + return 2; + } + return 3; } } @@ -1034,7 +1037,7 @@ export class PreserveBerryModifier extends PersistentModifier { apply(args: any[]): boolean { if (!(args[1] as Utils.BooleanHolder).value) { - (args[1] as Utils.BooleanHolder).value = (args[0] as Pokemon).randSeedInt(this.getMaxStackCount(null)) < this.getStackCount(); + (args[1] as Utils.BooleanHolder).value = (args[0] as Pokemon).randSeedInt(10) < this.getStackCount() * 3; } return true; From 655e3ba24c9ddd0f35a032f8debe71b8432e1fc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Ricardo=20Fleury=20Oliveira?= Date: Sat, 1 Jun 2024 23:30:22 -0300 Subject: [PATCH 013/129] fix first voucher description (#1710) --- src/system/voucher.ts | 4 ++-- src/ui/vouchers-ui-handler.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/system/voucher.ts b/src/system/voucher.ts index b3d9cdd01f7..9381dd650f8 100644 --- a/src/system/voucher.ts +++ b/src/system/voucher.ts @@ -1,7 +1,7 @@ import BattleScene from "../battle-scene"; import { TrainerType } from "../data/enums/trainer-type"; -import { Achv, AchvTier, achvs } from "./achv"; import i18next from "../plugins/i18n"; +import { Achv, AchvTier, achvs, getAchievementDescription } from "./achv"; export enum VoucherType { REGULAR, @@ -96,7 +96,7 @@ const voucherAchvs: Achv[] = [ achvs.CLASSIC_VICTORY ]; : achv.score >= 75 ? VoucherType.PLUS : VoucherType.REGULAR; - vouchers[achv.id] = new Voucher(voucherType, achv.description); + vouchers[achv.id] = new Voucher(voucherType, getAchievementDescription(achv.localizationKey)); } const bossTrainerTypes = Object.keys(trainerConfigs) diff --git a/src/ui/vouchers-ui-handler.ts b/src/ui/vouchers-ui-handler.ts index 5d45c1d82d2..4508a5be193 100644 --- a/src/ui/vouchers-ui-handler.ts +++ b/src/ui/vouchers-ui-handler.ts @@ -1,11 +1,11 @@ import BattleScene from "../battle-scene"; +import { Button } from "../enums/buttons"; +import i18next from "../plugins/i18n"; import { Voucher, getVoucherTypeIcon, getVoucherTypeName, vouchers } from "../system/voucher"; import MessageUiHandler from "./message-ui-handler"; import { TextStyle, addTextObject } from "./text"; import { Mode } from "./ui"; import { addWindow } from "./ui-theme"; -import {Button} from "../enums/buttons"; -import i18next from "../plugins/i18n"; const itemRows = 4; const itemCols = 17; From 289b42441fa705b5c5ff6087646cdf6ce5659bc4 Mon Sep 17 00:00:00 2001 From: chaosgrimmon <31082757+chaosgrimmon@users.noreply.github.com> Date: Sun, 2 Jun 2024 10:39:01 -0400 Subject: [PATCH 014/129] [Bug] Add variant Larvesta icons (#1717) * [Bug] Add variant Larvesta icons The game wants them in pokemon_cons_5v.json, where they are currently not. All Solosis and Tympole_3 sprites placements adjusted for adequate spacing. * [Bug] Add variant Larvesta icons Adds the icons to the spritesheet, and moves around some other icons for optimal use of space and spacing. --- public/images/pokemon_icons_5v.json | 56 ++++++++++++++++++++++++---- public/images/pokemon_icons_5v.png | Bin 29879 -> 83034 bytes 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/public/images/pokemon_icons_5v.json b/public/images/pokemon_icons_5v.json index 1a1b0fd46af..078e2a7d0d9 100644 --- a/public/images/pokemon_icons_5v.json +++ b/public/images/pokemon_icons_5v.json @@ -3258,8 +3258,8 @@ "h": 16 }, "frame": { - "x": 205, - "y": 268, + "x": 270, + "y": 272, "w": 17, "h": 16 } @@ -3279,8 +3279,8 @@ "h": 16 }, "frame": { - "x": 222, - "y": 259, + "x": 270, + "y": 288, "w": 17, "h": 16 } @@ -3300,8 +3300,8 @@ "h": 16 }, "frame": { - "x": 239, - "y": 268, + "x": 287, + "y": 288, "w": 17, "h": 16 } @@ -3321,11 +3321,53 @@ "h": 13 }, "frame": { - "x": 256, + "x": 254, "y": 271, "w": 16, "h": 13 } + }, + { + "filename": "636_2", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 9, + "y": 8, + "w": 22, + "h": 21 + }, + "frame": { + "x": 195, + "y": 261, + "w": 22, + "h": 21 + } + }, + { + "filename": "636_3", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 9, + "y": 8, + "w": 22, + "h": 21 + }, + "frame": { + "x": 217, + "y": 263, + "w": 22, + "h": 21 + } } ] } diff --git a/public/images/pokemon_icons_5v.png b/public/images/pokemon_icons_5v.png index e641e8fd015748bfda9ec4953e5f2beb785534d8..5a8b90992b124b75311cc2b72e8726a684ae4a0a 100644 GIT binary patch literal 83034 zcmW(+1ymgE4qdFcI}|DI`r}&M-Q8Q<-4+Th-a?DJySux)EVfwD;ts{%^c@ao*pX!> z-%WCJlZjMQkwr%#K>>k4=<;$>8Xyo1$G=~scfcNwC)R6VLuo0ep$r1~(SbnVFc9bw z*aiLr0(k-_9+`naf+-*np-V=ax-f9yorR*T6zJ{WPflm?PhbzChrF^h;-7c0?~vh4 zCsJsEi$U^IAGLf|Pcl6N@MZ7cHhYn)KEgV{!mCn#cJaQ?E96HoU@*y%b48JkvPZ`A zMm~c_mv=!LMS!Jgk*s}svgCI6uBN${Sv`1o&ARqmUs})1%uLN(e>pkG5O5!g@f5VlfUCj9jp>5`r+^_&?rFO(rRw}#=gH!StWI$hff8?BE z-+q=}unl6#g|fA1T3&R@hl?wnn40tLO&!Dpw>xMNN%TZED)+14%f;j! zgiawpUA{t)lAjiP6hTYCcmDfIFy{vf4pyJRMoWvbPXjgP1c#qgo(w_BSn(SLwz!r? ztLCTC)ylYAi9c{ON#T74o2@)Y9iJcmkke_E(r4#8y0gz0IIDy5{tJco9|9hLoXFs7 zn&yvtjz7!39ZbovTUQUPc&rc>mHVaHJJ8J~5quBSogO47+D-zJHtqs( zG(eOq?cM=K`-HhB`$pVIR@FVKr3>LdDGX=Z?}B z9sb&PCdCUpy9nf=q}6lU*Et^Ze)~8HmR9|Dbyp|+Hy>07ZLt}Twr}bP*gWJl8=ZsCVHKDGA7f7QuVHd}<+U85 z@;Sidd@8s3%otEtd}zB}nRnMne-C2#B|r#;y^AkXVzUck8wjCU`s3$7&y7mhTxv6< zT~S+IyHj*E{^x_+=Hw)L&G~O^OvA_TzMhBd38j1z95tK4CdfVCaRslRrkNj(w_v+U zXHmi37;H8AU22T^_TN8$0acz^YlDpfhA6)G%+n}jCswnQs}~-Ckd3O(;D7Rdr(Yj` z9BJS)Y+4OmZ(5}kR|L<^XPoMb`Uuz>xm^ifiGB%E8k)~wipc8ph~yfKiU+Kj%Im&7 zqu2Pch7D{h-3_y4Swz_OiNm!GtzhzG3|fEhF>R4M(Yow19Ol$#gSuSlgtCY*bDT^K z3gZ_#PU*jE75i;cMWu|o)C(0s1l^m8(SJ%qtN8BoVZ1FMD0(d}x~-HUYBs5@1iyDg zp{jghZ)6}L-Z)MPjZ&XDWkJb*6W#DAPY0g5y<;)`A_AHaA1+&R{=>}LOoN;3^yg>p zPXP8$ILzQdiDSv{lt6SRm>-JC<2`2nBlCg5=>Z{ITsr2@lzoNmIa+l5n!QOd6qk-d z3FC0mQrhuy@v9WKc;GWB5D7k`w#)@jj1PBjUhafVfl)KDht-7u>kEF#^Xi-YIR-Nt6+V;^b&1Y%ClDY_3geci^f|3Vj(;lYcM`8o2GR(t;jL3-uH| z-x|a|sThXF$Wd@Qq*RFj(XNJFjaaXc_cR}YJ-*sspF$W`RkI?8H1qgjU^#o0H|0yR znQ0DJP1dGx96^Uvm(sm%eZYkqlNGzdmkQQXC%BaVz^@bw)dFI|H2o(NT*lyJ#U^2} zJZcRsbl;?@*uYo}#B!RyaUQE3WUW+|@b3h(djNjln>unV56H?k({|llo8*qyUkJeP z)otP4BbKEKy1NqTmL9CLDOI#2iWfLfaiOxkC)TWUq8>4zA6uTCzx919;+qMjq3>LT z%!HcZn*N>Ss$P5wTXRc&@H@U8OS!{-exRLKTCtS48APjM!za=%yM7!6B^0GR%I`&4C|Kza8sEELJGmzto6M^8E3 zX&z^RQB1PP5j-j1xp?)vcANz@$?+vyAV89j=WaO!m#%xEtm*5N8tj%FIib3VZlZ!% za4xR);=irfS>`4SCA=UrL07-6Nvs^@AK4Ks9loeh{1j2HhRedW9ndd;3RpAYVu@D>%PQlP0F8bL7E!%de4SsbGYM$l7l`x&L$MZM6G-Uswe-9~4 zT{MRU-C~~7n?6)1O{XGO`XSf+o_f@S131MH0N|a&(~mAa%1&!Y3oDco2Mfb3x46;Y zOaANhhX}S^AAb`|f{N}bvp+@cU00Sl8Gvw4#%0=Ff-;Rmf3afQ6)qDT6%%w!K;$Z! zCEj=|Q#+kHr^JH+TZg*Q_a2{p0*y74NdX63-QB5KSd{k9O%}g>l5KW7^dlNXX>$cO ze9_52Z)HAF01@b~y;vel6GclbggZ^HDG><0Mr$N4x0Ze=^!_m7R?BRwm}_)U%BxRp z-6U_GZvYFmR*q40@HB)bqB$zzxiAJ$gq@Q^ z%s73#>S&x*OSaOZyp{Q*5kF4I*8WA;G?!>`FFjGl4&hu%njt0kbChN#aP}q*JU0Zd@&?V<%!d3X{ zGwVPad&Tt%#?o}2#udy{M5p{0@lN}Z6~$zwC1L~<3Bj26w7vGz+^XqY|CV5M^_@~y zA9z%=qUmFdpICQbSi0ldWB*@G`9zgtq z7|W&Z`lRV?D_&$0OM-7^8r@)Nsi4Ej!KX*JjTUSM{iT1H=i(qGbBMR$Alj9)sNS@% zbGt`dzbtgOH^|7hHZ{|ru3QpY?p)6>N_#e@^M)W{rvC9OUHIC zuA^TBpX@g4zO>#mM)W4X%@wYnG`!~^zSc|}rCmwu%nMWjpxeJ#?tjQp$4n;UmFWBR z@fUzz8q^^jNFl31sc3s&N;i;CM;A}AsjR?Q2kf5;1-R0Q`_YpoenCBDtD>#BBR)pcN+`>Uc+cG=03j zoZB@M&n^1?(a9HX@8gQb;$vV@aMnx zLJ73qL&N!^{%GzVWd&*!!5K5Db$bC9#?5IMjx4b&KwaSOdTQ8OjZho;lYZR8zU<^` z5?W#)fsjW{sNM5tt|RijC%!MDQkI4J1m7o9;`09*?Jkxu@KXHGn4>I&yV~fh_Oz4Z zFEs7^nHx<%&we$1@--m3o?J;*O;6WBe_aiMHOzWiJ)r6EXcu_fpUvgx1NqQK!PzRW506eJu8$VK-I?}GSD4id06UjQ#xn!5YKCc=Lh2oe_C`}MTW_R<&5JKW-P%dv@;Dg{&=m5=%8 z7f@;SWOk$MR|p$EHwz1z=*e7RwqZRfc<%HFPOW6|es^LuEYA^cS?Gxz@h+~|B7Roq zST!pjscPklE8r-ZNL$G;PcmFrwT~9yWol%Gd$!8yeq|wQpw!l|0H~=G>c!*s*#Mz8 zJ=ZQP0V^(UiBXQYt3lv=8jvBl9~7xbg%uwr;N`%}Dbpu(K%tiVjRnb@6+N%deq?w<|kwl;w=33V0d>o6L} z{MmzVOe!;lOb4(`jnhewV4nHlH->AJ3!Ip7lYMKp)r0x>9dkBbdqGWnvvGdxm1{p{ ze94HK0@1}|rt@3FvP5&Ci=oN>tx+t(ZQ8?Q6SSI}4h(SfI{~VJCT!W8I6LnDsQ7`q zd=I={Ka~v(lS^f5vpV^)*CFBI0j(8~0@^;-zy!5pRUALwRCAit14$IG$_^J5@@S&0 zlv@^12!#~icn6PI3e@w)2boBy0BO5i%r}k-X_4pOLL7frDL_9t(4Qybbqbq|F@U(P zXOY2gcBouT9#;sqD-3r5*uB%1vv{X`9Z;O1Z z0a{sh)hs-g%A<`YO5*u4_2aw*sToN@OdW-#_C6)sZ4BaY~O;jHnLdqQ+|f z$;&FzJAzCkEVz_TCz0X&Bm5J^2qMUHsi&$f+u($YD=n}%P=Ds}YfjF&;!gm!E@3* zEwotn<2NS%TN)!#a1(g~dT`~zP^@_7q~d()8c-#ioqJ760F}XRH}5$VQh{0}d(iztv<&8lPy17v~29H7@VO2&di4iAF_XMc)Anss^5O zrw{wQ9FC%=&m*B<)w;TTtbQ|@m$O{^O+JbHXSJd$rK<{wx$WEpcEoi!|2tIiXVpw` zqVu|UU`*n`GspMQ0H;Sh@C@f0iM%#lGD(El6NvziypovP0AzSgh(f7Ci8zIP(==HD z1czS%g#N5b-FqCrNBF7xxbaeXi^up@`|y-=pUWe2AOp|E*n#_HU zWQV;$;9X8Sy|2ZdPZu#4GWcukm*v*==x=(sL&r#QZPUCR05C$y&gI<>-b~7=F>pK2g8%Hzaez+i)%wS?1k^o+}$G=aZUej83mP-6hs$YeE{TI zuCXvPn?(nceY4xa^~ix-82}KFYV$TUFH%)hJO;Ld{~jM4%@swRn|3vYM&HlC``CU| z{QJ$b+Ta`OLr39TawGDVM8i;kA(i;aFmh(0Y>-F7Dt$tJGVj$=FTyX@gZ`#UsFVjv zIc9%+bnh(5)!wbNWp^9SrH*b^K2DIGTf^6Y53?)Pq1~cOiG&nX3-Z4SE}!pc*SV8< z>zS#?_|!Y^^2O1VrVGt%ma^6?0lsDwbbS6Jzfp}o+)!#(W72~u7s!l0`zq0&&KhtA zdJ1?^Y`orX-4|s$wssAVl?o)nqga$$jURoL%dfxPl<_&MF5%F5hb0^{{S5Hp0W_Ij zKtyFwZy^a*CR51|^82*Zd)h2xKUJcZOXU>)s32BN!KamAa=U-KiNVooX=HL7yLo=N zjEyxA2c|r_nUfJ5K5QgLM<*8`L_%58EhP3@lSPs;uC`IG>wrz-v$ z-sy)k1>m(c`yM^nx%|}v!0A?WyWixt{oiNno9iKflgwL5a}vxH05XDhn<^SHCtmj2 z1E_?8$8*G}(=bXR3RjQ;Nm%#|s%XRuwD8jK@hE&ex<(sFawW*3bt)kn|J%!@CAo;v`g z3TS=F2q$ftsn_NX$|`*P@-KB7(Chmz$a69wk1*h_UhrGE@s{-Hy!{e0Fg-Na=f=vI zPG|l7{c<#Oy3biDMoWg?-~#7gK74lGn3pcZnh$*y{6l>pMBeGq&-6J2#i$Q+ViE3N zdGy$9tDL;Atk>n6e&Xz2dqhKo&&*NaSwK#A5O44NH~l1ZGL+nWHv>7wwlUM23(LZ|w2ICOl9e zj7?TR#qU3ox`-nn0p-}mt#M%;GhZ{&xO+noLQFG-QM~P_drAIG-$nJ}=3>>OB~JTx z2(3zTh(tE38|$d&;WIsGVm?17pcY;((Dxw&15V~*sqO)dluu$R5swP3bawFFd}uNj z>z6t@%VFigw_k?{*No@+aKetFoS7r!mR-w>B)8$5Vnu2KKrN%_GXMA$8C35bq~~xD z6?uI^jP;iuYGyA^DJp&Zj51Oxr><`N7Jo6n&)FlM`PbNeYz4mE$^H}zYLMR^krzL6 zDmU_&Z*8`UXtuD1-Cz%m)UtNFyu3p-7G;enoIF8EHNMOgh{u4_y5PER_63T~79r{I z{aUhI9{#PO8@Ty4Iq+ztHtOenvqEdV{mb5lOybNxvf)3de~u;y9m~)-MuM0>*ukS( zz_VYsS^EEM?4fqBi_4laIYhYGGcGL%2NX%bw!8a5sPbtWP9VsnuDFM;ehQ zJT7JAR@f@?s&X1{EvWiW5e99+gh??*5z;2R;ax-6F9`QSTlo2sx)5FM8_?pLjWlLY zNQ$)mmOI!Gdnj-{(yKUm>q|8!=od!4S5hAI+!i#Z#&K-r71>~>}BM{^2% zb;;fwXgprp-koBp;^ik`beOmhLOoqp=QSkw8Wf$k|9qZ{!H_pkb)XH%*Dl!F3TRhv zC|J?AlmN+jsa~_cte|BspHgIb(U0wI_M2aOOF3Y}mX9%wISLpbP?5)lAF$BQS!{U& z(o6%?%);EjKehFmnZIks(ZlrO&9t{a33p3OpzcO>k@+Yqy_4o6h1Ol?-v|5hhY2W- zX@xv!Ik(&$MC5;WB&O3X;1YsjLeGBEU~te)i_;PG7(T#Fca=Cz6Gg%uqe>YK>1*1@ z2(y7&#jX;ng!hfArPK4`tVb*dkHtO}O9_u?zm`n?F8sjNIeG9s1G5#G*Cgq$#=70j z=C@6q`GL#*Tp@w$6WdUTJ=;eLr@0FIU%~5h5TC+}nvr?Fqf}f9;nCHICx$KHj!K|~ zk_%bv5Y+qn5lw4pXX6)=#r=!6oIEsE7i58Cn(y%cg^O*!NM=RQ$iN8Rm4r3*DE1G< zTq0Q1nxsOL;Bp%S4Q=0arG(AitO8+MFxb&^4VD6~mfH$HG>jg=eBe8rZGrD!N;A=M zB?oPuM?X(@NSNhXy^5g-XBm5-S;^Tpub#JRWkhv@dYVc3Nr5$OQFQ z&aFvKVl+)rYYcP>kT;E2iq_*UGX_-bq^!t%QbDJi)@Bk>t-f*%-FRNtVi-9Skc_`U zu2K)hNm;o8OLcD%>bmN9mdvWUdOnGEB9LSWR0~Mk{!hxL&ri~HjgT)XzWR4f|4zxD zHZ_w$Kf=%|MzH-@0Xz@RfJ&HHZYy!bV3Ucx1qV?EK|WEZPWF!RZ>ff~4g#=otf69~ z#`uUArp6~3huYo+3(Yq@4jV7zApt{+vhQjKf@ddU8T9dG=}~4@y{g~0#e(xzO~()P ztnKdP!V}OrvvEuxugTgxAG2tKU(Ms+cEd9-uUOca^Wxq&yE;l_dK5LBS2@hrGQb#_ zzDw)CXESM+Efp!pLF=Hz0O?Fj_WucXA_MX5lkY~A6bppuUAf#DS3p+o7gQ<^G9sP(b7 z$g9e~K6X9&%n;pVx;v97S4QX>FK)x;TW$I^fN6e2q_VUje(x;p`xr)kKEz_`;e zipCOBiJ1YK>IhU^(JIw4OvWGe4WhN0fETYdg)5A$(0hZygZnH9ej;g469cs{O8EsXdiY;w|hIWuRm8^(Y-x!`IS-b zMa;t!0;}Oq%(_L;y3h#^txdeH0=)zT9ux+CP{vVyJJ|2EbvMOBE_zl^! zPC`DW*4oPS;iQ2rAiY*aP}?&m(sLyN$W?Zv7L5N?Kas*RG|aiK&G)Earu}_$7T{Ny z$C2#yVDV6EDn$xB(?5dL>YOFZ;zMdIupRILcEG}a9T+cwL(Du_nBx{k3zCQ(Ejyxn zRb>^C+{(GW#BtSLt|)s}Pj9_uSMQ1vD!lT&I6cean$Bg859@T_pTUQ^c9(fh7Kf(E zT$yxF%%0zyAH%8Tzlj2oZ@4b!-eV#=w2qj2)^VRT^1gxR zfUz}i|BBP$SEVjXrWoAS-%8{Ax3r;s=q_CNUD8njuV|o>C+d&7q}RR6O}Yz`Dw!{i zx65C2zKiUN{xCaHbo`b{)q|u(2LUYQ>#*g$Q+lw?aviaGB=DjqQcR(ut=;2}Hvlpb z_E*uxA6zh51}VnsW3i7&r+{-{xRrzhA3y%Osqw)0m=a8jrUg^Gyy61)QgN*5%xSz5_3iIr3*kW0xfIXhprX>0q z;Sh&!czlFkdbbwtbH~c4qCKeU9zv~^0% z8e1+c3HW#ivvBi&ybzS$HJ8KaR)p%7K4#6a5fEbc;J5A2v01zQ(OXt6To>36)+@G= zK{UZv;FWrI5Xp~}vrv-4*{P#a{=8#H%=E~dor&{Fz6us{VQMy~_jTWHd|!KW`|NZQ z{E^!HFS}8P*LSbu>JhG;HEbK4d=y3yeRl#U(_p_3<4KVwUIel6M%NE@T9M4Rca*?f z0(_WgPp6Ue5YI{9pGY6K7=X=J$WU<%t7|OrTMu!$N2d%$ua~7LBBdtax7o*GWa`i z#B!W|`7Ou75Kz)y1^FdM7EB#kXGe_H*Q7C*I|Wkm4sRHsFdY7NTlCPEu{zk;Vla1enSP85PSxA)C2rc6NbjQ!ynlj%vT z-meN&k3vgIB}0wiV=L{y)`4c;(`?=G9hOr&HsG006U<1ag|=m)k@8=XhW`RWHG-z1 zu%$&utC~2Pss<+?rezvp@X!6;O`Y$VX-#pAfeVM+R5Vo&X+HVhY@dw!+4!{^t89j7 z{#jC-wm>ac0*^^6BIHXqy>t|w4nzXL(!cO38t_-#E7>7V%T{v(U!%8NXKsaS(tGUP zdbjUui(F&XgU1%F)u1!$zeEF|IPt9+wTv?v{s{-I-1*!B-YAfH+VIj4wd`i2|Duu4 zp^V!z9b+aW`A0a!=wD0)jGO#+uP8kz8Es)2Y94ryn4o$dbJYPHw)NVhP8S>MAP>Q+ zL_c}VHf?!5Al$&`6{$;RH~B#wvr%{>jbcNf`7m2+Rf+DPpr>9GhEGd#hzzTeiKw+m zD?@}?J^LLSI3@Y9w}1(jCA$&vUWQ!h&r>~owdLzKn35CCNr`ZH{E<9MgZ(dX5_xEU zk2*W*VmsL(H`dZ=a$a;qs{p>`GH3-N>uEmqwbAk}S~Ozy z>)W;E8V+Dx2JW9?oTko82nw0VO-TAKHwH`Q3}2`yGB-nk_*}~-&s8Q(d~lA@<)EHq zX3)R10R}98mnbL3-`>P|oG-f&tAYR0G-Xw+tB_`_u1m72F_~!n@qFcE<42pa_Rcdc zw-6!LV1wE9u#`ib;F9{jCBT@%xXVCwn}O3;mfyI1jBkHX$HI0r*cF)B^wl_@W)1Bw zlN3snePK`+=Y4yo^-E@w>3aXNE0+&quOwkiN#!^(EEl62)&c4}iXmE?9vZzR&bR)# z*gV7HbJ`SJX|MsnqDr%J3GUC|xl&J3uV1>WQztF1Qli}-_caqQHIVm?b?VY^&qv8U zIqm(|rfXjYs3}_4!QR&C!dbw2qz0cW>JfIas>DpNf!tN@$&*Jv|N9U z75M1cC{&aT<%{AxVTUuL_+i_vXMV7-{P(bHTB?oS6rj+o=YK=58wXC_7P84>p?y5i z7{wSB!AIC=>;Wm5TpHN28;n7$Mv3`mF-Hyk#b-rf3FjxA6GiaS*DaTCJ(4k$be-+r z3dFe^G1c+Hv%d9fC*e2z^j2po5O~7bYNVFm=KtOjd5!R6Pn>5g8e8t5+!UD-yE;=4 zj7y`~k@nrRV@0Hd5b%gCogUA_C;Xe=bOPigAK^^EO7n}KOu9jJ8nnWr_oaF7Ljnjd z!|hHLu7eIdH_khzDD0;$Uv8W)v&G_p$lpThq5c>|)+YE(!%mnN*(6WtzxsC#(`I#b zoAaz=8=lvT+H{jI@+zcrWyx69Cw%U${6GofQCVVAPaxp#UwZ;>Yx4%y(2sipU6Be$;kv3iLH`fzSf!fPtS3Hi01oe>y(~q6+yjEZ-QbvgGc?^ z6O)o{l6dad9E_d0bWm`+NpC@KaaZ~jg2j4%XRxCz;xAk75<4 zVVqS$ZNR=HLT^mk+4k(V?oJjVV5-P+-I6$Ud@=4YiKxO~=SQeA2(W2ulgi&F5o%;) z2popDypPY}1yd>+NPH^~4PX5~NO;Czg6bD}VMP0WP>}4+Fg?8->2BbwSizWp!>P-O zAZU*+^K5*mtnN5ppRe1szv+B9r|R0uVCD4w!Zvm&6Eq$O!}4B#uw)-7_*m9h8YpJ1 zth*0(QpG%mlcraxSCX;ZvunS90%k$}>|>%fqdGn`4Wu21nQ6mzz(b=f^xCec0q%UU zuItwAo=N5ERx1bVCst|UL_WkS88z>~gfq~fC@L))q&oEN`}Ei{4IR7)9xtFJ%sF(yt+h9=2r4id`_7<5kl3=Fp_+O1Q6HTlO@ z9EOx|8l*@-`t?JHkB*JX$GBb(+X(9r0n?W#|EIaIDpA?`s?);zPFBo--wCzHN6Z}^ zr{$){xHeT(UD1Cf07{avNv5>UMs{I%rU;Z5TI_4z^A%9yV71Zcx@G=dqKQ)mZNUV* z*)e1ZV1a*Zyf*s0&=0R9MWX#}qm2c(FPOk|mobqt$|L}D0eZRSfqV7B-3wXTI@&YK zQCc4mK38VVW1f*UkF=Pb=%n42erfi{xRWZ&{dL^Rui3GFQsQ`PZ42jvS6O6fN>xqN z+W;Y|jD|1SqJ3>Ia43OmI$>M+Tcn8!kVSGw0VgM?viU<(Y@O?sHrEvq?rV;}$YQD_ z4i@HSuw0}T=nW=S{whY&F-ngxp5HgkXYZnajUDs?5y8N>P2pV;er35-;)cHk&m)}- zohj@g!NRW8U)(X9iGetpNZXr-Xe%>=**1m$sLl5yQHJpFTr!PyO+SL3mvhkoRpIK) zg32kdMSf;39j65VBTZ`RHKcluwtp|`F-COcZ1+cYikZ^4(ES>~Y)IcW3ZpJ*xKhet zB7Jd)V>YkM5?uqS#{OoTbP@_gDI~|7K_S+lX;ZYKG3nzVi+v>@pUutPo7}nV^BMO^ z7168*Nl&9jR}&8$Lqy%$%1rK+Ru$|_KU-1ntLRHy=IFBHA`}QW8x?78CW+O4h62D$ zgfrjh0FrkS86dkw;@MiVeQL9Fb%y|qj1;ach=-LiE-0$4q`Q=Z|2UfOgBII$VD`Uv zk7v=h7kn_4%asUcXl`PVP0MO->Al`(f$XVfbHcHEm79t*JJ-qk$5l!rG-Ta@GpMX` zbygW`)hQfrzx$@Cp#!Ep(i*waUP~2vOPDw)A+dGtj1>)@E&V}uU2LX5-2gVx23+{(;I!$yh$Km>NzKo)E6>MEH0lmTfs zwm-D2Ne#3wXCMMGBL0b{V|Yo2lIvRaBL|hk50mB1#wt~PbaX}7A!S@BrtGfSVU88! zn76O-46KoIRK=vWALaiBqYHASDk>}WiVB%-Ac>X6RatadIcUp~fh)7}m!=y*MYs2J zp?aBgJ1js|ob&@cgE*nbi=wvFf6cf)g;$5U=m& z(LXDEH1I^W8%(l81by~up##aLm30ZgcJ%B9#f#>z#&y506lVEO= z{7|;97F{6w+LQ-~N#MxdkI%JS_Kt9#X9YScWazPr_sm^K>l!qvY=J=akMR&R*yO|- zfjk3CYHyPUV``M&W{3V{23whO^j%qNDMd6iw2;+dNiELcRg*;tOq2u17({9h%Q@*4 zF!B8t$R^0_HX{gcbXV%qtDZKZl0^@udRCoUe1~ns(I1B8<_5W)C?y4M<-|o9STxFy z3K5O}opP%6qSUB~gbyHegvD<@q?LU|qJ&$%{hYrh+ISNK;Tpgz2!rLEosxG%-O?TX zkILHq%ramJD!ig=awkYRZ^3=f$$J~+DBDZf1$he22dZW=V*MC)>?RKTc*ZcKMC@&P zuJTD(Kw~3+I)1DyTRo^ zx18zC>uq?~{0$H)JMg^bOMTi`WW-4>1|z6T(9pH+3a3IEM6}KmXla3=^ms20e<^VC z@?vNTj~O@NDUH*qR)Oftfa=+|-w~MG3?hxZpqy%gAOX?H58bbjB)K#{iY&_0jz&JPSu{RZFPu zLzOk{JkA}fJ#KsB(yopm`!?Yc@3ZPQ+uhfnk=25)k&myzcL_l0siNQ?gaJFHNf{~hcMwzP%7&SS7DI# zB+|&Llp+*M7dA6xqQuRW_`8*P#?h9DA%{L<79rLZ@`{ai;bfo=k`xik1EHs~tM# z!#Zz328>tN$wez>0%_X&j)B;*{ghEm288=+9;zQXy0T9tt_0E?DE*e;dHcAB#O7=& zhp$0&lW*S=u}3RI)eLxvF|AbF-7?_v9zD0t96y8WHliKfU2RwR*_xDFJ|}a^A3Gej zzC8*6bF`MXuq%GZ0~K&!LqzHkQ)3eFja67o_vMDd#dDT}^c zk(aAL@q4eCi{RXiFtBIoAtaLG`RUz67~bWDkwbd4zx>aH<9YfsTII39rq)+{x_`~@ zIV)9_w3AWCCcYvrK!$_biNY93z&?_H2#$3qa8)2UAoMiv5{9oZrh&8aV_B#9K8Qa6 zbfwrFMRh_6N_uYF47ZmFh#iev+befZ19>hEiq~#&;0JJ6^=XcPiDtJB$u6Ge$G6|&P7wMB>xnDWDC+knYk z47hSNG6bK!>iAf2m+bbbAVJ~8$KD<&JmIV?*R5Wg{HxT)@-GSkg0Il#GoZSb4z7+; zomIG^?_sH8VW}!hrrzG(NHexvlAc;NoM-}Ilrf?m6K^?E!SXV)=-XE>S3hkH{NttZaF>C3kN zYR`2}p*}n^n5>EMj0f4H4Y`F}+a8z5LpZ67YHP+tn?^wd;89Z?`CA-d0D``J-WWiD zVd%X5j;z4e_HldE2AOjHux%RtFEGK(J<=h!_V&ilFSXY@^ChBu?P+xF_WCJUvYDBK z?rxu-n4Z}whKoxC%V{0LNs^U8>2G^G1^QRvuepkrr%IWn8yp&YEHo?0KX%9IF7Lhj zecL{{Wn{wuA;)%22=`CXlqG@Egn?Md&gysI!2nw&@Va|4O>6I#Gz0XtHt?U%K8!FDH#bU4)b(!9VADn2++BC6gU@Zn=X`Jc!%T z+njClfcdzM~Uf*!wdMIqO^7L?dYV0qNa@p`qDBP z3}+hTMu9*6_-?5_IA>+sZ4Mo#C3`XtK2jewB6Rvt_sXGa@g-*`zEPoTnYkMTC5boV z>8vE{09M&s{HR$%EvdzS`k%_E-m@EiS6BHims@Bc3=mg_CXR&uc-5%H1XckRwOQpD zxk3nhZPo|x63<&21NWP2?x#y9DD~pWV5Oe4N|x})xQ3#JqwZ3QEJ?A_`@;k5XbYZu z?7X~TgLuDTUbvcKx9jEqpmv=AmUaL?9GSTVmkIhU67?U?Bx+e?sU*gr8)jWoo2zwHWu8 zmd2-WG+u?P0>F$M9dEINiiv2D{F#LBuB&eK+NGV6F5aNeQ}svqyVH-;aKUc-ZIRYV ztSNm=KFE=*sPlDfVBDpJ5uD0$KUl0kMWKHt8NfJ{#fi7Fp~xPJy~p2zp*#`~e&e1= z3(L$;Y_XbL$PS^gwx?$7S>(l4d0Z;4S)EJGU7aMa)wf5qEECDHp(}um^i$X|kD=c6 z55j|1*Z%?XyF{iNvrar>wUS+adO9g2_&5x3!)0b5FLE!eOMGoJB-%Ck^&T^phS=J- z`|vOEfDg@PQ`*Ew^RJ5#ikrD zs4{2M5-^bSduaj*D}KyRYNh$U+LdAV-FdqMLbnZ<$U|L8Nd1Y5s~b@zua_Vgo(l}X z*vQydhuj|zeJlMwpNEkvfO_~buE;I>_doM9Lj@BU546)A?OI0gL}+wC@BApa5NYix z6MytrJIfBrknk!EPGvgFC)1K8$02A|)X@VQ?(GfIR9SEe|LfJIVyRQ;*aQfGa%4^V zN*e}@L4F{5(r|Pu)^anV0w>`wiy{wqEL`9Br`^`;3#piVu-b;PAolLe4-1Z;4OW#w zsUw7cl3eqRbNQ}985hT#>>zX;hGg|1~6QLjkIOqE9gf`fR=E1A+SL`kA12r(q zya9zu%JP@FGu4%@M}{uTw(auKpVevx?_O7tdN{G*0`Oz3ys742?B~w3AF7exqqO#Z zIZ?3^*Jg*VFC*hGe6uVa_Oa3u?Y3!at3X#uRcx%CFMRw11dIM|v;=5;?s4QvM;7q5 z#*h0P3;YY??sUYSaq3TMdZkY}-%hz;egXbu-d^SQa$|#%q^Z4CL-|wxqdMMBmU{OU zty?t4zjphbb2LE%*ZvzZ_~J((0}AH`k^ca&;*1PK3Z(Nek*yvqDM0wR0PK!z0Xlq2 zH;G~pA_CI)2k%_rwN5YDBIJuG6_M6u-sDlD^YW_QE4y?%Qzd8tdUaD}J&F?Sa>G;H z%}$+DAq`Q8UB5vvP0GWQGsi-47(BZ>C9~RECM>1CUXCG3f=O_-km%kscX~Zx8(D>= zk%lEZ0RGY)llS1x+zvor%#53P#UC22b+fHlzFvjfqdQPgIVKy2_k8(#&KMS>QXc^^mnLSzrvbLTUm;5 z^Csn<5wvZ*rww7Ix}yFj;q>FmN>dy?*%8Bp_{iyZ>(|=UC5V|4sbzu9056wEXPH79 zei9UfSzavbb9f-qVw%L>!yKSb5#0sP&+D9^Mcnw$8Mp&Ut&8l2!+nx8iR0|MR)zhKhw7W1F1cX;bsGA< zoLybjj-}06%MY*Hd())gm!FhAbp||ZkSiwBW-3J^cQjBL(b!O!7FwFajyP;YTd-Zx zLn#{5Je0216Rz8^r9S`y9B;4UNNkVVT$d{yhl1m?76+~ECcmOM-m7`jV>7f@5T^q$ zdjHp&Tq3l0#r~=o38I>`0HywYn{+IA*U0xD7I^mX-Hu2z(}sJw0%iRwpUoLA>h<35 z2jW}na!+k^)W&WX1g>9|w9rCJR4=J>{HDsISRps_=pZe>p%F3~SEn+#${+SciM{~e zo$_jSKGgzxH#82$p@Aaz_CskulT&U42!DzmY^#l+pZ%(w&aAtAcoGtkL%N8HMdGDv z!hGY3=s>(5&u$8i`(7sdw@y^&Q}Mr>ood(ly(oTrjdQ|mwHY9BmkKM%6ct+Ik5Al% z%~$O(!I-XjFFZ)nGY}hldQr;CiO6y-xG9jBRL)u5y76;s-WypokZ{_q6&cw`GHM}J zK3=eX{Z?8xx#kP9jOYJoy2_|5yQX_nj}oFZ5+W_#oq}|Ccem1Af{1i?cXxM7cY}0y zH@t_nzVC-iyk)Vt=9)9JC-y#js`ODUjH?<=Z(m*h@Eqfg>mMWBDABag@#_mYi>1I$ zTu0oi%x~4-w#MFmU8WH)ZV?HrxpA)ej!uUesxK1R9j)pnvt5y^EKGjwXN%cagCt2` zh)^IoJPoZ@lg3tlN$0?zk{2Z79CMGay{9nPY+V{i@)`5Xu{G9j2QJmVbv>^$ML0Mb z=a6@af70}#?OWdNy7_@Zj#`t!vL9sBA2QpNA$Oh5ItTk=h$;wfubbdc-1;cfxK7|G zucmE6-^{q#+O&|1B`2MqHx2|RI;Hi~(YzbNZusf$KT&C86{On21>OD0&}m)a*wuYN zE3B;zSGtx+dy}I|V|!4!J4lfOk#Jj_@{j&u_`uKq`T~SCS~*sm>g%@m#T~LaZdb7( z+!nzZ-**@*c|xIE+51Yq>Mh}*Z_Bm6o4nYF2}$7*&%EkwV-(YRo4uy~gtxB>6@Le3 zKDhxiWLZJyP0SZVB6USLMK>lN*Xq(?2;KzFukkuL_C^+E2KnpVvOR%;4O3VO zkcgt7Mp|#1Bcf)PYyVRQQZ?Dabmnu!`GyM`kg;ud8+)WM_>Eg%QdGp7zDg#Swa|_G z{EfaCJzf0Rc~9Ii(W|ugQx2?DBG>}nbzErBD->j`kmUPtx>9%#CMHr0ENCoi6#jv(Uy5oNkn1y768}+Ufyfe1V|KQJ)Wx0-n zE~K}zfJv>^hA->UMt1ldk#_Kc-0ru`cYf~5P@q3E92e|ip>f)%p&tN|nu@zT{#1!^ z1}*vPVgC+6^z#FED_U9CWaLIh1TxO_MS~7g6SHp-)#&=VvMSDDSt)l=keh|?`_PKh zH$fr44|ba^Fs!xb?IB8rx59srRz&sVjBOw8E2kST@c*L+VUPINyU83DnQInp>;x9s zz0*#eR=<;EaVC~Z*s}LhaGwpvMh%hVk8~*HO}78akaC>HQWZ~)R zFf8M|3FATyg1!Bmi8|vzog>P`4d38zHydZnkD(fJET3+33_2w5!sEqp`9!anSlOoC z+va1!P`fRm<{RHRsn)F_FNk7bEp7`uW*&rHq0o^yyX447frwxFSmHZ3yk(NZr|PdL zOzA;yyJbV1hOnRa^Yt6Xg4j5Porvk9jh4mduQcDD;(X7KKyLOs=ms!6zbJUNL1_~d zzHgF#mf+0Xfc{0O7p#kWCFr)Urw+x;6vgf4elR0-^nB84lm!J32udbUw@tmpK{XowkS{cB^XKn8Cksl`F)kyL`Q4FNdHb^a>1Jq<1gBQKVG#rQM<$xUGNojbu2;P>QzR; zB>~&dMk`x|Hhzf-MS{))L4|n_17ZcI#x+asJPQ%~&lEQ4D%SX?mtxm{^uCUsm1z0> zdZ!T4`l9A;fknuD4%OwG8U^4fyl&?|(j^MNISZI7Qgz}s`ne=J9Z)JAiBfg8<{Fu7 zJN%9im9;WRuY{#F7r(8ew(EX{(E$jE|jlJ zJ^WwYOm?KTj)&qxuQ;p18&8moJFllj5}E{4=2yQs8|63oM-L{+GY+cIa!Y^fr`}J@ z*_*(K)Fqv$-rE-Tuu3Rh8Q$26V9G91kBt)<`S8&Z1B-1kxw|H1Ut$_1oSGVBV8;+O zk)I4!`aE5_Mn6LpkC~DW@|5y4p_Kc!r7|yQ9m2oO4eo~!1cH{6xPHm!i|JAfAf+Q7 z;lGD~d1!Ig>CbtJs&CPA2-d;^ZCnYmUjJZf0+9qh9A(&Wej~ZwT1mfveDe{myooPz zyj*fyYf1n5jL8vB#X&NDOhdhlMB1B})LX&$<8eC0P%-rp?Sh&yIheLo-GJv$rf3Zl z_pWubcU(BrEZ^8gd)iPh^Bda$;xd!Il#DJbpnX+31^Om05OKJX@t#Zf1l zg*xV{iP{H2T=%GhH2{4*HG;EF+S9^w5i4N=JZhi9#jh;3{RiZ4pt7L|%Hg%seZQs% zViTgcWwlGxE0|tDra1iM5cQoNiS^;M7Z{<;>z1=&s(O#6k3JI9tzn%FvN#9wf}j-hgLGGzye#dXKTs-?M>}VpySrf_4 zypL#iN4@x%beg6SCcx^Cj)G5}%re(8!xi?K2dAAY{mC?UtS;g&pB6G)s*Y*MCcBr_ODDH7aLHiyE<+FR-=Oi(b1;=PN`Uu^# zOlYoO2gwJmr+w^rm(;5{8+rEo7T3{65cDj4t%3 z>U(%|wl0(JVO?9sOL?SyO)uv*pbR7wmdX-fY1MK;pI0eSU|>tCAXVn-qg(ivM z>=gqi%wC~m1o7{8z10<$6`C_5jbh&WJ4U5@-O6C>zBFMnCPnG&K-Yk+2ynT4`Sh&FWjUS zVC2~`(7NIRJ)2teme(T=fPEx0?A|4O8A)UxRz*cC0@Wo|$<({fjOoO;FXaI8e^}CR zFi|bP#4M%d@Y9hfwk}7-2D4*?DIS9yRA^g@iRK0@?dUokp zHl|wBljfP^;;Qo?97eePnw4TBA;eP2&(chN5Tra?^9 z<&b)}BjldSc=uMnKVNEVcy)8-`l~O;kzl)pUm4T7D%)n>x;m`${w)sQoI*L6nBu?# zi9YmlbaY5iWlg>#11)AnGi}_~6|9}WOLYvU*0Qy>d@aci<;= zif@!M9DG@Fkt`k(n)#L=1lk#JXhEeOoQ8;qh*92l9=+dEg_<)`;y5cEFV5b;YeQ;6 z@eRDT1|XO9PVawqd>%l-$oPWLY0Ubga^ozpxPo5q?+R-Xl~Zbpq2vYT<9IpI$8)ln zbh6+FhL7h~KJhdt#L?cclw&aQm`lY48SsSHo-7;cj`nu@sFm-=r}JXGP)VN23rVIt z>gtwAh^ib7&+dZ*DZ3_yM9=<4`X&ng`JuZ{YL3rEHd(WZp_F0m25hd+CGAMRa>?5p z!5!Tv&T87_uXTW3<$^Zyk!q~85a-nTde>*a0Y}i&-N7^wZx6dLKaPQV7g-Yq<)gCX zIqRFUxq3NvO8C3=-tM92K-Nd{sT;luT6hP7Lz-CufAm#AXo1WZl5L&OA57uNOUAX0 z@6a9o*4L857_<_STi=52G0l$DX1HgS6X6~V@eqh2h=u;}i4j+&3?5=X_g`wngHx3$%kpju99?W`shsE%I?T_Cb z7>3^<$;A3l{Fk*nOL=cBH-P7iIV#LSk1X*R13Knv!#6Te$@zjIG>&woJ@Z;CJHkrS zPxvD1?jgW8n@mInb1JvkJ-3-5L4i+$?l;C?R+B%^wmF~AIn&UYlZ@ss*RuX8_DKDF z{R9XNXV?1v&CApI^mSNNkj^(gQ?JjqtHan*<#e3aqNWIA0;~)FYcKECaoFje#Z%OsLsYAWiAuVm&*i$G%G=CrV>b}>MO(uM@tZbh!bsefjA z8;M3YT6}!*ERwU$_;Z?x>p6ya(Z$m}`Q~vASFv=Q#It0C zMiv&Sc?G=P8FlKr8^hAeE6L%^XbuwHz~(#;#nH3X_P5piu^(oQ>(TqmNBuuu&)+ku zgw9ZX(uR_TPOol<3peaTi9BS7P9MyIV=j4AtBaks2NN^S!)AW)%5U#EvX-r83&WKD zm=GVP*fqEKsbBr*$)XS0I!!8BpDI1-&fz3j>f;uUrg6gGptfnB3QHxccjgBY1GwIcls0Gow(zn z8FDv6QY)<2kw4U7@pxYnY`z>lptHiCIn{Vk?NVcwCue0+VKrE%*i z!0QyT3MV_)g!F4vYsyYJhQWM#&{%~Rl<}C}sLLuH+K`X!?##2(`7}?C#TXt%u@xJ? zr=Ky+GZCae$}ykq7Y!wdG}ZF`y>KF`k!08mg}s zN9-k-@5(Q{Qq|0XTV@!ZY{s<>I}^F)pLTux(~+?w{5MmxVa{uVrUoH9QyB`)IY#=9 z*PJV7i8Zd?J0A9R1(^BAx#6;kVtTb35f=XB#2=ka{QhbHE=4C1K7yhvJx^VmE!d<*rxfUw~rXI=;`n1Ifc9at{aSff* zBrRrc0|bA&H%Z19)rRI3GQDN{^I^Zo4CfMj<6LZ^h8STMJzQ%>*>pwPpEEU#yI(1( zoT*hgV&Ze!8-C-*?sK~-XCI9~X%wiT+367WL2oG8tn1sQYcF~z*P#wovQ`xIO=!Sd z{PDq!Buhiu`I2p8)@G}HO3q$%ix8^hp_}7%b@MwPKRQ3=z6DYfYur;!Uc2eRou!Y8 zrchtIrIb)YsjlWXT6p~o9b&QlLlPf}-w@i*3l!hLzs*vSiqKkWsQVZEtYx!gH>sjy znWqDGy)Cb2j5r@u-caeFSwOwp=qe#%)d4_dm6>Cp7Ot>xu2@N{*79@%{2GcBO2Jt> zbp0s0P~ZD}r{RPsy*#pDn80~Gg>`>AvU!=}7*ykQ!h}5MJ)Viw*qQkNek9SkPPPw% zC2-Y@k@-)KLS7g|Q-U4&1Hw=-G#tQaCca&JSY0X$b$VhP17GgzqE8hWiuf~bnVYPij;4yPC`g>*HJ+E3;*?^ai5F-bP zRwU)R$+XSFX?%n(x7Ot70Af(*oLL8a;E|^Edn`A|+MAT>SUkYw%S1V)T~~ThO_VJ`S^ee9pm%tF)pszq(%r1XNFgY>ATyRDS467WlbxT*&l#_7FKEO za7*^Kt2tIF=Hk8XWZlzzWrqy3YK(m0#p(D(oaszqDR*1XpZN(^vg~9pZ>^&blm>by zjMOPyLH%N-;@hMUE1QoFIa!=0ZoAiT-huAE9zt*Qef{?=HU1E(#q!vau`lgsJ0FP} z1f*d7o%&v&S-yU-6O20NxBul(mmqSRv?p(I^VEEFrJ)dJhByWRLZ!GE)x;)Pkg(*B z0@V>Ml~X8QfX-u~{8b_*{@5QvpFv5hSsrqlb&;>3V&%0hFGL~x?VVye9F5yRt#TY@ zeyaa+-gfvItfgvuRrv$No^M}OR>*1|L{8h?zZ`{R9EC9Db|+F_>69Mg#ayMl@bWrX zn}{oMGCQjQAPxgO2tUt|%?Aq;d>}!29H^Q7vnVTs4U7*7M@EL0&%O!gI(wKF22bFm zbTe0eOo?yudc{sqK%K|{>*ornoPi<3g&)71W&pD>TPVc^TXUGr6w-=}>OiNf6*Y1r zHjRQ`tXh=;_h$z`hnGV_8Azs14D2c;=sN<&)j9e7~Q3 zL^w$(?tsKjHLnUjqd&CM3&2)Y#AOE_`*GnBng17S+n|+({LJL^b<48LS6`KLw*ZiG ziNRojFhIqPW8e94$m}&0k5Z@E?>wgzarU3!ukVeN8d6P+n!i{rR{QnyJfK~VBt$B4 z8zw@^>VB~Sk{Q`yN|k%*(NWl$)qXhFA@iW{WoJn)4w>n5bsC9tie`hi5wYtJ+nTa2 zBN6hC%eK9U&OVBy1==;ty61#EH`+BG9#$7VWmddn7JxrrqOhX~ru!^)iShdm&T1^+ z2viHo1^+}fS_=~}qQ;Kj_teA3@^4GPCi!jEH)w~^!|v3FwtcLs%ZZK3gpW~Jz=I@N z_`Wou|9&?@b3-=pPg2`sF^-g-=|`;G?hXz{%Ku`f4SXFEcuX6)_3h79GpBTG1*)#q zA-N-J$nDdRm9Q{g(9L`UG zB^gJ#3~FGX(sYRtLH4`Y{AS8nz@#ovjDmML zc8BM!x>W8ji96g(YFj%)e4yLVpLg??*!#uZA)K=Fgl^&Kk{BpXW-O!zD@^r-x>?n% z{AULG`Gsu9rm!C*s{FM$JMtaeC}gvV*h-UiZ+qVbCx0}W)tDil`N^z)z4i3;KAZ!E z`%b)_plYtNV^r^e)B;aYpFL z4Rg*O{7wiXns`>A=2}L&KjrA0rMk4p?m^G2FARP8Wx7}#GfaS10rZEJYguWwV)z2~ zZy0-Y?Qa*DDJlPCepvoV7OyhpqN?!Ks^C~*hk6H~pAG9>GS2s|-z zUl%y+?|Q}nC+Qrqv%OkHQY>C+a8~oUng+c13)5@wQKnFOmp&aYwR{w}e(%&8vFgyV z-cWbr^1;Plyulf4lDvNq@{N1TxNAB;qu6&WG7Tr`d>$jGbO5!li}E>+%dVSCCqGo1FdBL}OCk=&qaO@6}X+h!sYeU~{>rDju%Rg2+YSc*wz(B{g# zeHZpZvpVhagv?12lC8|-z>JrpnS@yt8fcm=%+ zi`i`^udyZZot^~*S%K~UimWkHyAKgrvvsigTdUW%x5*~K;{3FCm7(;Jxjeg*$N3J0CfWg6+54bIKswQtRZkolmLZ3HI7KJxBozDsmHO_cul6oth`x zW~#j_#xd5%WySB-75a|B-Y%~pRfR@suBPFU$4x(Y74AFmu*e&gT}AP!LjCx6QlmaUuvV8&|DjXY4*aIQCDPWH%BAlfgyM0h4GumD$AZUVj<;^mf75? zz>xFC72lFrSWRdJq=qyzp;!Wf$t`D|0dm&E_Az zAQ3Uv$j$blzfZXPlEb2OpuebD-4ZVr%AOcTtNo5$BC1N_l^_VX+F5=#@Qk1MiWEM^ zjimSkjgAyfgxJDt2z^H@|J4KKN@I%><~FMZyB?opr_Bs?iq2fZTQ=uxT8V8@XX^}g z-sO2E-?&>WqC{{0<1b}T$7nmJo9nb3M5*I6dDA8X%3AU?hig46LB3zu z?HB}x?Wv|z`o7S)8j=~o#IUrj)bv=-{!uLofBAJd-{Rf;FU04F70Z<6KWir&Qs`m zophpvDSs2^Mr-2Xg=)jW|M&_hzulzAsJev}*eT|2Dg1jQUBFs0ldr3|b4dM2F%(Or z;;@>7k#^2|nm{?sOo!YZ0`cGgGIok)OAgbrTPqz+p=QZSXto%@re-7?;vx{P@1@+bqOZ{e=?Zz}&X-OS!M za<~GujPG2qw_p~i*tThgi1$aZmeSfC)s zi+N2;3?(@?1C(I~nYBE)LRFQBb$Hw}4fGtmZhc5tJXYTni=PzViFJC2SZAS^bUX`A zH1Fpdltt52GtA+KMV0_iTjFxm+|$0KTnIx%Nt5M!iN?!%`e zrc?HF_yz`!jZcd|#yUG{q+@S}cr))SF}}!0fNC=X)Jcbl^7i&K+O|7`Mo~UApE61_ za*WOw81?r}I&xv1TUKiPH+iOFy58e+^u+}Ru)xm8$D&)>E zYbCUXkGyIi(xC&?!hM@b8iVl~$ko+c&ZOe4d8 zyoCXmesXPcWu9~ahsK>J2Tu(p5G3BX=mrm*v7F!_MHDOxRpFa+UPb%ip{n0((O1&s zRtD;eaUa~KMM)`H(c<%;^JkCdOUXA?f!GdffePqGLs*$LCTd*P zH1Ad4$ZGUHIO#k#JSiBBWX#Y~VP<-}n@rGD)3 zy|$icQ0_LIk!vpw;~=u@KDukGK#2XbM+7eR6neTX}S3$k@5H9aBAJ>;^k(Hf6= zsug(87)Ln}brH90g50=- zmJfUT3k|t%>DnW$Q}NuaY>(Kzec;QuK_mZ6u|3_1U>YOslI<|{*W&My=-ffhg1wdn zHN+w5Up7a9pn|BW86hY{3b>MjgTD5f?T?Uckg-i)>yK2c7sp!}_E>gzebj20sr8>b z$3R>68c3@lO_e9Y=~HsspZWENcD^D4Wwo;qpV6j2G{mx&fiQb3L3&@azp-X_vyM-l zD+WZ5{Thrcjj8Cqj2bGa)3~oMK6iuP(*RuecC3<6U{aahH5Hyk&ISAqwj&ODnP zUmJ3MPhFy{Qya4Ejwdbd+HK9hrRKL`+dL#dHK(~`z8h@z82^aDq~`S!<7`|Y_BGO) z@{V>%>or2H`_L3}E3z}2C4Ryq>|LlK@uTs`NIqNi>o8pi>31mSuyb7jHKh`?)#*GP zl>ppAxU!L>g}?StinZ4r-4ps7whTSGhWYx?bogMLD_1WHq~oXtM}-Auiog8ntY;X^ zX$j7iTDJ|ee~qHn*4l;Uk>st|)f+#hc+E%+(DhaQ~;{tym8IWm;b z*|c@-@BxVxiigms#V)P7v4Gv8xqEM@rFxXVPAO=C@j8u_ZJ;4(8umWE=HO_Aj99K% zi=G-vd%-(kJpmPIWmXzPzGz|ry5P34jrHogtXgbCw&fjMS*$i-2&o+-w%^}L(*3iM zES5mir~3_$8JjcmA0*f5S$xOQYV*tH`=?k2ixH>fw~fXa&7U1>=`Jkl9GMcyAIvA; zASq5OK$5>(*R4@6+IYL%C>JPB}_ z@7&t2`~Ic)Z39%A`ofo zx@ghIw|G#V{T?|qv%l&DOa8Hf(!R4J`IRr%&+m;wW#C{}TtmwF>uTkE>`CW?Nc>+f z`T3avlyS8qBh?o%A}C3V6I3GaL0#4`GvWE1cHxBWY5A)Q_7*uKqp!F>4b{hbKObV0 zyrUjRz^MPnu@XbM7MN~9z371Mjb3ul{w}IRo4zW3AgnP}6^;O%l4K4!BdaFG%*yoa zO1GPIQ_PZt*W*rKfvx-f;D<%u>Fyhro*T{x^}sR*A}cZXxRN6sJJydYSEML0rX58i z-d*mur(fS_$^psA*>cVni!0;1XIy)D`ILiR-XUnFqBDu>KI)r=&y)UhSpqnqeY6%# z>22AC>ZR_WuuO7osW?4>UZO-m@&6(Y2UGQ&wX9%mVf|uiJO*T%??jq4KKw+L!ca*Z z&IQB~XB*ZxGB8-}fn9ov#oO0m3%TExk32b`+7l}<44zhB#m?EkIz-hw(1vSkFnmfL zi^1We3#qfXw}FoH!vqUlB2YqZ62$BgikvZu8m z=U6Wp&6j2Ym+T!6Y08nA1NlJ|e7AoLYV)@Q1S)(_3ZN0@6PN-OqWIIoe(ux}io4!a2~yLzD3LQft0!=x4|4X-`b$Zns;J z0fdb232(8zqQLZ@(P16E$lrWk1$c{`b}^TbcNqQVo7=YOJ2#HD!C|hQCd^g1ScTB; z{B7_U`dh{s%d-=PD8eGY-!kD7u!(8B@b!5Ax?Jh+6k6Py&teEk!pQjABF6b4h^|H& zQQ4wbxf`!}CRnM9!h&D2&Z-C>Ob<0&8rM?~Q(!4emN>CQpC@{L*s!5`kFm7LZ|vm! zXr#5H|6TSFA-e@$VY0el$o=={0U996s%suHfyCh~yonxw;9DMI__!x%UrI^h0yKcKN-qy8UR%T zC)kX5UZtx#w_HvtG!<4C_y9A9Yvj5=*Xd<}nOZlvHVg7*!$j-v4K7-hlQGLa5_@pw z7vFubnD<*nf(}0%3XiuY&h=f9#Ob;D9vsb@=wy9B0YXuXki zkHbR~nU;PtHmO6@6cd$HB7dSaqBd@;U=}Z0MEUfnv;|s$YtzU#Fc;NN|Gd9;1B@G> z!6FTf0Yu+zoE%CflqF^+5$489SOk%UUy$=&Z^Htv5>9Qt73p9o6(;Qfi*acE#*)0u$smDtiBY-aBk^dM z%zt)Z3~{I8p0^nGelZRe!>iCPq);-AH!$W+*jR(Gl7KWpge7&Uc^GCh?QKb>;c_Ty zF=V;+gUHvRM61DhXK6-CC{4PNs|wdvx9JXc+Y-c?Bcpv*2b|G=P&72y@yY_T@tw+u zFkbdoZ*>c172*B`%+kubb-1{zcVE-0(!G4knSsFmS~6A~=e#_p{S>EfH1C^oZE7EV zp<*qzib{0NW^V!ZM{=_QL8yS}NT^T3&x9=fNi@EJ zi%&m0+9PsjQq%`al(04~KWz~%lYOd_+wlkdh2#G?_R~m_9@3#@LB^)$zS?8kN$IY=T9?V0@MQxD-to;mKMY<8GAXcy42(zUoM^S0Bs_D?2;XX#! zb<^GudZ9N3OmSL#kEO$QD_#N-WBcbcYf^ryD{`*mJR!!`_|X0blb& zux_S$SJ_{PD|8=QRdcIOd8OR>#Jn*sKifbv-B&qRa^B_sl2GYiksVc7u42o}hW?u% z<-Yzg!UFpg(fRWrSZcc!+PycqP$jk&K?6Diu{xW6)))ONp}*t*edb4y^d|8n3S)ga z@;)*h6?==~VCGdpAp*uAfA_lVN5g-Oaon?#Pc(1pb5)s;jX2Wb7L}8LxH*}#+i^`} zPBIhez%{YfOQH2?1$wJtwQhjY*tw zL(&b!)^N<_!tng1o5Qf+&BvJCJYuWenZ$R?iu@_+S=G~=nX1_H+=%ie(r!VFNGhWj z;8M9?MLVR

L>IbR)v<@VWqn?{x*dXXbI?k0S*2K(=UvFNi>B)eOI9Zd{o%4x!1S zwLbg&A1ho9IvG+)eXs<-fc>rgHt97~0~R$Povq_(1DX$GLtVx$4k}LP@=J2iI@y2PZmZo1R-!*t^`)^XvIGPlSp-l?nrmkr=^V|-dmS#;@ z4Q_#faoq@9?_1+}#F*e|hK?ou{jBx7^WObmA=~4o`h~N;J}oQp z@ww#hRUrxN*B2k3&y!4RKK8Qk<;p3c9Y^Eg>>`Ga|z36 z63x&3T)f$pRn%LFrKxWyEG4{?z{+#1GR8|Do6dCCIHYpewaG?)8a5^f(y@V{9%c@lwvi^?n?naJ8_mmh<<4HSkkH3bimsA%fD;6$Nzyl*D0lg>)q< zWr;}Y=`X(}*&c_};oe~Lmge@{!OSF+ZC;{4&SQ!&vrz84*XS3jHS~WBbr({?jHUl0 zSwc|x42+1svCz7Ouvn)V0r@gDw!81Xla0Js_pk*Epbv@I`L^l|6HJ>E|#&y?Imk^hlh# ziiB(IE*PNAcRKtt0COuXe<~3U%7@btbD3%1>pLYc%P;y}T$az3+)!z4S1}SFOp4!k2f^<$e1(TQdoR zgz|>ROd-$sUZ*Mkd6t%J_)r+==e&N|-|KFUlwnz@NWJhwu~9LaG7_k_70y-vgl|C2 z6t)32#$THa9!lT{6~9`h*;cTC+P^)P)kNeFJB0>OFUymZ{V%I@dyoLJEA`_>`qHu>cdX3o`3#wkQHjjEguI3xBYshiFtv~;7 zob#%}phR9SE7H}V)urKB=}plrn`fOn6jsQYA+WRclU9sSj2&aWrS#qe>Xth6?LOpi ztp*WO^0h(L5HnH~%O`9k2O<71%w#=O$^BxLur-er+74WpJQ=fs#hw9G-X6Zlnt;>3 zYDZ`P@IXvlu(jS<#Ad_Za;z~>qo!Bom3OOSi13$H^}uT~wG3s-Gxb!8cp_FVAzqg0 z>TqCo&IW?oeg6*I>Si~4*$yHU{J{Ivm>J?!hH+tV*56o7gpJMrT+P>3lJNd?Q>0TLA?sT}>Y^~uq$Fv~tZFX2Wd}#w#p7~P_E{>Y{ zQR}9bo6fL@&sO8Vv=RUr%pA0%o(80#U6b^`ey4~&UMHRDm~{ z`O`ORmnm;?_kK4QYd$e!j8nrE9GL`VPM)8mE#Z*d7+Wi4`rV}eCfeV3QoU#fKUIN> zZbF!$T*YKmZFm+?Wbx%DK2_cD*zOvk!39}a=*EnpsegU1`=JepC?Spc%mFurkf`!F zUwb#%(25{xGhBh)A^tb|5R0AB$^~AH)frG+NVhutv1Bf?V|3Q0e;yCvOBth2Lgw`R>3&jI!=BAd@) zpoI(#gqoT=81R^SZf!B?#(acgY#5G)JpPMQTGV#ykcjTg$zJqc+Xt1$Wwu1cQ}w5| z#bY-jVRYQ&1yNw1)8%~HW`;sj{DB;)u055qEiby0_2%Mnfj6)WF)5(Fy|%_Jfno4& zSrpz-F!(L)QM_Nk_!UeQtX@p#r~GWDuHF&(mR(H#YE$LWzM8G7TOJo{{_jtagxd8V z4(G3rQ1J~Bt+K4sU!Cse%{U+88o(v9yI59gz76xx95mq#H;W-JkAax{IYw#=24Qsf zh%=-E(S3MEetYtl;iYJxF3mZS>5G5RFyctr1;@6rEEoI3hdci{7~RhF6YctBS}n+2 zRQLn8YwkUTk)#ncfg83YaG&8X*`R0Yw8hRf4f?XzJdHa$h(i-!Z-DW7K>%)hC9i9DLY++x zaLF~8Sb8&KHjq@H!dH!G$9>}RH$cKzaM&M7>ra@H|J+k;Pqh!Z(AvyRy2*D=B27>Cj~0$I{x~_ z7sVG;aeqbBCX%8GZT$?vkWUzbVOlJpyZ2_v0|#dei1qynoBa7OJ3+b239bJ*s~nm> z`$Zank^HJ3K)TuZ28JGK$QM^@3`7WAj!;_89^Z)fE{5E+g!b61hn!(1tCq4*JV(u?uFs%?Tig#s=f-9BsC#2vxTgMS#3`^ zHir^!Qp0ZVvh;|jYqQ|!WxrB5?Pmfd4T$vb*tI)>w3L**S*};9s^mlI_bZ$!a(wpzLU^d#}k~Y z(6-zVqT=*7#5fMw9r5)-3P4lNZobKix$BK8CtF#V^O~%Ox@I|?{MpurPkCjDUjI#p z8CW#ZemX{%=iY{jJ0 zO~vye*?&qe{&*klM8_D}cH1N|#P(uy=9_hGi|@Oa>R)Oubr-ImeK>d)CTzylXGYRU z0e2^x=Y2Rs*ZLrD0bJEB^=vb8MaqXN{PiYHD>uO~$*z|DRah#%!zC-Q02RWfi@vB% z?{>BQ4j{u=!3>_y#!@Ub(|0zXk_5gv;Y>?aGRh{*-&ZBYkK1O1 zdZe^Ag-K^gT1O>eNrpzMc`T;48=i8Xyw1X^&B|@ zz7xC3|FnsPr^^#x_7*6Dv6YDb07ZEP{c^r0By=k-? z+$tST+fc&!q;U#8*=+|U{d%v{trI*y+RH+LAkm4}{m&aODb&VMv`TkS9-*%vQioi= zNR5VD(`G{$EOeEvEYRf1uW}b62O2M-E=uFQsqFsy6+2++P^xHe1Y>AvZflCppZ(<3 z=!no3{HZgmT}SP%X#a}t8OoDVev$IG*$v!e)tg&`K*0JL>e{K z%7rT#tPZcG&(QvJQH?}hs()NT^9)zMMWr&VhpS?rX-b6G?NDms9&-US$6unlv2+{A z&X|RrBp-sp9Lb>$(ut--I$AZ!bUxe0yB0eRPNfsC{`=Cl1TK@r1#!Mo`lT*!sNj9Gm&p4bxFJ3+x8^bcn_t- zi|zx=lwRoFu3x%;El(?c9xSFU&oD5NN!2JB!bE(ybxBi*Cj2DZaqwdd)}{GofHip} z#=p2nMEzUchj#K!_hm-gMVyTZMjks9Pd0&>wB_z)w)?R;-ydpG;DD};bJW4ol?)&9 z$$^H{{e7vx0~1!nC)`-#cEs|JYaPuzq2-id*wOU#bG^mgu0OdjdK|BqF^|#+uh9Sz zm-ulWdu?k&ef*Sz*dl$r|@RcqClDg>w5MD0e1w$C4%pH`b<~wE#R^DgQM6ZY|fiNYq_@SedAC8hHo^+u$;O>7UU1M}!UDLk9hHY%ywrv|v&?Jr7*l29C zv5m&IZ8f%Se5ddC@2r!1t&=@-?U~s#v#-fq&_2tQB#8e!@OxNG^2XSQ8$@S+p*pQw z_zLXYdVpV|(@IG^xHI~u?(pW7y+W`}x>Z#TUrE7Zd(-o$63%&-m0^0`wcalpW`&Mv z0WKh`>A0szN>~(FfBlbe)6)? zX*uP`(N}L$7>U5^EC`Obf3eIF?HRQ!_L2$bNr!S^&A^CmD9Y1;7>&t(_r(b(nt8d_a_aWh;<&YE0pGI*1uW?mg^`>$sceYHHoI{t4OBDu~$CM#UKRhmlUUkfED{@oXQizqQRw(2F-wjf4s+iJ`AE0V3w;R^|E(KlD%x7~rk3mcq z*}8u{n;TO6{LPjaF(vBY$Pv_?#pA-1oe(_RCJDR#bFn}zEJ6;SH?`*V3soF3&B;I^ zs+R(eqttfWH+|+^F$!qQWOlseLRMG8_V~3OUqJM7Rn;#H0v@kY2M%hG_VUY*^oNt; zHTPzM=?W!di`nlb)2-lU$%F%c*D^wgFVoh{@PD+GSid<;Ta|Hq#*t?Da`G7MVQw{K z-_GYHb`*=#g0~0mrM1WL_UVArz~`@}Yaxc33!4GEz8gR5x2CqnB8jDx2Y~jc^D#g{ z)v(Rk&`M!S0Pp|Vf2Y}dVD(`8w_wCiRCPXY~X+O zWmy@@;xh~HDe>$XAB&1g<^5v`9LQdFb6^z?o_EKL1v@k0(8Zyu_%68J9zM{k2|I~n zJilH93J@KquwmtaQymOv2Y+dr$E9oB{eu``hLD0#AsP}L7!-6pml(t{Rj%Ya+xj{T zbVG==e*0~L>~FN0RlCj_xRb03RSSrAxUe~4FQJW(@$3T(5PBlfLoYWuz{fMseYl#A zCvZC@Z@u0o9d;BIJr2sNC2paN=X^(&9wy+-%`m`VD+1KRuQbUq@qF5ak`eAV2t|&y`G}cpD zMUo=rd8j>V?M)uc(48W~dI-F~%Gr^wDic)2oR-ddaBO;@ZX4t}xn#nS6b(30KWRO$ zyFF$wF!x~{GUk{nFJ>@Zh5HtU#}qf^eUD*GCO{6MYgIZeFH z54kiA+X!EAWu-K1FPS%-N*!#2e+){1MEk=9)hYi2#!tVo{UELuSwIq~7I}C7WJCPo z#W~DI{P&AO9dP@QRKmKK-b_A$S9~%Z==B{gOnormDy~1uK0xG>&7PjJNyxx379>|YyX2@dlRuXY z4^>o5KgM<0c_dDT_fXn5n&0HD2a+@EEase^g1ibWR1;8JNrBKG_=J-^Sxgq+!H7G% zj3j_mH$aHopTCX3SrFasxf{ViUEY(CBlGLFR{`cCGQUPN|JC!D_OeItrRKtEha-5t z{M>w{%J*(CRGDw-{t~Z4Pb#pCNmPtu@i@^GbYek2}mhMc*J(^g-{> z>froIdFKzwZgob>As`B5g}pUP%XY$OQ3?QX+IPD`48Wo7%tl#_>oQ4Xwe3ui<1M?+ zo9ZbaOMn2+|J7!x&7v8-Txz)xmF#(sL3{bIAa}mX%pj5q@RR+P2D{x1nWdMz14R(8 z&O@q2qoc1)F~gapl_uW-<6tCIjKQ4DsHF)um9-9|p=};>dkb??7yVb(Hg146%H+HL zX4AINH}t25EoT6n&7PklBlnclTPt%kuPjZjQPS!t;fW^WYw#zG>e3_a|B%{!aJ1)D z%2IYh?4&E2OMafJUV{M-eQu`Fqw{93_wK6JEBJ*s$~~O<&5@}}ny+P38voe$Zm$-y z8VDT{L7A&CG_g@5^S^)kmTqxUs5*w6G{PF{M zH={+Fww3C51SA*g6$(ocxd2dO8f~;@Jc^6;Lr-YkHrM99DURz_UzXSNH=XLqJiXk@btsN?XxzY{+SF3$|z=2D1vl32=ZGG9HuW(RT8fgY7XV@DHno zkP`(LPv8udZ*vtgd_<_UF=mkYI}^MwE3cX+a%stCmj=gvvVLjFNwCt}mSQtlx2nxJ zBdIFY9BJKh-7+sxyC(-`f_wdb%{zN2g%zbYI`AL4Y<-d3zkLy zPji6LWP{5=k%O1ZdEmC7dPK=P@bPe-BM=}ul2{> zKDLTD!W-MDV9op%kfLA9C{P@U=4!bH9_X+Tg#aQBR@pQXWdyFv$mG(rCcvaFirv?L z%f0@9|NT+K0QTBh!?vac*i!MQO_+P6C9*7yDOa@q3WI|9q|GZx-fJ zw<)C2H*rk*ZrP_`e>jiDo75D8TyKDA#3<2WSAXHi_NzWPkq{KWm3yFxg8FDPb8oie zAxY|N3HIiDSy_;uf*zWjd`aex0Vuhq*kUg4w7QgCCc*Vd3=kCY+&}=%L0PfJIO5x4 z_rI=yQ@N+-FNE~j7>Nqe>qTA^1uqqO&f3+4F)25FTu(LBIlQkR+S*U zTcjH?edX-H^&8N5fWL*hKn-L)fmz0%l1LdaD}Hwc;PAHZBI2*ilUprTkx1eRL$Pe50_{uZfAb-M5VUBG^9#jw-T4|ThCmwD+I%g0=|;10 zq%#cDYeA{AC!HNo3$+p}$yIsPtkol#7zD|G%9L)z-TdTOSQ0iaYA^a4f!Qif&S>g7)7?rg-} zuUZpJmQL~lkP=GPN`JTztu(!hSc10?F{ChDfc1yCOchHn^#EH@1LwM-3In*+*iZ)b zkXzB3muEz` zhKcx6!M6cUsTRNe8s6Fx%n&fX4Iod?4msiMN; zeFJZmKbygO=v0FAGEm^(Ym?4wNxHgn#F{BnuUPn~{TA%8fkf#EpCF9bPn>*+3;xVlJl3fL$# zyQd*`CHGn#RojR2=riR@m-I0flGIS4EAaXcjndYfm+W#Z%tRU>Q~e@Cqep}?`f(hH zIi5c}I-Q0_I=HD)m_kL)rcn{*E81Scv&>ypfzv|u`H9gyH6N6rqQr&o;2A%g*O+lC z-(~=!(9)Qh(xS$0x#jlNrX#EWo`)3i1aJLW+a6RJ{af4T;{`tr-9|!M9U)@}LX`DD zT1jHIcbx0HZ<7o+&7gW|Zwy%%3Na8C*F~}MMHr)>3~et4v=m0s55sF%!-I3wTo)sF z{l!s8^qP7fgX{Z-rPbDd59%MEugWZHMunI*d0N-wu|2sn@{PDh(b>tK6#_9pzC!m(4$s z2RkFkRRhSNT`$$T`)kGremkT^)FfqgA;sqqL4V@I+q@<0e=6=!ICKbxolTenQKr72 z+Xz4lxi`F||0f-G(%}4?{oZe$|8~qN;Pb&X%l@+ub*SGO6X@ZCE zs60r#os52qE3-o<+en9MR3%mSTDdux*pAIJrIrTmZKb&dP&r@-*-M!U>aVlFE04D) zTHvqTI^@A6pzuS+5>$PDJ{@Vns&Cs0KlhN;YCInF@k_mcLsHP+_QJoiw4RR&@1HmO zQc}Q(YOQ)jfAiOcI02xqh^sEywLQt~^ldpHfd9O<=|3A@NG0I%Yr5=Q6F_~#+--*_ z>4J9%tgN=x7MNxq3Szl6a5^;a8O!2NPc_%BKz_7i$9?=cjKxH zr0(3VMFI7la2~dF25R%q!Uzy>P`*pbLNAqgsJd8pjlWl%xD=OpJ4edCffV#P|*xmjT1VXwRtXkpC8_xcn*$!lKlJ>zRX9&%eT3VA&Yz)!WCF5rQik`=7AhpT@> zEFNsv|7iH7_c_TnQzE`2+ymQ(n>PNo6Gv={v4$E@c9e1##?}I0<8`8{q>o4Ou~cq# z74*NwTg%Z#m2nmh#dMv_iz{KTjhqUjG`sMxnsdf#Ir5>M>`!FiI??O^ODjKwL3%Cj zw(9LC0KM@U#o6*!1wVrMv{9Yh1dYXorD-<_w{tN?2opPc%BFRJZ{ga7STlJ_yvuZ4 zlSY02{yp@`Ui=j;)ET&X&IxF?Vho^KPhDzb-bQZPUA1ABiQk)0PMmQ0o394dWxUr& z{yIBy4jaRWw7aE8;@Dr67!?X@-M5Ngf>OsK0 zcv_bBJy8P8=cHi4c~WaGr;_clxbr3MKURz>A8!aHTM{GfZ!PbTyKNft^l-Qd)>y87 z8u?Z~FH#u`X(h$)>%k5E~!p-}?!R@&@>hq(QWyvE*~edsbb z(+n_Dd#Q&Wo`Jam&38L443dM2YxQSBAa!8nTAB>NRTA65G+$ez1E3uIOzZb&(`{|x z!VGzYnyZ=LGw-3Vx3}a<6?MxFdXrkz%$93@5H~L7=q0nWomid*ELKn(naK3@rxOZ+ z1MZTwd&~Ql%^Jvwrt`MJOZEv+0@G*sRZj9mcz2%YmvaByX%e(ax==2?&g`` z%x}|+{l;qYm68tODJJ3#PAG-&1R!Nl#LLjqj#}FUkaM$imkWeU7(_+thz&ykcu*>2 z6HD8|9L^J-s3-;gW$fW}UKVFB#Jjl^`% zP&;9(t}ETn7CE$*n6f?4CD5EOUH#kX^t zH8u3N+46$FEh-g_NC>o{tFiu|w)G2!u$0K>67=4h%bA_{3j$`b(GW659b+C#Y2cVEZqb-XW z%Hewao0!Ng#a-CB9>e7pX3ZWxQy*Vegjn)h$CZzV`+1E67!Wd4+nMit4DL7nN<-1M z3^X<3(@YVxrE=-lSB`FkHzMZ5HDStrWeZLEtEh}d3MDC3FEYkV2YJuCC+Ms&UHBpN z+-8GBYSE8xuD@d@tt$MmuC`RG)>*aOpz?D(@&G2W zDf-H)1f(I@UO6M*)FP>ie=<4Ac=yW46YEA9RnVsRa};NzI>4CN>eQd01NGdL;yIdTi1>V|<3H)s4v-!+yW{E!iHdRAP z6xu4-SrFGGEvxlY$UCKNVSWGV&(p&;eh!uQXgMq8s;2L7yDGJG62D^PA#f+Cn5N?q zi?)De$8$v}Brp7HzreoCFW)jaBq$@&IYFo2{ubIPJ%)w`#dRRI%|8(VqC*ak6F+_y zGmQS_OtI)ROR3roFRDinrX8h8ufyNLL@oRJ;(M0qs)Ys&o$SglX$}tvhXd!pLV{H8 z$5V1bheS0Uz|+uUuH>%du7+Kr17OaaYt$Q>10GJ)(+>kj1&!u-@)*N#i!ewpWidZr64P3n6`nGhvbWjx$60HA z7C%v`8VzhaAyxWar7d`~z|xc^gwE<*leez!&MRtf`N}xGIY&a^&k~6AMgirfo;@E) zFp$BZzE~;r&q|Mnr)(5dc)3iESH|;-h&F;c|`W{LX zekOGpiZ5hnDc@5lxDg7E&CIR1h$Dtu8u+>GsZc#6(d_grdAeQQ69h+pGd*9?EGwzw zKUvl+406C5DkiU%)Vc8}FhL&3&o7UF(HF^GO=Wmgq)C#At-}IX^lM#gK!Z`YW|zS8 z55qaVEW}i}?C6GRA18=2w^z7>O1xUPA78wrV8u-Pd_v1s5H`@;PS?9g?x5?}oFVSW z?>0ex#l3 z*y*rd@Zp1fG@rqA*;q`G<|x|(q7`Om27ld789zT~rZ#$gOU~XL*&<^@F2(k+|8Q^2 zPA?383T#ZQr(+78@%7$TYPQ$Il$NFF#^{X*x7XGr#2RvVv&-h$Zc@yQbxPRtWL(H; z0anWlY4onSC>e)(AIsI+9#3t7%Zk2&iL%@nOzfbuCdkZHy=*jFf_I+_<`r4%P;8*oY<^}xJ0QK|0*YOL#%c+yy#SK++)d6^F0nVACRS&O= z>`JH3GMm+`nUK17L(@&j-9cp9&`yY0QMq5(BMx}J?)jJ(9gf|rO-m~eW)MZa3$$Tzmz~cr*UxrzY{|AX?$PodWTzsN$JSRCs#J%DLZJk6-)w$J8YH=T8 zkvMzVtCXtsvQ$q}5{Wo~NFm@P@dCq+N-FXzHP`e#M(p3f~VgCSbeJqI^J zBin;b?>Fn@<9zWUKr7~WPBNYl!wqW|D^@d0t-L4J@M2Tyz_d_a__qYn<>rRqqbKD) zLMod%3gAO573b1EXM~ffOb1*V`KUWTkp4hz%?{#?RaJ{CMnZrJ>~Kq1P(@dz-D?eW zb#)6ewj z(<-;yX8KGe`E$WNns6a9o}^j!K+eWRioB8Z46f<9tcoa|$4 zaS;QMvuJ;zu1+<1Vh47}*6|8@*Cnpvs)Yu8V`F2i8o({uN7y}g4auuv;6^P|eZ=`s zxGn@?y_2NMSOOMMCvGc-C&hA3)NLqVE-J;HP!|+_mJw6S0D{#wOA#_X(vy-7H{GzP z=LbYfACN9^UMS$}|I0PI?!ol^BwT96#rdcAJv2yY4mKYQbV$J`siTAQ;bIQ6`!@-@ zy#&F^Q>7YfK*jFS53?IS77UT>w+N=7)*-UR(dpJN&r7`Nd`El}fl;A@d<00muRV4A zH&yct3FB4evV`LKu>dG;*zDOL7X33qkWcobf(Pc94Ih};=&~k%zufWjyjQ5?r&Y6J zH=7TO`>?@e)7M(ZKlq>hhc`T_?rGgy;Talbk9Lp0TTakp`oj-u_>ASNC zTkoaQOiOlLoS38SVq|J0tz<0W#%>3Et%DU}Af+=JewUFVW(WS-+N9?{(>(YQ`+0m1 znBpo}kOHp$ce^>e<+VH9!H?|F1E6*sImzE=$= zH$uZ0nelm90t^>kfCFmB_VuaPo%5A2prI#I`Jx$i+JVzId=(YD%3y1u(K>D}`&*_p z0+$^&FCLHDeLeWb+{O84Ia&Do8wrcs6nmN#%(Uu}b$;yTB~8Wk|>uF zK^k`ZB2f-M*Eqk5B~jQp&{BMP-PI3|KHk>>LtGzUX=VCT+$zBStWW64`t`fTzC*L_ z8TJWxBgS)=zMr_!^Z-xJ6ZYwOVWLDYmk_Gk*YHTW^2T{PV^N6ga9J?>;tDbO(FD`zSeOOWN z>@>pT>ple9%c$S|FAYQ%HW)pkkH4V=I9Nx}o67M74bVy(R2%q2{_rF_Iu-KiLbC@U zJqzc}J+4c+5uZTFBAZ8`>GR{dK!EU?-Lcv*tWkT$it16XV22aE!Dcrp!7pk3e#*{N z%p0@3gfF z1O`Pg)fAhj`IAjF|C78|dBDWFL(*el@0dj&ZQ$4TFXbxzAy%g>nAhb#Us@9`9d?dx zx((8Bx=5WiyA#^ni)S9V0KDUC#tQnOCX84ICU*0euon>OE|T^B&%Wg9el>oDx#7hH zWIcHF&(_o=bpa=A|4e09dQI!sh_!3zRGbfgm< zv4JfHpS~+$WQ%2sF1T-?OE+9n-wK3oL4oA|o-?7x)LJWIz3@B@*R!oC;J+Ip7|zFi z3KWAwj}r`jy*fS$k|ywFDdqIM_OL=)*U(lyUpxZ7Kv&>E2S&2|6uV_vr<_ELFB#)2>?jUrV1zgDOi z4Mf9JCTN53l!1YapEYu;@sX$>9|+bUu~qCLhxVih#H_*M@-s}6AnLq}A)|V0&oJjo z2S-;JoPaUew{=Y_3Y8#uizOmCV#fa5Fu)4=lxRbeUN*)AS*6D9w_P z4Bo4#56{bJSLAdfQr_+ln+>9vfvd; z=P#5Z)WqRV?oMc zdk=F>Hipbjzx~3b?S`H~h}ivov9gJo z_0CoqR+SuK|6MMvm-temxCn{v*&h@vyO>}gIz7|8kDAf(-v#8YRKVEblmz*MqMv}I z)AM-w@D;c0W3f5b4PgDmj#4bA@d$18`pyd?LRnPwj(R(6fBZ8TB$OxH7spQX*L7f^ z-#mMfnlrrE%{z>HHT%xK zLH*6Lr%`@OJ3VUZ(?Y|fU}NkWK9DI3(i}Bv%nnvBxOfcqu1=tOeNI@V7AN6Uq$Uu+ zw<10h3zvd#^Lb`olaQC;?ysj5W6Hxsk=S^&UXJP+EzZaENcoK1pxF`^r3u_dA1{EM z8KfH5PigOGc(DskA*n$VkJVW*WYq!!`nR{2TaePyhT=YROqW0q8#VN)s40s%ct0jI zh$=nL0XmR|+7}0lM#}&oW!1@{en{EKzT(xJZrv7Neo+(ulcpPs-4!Yv$xz*X4Dy3? zsKVR!GgJPdTsWfZw#Z-B(8Y!tq*_SW3~QtioKGsl4x$*#fdXy5V)g~|(}IyM2(}+l z^n+!&^@>0;f{v#7!HSt+t}$Wy|65J{=SN=(Pm=Xy4nhxqfiV2wVwL;-DP8}3EPL?Y z6q_-=4Xr^yqPFZt=eNt+fb6jAK`jPnvIqPVU6SaN&vn^?|6E_)58)=>-|Yo*3f zn2FUK$ZXV5G${#q!5ZpwG7d(;+-?g0$8x=-O~=cTE|h@Q@HYX-TPird51HSOepxSZ{#fN|AK4h(u^`%@-@QY-U zE%4#r((_PANw9i_dFA}@VF2PVgEMhBMw0UuREl=l%7W-bFpwa zdbF`c$wx7eaRVpCWNi~CMT|PFsHjm9&rNfxlj(iiFVxX->tqGYT{|Khhf$$O~q z_+5pa*vb6!v+RbyRk%lb`WTG+*OVF(UeSw1%3{XUuKpVt{&U!hqJ$(sr;+t=#gL0GoFe?fhWL zgR{A2z1LboQ!K?m&HZ{FwG54?{grJ)5U2T5VBWYnCyd{Vvedv7RIHbkn(e@z;i!3@ z1>>gBM?G*L-_?-&YWj^dx9=sj?aoa+0flI5b}_3a+xXLdaPHei4UMx(K~N^o-gu*O z!p}5oq(TR^--{nAe-zC(Ry=q`*0M(g7qW^xjm&X-8;{J%@Pi79imI}c9*70AVU@1@ zp|aW|vk=>Q0PhQtI*nIPeGWPHGdWa25PlQ5@aROH+!F!E9}=vr6k;Hp78QiZ|7pBg|wnU$v_aHXO6~dHIDdi~7F8O0acH zt-D1Wp&X39e9|wj=v~j%HZY?6*m%Yy2O@TfOS6KnrU{K?qz=FSlO4WvoVxrL^U~WY z_l9e6?Fc3W$p%+F=2|B_p`ZsQ@k$yPlbhX?vqo#3+gSrd<7hCU2=WP8?@fA3Z1bym09+2sMN zF}0mtCNJxdPyee=-fiB-f1M zwGs&ScZ>HYjt-lRA1p*@Qtp$a3CuXWN8rn*3*l#!H(HP~!nMmGn9+l>+sNnkh?0fV zi#GBwD3p!e(ghmOR1In|a;pV3NSICwtlv)=Ho6U_xsid*H}$=8#}9LhxtJaN9UKe< zD23#nZN&EbzR|BT1rwPdl6C}i4?BjD039&yb}mWIb!l)MQdrfEl%AK^G^X=EFu(J0 zc@6N}&56p#n>qlJ7 z1AJ>&5XH+^5V`~*d~d}97IB5SZx>*6e&2nuB`}Tl5hBR_7R$wq)1vr8{c+;?Yc`Ul zf!YVGYJ7Seu3~miLO*TE$vlc8N_CC~x$fRUyYR!*e;`8;JRtQeh}@-ys%dEVsr_qt z5h>-*|At@0`e>^7T7r|+Gj>njL+D5hG6>JI zlixn3KPX3g-yzbHoR9nb@`uFNT%2bFA*weHq84Hi|Ltb(x1eYrxT+ddvN6t})Xpmv z_R$hJ{b9L~?5S>WsMIS1VlFpMj?wx`$}mk3NIg^MAJ|jVGl)445eGk+f&NmP0l8nZ z)U^77zz+xSv>_8@)MFptu;pmf!9aC6;+|hCu=Qg6AyLIL(qRStp z6F=^vvp#V`9qJM#pxCGGvd>(wp6DMG=Bsbo zHpKZ4T+5@lLyT(`p=%*Hzx|n!B*dPl>JN5n!+j%Gmoeorq$ZT6p>l&I9F9#FU4F2j zHLcrs$zCOC=+TTir^{@YLi_WhbOd;#`n(Y4#i}DS# z@58dQ$rC`#@~N>kGM4Jdg@F_)6>O_x89O+@Mh~^QnB}QRc7;u~)XOsGY@t&QocAto z-q2GBbZO63?<|yWnP1HqVj%#9d*|ykur>_aZH%~lQP?Bf^OR>+Z&%A-IwgzwB*f31t?Vxi9uNp~z4=rI9+;Y@^w1nWU$rLqc+Yq8&r;^D=;3eG~hGs`1V&7RYsi)?&sDiQ$KD{zxdOhj9 zU>~2K?}vcx3pf?S*|UdW{Q&dtrOa{=3GvVII#at`(XLjtN7V_8^$)G2`X6AGpUcmJ( zQ=nW~zFw^H9IP2uAW0Q>2gBGWsyr|V)mQWpOvAx|Uf`VYdl3! z^29?8 zp1RMUP9A}#;W6vddDXCy*EzVmq&v^36gt44EP0CyrzffD;I$7dsP>6U`g@1Bn_%Wk z`KSq7?Vh3P5?oMnVOdp-sToJz!=f@Q)8d|8-M;wkfV_|lp z5WkNhbf9~okZ>Z?7*2#oN=sB_ILfMBY}ZGn6YvPM_>QLkau;H$r)GUJYW5wlOkg>P z2+Xf-&8fy~braSdz{>T5L-GQOtM$DLNM?JhPF$;makRd}EyM?2_BdeHdfbvBtoouT zTDJ8y=Rsu6yO`lDpZM7_*>Oln_n}l-&EA!U0k(&rG z)GE1AGeG>D5Ji0p9Gcm$uV*VX>YcD`FZ1#KinqWKW|Ge{SV_o z9QAG9J2)#zo;fd{w=6Xl?U4DAz^|9~U;{#~#a`Bo^0DtIg5)-?*GeL#N+vyxL1na+ z%KAiy_I%!d4%0Vj_a6YpK2P%Svq$gKclZ>cx|z&bLTDNB_(=o*dkO zz5s-WFbfv2LY2}z3ojm$M@Z72icB0?0>7?(JY==Q#eyEUC%Owh;bDDesnp197Gk6`V-gcsXc@ zVBh|;qT<@p2K<8w0|p*fRnJ;M(_WO8@TwS~vlQ9V)Cmqb$9-elS0=nW^wY-(@@QmSre4C`X0+%SA^&v4u}m+Qgh|Mcp)ideYMu+lBwC(cjgh8^71~P?wMVlzpP0hR37}ON;fpN zdbN@0teu;SU*AIK*7V1S=Pap~IU4MKA+wenbzjBE!Ov0nR8>0jC}b!}zI0*hOW^N2 z;}qiJ=1V8IJT-KD^E`%U`&R&Qd!#|Q`p-J`pRvXzt}JvgD0 zDpc`eL;hr<8b(`tjd%aX`Y=X&5NW}NO$_lXslHl3*k?G6I3&6%ND;s?84CwOBVrlx zu?a%F!XVN4A}MrTf_z*4^8!SyOd-sNy@3-0R9YU{AX1H!gLMqBJHgC@tMW{X6x4#` zOTe+&>Hmg&Rkaia4&tp1?@3ATb7X*pHF^j!fw79QY%MMp&2#X6MJzm5qn&%edr&xw zna&YH(6z_Z^?bnek7RgUt%2Mm>=HLuA`d@$58q8lfxc%fB&!Mu>L$E<7KBv5YIjsNYl*TyjE*8+G4frcZAZ!aOio_Zad+yP3Nk+3}+MCKuS4BiZyW zS4?qsr>J`&0#|*h5((J-=23mj)Vz0{nDtW|j-J<`=`%=bq8Un7_>HA+J-UmXYBb`G zctSv_!SRCjW%{4J7^$T7b)S}Kw6RR^urJpWm3}>`JqMAHRuel8@qmN?P;!ALkYBi) z@>6g1G2_sGVKh+xPE>gR@8CyP&Ttb)$z~&>4hL3nwm2)(E5wMI;J%HtmM)5!05k8e zq!+E8In1eEQ3tk8oYv-4QrkuIW(Kln5i9$HfhYOQ(1hDWqP!uaa|l zqORh5!1xz;nK^Y(cU|z?TB%zkO)dpt)H9FoCBZo0G-WTu`AW~D%N`karEOgKxN5)v ztJ@9umL2&p(14wB zWG_~;v)A=Ta@vj`-Ti4RS?gAltNUnvMhZ&xsSG~XE8_iSM+=cW9%ebHXzw`I+{>Y2P=Yk2`gUfydRDIu1X3tW@Uw=iew2&` zpVkvL#6bh@QqN$<%5-$!lK3sa^Tikm${AV@s6V@g~oZu4s&T<^}hRX10ov+xritWbBuMa53;4l(_q-~`0~$m4R|S*Vui zk#DN<8~5ZkB5riOS|W9Wwyq1&tus6y?bUvoM-{00{;^4+Sj}0Bn;oYtI)zzhbjL8) zt4tex$0QWrk$;ZS^6*Dk`dl#zQLvd4DE=F7rAEmc*S!b`dbtrQ%TiVAsOjp~1@WBh?3Y0h zlVOKPQc$}bHP<19%m|38eS={wpiWhi>5DfyZXUL;1KD|!lt-Z1G1*>Fuu2I;YK@|X zC`V2q>UMv$g|X7kscs*oBdtiJrCw9WaPizcJYRLa{=%dRX?tK^1ay&f;LH;UYZ=rs z2o3u6?RPvCLG`!pNZPO4hi0~Br?e+iBKpb8)d_?-=>EuragSk@-|Wo;ixn@!qh~P9 z#SQyES2w8tX=$aE9MiMqfMPP z5TIOZwT>6#fansHLF#~TZO7f>tbY0}e=WrJZ{I7%XPBtfNcL5MbPo@v7dl*!CLkk< zWIPCrAWRUs8*Jg(JTijOJfX(GU{H@NR`-j`3I=2{UloSFx90iab$Q73`?W6CN)q}x zq5U0~i}P`TCQ|Ka1SDHoKpZo5C|3hA0lvid=K#Uw59&XkMigY|mINw(9Uo$KVHjl1 zB{1H)WjO^{9J$p~W6e8AKHbF6Fw*x$H!>sNWvfG@|7OrkE$P3H%VrX3KuWyT!916! z|AJ0hxfyrK6d$_Sq_x`|Y)sV|R`RP<_#mEb4BSfJQbL2bMKUxsG1)zyYkOwV<<|Cj z2dd&b-Dh95d`f}N=e(+KEPp|v;6Yc@G{fy-))3H z7-Rhj{o4VrR zwdx(#^T^z81YYH#puQs6-k1N`2ZYs2(WE0L3y~@)hpev>mN(UvUM%R@8xG4K!~t}%^mG(U3Db&9n{z?AbRKEZpI<+;7f zmLAt4aQWK@E+78@s^2fkMe(zJ$mVq1Tq`Yo|897tj~Rm0zhL~Bv2nY-Sh?!#9g-=? zw({Nr+^&M{hYvJ~0`Tv~U+NrJ9{N*B-SFhg=$vtGQ)ZpHqj&Q;7$j8I`MFvbHL#m? zIvh^D9<{N~%8r_sTs{MdP}5V?*q*z9Gj10eW5mryr8rnQ5ETso_KE4O+xB0)L~rao zx268leFBwb2Fky>ITEsVElW~1z;eETCucn62j!)K?ZinDeRkILqMS6_ak&t&Vm3{} zYr3CZ3GzG89~TE`w}_Hx@d5WC#IDYIFxL2*ZTk}@3f{x=b|)fi_Y6>DofZq#qC zD{ff89w?H4#o~G;NdGC8jp)xXL1X7Xzsh^;@1|tJ@rWhoFFugw@Pz@WX7jo7XN{!-H%NEyS5zf zmS|uC(QK2iYgCzFH(C#y?+KFs(;X@&;^3nDw%k}=9-I-Hp_*+KJb2CqrbeoQ3!DTc zrR&~7r}FbNp5@xs9hR(pe%^N*J?;5}>T_==9RIiMH&!6Evsr-_L+t%7238J4O-vTE zPLO)dkdNM%*Vghwz5{mB2J%|hcqh|Snlct}j3AO*WYG9me2oB1#8Oa>OIj6#QS5QP zzXe=8Jr#2mS70@c{g-M?&#*1=oGRCtbB-s@kNq?qiQ%X~`y!Efi>4tW51?9vRV7K98WNkG^8T0#==F-Un5%Isy;#vh zM24%e9aszpkwY$R+wCeH_gI32ZhHO?2qtM#M)jzgoqUsxv5;?qR< zbHSD9G7hj7(M=`F&N6_615{GruY5k1-$w<_%$gv9C6Fa&jh-G-Mnn<0s)?3nY*0-Y zO45yXei`5a^G})CS$0F?q)TFJl1+-D88z&wGxe;EUm9j{_0{INSBeRFLbPLP_~-Gn%K_x(y_3LdAbwwDQ6}@qA<#meir0qZNEgxz z80#z%x#kK5pPjaXl7iIXkqH|D);~<0}2$uTQqENt0buldZ|NYqB*N zlWp6!jcKy2Z?@g!=kE7^^}INBI-Ne<_jUHZxYpV-sX(}kGuF*Y(9IAbCV_y)!&@1@ zgc_DrCLONN-tQY3AqopiVduUBTt|r`dZRhje!IZ3v zl+`^c=hUmojQ`Tdq=`E!m7VHJ)I2^)13 zhSc&~V6KaRmpc;OLSEQOp*71=oBc4xc+GN>IK-p=RzM(Wq71S`{jk)ME&v-ua{q+U zXd_Xt^Kf+@8Ibq&F4u64QBwM}AHZ0N6c@xK=@Jwe{ij)9#Zv!^Nv2;N@na67I4j*) z>`l`g@I#=j1MZtGN?ZI$Rq{S7$1R9~%nD0c;%;7EqeDs~IXMAmR0u+RmIg-J6M7V8 zs*XwWARup=;{buvllJ=9$WwkB`^E^QO+g6>JQwtJA&Ns|T5IZoDGJ4o==1^a2XKIM zT64WfWi_TvX3{IZNp04Z0AV1F7WiQkukQxehxMVE z-4$TF)FcS+jV%Eac4+3h!lh(~N?SA+2886H<{CeV5XDkUv#=?73Ym=j=zry|$)z}M zdXP1fB2*2xWtS1VoIs_`&81@{XJ76DfKD^gGqz22Fm1nX2$7expW!MyvE5x^!FX{1 zj#gV;_B-`HN>pdfnG|Jla(!udXC*!7oq327wkpf3ynJGPDLiPH&j%^N7-#38on&1H zCvdbnN@(}*{H6iA`{kY}$lB6!(9g)y5;|m+gPENoZcV!VvA24UAT*qNESK!thqGs{ zquc(zw~)%bCUjY6ClfO}yRi?arWVobz^^zI*|}RdJ8BS%$vd^Sxqv#-=P(vSZ^+i@Zq{Cs{c(bEf**PTa*1@`y?eoA!AiEb}6CF zgQ@2}LgSJ1*+1v!#_p3CA`AWSB4*dI$~&F*Dc#nJ=#xoV1qWKo5->JKSM6Di(OpNb z-uxv>xKN-djK&<^Nl{fEXZ^S3^Y)^q;f2- z*M%w(;sz}(Q)YjZCg>hLva+*_0DgIfy{hsg6dKjM!?~y3%Dx`bM+K;{^E4ZPgJ?RT z=jg1KA1cgtYh^}Hi(DR$$mPDYdR?<7i%Tfpc~6E@?%JL9*SS}gn7co0dV+xvd@d0{ zWId^j=gRiF(%#gx=!i%9=b7)wZ^DNW&356>zh~(r-x<80Dd*d7;*||{1dAb00BS^k z#gNj9=#3no;gWpz>BsN`pFK;mS^Yxd-`$(ssDBFncl(1$bmsH*6to>}WIU9}->Y?m z_$1IdH+~*UX7LwL6XOP8aq?q~=6=?78S9o|#du1D!PI7+P!s&9qa*q4Cy9ryzVQFE7pWSdo~e_&na&_0|%bLRXkBZ z#%V7}u@tY26u3e#xURfR=lJ14xof@h#1C`i6WZCRsOy^xx<(}TGUo}P#<`Kt*?@|Y zR$i~Uc%tqv1T2Zj`UnZKnwi8JYaS5B+2)Ma|9(yHctJ`2S4Yo}jP)j6$}LNv9_o^6 z3bMs0PR%LcckeSLJ!o?uf)6v2C#fNL_NQxS!p{s#9cGjaUOE) z>*r4KUDoqm1LLc6crXUP1F6H}ViFpbOY%~wdzMW9T91C-+2uX}RgG=);6O%bX8&_$ zm=4zKKzONEgDe-4?WcWz;&yw&#YX-(-X504-tsx9{Jfsb7YOOqyjCK-aV}qzh(&=) z?FyK%MVzLp0Cofz2oUxKAB1R)sg^npl^Jv$quiti*Ii8!dX+Sqo^uDI@by#3aZ4zF=u&bO$?Pd1srELUBgaMpUPJQt^uC2*%C?nk?<_!is&>IyEx)VcfR1@ z)uGklk!Bn@F02-OSY%*9xcZ;0>)cKnH3_Mf0H<;)y{BjeT*;Ho#3R_8R z#w~Okmz{xr87*?~mp^^e#zzUNklyu@OH`|C{rRe@)*B2t|EwJyBjotMyBO&>z;4=~ z2^MS86wju$S}+6Q2oZ1>CR(^*{lr8ZIzt(9uPQ1HN-{;{k}u11cx9{lvcCoVc71B`;J>8bA_KcgZ7Rq`iN0Qgn$f_-zReM76sP!Nt%oUjbuu98a4>RXJZKiC(WAGa`lYFYtkUeiEMx?^{(s-q4k{uO0SuDw_wc zFue~MkUuO>X`#Gmo;on!;R4B<|K_vjAD=}J1(FjwOg&Jn4+GZsB`6ljB7JEwnuv1x zWmQ;~JQUemBqm;}4u0Vm2N&p&3O3Hq%=*M}*N!(O{IA`i@#;)5Y&H)jfEKT9YWlm= za*M|Mi+Ygw#BWi8$JkpGJW@R2Rd@4ytkhvujO*ZUn~B8k(D|=X0M1|9hKKCnL04;^ z5HwR?6LY10f)IoggtyR%wRXT6-P7cO8cj)Adw%yK@^hlUuXK7}zEZ)Di zdywB*DyI=Qd|`bC9WTraOQ;1#7sA>o2_-Ai(KBBC_^aM?;}5&;vNk**>*yOuB1m0? ziY2}KmWF58ZAhS%B`_jL6-i{d<^uT;qL1@5b_WOk`bwtY=tQK!0iUKBI>iJ|vEnfB z6+N1FRG9keNi5eSnxgUj&-=iyA3z#$V3Lv+^e^l(9w-~eN94pf<-IYV zP;XH3io>Rv&fyz8)Ji^g0xn6X+gAkG3rVZRSNY7IJy-3kKA*4yi=fNiMb$Z$+Cm~8 z%A+~s+@9~X6rflzdf+)oa3nlBRuvjOjlM+#{5Cr#gAG+Pp&U|5<)Y_wMq~NCF7ms_k1CBb* zFPCjakj$WQrs4=(m1Q->iu_Z+{rF$$Zf4jSX(k8BE~}Tr-+sTq{m%o7=ioJt;b?me zuWB;K06%@&8Pbn?;$%mBhX^!4O!-azJ`U(}iIq8nz{@@j?XIb*!5&}8hAqdJ{{oz< zp?-s;NJ}JW`@rYzfpa(+OYEPY&_!}c2R|Vrd;ScJ`p*pG&bXWiL&`EmA!{OZqx<$> zzE>XmNHv}tr5M}mwIWzpSV*mc6UnN)R>S7dKw%}_N;Gc)*F>#3qi8c-0buCArPg9} zO#L<3o6!gqcnt?UzwymHqtwttrFFQO`wiSx!^cL%itnWu5Cs;KceofV!}I3XySXoh zev;pB5P^Ct`T1$LizXsFF^gaS=ql6*IP>WNvx3pcX0bMb{Kx;3M-&$n5 z2$y49t&7{}3qR&xZ#bfV8NUEM$Yvsk_hIbDb8c{#^o5@`3ZYFlhm5m>&S+vbQvbp3 zm&a`d301ZCODkrK)+BT>i|GyvcPg?pCNt=tkwLTEhj1~i|Lv#86a!B0G&o{8$sMwf zU9JKAtRLEYZ!(Awk*~O3RxEEt8Yr&_WR^!7Sx+{@yE1!ou|2uKBrq^=TTeQ8c9T2U z@MV_ERPKj92Kc{_;4j`4jMWv6Vai+{g;7xTI|wim01FX_(-!rxj6te`bi_}?*H|?M z26bK^#idN3-pr@+3p$&e=Qik)VAA^+l(V?`KN?m9d){TyC~u)^UglFEMEYw8DihN~ zDgRqtz^;R!@wktfI#^&C2E{~+r4mP4{tRm|zLuVqH;?y-Hg?z@<#rntCoZDs$-Okn z|Ef@hND@1z3J+-j#X`~R+e^J38PWgL;HWQ;I5?vymfGVGa;d?J;m3WY4OVNeiiH8T z{NjoYrU1(dOlZlVbO%Hf;W5Pfi6wmwrY*S5B1701UAcmPD%~8Fi7(>!reBoQUh9<0 z#VWeU0<0y)oDg}P{_j}hv(P-b{8K|EjDM11sCY{D z{@#2s`deRcWv(M@Lk#Bko8D_**iDTIIYFP6>~a_3Tg#)0`vzc9QpC1tipKutM9BIy zZZfx`MBm_9I_$D2Tn)G@X4-I!7>w)Z0EWolp_Dyl(%g|C!uYhbh%9MCabXwwYE7x* zRcB1rXWcLODP>{*IMG4c5MQGsEscnYBDVakbIKLqfsyHPz0+5xy&m^O5Id&> zm;x(r4stZiu%Oz%(IACPITe6Vg4StXTTxooJCl!FD{Y8nKLX`Pub=BM z3FcM3fJn2S)5l4Ho-X>z10FNe?Di(aA6mDZ2BN$7;q^PY`H95OqFNAHzE$LgNED=l z+XL316N>#n+6c?*^&{zu$u4y8=|GX2Ot>abB`AkGL}|}6^uIeR_HSi0)#ct?+o%b!D z`97Me68PvXe64mUA9{A&e7=r=W%NP$S?e6>hX_T4lMTZY81)K3q&~ObQmJJr&umZgDl$PKpEVSApEc@SP`2g>d4U*VH(NX1j}-? zZKpc7Qv%>$1qB)N&9G?xC{((Omc00Y9Dp}6ILfhp`A9FOL3Gu9;+)AHIZz1{oGaob z;?sv^%`+7jv-1PmCB!_m#OOU}M1M0+h0&n0M@b&;$0QCO(SKJBqgZ*pXNSj7Y6cxt zpmP60*ue+-LB0gEIgUo2lpLE$4M9rNNj=9nMlXZ@!&2=NTUd^w@Svl)BnUwgwP_ID zFmd%&h{IFwml^)Y>_RCQp?`%t{g2@fZ5|I3zt-$@|0r@yvTc(0d=z2^yQUDM!uuCn zalprwQ3R%NqX|cen;>w@uk`~{OlrLyoMxt;_uBqe|MI~8isc6)RN=~>vus7^X2LUA z=H;}$5DJP z>zE+q=XEM&YLXar8)(lZ(5=sn|Kn$eGr_7vEXkDb%jHoHdQq9E_iAKdhtsb+3%2+w zm`nAyDYvsD7JEXf+5SMX`QE^Sgb&jvAlH;r9Af4sM=D^OwdX!&_xx#MQsA7nBZF~z z&y3a*{McW$-DUr86saj8FX-koVO4)T=G^H)mUvy--oD2{@cFVL zqBbOSL8*XEKvOd%6OwK*=3qT~Z=yqr4Gm7fsnZHkk*O&LS{k<365KJ)3hX*-wYEYZ z2xaA&e9zgO8cG^t*KZv)%Z=v#ljjwcujfqJE=-kT?hMCNmJd1fn|Nh-sNe0FhEh6j zsSRhbzhpb1q4DE<@)1S<>2y+0k~6S_H$QxVhG}F(S@u6aT?*e46Kl1Z-cnp{GcJ%@ z(gZp;*}8EsZp9n!k(R$-qe_xr3jQR5K#n$NcV|IYTFlZ`FFtv<6fp!Saa+#dXKTvmVc z9ZTyihZ zdA96fUpwve+ORf5@k@Ssu$Q2{b*%b?)ixVQ3AJ+^jHqP9W_Iv()27Q%mHDIwsoQybD zLr%-8jZywmR6U%757exJ6I@k4WeUJ6*HoyYj9nI;>TqQoRF~wBe25_&+GgQYJMG)0 zd*+1Gex&5z@{^Ile)$5iy`9JZ*F{t`R*AsxxzrwHuC^Isp&@5mEg9YcCx}>+XFSLf z3g*Zlqr3JAq;*y0@%e9{AvA_Vg03d)AFu=Yi}*=oiX9Xcp;}xpri{+YU1|T20{E$~ z%fFB>akz;q`1uFM6$=#FY@}1Wt_6FyVHMZPw+?XHL4Dm$ckoTMqZH}ufc0owtS}5A zyYmmk#L#q}%?+z~KYEL{ieKWg&-bna~K}ZWtC9 z&DkW}ebh3z#I9ip%aKCh?SCvZSNy3#KMgyy**$U{@G%G}rCfgN3kfvGXZS-rm|(RJwAUf!7hx zmV9#5<^1#K=~(t1rcei{iBd(&_P!LL+`?b#I$W65=z7BLjY$}N5w>#@z?{v*dekN` zgxDY0%@5#y#zgqJy9s9ImeS&cNd8Lzs41T6-KDP7$_W@yC3Jz5o?fn698zZz|DoRd zrvTORIDD(L1WsJK2h5+hL&Me&UQtoVZJE^94R_b{VZY>L{JaLX6ie+M@kt?+95pwR z|8?mwC6gsYaq)$3y`tno)?&;M%CcwbTDxw4DVCUY@^C3UKG^s2B zX;Y2Xrh|J;`usYlewLkmXp16|%W5R>_G!0Lw5``U>3Pznj?)~<6fv{1FzlEdaC|6} z5+hqsyuO-{0Q7pR?}@tW`73@9VOudn`uv$+oE7W<;hJoqaYvXGRglhdAUj);H? zbYH}+8F_-ri;|h}e8Pl2rXM0G_Qr_qF3zcpSiz=M_eDuy|iQ zndx%#HFf$bcb>E`FDq+(++uh4+2Du-p$m~Wy`}{lBqh_Q^_Rbg3Ic=R%9~#| zLP}2rs1hd?1m1;@iX2D&jq|W3yuf*IvE)iJc$dHTt%iG66R#OLG1L+*i~s_hxRxa( zP)*Q+MZF8Az?=!ryR;=&rN%$?4Nn|x&%yVIkFqBgwq}$ntV%)h+mHc~;(`xoo2goT z_h%pu_30ygA}WSt4(UgSjiLp#Qc3zhRV7p^jB$mtqSX0LW2wmA~_miv$8^glF2W zuNL;!XM-MvNuurqJ_oDpk5iTq=EJSmTMwbgwAic;=fS#yA>2;!YKsVe zqE(}#)Z4Qyk1pvvMPv801{itC=J5n=*!`%e;hyMf@)*D+DxqxIEqzU+IDV;;pZ@Uz z4h#J4_{dUdVe{yUWM~Mm=rikCL(bDQy6DVsT=(swcmDzQ1lBO=>pxoHTNt=z(9Dxc zsdCR|Dtm3yqe8py7=$Yc%>^YJf)y^`(D}w9XNkQHkQc3A*i0+}l2{Ju~DuX_bq0k0kfKyCz! zIuJNP$PQ0r(wl!!Qr;}BhBo!)X}Jo+Zu#b)G1UHNyc=$Ic2=QeLWjcb*isz+@1zoP z?fI+?$d5_YvF>q2voR)AnddOpRshz0pZ){J232AAcXi6*k3cOA;lK+UePX9CvP&&L zx4=oX7F8pULW+pJ=KkH{0?XT0RD$$qo)+|Lraqw65MS?F4_fhLxn$>JUH7om6P`s7 znfc_Sow?`h>NrjKFS-FN{ZSdliDcgEp9nbxZ)zaS83u!T`Be_=9I}-`igd z3&Uh2_%9Fdgpk(DGj(&;zo5*_^|*`|apRZ%oikY`c>U}Fo3X8;Hy$pp!5qJnyLZ6axxbO%b zb*<5r`IFfskLJ(_Wv9}Qv%9!Y0nw8Do^;V&n;ecoME=~qIw~Z`*ekcEhKT;q6r~0lS z|HTl*lkyBE_CR1PDjqC^pNgs%q{!7#OXq9V9^=f#pvr&J48+CC7iqmBi6wK_?Z!M{ z^XJ2QZ9C52Q-8CN`sUoOn^Ni}`^I$C76i4@Tw4_r4=u^X2iDZ7wyVvzQ0M>Gc1oN= zTcSDp5t!yft8PRDer4yYgI(g8G#LYm^qqo2uu8tQt%c(v+NmN?fxX8&H=bM*PeEo> zr{w6V^NpD|m2IvZ8>RU&BvdM@NqMuX-MZGuncli1n8&z91inp1Z->#Z$`v|dxTt?u+3NwWa<7tIA7<^IN`>O?fTt|8~2}W84NrxdT{ldk^~E ztQKZU$v+fI5+@t#9R5aQa-=kaLu4Q~1!7mDWM7BJ!(S#4 zNvfP^k#%Otu$_2-u4=Qp2MtQ>x9`M%n0i+mkfh!JIBe->br&txtamms_?{c#`x(+N zTD|G}8jcg+SXiEZScV-`*(m-sq6D2|O&VidFdud?-Y`Gsul6nmChfKGc3 z7p!eRaqKj=)%!=%q9DB~f3(H!k%o%UFj5gJ!O{CcwsJVyn-1v!89*#TMy94YE>PMcpbMgD`7fw7=LC0y@WPUW!tnmI~hEO1i!O7kfmz#LTc5oRDFKj=_vC-jS zQ&Mc!1T--NNB{-5Zfg=^wD!v!Dz(;)Q854P*aN_S(GLilvVLNZR9tN$XAJ+a3^|(T z;Skcm(nvPckUZbm9S!+De*r4gzmH6LQawbLuHauge}l_id%?5NZqKevEY8|>)&Zf#pkZfBSvx5-jgj<5S2hzfZLP`o%zQ5fdhK5Lwyct1K1b% zwq0Obn_%-F1AMvH24&y-uj#Ggq(XthfZ0&H;Td%a6oeOq!*Enl)4!uz$0OIxS-5Bi z9eV{2&$<0H4kIo>Ty_MW3oI^sa&CBH*?M~|d{87+9(K#4H#6tsXC}DJ9cf5z*NT-YN-!@s3ed(0GDdCn(evYFL6bC>~gW4+vvl1(F&fY&P?hc{L2x-rsXV z_g(F*Bx^PYfq+`tQnSUHpU7f2(KFVjk;{D7QhxxEZHj4rpjoavL2MSpWtG$49Y( z!_}7egGT1KXgTEj`MT)g(OM1}^Nuef(PRkw@!*n!|fJW5Ka*mFf? zBG|XC6z9WK3*o)_*97>BUNT?>r)=$itOr#HXSXlfnzz5qJg?r>7NH)Va*D6>)m>h~ zUSqHEJ|A8Ul-l>*#CI)kd_1zpMe^D}HtPv|K z95N61IG+|M z?8HWUQmHowNM|USp~Fv1WSstf$0{zk+87nj0AcEA?VPUJGG(bD`AkJ|bA zSDBR^P$!|QQ&=Bi6PyzJ2Ow?Hl-kau&n-+ermr2H8 zjs(!|`-p=VXv;bTC)viVu8;=@-9{*`bUWnEnr9SG!zjM-Yw7snU{g!n( zl=oL(wZB3y;Q{35;wn<-duo}rR!|T}rBB%3_Y}wgxCF(~d9Lwj%^kAxeigeG04 zpZ)gO5aPRiJCtq_TdK~%FuYpq3bXz2+JIcf-#U0uysef#gG>Uo+B>qg$G(2vr;uaE zLPdZ#!4etAHnN)aa-Q-Vzl}{ap#SBg?AhC2K*osJ5o3t6NYeaNgKX&uf>Dc)ENIbfYW7Xg)iL>umXX|5}a1LO_7U1epNif z>mmJD5kRBdPfsf~tdMi?-gYSe0t}s>#d<(xJ(@e<*xPo;U{$5-cj0jdKz|#xsGRq&`JG(VCe7+2QB9AQB99*0ZlggTCdCJ;g6zOD=1Q6?Z!ZGZVAhsHb>E>Gw(ga42}vO6@(>O*POY?!*RRyQ3*2AAo<7u(=Y@1h{E+z8#k=wT7JgoEx=Jz#=rne;u;1E&`Ar8#<`9n= zK%?4J&Rq;?z5IC72?qK%xc5$Yv{3uRV>Q&zLSXyHBEiiSOd9NRt*`PCI|3E73U6!A zubZ^Ml}lLSgp5hvC)gr^E%)mn{G;d1d%*PuiT|wFbo^HSGm^VuFg&`e!#+!{0pbRM ziB>ptA4l)3;Z2%Ed|%-01>>j5ea;;fh{RyOQbIpn zjpMU6UNe)=oO=}WT+_tr#GaP=a%D8WVX+j@1}fa(ziJT@CppkC4RV;R5TGyp! zOzr1V?{7?`(kS1Tcw{`xhphX05Y2s7RwJFDu(=$u)Utm*{B_GB7?MXaW8P5STQ@!M zc`!cTY45xgEh3%KYw^tu&U@))8<*t6;%^@;>K_D9;Mj*+Dpqo7c{&m%;WWTd)5j(y z{)vD)jR)Z27??kqKp(p<`ptSxa`{5%%K9PW-}e4HSrA$wRl9lH91SH&c4HIMP8jcF zWJ7WX)!w&HJ|Gwt$0&rtwfx6}1wYw_4%EY!(!Q96l%`GmbPGN$P*>Yv2EiRjQgW|Y zU|B5{XMdP~5zt4gLZ>KDd%YI?$RK|t6u!uTsJq;TNwSGvcY@WfXp5LsrNU;{{l_mN zPWsiJ-1DOZn2zLg-b>G^FwY3-uD|4_?xUzw@8s5R`z75j-}t6?29X9SvTIXg>M8SC zlLju&r1fA)F4)1>{s%PxM(HuoW*h&4J(8WY(_Twd?C;`0 z#qrTHo=T-T;n=vV5N#CG1kHXFE4XPsjoUN*;)YA1&9YkGA zPN$^kz4%E(5$)(qN1J*50Y~)4gjfnU;4~N`IChW5HlT@F_7hIe9|9I-Go$!l(C zOfB-pLrDbfmA{r+)!(bB+tc;MU~VNFOyHnXl49;d8Xo^@zLyKmHXu+hX&-iS!YH}ZKHXOy3|y48PU(UN3g?|M!t1M+`axh3 zf`RO^eSt-*`|OL&dVwb`Lq64hNW8&asbTur-q@c61`LvI?A72U z3)Is+ww?$OkQif4$iZ{C7!8vJ%FtOQz$zK>ZD2~?n1*G6X9lIR z{bD|t`p+K=sT&MLxk($`ddkA-kvuR17c=)Dv)LY8unm)6X6ED62ej!c$1}@i8p97$ z9@B##uc>ewH->TqEmEST(#)e&*bQ`BNLeq|Mdv2qa?fC@^POBQwM<9d0cE_ z`YpsfHEWL@dT$QCsQsrkb2d-e@5byhm19yots^1?{pCxIU4hOUe`%_?I3TRT75tYo zIDG#EPJ#fhmHH0ku0WUMSAvKBVSe>vh6|U!6`C+%#C%U9tze_BuFbwN&ObsJB^IWM zRtg9yKL&)Zl7cn*>TdA9%96lwQppnd<4D_)m`>omwu`x#ORnGJwsN?Lq`3A#kZhsD zL#0$t%@5d;oC(N?xcx)x{1GLb<1l9UMEd2sYzjE2@+`MbsZAw0sTrW_LDy*B%CdN- z-pq?p09;44!P~{EScgN(%2SR&7u7bW`*!$LII#@V;(vC!OB*O4dn)!+S3<&}?Y;Fk z>bY7l?&ix}K!`ZBdKITC{mr8;Wm2^P%)BaHwg}L;m{XHbeBLI^s_=oBZaew)$q zQL-#u3G#@eN)iCs#VWBE!A^gPvQwN3(!Xk^<2IQRQrN%7P3?jP)jHy_z`$HF0-LHt z4WZ!5(T4W>0?qOfD~OQqrDbweeR!4Th|RVQ*5tc1!@@|UPOa>FhuxOApf4R|2-oc~ zKLyg6`>7gePb<|$)!lHSIBS3G+1wPb<)R?MlbBE}^k@?w_TY5&1yHOd*6SJt7o43@ zx>ZzP@pz#Hz#}*|b4y+fk>xAGGb;Blz^KLkUp=Y5Y7cvpK;AAJ{j#x}szcsp1fX4r z=oJnjQn@u>iyaLim7ZE{gRo5j^!r@Y+v|>AfmD2Xb@k?1W_0PUM(z*gmmnGNhpPD^ zE)cEIKuSZ^-*3zyKLgE_I7u9ti z^-$C%EnJWe?jd+(6k3&j@ApPc0u;aA^HLd2^?M1SCZ>dS)IjT0a1ix9JAs|jUv?4; zcP`Z_Y1kTQ+k;lDgaB<8F7qwZXy7LjIj^T*vd4S0B9iC4s`#ngGsS5^2XWS^Vmn_d z&T3tPIH>irIVbhIMuj(S%|N*c4`SO%IeicSc>16GREzOJoK^mL!CFaOf5TdU79L{F zXEk}X3CKCCHg=uQgXq(UW2&<($@aJ00=A2)->wS&*qgraF1IxeY{88{w=h9!0D|e7 z_(a}sy5Tk(X_$X9Z-r&0IlEx}CRd#ApLm~aE;o~>r}K*uh%-F2EqE3N1G{Sn6FdPz zouxj`U>ozCgIIuFs#is-ls;}#d|WyMq%>?$6{@H!`} zPNE_st^ZweL*FReRXkUfzxlZ@;C8OM_Jjhj^R}IuD7>(4Vgr@c8&DX?(0Sr|D;^$_ z%r)nh>du^lM6ZpYXrRFU{0_!3ST?Z%GSN=@n=kk0VL2CD?*Sm#&cIWEETN*4(Y{^1 zTVtKof4a&~(5!2&RgP}Sy|^{iJKi0(x+}aWXE-)rR#OwWdEDP^Mw|67iBV6O0>r|~ zQ0WM%D9cuk=EjGx9s;b+yA`qdtJ5L52omf!s=5R&P0U+G^K4$aO`P#pBhXCm;J%ko zucf=5<_FwO{iU{@zSwdHS^kWqcTsJHsa5&2E`D#D>cF}c4?749g8NkWUfW95K_?KH zN(6=^3N3Jld9>8LMkay@HnDh%CA3M9-(Ql5_lSQ8U;+0w+)m6?W~U#6aw^?j0MW3t z1>B>(e1qXTlh`M=g3Af!Hj6k2VqtIXlKVaUm*ly}Kp+StLiR&cNCk^Xx{1+74mf^5 zdm!i%%Z9DNj*OLpy>F~WmRV_24kXyfBx?l8FuN58xrk&*_M*v(&r2J=z=H$PkOGR$ zh!)<|I35jBAA~@@5$2=A1qty}IhH|&{`Jk{8IxpGL9z3w7BoE2z-|932zDHxI&G_sQ^v97gs-BJscW+$5*R-qc&uRDJNQ~6 zR-VR(kKz5Y1qZFF)(*ay4t&eI;`V)T?1s>ipZqT-7OMg6b(kF+(oxEVFzz zUeoBRvpvS6ZD#tA)#drbBamIs{sD$z2vj#zdBg6nU|#*OLj}fA0QCe61Yl`LnsL5b zd)ytvfjDs;NjA>`L2V{Gk=a6dK;Nn6f!?ycR=_&u3z6c;0Q}T#wh>^2rh=I8CG7qm z4v+J$Fa& z*_{RMaYv@pJr%Xi0VI!%Kz? zUIJs+56}BI{E8UHB6YK_#ieFlLDd z|_n_ayKZ;Svx_-bYBP&U)H6rz^4IA=*;f&D@}yUooqm% zKQp&AeplOz(?Xx2L(aY4%cgBmQP5AN#fXa*Z0A_`GEI9#m(#pOe$tqRAi27Gu|++< zG6M-l>;=HYpTR^BZ_+pItWo+|_GR)&b0v_fn09s<7|L(nflZUw|>q+eo1-~vjG-~+)G#n^dF$OzMdZkwrhvY&>32W^U1|v1WU{&rE9Q&0#|b+b<2Qmb z=c5^DkpJ=x2NL2J!x|H#AgoLI&wbOlVOlsFRzppP@AW6MEr7-B!k}nt0q~;@)A0YB zi#Io1e%g|k{XD=?VsRUInl%YU;)mm2GS&}L7lh~yWHAKcd3$Y+hglfm$?NsC+lpj* zr}^KSF&(a57X8~kK9#d%_Tj;eCSx-tn<*#dh{7Y~cAfg|KHef0gA=XtET0;{Q`W~V zv5!z;Iu>re1<+ax|9^T9(Cd3k#sd3ftbPty%gXQ&#Ic0Mk9MEsn`TO^NxIM3oBsF={Z>4 zrH>I^)}9k--~HY#5Ye0{A8%@TNm5kbc z751YE|1(G5=@Y9(qLot2KW!oQj&ac2!>XwazoYTti7wUDTtpS`=G){Rt=O*NJG^zXH5-)n9$*NQ zQIcMMo&H(W<$JO8ro_|}{t+f^!pwu!iNrLEZ29^Q2oP;=#9x%|Z6OL?pjfTHsHZW} ztCd_rFSN_){1;Q{X&2W~OCes(^+8s*q>}cef9o` zly8YPdMK~5{`ejCXXSY`Hi47yR_`0AC}O&IairmZWQSzLk)UhGPtB~mHb$_o6%6r> zj~02nj9a}5x|p^U*j5oKSIuz>M8aeWYH=MOb5+AEX8pGIt_y zpx-9Rkh29pE49zZ?g-y@lX0QkA>Q-a%zSdU4SbCHuWGpu{{H*H+u=fa!>{iRz1~pO zK{IiGyCH%L0AW4I%Iv8|X4200lNp$#(|ILe8c80c%id92;e+G)auT@9`8nYEhn#2@ zaDOQ-lj8S>6c%YUB8b$G+e%+$7u`~?e_y)8SD6cOvxOsVfyHpyU9qF0rAchlv?2~W z2kC=g>Ta={yZH!er=_ImqAPsg3vThquhqzZui#00x$n#&j8nu)mzufT z+mh5QWBMyHv@K*#L>#@2G4!tG4+cNv!xjbAiZbpGH+ey@hcehRi=qCrzk2P*Pj9d> zz6&U?nkkjRv+C*fPEQNe5#1u1>vmzfzWwcwZh4j;bVAZ4^b1TlHB`+Shfi&{MzHm0tZiZxke$TH0 zSh=r&+5#~JLSWGveEVDe{=gcdO-w>j0yLN zP{>NR%eyKwcG%GLlyjVh9+Tyf3Q_082mqA5o!X|vyCxfPfu=XHBpWfrbfzG1Y=RF! z2DrMy4Gj$?$&7>%{?4$l`YQnJLH_^M^%X!>M(w(r?(UK<=@d!n?(Px<1d(poAT244 zAl)s9lyoeWZYxd}dbsD?!?h@dFpT)3k7}4q(s1)D zUAX8#Q`Bf^f;e3hZ~Gr;Izv^Yd)BqId;_J_o>Y0!Fc_xrwThLCy&c1=VAWoj^J_hm zDNfgNd^<|(saH1UN6}1Cmgus+L^pwaY-yutt{gwtnobdv--ckDNrxzRhi#4uc6`1p z=?Ko$`&GgAjgfH$^zxzTc5m*@HQ`8F?2TX6rgAEuXy9)ryP4_C;Db&JWZ8JF5tLb$ z_>&|+4MMtFz?6Ad1|WeNy<-#o#D5nRo*h|kPJC;^=%ua8~#iwkj;YLgh#+>0f%SC{$mr9=Sk6LnBugv)1jm zw#ucpUQ*7!QBv$#uimhj6QS5Di;qsA{px8spMsS1zN9qr`^Pr+JlUxU1?@+^JC)D# z<6E^LQN-?Z!Brv=ou%`_`{h;&9U=l&q%n)uZ=z9FTb4HCCcL}~xTa#h&-S7(W4_U= ztI~cNkxTp1t|W`fW4t20knPX;tV`uHhg>5z{M5@c6JRX5-Vq14#;g4OCblzPw)T6W z;Llf^L7o<`GoKn-q8Ig_axJ8z4Hp0S{gqk>rXriHft|^Ms*L?R{Ax5XF~sbPhV%;m z>_W<(0@s?y^@wf})g;0qXWN|1vbx@SBTKa~a2K{FE9)(7V~>OWFEwMS{K31Jj8H;l zvi4RPwCsyKq!eT5)#BRmBR-yQ$s{)X!+{7YKl;G%-EkYQ@l=?NhR}xE&V8X z@T{e%pNV-a{Kkw3C084P`}TOY=NF*>T4RD~dQvFi*6HfK7$(jSql7V<%06@mbibp= zK08=c7iQ%=GDC-;e-O&R!yzsDolI6MJ?U8(OQ1O6OPeAwT(4+zDCN&Jf6@&*Bg-li z(v6{ucruFp@KL!SH0Q`iUX~h7LCeEyTUC-p=!<}7t0vy-Ov{fJvgWXx&b_V$2hqi2``n-Y$WLYDj|!spSCE=Km% zpDDvy^F=5oP;@hJl7*!2*Oza;~*OZ)KbnmNr>Jh6uSN zkO+Q0wKtLXhrvu|7!QmRT-jR-gpEweL4q6qHxOkzPD0(<-%uOy z1}3*McC-H>dP@o=EZfi*05bsexNs0$QR_F9IJb378IYChTX}o0KT}D1Ola2ATU}qO z5KbV0dPr^N=oU4<==F@(h}jQke#N>5Yb;z`Bn8#Wou_j;ydQEj=A+;vt?2$lmLPTF zoiIxdJu0bWYAEJ(IM8)mL{$M20St804NU!Dc+Oe^Y?VC2-xec$AP_d8X)Uj)(z{%Z z%rcAB{uwg2qx>m7@w7B>{`Ne;G)h61gK9G6N{5Z-=yUa`-rCBGuj77c`A;CpM%Y!~ z@X_m~geMBLutv2m=zA`Te(5hUfL(qG3?ng^gfBeDr}Gx*QqoA{eTgCe8=X9M_H3CW znRn~JBNVpiz-gQJ=UYxEH>shE{G;|HE|-=2&wRil*_*dF3SLunH$78n4kHTxUdmFt z;jB*4KvUzJQsbV)z>4?&v7#)g{)$voeE$Kb+dHQ`joG<&J?`TXT}Hq~uXUde1X}o> zasBMO`f|Yq;`voyp#K0S+f6 z{FK*K3;VYQZR%^3{;bA9fojB2ph~k!gXl^YX&m;m_7LA1p&~7b*;v_jAv?c z_fJ}(50ZYYug{~|;+Md}RdFyFrgBShon-SCWm|>Ya{@HMo zb$f!@gb9BP3W9DtH7hO$`=fL)cA$a^jgtGYEVpFqn~MBA?<|o@w;T`!^EdSS#eJvJ zG!n9ir%t{(jI*a(q>6ZP=cUInB{fK}Jd79*xKG_41M5?6E!ZYnc$&c}j!&7@E^Y*w zj@x^Y@|7MFXK%<4jNIBPl@iuirpbElO;auhct$QwTnSPh8MGm*B;%Vs_IBl6vn|R}8axRSe{i#Ed z9Yp!s#!M0=C@0>DrceZ=EAx@HiUtouR5P5JEJ%R6=_~SO7m|HdCNU&whdoeiB8~^! zZrFdab5^wK?RdStsn;e!lIdb%f5&cJNE?#Iev<2;#x~StNhK`2aaW4}fC@rYkCjZzr!E7L$yfKo2_x`W2ya%G_iQNM#i*vKQoetz(Z=q>2B{wRB*lH zPPpLURFAB$wEubf$uK@GbhozfW!(-|!`6i`^X9Kze;bnPr)nA)wjHS!KA|r}FM&V= zTDQ3cFI;qAb2CRB=dz>jrK|DXGPNAl^S`U6ef4HwSvruAge9xH%3(@Rpnz#39N!S` zQOi~omb4J}uR=pO2%_o38AYCEr-O!hcVC}6xLnP|cDEKMHpHp{dgVBmr*A5)oPJx- zlCSMbEXJzFk_x44WXI5w_g`?lp!a1)&7x6oadvi2J`tb}< zwGUbuu2{o&`+B^w+74AB(`2M5HMA&3*K?W0W3xZHP6L8BdI3*CnA2?1g#|CU3`H|i zuW+ zVgd-B&v_}DA3qeZJc`E4AVyg%y-6?deya(3{7_??F@Htgd{{^)P0;H4Dw0#8e`>G# zdQM)DC-nN4uy8vsyY@CQGyli)gJl{v)|*F5fvGw?HpXSf#DayADy1DBpX4NXLE`aZ z7shBj9_E&MUe_Mm&yfN&b3j}on1Qpe1ak#X@Wix5hrN%z%yr~xMgH*^SV;Y1Q@1>d zGz6N)HFv7Zl>D&z`32nNiv6|wOku0RTx{ykH)t*gU*(nh8+Bcvg~sYaXSh^=`P~C) z<1gk6@tpUbIYTHc@yk!Mms|;E7}@6AGXlP+g5_-i9U(%@3iG-^ZdR?NqJr(W3IjF0 zApQ^1J6Ni`gwAy^Fo2xz%ZqGslcra$%IRQ!5eRr~%?CK>m2P59(G|^UL7_eLrcCzh z9kN$YA557~eO*BkbKDp;m|Ov zEet83@D;u%?k@E|!iXK*3J=~&RH$P!^uek)f=$|WrKt>~?gJ+fYdb!R%Kwq}fROl0zehr|=A9gKIJXMG1GY(dfZFc9v0E4UMU~KW_WSPx; zeZ@$cud-L};o>)5w4aP4zjw*+PG?A~UcEac#jm@(P(;xb70M;?XEvX!<9zdN&+Z{2 z#b)-769jBuA~nHy<&}B4*uO7_v-^+wb8E+VPS?QPy#YQI6A>JOs{E%+V6jArB_`Dy zYJMythTc4GQiYA<&{v9{mrcdV#T+P;DP%s^E-L4Df~{M%+-a)0*)==z^$TUhcn12C zheBeR;vrHZAYDKLb6MQtugpU9Hlp0{a3IFAj|L?tKdy?@NQwnFvnrXjosUw?`qz{J`8`9$1 zf5W`D%L8RT>nqzUoMx{}>9D21j^Is1o!Zd%$ z2NUa|u=SC|3W)|=jDiMt^*W6ZuRH zkQIYHN#37~0mK(N>{>pR?NV>!aA6%5>f7YALLW@bu=4=x!{Cc}PAxReD{}M+fg#X{ z2SlGs%CHZ#u#PBnbq-|4Cw zVZN}YEmm`ORl*3&^v)oOJUZB?Zn$;J(d6R zX_4>kj_~TDeBF+!!oV&!BQ7MU<&KskroG(u?9wjwm0_iq>+{YB@VAeLK<^pbV+{sy z?G0r)QXoCgbPk+XZ7HS7Y~#>?N>?pXl@xYf+$21o z8v_l3o_J>&Rq*3$%)MzMS&oE>lkVyg(prsTZ%n9pC0tYau2BX2v!s{U=lJ<>iK54f z;Ll8_(OSn|clwdbNYRK8R{f{2(GxHvuDhPKstzbvR7Vt1Ih)E+EsmGOI%lKvBNhLt z=Q9O;bk)oxWWYz|g3!d>#E>IGZS+f5HWvaIg)T>xYif-&)mW*p?4kucgGOS}Q5K7J zUz(9ld7nF@lD#&lJ+4zRW$)R>nYBcmtb@R^K;%#sKN(2g)>wgs&D5yv*T3#;knD$H z6{UqHE-2mA`#Khbd@q!-TJXtx928dH-y-4vWTv;%o#t1DfDI_DojJ1oK^>(w53 z$eWVLvL`<5JP1$4!OPRwiHFd_3>`)Zn;(J-rBKs7Tub8i;AjI~o4kCh2iD{s2qJbk zOj8&yvJlzcxB)!L9wlBUni^uNBliSs$sh5Qki$vj0Ap&za7o5udAU{TA8e)OC_O)) zRB*a|Wicm-R}>gM-u;bDQBywfTGGr^l6~13m8ON4U|moGmLMXrpuwisgLzZY+uHEm z9OY%xe4dV_DUhfv4UQXsD_pxHFh6z>d_%$kfrxr6(QJSCt^l1lt6+&kN0-n;S-s$U zfP);8wgNn!<{JHxe+d=rpVk;r(h)i68&-yk(D^&JZ98x0zUM#qn@JB{JFP#d4{r%n^dOet625;Tg;3sA2~(6DP==MtDe38CtE zV|gWv~UF8cGrbQn%}UI0e7kf|X%eoIhq2*d_!+ z(|M%HC&KRN*%W3HHO0G~v&7YySnkmX(}R!)RGvgWo=qzkO7S1uZ?0V1fo}4I^SNuj zRKkh5AdQz*7cRQ6yoL0RO@SJkW-w6_&*zw~mKZn))!Q3aDUu$$sdS)D)se-n$;sjW zdf~BX2p?j$5V??udo!pN$eT^eEa0+WtnytI|h}D zv61xCa4^zb%PZtD=Ovqq2=(xrs0TXOl-!n4zfY;%0;%^Jgjk*Zbe92Nz4YS%u{C8H z!&QZ>5tU}I@Vr1y+M)G>EVXT3&YQ^o!{_!(6?CJ$Nf?UHTD+fhL0Lqzc|KJ& zbd;@d&}M$Xb7I^G(K!#C{qiaxuR)plewSN1KVrg)q-D-S`Y$s5Gcp~hl>w*m&O+|` zF%_0gn@~E>S!Mq@!yeY^wvx%su)+LniOOE64u`AcRb7mU|NR%1k^`I)91~fI{T1h6 zA<>r9lPCHXuzHQ<->Bu7n^9V4s+%d5dZE0jU7T<%W?ePcfG=f)^^O|vhQd5slxwf0 zsF0TL0hG1x6TRY}WH;a27@IOXuxIV93po?Zj9!y*m@M}KR5;GQ1Cr2PO?53Luuhay zz~(!|sy?SZI5{@ErjrNj(X~5t5(GOz*f&|!d1mU9(%U%wfySg~>pnh#=`j%ILA9A- zEk_<)2#BWj!BBeEVTq)+ed!Emw;WNCP%fK4{KcUe%>x3It!S3IKPHhmUV=oEGs5ph z81$T?U2B)QvV8;>DtC}H@$CeUJ|lOWTIx=}&@Jj#tXIms)hgaiO~~w}Vwmby=o^#V z-ukw)m~qb@V~qCh?uYn63T|P`*Mu#vcgB7ZcQhRT80@a;9%WahCDl;xep*M^%Aq{5 z8vKMG2$vKuRGE@lD?_y_z;(*h$}jvVcJcA5di)TiTqbL78u(H*0;Y z1(KW*^=xA91e$_FPf_?_Qx=)+Y_J|(2gDZD=B=lmfvAl2mP>(4s2gPneOq$Rxr-WE zf$8WJdX>v^_!2=u*|D)y4}IrSCFx*V`s%?CG(@BZQW|@OF$ZpB7p_ChAir%Jx1M6S zO!j4%FYy~X|c4w6Nk@rr_WH>!}R-Y9iXwu;sl z5YEF@VnmjvO!octyWIA4UN?9h_Jz(mF5}FbDPQ=0;r_<;ciaw2l%6OLAybTns5`7xo35gb$9!OJkKJC>Qa95pR{9Dv0cNemW(x#So5!ma!{ z)$Gg0bK*(A6P?a3O&#j6u+>L_RQu4v=zkp*7jTchkJx@&WIj6A*gxzWgE48huJ6oH zDxjBpMmZ}bT4Sn^dU}}tQyq=tDR*+>iclWoZ}+tF?qkL;_ndJ)F6oabR{Ku4DMSxN zr;eH6jCV~x=`hAvx|~^=9X>st4^J`eGAW_}I9NGXNzT%%VE1G@>z}ynd%O!RRF2i6 zw4V{^r)sKR@*ofh){u>JsESfVM=Ae`zP-GdbN8dThP(;;RkHE3=~V3XwmObQByC*e zipI1Ub$4f1ETn_3Fj_(hoD%w}v0xPJa5iDdTqF`Dr?!1ypB&whqjTFBJFpLZiJ1(> z2uEb8>vGrnZ@eg%*LI6}d4*;Ot6WSYA(w(z7#fsf63uoZ9|g<}bHo8?Xox- z;Z0p-7>YAnZIcT<{N1R!R~vJK;BCTO8y*Z6_IF>wTP2*aOSPycP5XCuGMlh3=DEL< z`tqP!e9-r=x>GeJgU;+q+?4#Z0T|X-`Q$BXk0s{Eo!F?HrM6%)k;}o!3}WVJN@Qs_ z6ExODf8w$>zVIo(euglf1KqkpBd?`*LQXndoedo#k}T4Ju_cUYgbNEoCypEigd z0#27%N|odxtfZ#1a=!CA^Fisz&GNh&S)oq$`o`ES1UbxJK$xwcgTeI#YhM)Aa_rNfb{3YUS2_S|I z`)B^M#(*uVH_*8F1VM&Op(d(4t>=XWpdghhdu{>+Z4=HTs>pm z#`xSYHlSjO`7k;n;`DCSG4)T{n?XwKe3;0aMDn$bh;JaY9$=oOXy+KYV}B9ppD&C zqOJ5DA3&B6Hco~4j2)*6Ibl_jw_(NdtJo~WBu1RstOp}3q*HSrzM2UgRPPU%fBLul zQA9f7HtSI-QVpE!R<~Ea2L#7rVw&I1902P+1ohb4^^_lMw}RFrk(+qu>qNUd|8*S# z(%bnI7I3p2Pr;Wi%)u>-K4&FW+;k>KzivRIdg9C&_ZGA#y53kGjLEi#QG0V-7zsLmIuF_+YVu+Os>0n;l{ta6_o$> z&NVWR-JX@1Z^P?vw4J0*NVxm>y>DRX+lUEBu?U^0)d`UpmSuUqe<1NAqUE0w=P~p$ zaH7@5KtMVU1)8Uw4YDtV9zRHu0;bWC084{Pxc# zj9o7IQH$^|FaKokRVN}~At1cHV5+-Uh#2ArBax17$Dyk!ZX99-Ou%_1I;6$7fGIlA z$+Bx;`5h#eJK_UiIkBqR+M$w)CnvXPT8;)dkQJT^`p#q%^FCg3A+=rao9RJR+*^c+ zWSab)rdC@wreF))_3KzuXL_y(r|}b6rpk%3tbs zb#UE*QJ8I}V?KQICiGxM1vi6exx!W zW`Dqr6@ruJ0gpFnSaJ0qc%?R)Auxl(9GG@SC5F(nL)tgRPUoBlN^=AyhXMRyVTxYu~FofFkrs-dAGwOE|3|dJP4Fox$VJ$hBaHbazHE5(BrSpz!6q7~d33I=Kd8 z8R1cbX3lK&Lzc%W?enznQkDWCrLDGtGjg7s)2&JYQ?PyU0S8PxLUg{5Q8-4ArGv$L zkkO<(E4#p{@OK)lPAZ4#|_94NYRa(jo z&9BMwqnW`-#HW-suIKxTnI$rTOys+}g(+bTSYTDKP{JLHc=jGy%U^Rs=!LhH`5P%$)u@r|DtbC1#XTrShT!&46p3`cuH z*k`cQqHjADXIo1$73vD|=`*6EVIZTuy*3vI6t_AhAvolE^9ezTj{GLYMqlxJDvTBo zu%2ZK)X>OP&M4ZR+Q0eMt2)YXBKd1*nkn6N2C>tSQ>vsTzKC&*!xuZ;nCmwuEe zFm(e(pWk;Rz?+4V4p?}E(1`$l)DS5{zi(X8?CSu2*WhS06uK8pZeve_KX<8spZ{Mv zFd8J05E25n(YyOd9IDw?2-WcoPe$bl0hCLKX|>ijw_q3RFAsS!9$<@qW3PhaOTujo z#ihD45jLzJUV}Lcx~T@GCk!dNXE552tENlP-c7aVBl*^(#P@#;Q!hTkO|}GKl($HZ zbXJN8{}VhU`Auov{&$1~SpmDF{6n0Pi!LvhTce6OY%g%PR=aiz`%I!yUV%60l06QF z+aa_ZET0`_XI=A82CLLOcYQ`LS0CoQz3(0DDVMNz|NZ2`8sAqfx2__%e|Sj12>K=} z-XAGyZtT|``mIQwsT*?RN~5iQeLi<@q^K@MI(Pn0$Q)p=T8O*DmAp(5hsvfp6R)hm zk*7kSamz9?Svz?+hEWiQxUH%7@qk30FHA-`FL_a;P+awq;8X1P}W>q7Nyy!A-Y^Uuqk{SJA&m+*}A$rROg~ z26x=fTMdG*7&Uz5$2-NQ4oqL|S6@02AWic+;`XFmlvr4#pJ#2yAlS;RVzZhpm=K)d z9XF74sII&G%s$-;oX5R;0xi z?lMrTnGfNIoRcBYo%33zim-a+g&Q4SEC#{?b)a1ZX;0`&V?unW<8U#iN98Y>rLm-9h&%|4+0p8z<-auTg7X*fF=1HLD z(41MO|3S)Sc9OnvMlgUhX5q{_VD}O|e;U6;Rav^|i{HdtAq60|*FIz=0dUFEO!}w_&x%8}Ll%*5TA{he{OlKVfClRa$5+85r)w9use>O9+s91P zt#pJ*C%01)>Pud3YjvvxxUwmIbe1l3G3;aYu@7A&Dr6=lBFA$%OFN61Z^~S*{Os=v z<+lXfi+`GA-7Hfnkp2gM26GKEvF9(=n8jSW*~FKV>epDDL6c*;R4p%5I5vlTlqDnD z>HF)bl#973+WvS3cG-SwB_rLU3jVZzybeW`hNg(&TwNKsE;LrYSt-~RtTpt)!8_#! z)J!4UDJtRk9u%gfs?+ZjAaIY9XNL`5k4&WXhe5`w+j+`!T!_R_h;_`S8nJF- ztN?0?HVT58)ogFEUdoI{{_B-roG7Nxnm(!Sb}_t!hhWds{m+U*5|1g*G z05tMMB0Jxt9n3XQ^xCW$KyMMzvONY;2x@XUNidfmp}E(tN9t)Sx{^9#yHKW}^g;^Q z!TC^EvE!|*$_hIin252>n%JuC?o&)zIW%LL~)CX58?viG%tgy z<|{|Vg$q98BH)Fn3ahUV^sJXIPoz#p?-UM+7|d+U4iG6d(uxzMwcqwh=dI2K%C#j& z0_mP=4>WndOrLvFIJ(2JuyRJy_`d4Q2TALP#-egJjZ{5k=F z`6li-7>m?rGo_6S<1z;)olro0`bOw>XznQ4d#4_Us>NKnR?f>*w$w=8k&F#LTWy1(65h%23WPhGO zp&9(6BOc$@* zI!XL`sZgAHKsz>4AK;I}lykDsjIblr2{Ze}Key2m=T9B|3P^2l`{Ltb-ph;eNpPS2 zXfg0~z10ViTtsM{zHw3Hfv%?tlvYbK3k*_A@DQ2#a+T5Y3N7qo>xpqlFm{iAdY;d~ z*NDvLiuQ}TVfrO^vGEYUE_2WGMPN4h>ld;(Y?f+pTVtnLI$98OR|Ua^nfgcbv}Xyk zHLUBg1fmyW0y7te@_`Ko@|D5ZeAGAdLuaYJR%nK2Gf9Ft})fKW}Rec1~(DTk5;a-5=M7?X={B zktt3>fsvG@6l+5c2D~>)Wbcpkq?k2KcA7QbJ)-2D!c^BI-02&oTiiXbZ~Od!p~)M( zh!cb#rEc$};w4QdZ2)+tW!&j$!-;`L#X)L2CjsbNn{15F@{*Pi-xUDjg|iQ(Ds%dv zZu$?^|118PF=fZJMx#p>(3=7eS^TDfK^t139;65GApReV)b;pcA-aG7b)ip;@lffW z2U94NSRx)ss6dNIQ^-iEB8cI$C0+-4^3#{d9-Z znaTe3u9`pb_by%HqfEPYpbcl|=jh#9WQL?tGjV1lR;EqpX2EjDzLk^vhqBq@R6N1K zVN64XFC2M=kBY zO61oXc~w7$g-5>73R@-iOR}3;LyQ`zaT+3apJ>dEwtSU8Nv*-=TO*Hq5ny-@Fy=sq z3tj^fNa%<1)A0|#$;MRC2`q9x1eWa<_XFjkZDM~(6*9-F5J8Wd_cv^lPLG$7`}4t_ zink0D7u&9q?!*3X`wrgSHG>B=%b(vgb2bk*Rqu5E6Wd7WCvwtXqi0TnmPV(T!{cU8 zRtban&KF_xVrqh>@SM*jBtD+nyNHPEL0xemUTpK1@5Z)kNL7PoJ$3Hz0k)NO*f(i; z_lQ4~Uvg9ckTy6}mT5GsZg?$;{02y?VY~d^{cD*Bvf8Q1WH=Vao8rt^l~)1RYmhlD z#?5*opQtI%gS?bx2zE7ko3GwG**1U`hYx6|I&*^^toz;2Rt=MtT-@#=}m=YJlU|SuPn{9T+pqyDh+{gMiO(_9!}e| zTNv0;+47oenCi-YV4T>JHQmC}Eb?O>JY=3uG-(>_7F$m=xk+-^{)ItWA=X^=wIlET zZ&SbJ>-Vl7Mv_%2l8azXfe@AD9c^;OyCIcN-qo8Ku{HKLJC4-l81x{&+aUGB6{RPt zoqiMRx{XWAh3Z1Pqv^q_4_Jk3(~sl_Elql*EFW`D_ZGhRAWjL>3IUaq<)5oH9J}pL z&&{{qt!;&=05G-_<*Z3vw4IJbiP{*MEM{n#FePH7=ODXD=FRAcz-W5(meRl4Eh%bgVt-+PXz(~TT4_m zUXeI;;hYN4Wvb5akYlxd^GIxAK}iE#@AUf;Ei)Ttua0ml!AMsc&kzn!%*D6RvLWsX z$@Tfyo^(Hy zJ-`|m?s#G{d(mP-*p~&6n43(srY~-@*cUP+#1%-o$k-5la z8CPDM91gk8%`ej?3RsT5sr3Dw+$CPHq{t|{4u4aKX-E5NxqUsAk$UKd4esu`WdR)i zfx#izYay!CY)j>=vpBBB2jKJG?CaQ;w>KBXXC@6Q4;G!{k-)TxEJ=m4tR7cRhfZRy zXkdA3=03)b{P@unh3aU1zRSo2 zlX({D~db^^_L%S6Fu%u{151buO1%< zzO+3hHuEyI@P|~IW9L=1Jqgo0ueAXjB+6%G>un9sXxsa6@u*e++mcrC1u>_8r3>qy z$&G61Q{FZAVxKclQFn$gBs~|;6u`UyS8hi*p^E2O9r=vMt1OcOZ0V|j@`*>yNPIyyQ$ zT3&Xu;+5WfHTt^N<9xwq>}Ir)>mbWjvEGq>91ep(nDpgI?qccHUs#B@!TYy|a(U5G z6Ka-gL-HT`_tP`7BS0d2|++CHS!3i0$xw|VPvx<_%wcG5G`=d!qJND)mo?>q2zeY#wM!Y;?7aud`!G9#Rw(E9n zcik=l1S?h0Ks|qV28F@*@mZM>e3ZDH-|JOoj%rQnFO(uIWlwvn-c?3qB?QFbrcj*^ zMXVFBNM@$uqd~&|#G$vibvQf)cIWHmm>#9^jHR6Xuqyv0a*W?(bSM!J0!sw;7bMZz z;SSoD%k$mlk_9(2B{NrgoHABf+jV6e@M&ToX3h_x@1`XnhW`pn=dc#@1uvHbhsA{d zz&HOwzDBZ$DWQ~$dN*-|HNxk=B9q$yJ9K-BK)5a-LnvU*6@%8wYDjnXUR~(D1-9k# zTqMm{t=1J#(B9o87FZBKmmyiPUxzo<*;w-r#t_DtH-}Tzd*I|OYMpV&)6ut9NccM# zAN_46T`f)HN8URj`LYrrq_Ys@g1UfHpUj+Lqu5}{k~!a_VutNs88K=EOms*O4<1;Y zb^W1Hop{72u4oSx(***__0kD1%BF+f#g=j2OhXjaF?$zH_7ppMNDt=Xi*6db7Y6PR zl*p?bq@4Tc4;mM?uRLa4IA+ILXrH zA7tWrx}h6dd70ryY@mI}8=A&!Z70(&Hs5d(_rE3PoQmT$MI;i{YOR3{Hv|p0!<^|J#$7!It;3o#n9IR*> zDxPEkeb)+J9V2T-V(iKHjv)&|V;w=MQTGSySfyO|DU|m-*tzs^-k0!I_CJ(y;%|OvK)jgP!P?6YN$W#SEz|k$j}%4(hG z_i*rt!fPIxjZJ9#y53_3RLexzsS#PY9(mtYz1B*+??FJY@GaToZMQR{DPdX1vz%av}|L;Gz+B#UdxSBhB{NJyE!6^qMj1Y*tw2D-vgjvvk0X!%P&j0`b literal 29879 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 u3jSU$JyFM!nqy;va}ALGR14aF%JyGJ3Oyy Date: Sun, 2 Jun 2024 10:40:37 -0400 Subject: [PATCH 015/129] [Bug] Female variant Rhyhorn line missing .json palette [#1681] (#1718) * [Bug] Female variant Rhyperior missing .json palette Adds the missing variant back palette map. Taken from the male variant back. * [Bug] Female variant Rhyperior missing .json palette Adds the missing variant front palette map. Taken from the male variant. * [Bug] Female variant Rhydon line missing .json palette Lets the game know that variants, both front and back, for Rhydon, Rhyhorn, and Rhyperior exist. * [Bug] Female variant Rhydon, Rhyperior missing .json palette Adds the missing variant front palette maps. Taken from the male variants. * [Bug] Female variant Rhydon, Rhyperior missing .json palette Adds the missing variant back palette maps. Taken from the male variant backs. --- .../images/pokemon/variant/_masterlist.json | 32 ++++++++++++++++- .../pokemon/variant/back/female/111.json | 22 ++++++++++++ .../pokemon/variant/back/female/112.json | 26 ++++++++++++++ .../pokemon/variant/back/female/464.json | 34 +++++++++++++++++++ public/images/pokemon/variant/female/111.json | 22 ++++++++++++ public/images/pokemon/variant/female/112.json | 32 +++++++++++++++++ public/images/pokemon/variant/female/464.json | 34 +++++++++++++++++++ 7 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 public/images/pokemon/variant/back/female/111.json create mode 100644 public/images/pokemon/variant/back/female/112.json create mode 100644 public/images/pokemon/variant/back/female/464.json create mode 100644 public/images/pokemon/variant/female/111.json create mode 100644 public/images/pokemon/variant/female/112.json create mode 100644 public/images/pokemon/variant/female/464.json diff --git a/public/images/pokemon/variant/_masterlist.json b/public/images/pokemon/variant/_masterlist.json index e6c9a5e2893..333ed303443 100644 --- a/public/images/pokemon/variant/_masterlist.json +++ b/public/images/pokemon/variant/_masterlist.json @@ -2750,6 +2750,16 @@ 1, 1 ], + "111": [ + 0, + 1, + 1 + ], + "112": [ + 0, + 1, + 1 + ], "118": [ 0, 1, @@ -2835,6 +2845,11 @@ 1, 1 ], + "464": [ + 0, + 1, + 1 + ], "592": [ 1, 1, @@ -5598,6 +5613,16 @@ 1, 1 ], + "111": [ + 0, + 1, + 1 + ], + "112": [ + 0, + 1, + 1 + ], "118": [ 0, 1, @@ -5683,6 +5708,11 @@ 1, 1 ], + "464": [ + 0, + 1, + 1 + ], "592": [ 1, 1, @@ -7650,4 +7680,4 @@ ] } } -} \ No newline at end of file +} diff --git a/public/images/pokemon/variant/back/female/111.json b/public/images/pokemon/variant/back/female/111.json new file mode 100644 index 00000000000..5ef11d8ed41 --- /dev/null +++ b/public/images/pokemon/variant/back/female/111.json @@ -0,0 +1,22 @@ +{ + "1": { + "5a5a7b": "261e2d", + "bdbdce": "6a547a", + "8484ad": "402f51", + "3a3a52": "261e2d", + "101010": "101010", + "e6e6ef": "9781ab", + "ffffff": "ffffff", + "ad3a29": "ad3a29" + }, + "2": { + "5a5a7b": "ab4355", + "bdbdce": "e18db3", + "8484ad": "d76688", + "3a3a52": "6d2935", + "101010": "101010", + "e6e6ef": "f7b4d1", + "ffffff": "ffffff", + "ad3a29": "ad3a29" + } +} \ No newline at end of file diff --git a/public/images/pokemon/variant/back/female/112.json b/public/images/pokemon/variant/back/female/112.json new file mode 100644 index 00000000000..774e2d1cf64 --- /dev/null +++ b/public/images/pokemon/variant/back/female/112.json @@ -0,0 +1,26 @@ +{ + "1": { + "52525a": "3c2945", + "c5c5bd": "6a547a", + "8c8c94": "523c5c", + "101010": "101010", + "e6e6de": "9781ab", + "735a31": "6b6373", + "e6d6ad": "cecede", + "b5a573": "948cad", + "ffffff": "ffffff", + "e6523a": "e6523a" + }, + "2": { + "52525a": "642224", + "c5c5bd": "cb568a", + "8c8c94": "ab3f5c", + "101010": "101010", + "e6e6de": "ef86b5", + "735a31": "6d586d", + "e6d6ad": "dacad3", + "b5a573": "be9bb6", + "ffffff": "ffffff", + "e6523a": "e6523a" + } +} \ No newline at end of file diff --git a/public/images/pokemon/variant/back/female/464.json b/public/images/pokemon/variant/back/female/464.json new file mode 100644 index 00000000000..9479b6f2ebf --- /dev/null +++ b/public/images/pokemon/variant/back/female/464.json @@ -0,0 +1,34 @@ +{ + "1": { + "523100": "3b1f58", + "bd4200": "60418a", + "ef5200": "6f4d9f", + "29293a": "1f1028", + "3a3a4a": "3b2d40", + "101010": "101010", + "7b6b7b": "6e5d7b", + "6b6373": "6b6373", + "cecede": "cecede", + "efefff": "efefff", + "5a4a63": "514259", + "948cad": "948cad", + "943a00": "4c2f6e", + "ad2900": "ad2900" + }, + "2": { + "523100": "492133", + "bd4200": "7d445c", + "ef5200": "6d3950", + "29293a": "442339", + "3a3a4a": "701f38", + "101010": "101010", + "7b6b7b": "c6405b", + "6b6373": "b66360", + "cecede": "e8a797", + "efefff": "ffdfd1", + "5a4a63": "8f2c41", + "948cad": "d98f87", + "943a00": "5b2e42", + "ad2900": "6c7c00" + } +} \ No newline at end of file diff --git a/public/images/pokemon/variant/female/111.json b/public/images/pokemon/variant/female/111.json new file mode 100644 index 00000000000..3a43aa13e15 --- /dev/null +++ b/public/images/pokemon/variant/female/111.json @@ -0,0 +1,22 @@ +{ + "1": { + "5a5a7b": "241e2f", + "101010": "101010", + "bdbdce": "6a547a", + "e6e6ef": "9781ab", + "8484ad": "402f51", + "3a3a52": "261e2d", + "ffffff": "ffffff", + "ad3a29": "ad3a29" + }, + "2": { + "5a5a7b": "ab4355", + "101010": "101010", + "bdbdce": "e18db3", + "e6e6ef": "f7b4d1", + "8484ad": "d76688", + "3a3a52": "6d2935", + "ffffff": "ffffff", + "ad3a29": "ad3a29" + } +} \ No newline at end of file diff --git a/public/images/pokemon/variant/female/112.json b/public/images/pokemon/variant/female/112.json new file mode 100644 index 00000000000..1c668cf5aa7 --- /dev/null +++ b/public/images/pokemon/variant/female/112.json @@ -0,0 +1,32 @@ +{ + "1": { + "8c8c94": "523c5c", + "101010": "101010", + "c5c5bd": "6a547a", + "52525a": "3c2945", + "e6e6de": "9781ab", + "735a31": "6b6373", + "ffefc5": "cecede", + "b5a573": "948cad", + "ffffff": "ffffff", + "a53110": "a53110", + "e6523a": "e6523a", + "e6d6ad": "cecede", + "732110": "732110" + }, + "2": { + "8c8c94": "ab3f5c", + "101010": "101010", + "c5c5bd": "cb568a", + "52525a": "642224", + "e6e6de": "ef86b5", + "735a31": "6d586d", + "ffefc5": "dacad3", + "b5a573": "be9bb6", + "ffffff": "ffffff", + "a53110": "a53110", + "e6523a": "e6523a", + "e6d6ad": "dacad3", + "732110": "732110" + } +} \ No newline at end of file diff --git a/public/images/pokemon/variant/female/464.json b/public/images/pokemon/variant/female/464.json new file mode 100644 index 00000000000..e97690927a9 --- /dev/null +++ b/public/images/pokemon/variant/female/464.json @@ -0,0 +1,34 @@ +{ + "1": { + "6b6373": "6b6373", + "3a3a4a": "3b2d40", + "5a4a63": "514259", + "101010": "101010", + "efefff": "efefff", + "29293a": "1f1028", + "523100": "3b1f58", + "7b6b7b": "6e5d7b", + "948cad": "948cad", + "943a00": "4c2f6e", + "ef5200": "6f4d9f", + "cecede": "cecede", + "ad2900": "ad2900", + "bd4200": "60418a" + }, + "2": { + "6b6373": "b66360", + "3a3a4a": "701f38", + "5a4a63": "8f2c41", + "101010": "101010", + "efefff": "ffdfd1", + "29293a": "442339", + "523100": "492133", + "7b6b7b": "c6405b", + "948cad": "d98f87", + "943a00": "5b2e42", + "ef5200": "7d445c", + "cecede": "e8a797", + "ad2900": "6c7c00", + "bd4200": "6d3950" + } +} \ No newline at end of file From eddc6d73bf7a6a04af67e1658664ca55dbfe552b Mon Sep 17 00:00:00 2001 From: chaosgrimmon <31082757+chaosgrimmon@users.noreply.github.com> Date: Sun, 2 Jun 2024 10:41:05 -0400 Subject: [PATCH 016/129] [Bug] Fix undesired opaque pixels (#1719) --- public/images/pokemon_icons_2v.png | Bin 61556 -> 61559 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/public/images/pokemon_icons_2v.png b/public/images/pokemon_icons_2v.png index fd3f3a9c502005c989b2fecad6693321437d83cb..90798547580c6d683b6032ecb0c75de649dec41c 100644 GIT binary patch literal 61559 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

c6cueugNeTY;}vlWl66E!3SC4ubD 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

PKLEx0OZMQm-WLAULnAR|Cd}Z8 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 From 50c1f8aee461a45d8f69de8f02a452a34b59f78b Mon Sep 17 00:00:00 2001 From: GoldTra <162721984+GoldTra@users.noreply.github.com> Date: Mon, 3 Jun 2024 02:13:54 +0200 Subject: [PATCH 017/129] [Localization] ES Weather and Achievements (#1722) --- src/locales/es/achv.ts | 128 +++++++++++++++++++------------------- src/locales/es/weather.ts | 58 ++++++++--------- 2 files changed, 93 insertions(+), 93 deletions(-) diff --git a/src/locales/es/achv.ts b/src/locales/es/achv.ts index 42b1995bcde..071c0cbb860 100644 --- a/src/locales/es/achv.ts +++ b/src/locales/es/achv.ts @@ -2,170 +2,170 @@ import { AchievementTranslationEntries } from "#app/plugins/i18n.js"; export const achv: AchievementTranslationEntries = { "Achievements": { - name: "Achievements", + name: "Logros", }, "Locked": { - name: "Locked", + name: "Bloqueado", }, "MoneyAchv": { - description: "Accumulate a total of ₽{{moneyAmount}}", + description: "Acumula un total de ₽{{moneyAmount}}.", }, "10K_MONEY": { - name: "Money Haver", + name: "Ahorrador", }, "100K_MONEY": { - name: "Rich", + name: "Rico", }, "1M_MONEY": { - name: "Millionaire", + name: "Millonario", }, "10M_MONEY": { - name: "One Percenter", + name: "Elusión Fiscal", }, "DamageAchv": { - description: "Inflict {{damageAmount}} damage in one hit", + description: "Inflige {{damageAmount}} daño en un solo golpe.", }, "250_DMG": { - name: "Hard Hitter", + name: "Golpe Maestro", }, "1000_DMG": { - name: "Harder Hitter", + name: "Golpe Devastador", }, "2500_DMG": { - name: "That's a Lotta Damage!", + name: "¡Eso es un montón de daño!", }, "10000_DMG": { name: "One Punch Man", }, "HealAchv": { - description: "Heal {{healAmount}} {{HP}} at once with a move, ability, or held item", + description: "Cura {{healAmount}} {{HP}} de una, con un movimiento, habilidad o objeto equipado.", }, "250_HEAL": { - name: "Novice Healer", + name: "Sanador Novato", }, "1000_HEAL": { - name: "Big Healer", + name: "Gran Sanador", }, "2500_HEAL": { - name: "Cleric", + name: "Clérigo", }, "10000_HEAL": { - name: "Recovery Master", + name: "Centro Pokémon", }, "LevelAchv": { - description: "Level up a Pokémon to Lv{{level}}", + description: "Sube a un Pokémon al nivel {{level}}.", }, "LV_100": { - name: "But Wait, There's More!", + name: "¡Pero espera, aún hay mas!", }, "LV_250": { - name: "Elite", + name: "Élite", }, "LV_1000": { - name: "To Go Even Further Beyond", + name: "Supera tus límites", }, "RibbonAchv": { - description: "Accumulate a total of {{ribbonAmount}} Ribbons", + description: "Acumula un total de {{ribbonAmount}} Cintas.", }, "10_RIBBONS": { - name: "Pokémon League Champion", + name: "Campeón Liga Pokémon", }, "25_RIBBONS": { - name: "Great League Champion", + name: "Campeón Liga Super", }, "50_RIBBONS": { - name: "Ultra League Champion", + name: "Campeón Liga Ultra", }, "75_RIBBONS": { - name: "Rogue League Champion", + name: "Campeón Liga Rogue", }, "100_RIBBONS": { - name: "Master League Champion", + name: "Campeón Liga Master", }, "TRANSFER_MAX_BATTLE_STAT": { - name: "Teamwork", - description: "Baton pass to another party member with at least one stat maxed out", + name: "Trabajo en Equipo", + description: "Haz relevo a otro miembro del equipo con al menos una estadística al máximo.", }, "MAX_FRIENDSHIP": { - name: "Friendmaxxing", - description: "Reach max friendship on a Pokémon", + name: "Amistad Total", + description: "Alcanza con un Pokémon la amistad al máximo.", }, "MEGA_EVOLVE": { - name: "Megamorph", - description: "Mega evolve a Pokémon", + name: "Megamorfosis", + description: "Megaevoluciona a un Pokémon.", }, "GIGANTAMAX": { - name: "Absolute Unit", - description: "Gigantamax a Pokémon", + name: "Criatura Colosal", + description: "Haz Gigantamax a un Pokémon.", }, "TERASTALLIZE": { - name: "STAB Enthusiast", - description: "Terastallize a Pokémon", + name: "Entusiasta del STAB", + description: "Teracristaliza a un Pokémon.", }, "STELLAR_TERASTALLIZE": { - name: "The Hidden Type", - description: "Stellar Terastallize a Pokémon", + name: "El Tipo Oculto", + description: "Teracristaliza a un Pokémon al tipo Astral.", }, "SPLICE": { name: "Infinite Fusion", - description: "Splice two Pokémon together with DNA Splicers", + description: "Fusiona dos Pokémon con la Punta ADN.", }, "MINI_BLACK_HOLE": { - name: "A Hole Lot of Items", - description: "Acquire a Mini Black Hole", + name: "Devorador de Objetos", + description: "Adquiere un Mini Agujero Negro.", }, "CATCH_MYTHICAL": { - name: "Mythical", - description: "Catch a mythical Pokémon", + name: "Singular", + description: "Captura a un Pokémon Singular.", }, "CATCH_SUB_LEGENDARY": { - name: "(Sub-)Legendary", - description: "Catch a sub-legendary Pokémon", + name: "(Sub)Legendario", + description: "Captura a un Pokémon Sublegendario.", }, "CATCH_LEGENDARY": { - name: "Legendary", - description: "Catch a legendary Pokémon", + name: "Legendario", + description: "Captura a un Pokémon Legendario.", }, "SEE_SHINY": { name: "Shiny", - description: "Find a shiny Pokémon in the wild", + description: "Encuentra a un Pokémon Shiny salvaje.", }, "SHINY_PARTY": { - name: "That's Dedication", - description: "Have a full party of shiny Pokémon", + name: "Eso es dedicación", + description: "Tener un equipo completo de Pokémon shiny.", }, "HATCH_MYTHICAL": { - name: "Mythical Egg", - description: "Hatch a mythical Pokémon from an egg", + name: "Huevo Singular", + description: "Hacer eclosionar un Pokémon Singular de un huevo.", }, "HATCH_SUB_LEGENDARY": { - name: "Sub-Legendary Egg", - description: "Hatch a sub-legendary Pokémon from an egg", + name: "Huevo Sublegendario", + description: "Hacer eclosionar un Pokémon Sublegendario de un huevo.", }, "HATCH_LEGENDARY": { - name: "Legendary Egg", - description: "Hatch a legendary Pokémon from an egg", + name: "Huevo Legendario", + description: "Hacer eclosionar un Pokémon Legendario de un huevo.", }, "HATCH_SHINY": { - name: "Shiny Egg", - description: "Hatch a shiny Pokémon from an egg", + name: "Huevo Shiny", + description: "Hacer eclosionar un Pokémon Shiny de un huevo.", }, "HIDDEN_ABILITY": { - name: "Hidden Potential", - description: "Catch a Pokémon with a hidden ability", + name: "Potencial Oculto", + description: "Captura un Pokémon con una habilidad oculta.", }, "PERFECT_IVS": { - name: "Certificate of Authenticity", - description: "Get perfect IVs on a Pokémon", + name: "Certificado de Autenticidad", + description: "Consigue IVs perfectos en un Pokémon.", }, "CLASSIC_VICTORY": { - name: "Undefeated", - description: "Beat the game in classic mode", + name: "Imbatible", + description: "Completa el juego en modo clásico.", }, } as const; diff --git a/src/locales/es/weather.ts b/src/locales/es/weather.ts index 1e4602f362c..04ebf977bc2 100644 --- a/src/locales/es/weather.ts +++ b/src/locales/es/weather.ts @@ -4,41 +4,41 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; * The weather namespace holds text displayed when weather is active during a battle */ export const weather: SimpleTranslationEntries = { - "sunnyStartMessage": "The sunlight got bright!", - "sunnyLapseMessage": "The sunlight is strong.", - "sunnyClearMessage": "The sunlight faded.", + "sunnyStartMessage": "¡El sol esta brillando!", + "sunnyLapseMessage": "Hace mucho sol...", + "sunnyClearMessage": "Se ha ido el sol.", - "rainStartMessage": "A downpour started!", - "rainLapseMessage": "The downpour continues.", - "rainClearMessage": "The rain stopped.", + "rainStartMessage": "¡Ha empezado a llover!", + "rainLapseMessage": "Sigue lloviendo...", + "rainClearMessage": "Ha dejado de llover.", - "sandstormStartMessage": "A sandstorm brewed!", - "sandstormLapseMessage": "The sandstorm rages.", - "sandstormClearMessage": "The sandstorm subsided.", - "sandstormDamageMessage": "{{pokemonPrefix}}{{pokemonName}} is buffeted\nby the sandstorm!", + "sandstormStartMessage": "¡Se ha desatado una tormenta de arena!", + "sandstormLapseMessage": "La tormenta de arena arrecia...", + "sandstormClearMessage": "La tormenta de arena termino.", + "sandstormDamageMessage": "¡La tormenta de arena zarandea al\n{{pokemonName}}{{pokemonPrefix}}!", - "hailStartMessage": "It started to hail!", - "hailLapseMessage": "Hail continues to fall.", - "hailClearMessage": "The hail stopped.", - "hailDamageMessage": "{{pokemonPrefix}}{{pokemonName}} is pelted\nby the hail!", + "hailStartMessage": "¡Ha empezado a granizar!", + "hailLapseMessage": "Sigue granizando...", + "hailClearMessage": "Had dejado de granizar.", + "hailDamageMessage": "El granizo golpea al\n{{pokemonName}}{{pokemonPrefix}}!", - "snowStartMessage": "It started to snow!", - "snowLapseMessage": "The snow is falling down.", - "snowClearMessage": "The snow stopped.", + "snowStartMessage": "¡Ha empezado a nevar!", + "snowLapseMessage": "Sigue nevando...", + "snowClearMessage": "Ha dejado de nevar.", - "fogStartMessage": "A thick fog emerged!", - "fogLapseMessage": "The fog continues.", - "fogClearMessage": "The fog disappeared.", + "fogStartMessage": "La niebla es densa...", + "fogLapseMessage": "Sigue la niebla...", + "fogClearMessage": "La niebla ha desaparecido.", - "heavyRainStartMessage": "A heavy downpour started!", - "heavyRainLapseMessage": "The heavy downpour continues.", - "heavyRainClearMessage": "The heavy rain stopped.", + "heavyRainStartMessage": "¡Ha empezado a diluviar!", + "heavyRainLapseMessage": "Sigue diluviando...", + "heavyRainClearMessage": "Ha dejado de diluviar.", - "harshSunStartMessage": "The sunlight got hot!", - "harshSunLapseMessage": "The sun is scorching hot.", - "harshSunClearMessage": "The harsh sunlight faded.", + "harshSunStartMessage": "¡El sol que hace ahora es realmente abrasador!", + "harshSunLapseMessage": "El sol sigue abrasando.", + "harshSunClearMessage": "El sol vuelve a brillar como siempre.", - "strongWindsStartMessage": "A heavy wind began!", - "strongWindsLapseMessage": "The wind blows intensely.", - "strongWindsClearMessage": "The heavy wind stopped." + "strongWindsStartMessage": "¡Comenzó un fuerte viento!", + "strongWindsLapseMessage": "El viento sopla intensamente.", + "strongWindsClearMessage": "El fuerte viento cesó." }; From 12bd22f2ca2204af125a4faab985c4d2b9017aea Mon Sep 17 00:00:00 2001 From: Frede Date: Mon, 3 Jun 2024 12:49:13 +0200 Subject: [PATCH 018/129] Added "Skip Seen Dialogues" option (#1431) * Added "Skip Dialogues" option (if at least 1 classic win) * Removed error sound and hide option instead when classic wins = 0 * Add skip dialogues option to Unlockables and show unlocked message on first classic win * Only skips seen dialogues, removed dialogue option from unlockables, seen dialogues get saved to local storage * oops * dont show charSprite when skipping a dialogue, small fixes --------- Co-authored-by: Frederik Hobein --- src/battle-scene.ts | 2 ++ src/phases.ts | 48 ++++++++++++++++++++++------------------ src/system/game-data.ts | 49 ++++++++++++++++++++++++++++++++++++----- src/system/settings.ts | 6 +++++ src/ui/ui.ts | 37 ++++++++++++++++++++++++++----- 5 files changed, 110 insertions(+), 32 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index a8e8d238ab6..e28dedccbd5 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -114,6 +114,8 @@ export default class BattleScene extends SceneBase { public experimentalSprites: boolean = false; public moveAnimations: boolean = true; public expGainsSpeed: integer = 0; + public skipSeenDialogues: boolean = false; + /** * Defines the experience gain display mode. * diff --git a/src/phases.ts b/src/phases.ts index b896c6f9484..67f33b5e031 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -959,14 +959,15 @@ export class EncounterPhase extends BattlePhase { if (!encounterMessages?.length) { doSummon(); } else { + let message: string; + this.scene.executeWithSeedOffset(() => message = Utils.randSeedItem(encounterMessages), this.scene.currentBattle.waveIndex); + const showDialogueAndSummon = () => { - let message: string; - this.scene.executeWithSeedOffset(() => message = Utils.randSeedItem(encounterMessages), this.scene.currentBattle.waveIndex); this.scene.ui.showDialogue(message, trainer.getName(TrainerSlot.NONE,true), null, () => { this.scene.charSprite.hide().then(() => this.scene.hideFieldOverlay(250).then(() => doSummon())); }); }; - if (this.scene.currentBattle.trainer.config.hasCharSprite) { + if (this.scene.currentBattle.trainer.config.hasCharSprite && !this.scene.ui.shouldSkipDialogue(message)) { this.scene.showFieldOverlay(500).then(() => this.scene.charSprite.showCharacter(trainer.getKey(), getCharVariantFromDialogue(encounterMessages[0])).then(() => showDialogueAndSummon())); } else { showDialogueAndSummon(); @@ -3789,21 +3790,18 @@ export class TrainerVictoryPhase extends BattlePhase { this.scene.ui.showText(i18next.t("battle:trainerDefeated", { trainerName: this.scene.currentBattle.trainer.getName(TrainerSlot.NONE, true) }), null, () => { const victoryMessages = this.scene.currentBattle.trainer.getVictoryMessages(); - const showMessage = () => { - let message: string; - this.scene.executeWithSeedOffset(() => message = Utils.randSeedItem(victoryMessages), this.scene.currentBattle.waveIndex); - const messagePages = message.split(/\$/g).map(m => m.trim()); + let message: string; + this.scene.executeWithSeedOffset(() => message = Utils.randSeedItem(victoryMessages), this.scene.currentBattle.waveIndex); - for (let p = messagePages.length - 1; p >= 0; p--) { - const originalFunc = showMessageOrEnd; - showMessageOrEnd = () => this.scene.ui.showDialogue(messagePages[p], this.scene.currentBattle.trainer.getName(), null, originalFunc); - } + const showMessage = () => { + const originalFunc = showMessageOrEnd; + showMessageOrEnd = () => this.scene.ui.showDialogue(message, this.scene.currentBattle.trainer.getName(), null, originalFunc); showMessageOrEnd(); }; let showMessageOrEnd = () => this.end(); if (victoryMessages?.length) { - if (this.scene.currentBattle.trainer.config.hasCharSprite) { + if (this.scene.currentBattle.trainer.config.hasCharSprite && !this.scene.ui.shouldSkipDialogue(message)) { const originalFunc = showMessageOrEnd; showMessageOrEnd = () => this.scene.charSprite.hide().then(() => this.scene.hideFieldOverlay(250).then(() => originalFunc())); this.scene.showFieldOverlay(500).then(() => this.scene.charSprite.showCharacter(this.scene.currentBattle.trainer.getKey(), getCharVariantFromDialogue(victoryMessages[0])).then(() => showMessage())); @@ -4014,19 +4012,27 @@ export class GameOverPhase extends BattlePhase { }; if (this.victory && this.scene.gameMode.isClassic) { - this.scene.ui.fadeIn(500).then(() => { - this.scene.charSprite.showCharacter(`rival_${this.scene.gameData.gender === PlayerGender.FEMALE ? "m" : "f"}`, getCharVariantFromDialogue(miscDialogue.ending[this.scene.gameData.gender === PlayerGender.FEMALE ? 0 : 1])).then(() => { - this.scene.ui.showDialogue(miscDialogue.ending[this.scene.gameData.gender === PlayerGender.FEMALE ? 0 : 1], this.scene.gameData.gender === PlayerGender.FEMALE ? trainerConfigs[TrainerType.RIVAL].name : trainerConfigs[TrainerType.RIVAL].nameFemale, null, () => { - this.scene.ui.fadeOut(500).then(() => { - this.scene.charSprite.hide().then(() => { - const endCardPhase = new EndCardPhase(this.scene); - this.scene.unshiftPhase(endCardPhase); - clear(endCardPhase); + const message = miscDialogue.ending[this.scene.gameData.gender === PlayerGender.FEMALE ? 0 : 1]; + + if (!this.scene.ui.shouldSkipDialogue(message)) { + this.scene.ui.fadeIn(500).then(() => { + this.scene.charSprite.showCharacter(`rival_${this.scene.gameData.gender === PlayerGender.FEMALE ? "m" : "f"}`, getCharVariantFromDialogue(miscDialogue.ending[this.scene.gameData.gender === PlayerGender.FEMALE ? 0 : 1])).then(() => { + this.scene.ui.showDialogue(message, this.scene.gameData.gender === PlayerGender.FEMALE ? trainerConfigs[TrainerType.RIVAL].name : trainerConfigs[TrainerType.RIVAL].nameFemale, null, () => { + this.scene.ui.fadeOut(500).then(() => { + this.scene.charSprite.hide().then(() => { + const endCardPhase = new EndCardPhase(this.scene); + this.scene.unshiftPhase(endCardPhase); + clear(endCardPhase); + }); }); }); }); }); - }); + } else { + const endCardPhase = new EndCardPhase(this.scene); + this.scene.unshiftPhase(endCardPhase); + clear(endCardPhase); + } } else { clear(); } diff --git a/src/system/game-data.ts b/src/system/game-data.ts index dc73634e70b..35b6bc1435c 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -40,7 +40,8 @@ export enum GameDataType { SYSTEM, SESSION, SETTINGS, - TUTORIALS + TUTORIALS, + SEEN_DIALOGUES } export enum PlayerGender { @@ -68,6 +69,8 @@ export function getDataTypeKey(dataType: GameDataType, slotId: integer = 0): str return "settings"; case GameDataType.TUTORIALS: return "tutorials"; + case GameDataType.SEEN_DIALOGUES: + return "seenDialogues"; } } @@ -201,6 +204,10 @@ export interface TutorialFlags { [key: string]: boolean } +export interface SeenDialogues { + [key: string]: boolean; +} + const systemShortKeys = { seenAttr: "$sa", caughtAttr: "$ca", @@ -716,9 +723,10 @@ export class GameData { } public saveTutorialFlag(tutorial: Tutorial, flag: boolean): boolean { + const key = getDataTypeKey(GameDataType.TUTORIALS); let tutorials: object = {}; - if (localStorage.hasOwnProperty("tutorials")) { - tutorials = JSON.parse(localStorage.getItem("tutorials")); + if (localStorage.hasOwnProperty(key)) { + tutorials = JSON.parse(localStorage.getItem(key)); } Object.keys(Tutorial).map(t => t as Tutorial).forEach(t => { @@ -730,20 +738,21 @@ export class GameData { } }); - localStorage.setItem("tutorials", JSON.stringify(tutorials)); + localStorage.setItem(key, JSON.stringify(tutorials)); return true; } public getTutorialFlags(): TutorialFlags { + const key = getDataTypeKey(GameDataType.TUTORIALS); const ret: TutorialFlags = {}; Object.values(Tutorial).map(tutorial => tutorial as Tutorial).forEach(tutorial => ret[Tutorial[tutorial]] = false); - if (!localStorage.hasOwnProperty("tutorials")) { + if (!localStorage.hasOwnProperty(key)) { return ret; } - const tutorials = JSON.parse(localStorage.getItem("tutorials")); + const tutorials = JSON.parse(localStorage.getItem(key)); for (const tutorial of Object.keys(tutorials)) { ret[tutorial] = tutorials[tutorial]; @@ -752,6 +761,34 @@ export class GameData { return ret; } + public saveSeenDialogue(dialogue: string): boolean { + const key = getDataTypeKey(GameDataType.SEEN_DIALOGUES); + const dialogues: object = this.getSeenDialogues(); + + dialogues[dialogue] = true; + localStorage.setItem(key, JSON.stringify(dialogues)); + console.log("Dialogue saved as seen:", dialogue); + + return true; + } + + public getSeenDialogues(): SeenDialogues { + const key = getDataTypeKey(GameDataType.SEEN_DIALOGUES); + const ret: SeenDialogues = {}; + + if (!localStorage.hasOwnProperty(key)) { + return ret; + } + + const dialogues = JSON.parse(localStorage.getItem(key)); + + for (const dialogue of Object.keys(dialogues)) { + ret[dialogue] = dialogues[dialogue]; + } + + return ret; + } + private getSessionSaveData(scene: BattleScene): SessionSaveData { return { seed: scene.seed, diff --git a/src/system/settings.ts b/src/system/settings.ts index f2a3548da4a..5f526376998 100644 --- a/src/system/settings.ts +++ b/src/system/settings.ts @@ -19,6 +19,7 @@ export enum Setting { Window_Type = "WINDOW_TYPE", Tutorials = "TUTORIALS", Enable_Retries = "ENABLE_RETRIES", + Skip_Seen_Dialogues = "SKIP_SEEN_DIALOGUES", Candy_Upgrade_Notification = "CANDY_UPGRADE_NOTIFICATION", Candy_Upgrade_Display = "CANDY_UPGRADE_DISPLAY", Money_Format = "MONEY_FORMAT", @@ -54,6 +55,7 @@ export const settingOptions: SettingOptions = { [Setting.Window_Type]: new Array(5).fill(null).map((_, i) => (i + 1).toString()), [Setting.Tutorials]: ["Off", "On"], [Setting.Enable_Retries]: ["Off", "On"], + [Setting.Skip_Seen_Dialogues]: ["Off", "On"], [Setting.Candy_Upgrade_Notification]: ["Off", "Passives Only", "On"], [Setting.Candy_Upgrade_Display]: ["Icon", "Animation"], [Setting.Money_Format]: ["Normal", "Abbreviated"], @@ -81,6 +83,7 @@ export const settingDefaults: SettingDefaults = { [Setting.Window_Type]: 0, [Setting.Tutorials]: 1, [Setting.Enable_Retries]: 0, + [Setting.Skip_Seen_Dialogues]: 0, [Setting.Candy_Upgrade_Notification]: 0, [Setting.Candy_Upgrade_Display]: 0, [Setting.Money_Format]: 0, @@ -198,6 +201,9 @@ export function setSetting(scene: BattleScene, setting: Setting, value: integer) case Setting.Vibration: scene.enableVibration = settingOptions[setting][value] !== "Disabled" && hasTouchscreen(); break; + case Setting.Skip_Seen_Dialogues: + scene.skipSeenDialogues = settingOptions[setting][value] === "On"; + break; case Setting.Language: if (value) { if (scene.ui) { diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 7092f16a57c..90cba657fbf 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -259,15 +259,26 @@ export default class UI extends Phaser.GameObjects.Container { } // Add the prefix to the text const localizationKey = playerGenderPrefix + text; + // Get localized dialogue (if available) + let hasi18n = false; if (i18next.exists(localizationKey as ParseKeys) ) { - - text = i18next.t(localizationKey as ParseKeys); + hasi18n = true; + + // Skip dialogue if the player has enabled the option and the dialogue has been already seen + if ((this.scene as BattleScene).skipSeenDialogues && (this.scene as BattleScene).gameData.getSeenDialogues()[localizationKey] === true) { + console.log(`Dialogue ${localizationKey} skipped`); + callback(); + return; + } } + let showMessageAndCallback = () => { + hasi18n && (this.scene as BattleScene).gameData.saveSeenDialogue(localizationKey); + callback(); + }; if (text.indexOf("$") > -1) { const messagePages = text.split(/\$/g).map(m => m.trim()); - let showMessageAndCallback = () => callback(); for (let p = messagePages.length - 1; p >= 0; p--) { const originalFunc = showMessageAndCallback; showMessageAndCallback = () => this.showDialogue(messagePages[p], name, null, originalFunc); @@ -276,13 +287,29 @@ export default class UI extends Phaser.GameObjects.Container { } else { const handler = this.getHandler(); if (handler instanceof MessageUiHandler) { - (handler as MessageUiHandler).showDialogue(text, name, delay, callback, callbackDelay, true, promptDelay); + (handler as MessageUiHandler).showDialogue(text, name, delay, showMessageAndCallback, callbackDelay, true, promptDelay); } else { - this.getMessageHandler().showDialogue(text, name, delay, callback, callbackDelay, true, promptDelay); + this.getMessageHandler().showDialogue(text, name, delay, showMessageAndCallback, callbackDelay, true, promptDelay); } } } + shouldSkipDialogue(text): boolean { + let playerGenderPrefix = "PGM"; + if ((this.scene as BattleScene).gameData.gender === PlayerGender.FEMALE) { + playerGenderPrefix = "PGF"; + } + + const key = playerGenderPrefix + text; + + if (i18next.exists(key as ParseKeys) ) { + if ((this.scene as BattleScene).skipSeenDialogues && (this.scene as BattleScene).gameData.getSeenDialogues()[key] === true) { + return true; + } + } + return false; + } + showTooltip(title: string, content: string, overlap?: boolean): void { this.tooltipContainer.setVisible(true); this.tooltipTitle.setText(title || ""); From 1abc591318c68c645a0e701b818f8a85279a5013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Ricardo=20Fleury=20Oliveira?= Date: Mon, 3 Jun 2024 11:30:27 -0300 Subject: [PATCH 019/129] [ptBR Translations] Dialogue, game-stats and more (#1559) * fixed translations and started dialogues * ptBR translations * fixes * minor fix * minor fixes 2 * firebreather * minor translations * minor translations --- src/locales/en/dialogue.ts | 8 +- src/locales/pt_BR/ability-trigger.ts | 2 +- src/locales/pt_BR/battle.ts | 6 +- src/locales/pt_BR/biome.ts | 4 +- src/locales/pt_BR/dialogue.ts | 2095 ++++++++++---------- src/locales/pt_BR/game-stats-ui-handler.ts | 80 +- src/locales/pt_BR/modifier-type.ts | 4 +- src/locales/pt_BR/move.ts | 4 +- src/locales/pt_BR/trainers.ts | 2 +- 9 files changed, 1096 insertions(+), 1109 deletions(-) diff --git a/src/locales/en/dialogue.ts b/src/locales/en/dialogue.ts index a89c90c97d6..7899ec21d36 100644 --- a/src/locales/en/dialogue.ts +++ b/src/locales/en/dialogue.ts @@ -1,4 +1,4 @@ -import {DialogueTranslationEntries, SimpleTranslationEntries} from "#app/plugins/i18n"; +import { DialogueTranslationEntries, SimpleTranslationEntries } from "#app/plugins/i18n"; // Dialogue of the NPCs in the game when the player character is male (or unset) export const PGMdialogue: DialogueTranslationEntries = { @@ -2398,12 +2398,12 @@ export const PGMdoubleBattleDialogue: DialogueTranslationEntries = { }, "steven_wallace_double": { "encounter": { - 1: `Steven: Do you have any rare pokémon? - $Wallace: Steven... We are here for a battle, not to show off our pokémon. + 1: `Steven: Do you have any rare Pokémon? + $Wallace: Steven... We are here for a battle, not to show off our Pokémon. $Steven: Oh... I see... Let's go then!`, }, "victory": { - 1: `Steven: Now that we are done with the battle, let's show off our pokémon! + 1: `Steven: Now that we are done with the battle, let's show off our Pokémon! $Wallace: Steven...`, }, }, diff --git a/src/locales/pt_BR/ability-trigger.ts b/src/locales/pt_BR/ability-trigger.ts index 6e7dd96646a..8c57fb5b98c 100644 --- a/src/locales/pt_BR/ability-trigger.ts +++ b/src/locales/pt_BR/ability-trigger.ts @@ -3,5 +3,5 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const abilityTriggers: SimpleTranslationEntries = { "blockRecoilDamage" : "{{abilityName}} de {{pokemonName}}\nprotegeu-o do dano de recuo!", "badDreams": "{{pokemonName}} está tendo pesadelos!", - "windPowerCharged": "Being hit by {{moveName}} charged {{pokemonName}} with power!" + "windPowerCharged": "Ser atingido por {{moveName}} carregou {{pokemonName}} com poder!" } as const; diff --git a/src/locales/pt_BR/battle.ts b/src/locales/pt_BR/battle.ts index b3563665d9e..f6f471e838a 100644 --- a/src/locales/pt_BR/battle.ts +++ b/src/locales/pt_BR/battle.ts @@ -48,7 +48,7 @@ export const battle: SimpleTranslationEntries = { "noEscapeForce": "Uma força misteriosa\nte impede de fugir.", "noEscapeTrainer": "Não se pode fugir de\nbatalhas contra treinadores!", "noEscapePokemon": "O movimento {{moveName}} de {{pokemonName}} te impede de fugir!", - "runAwaySuccess": "Você fugiu com sucesso", + "runAwaySuccess": "Você fugiu com sucesso.", "runAwayCannotEscape": "Você nao conseguiu fugir!", "escapeVerbSwitch": "trocar", "escapeVerbFlee": "fugir", @@ -56,6 +56,6 @@ export const battle: SimpleTranslationEntries = { "skipItemQuestion": "Tem certeza de que não quer escolher um item?", "eggHatching": "Opa?", "ivScannerUseQuestion": "Quer usar o Scanner de IVs em {{pokemonName}}?", - "drainMessage": "{{pokemonName}} had its\nenergy drained!", - "regainHealth": "{{pokemonName}} regained\nhealth!" + "drainMessage": "{{pokemonName}} teve sua\nenergia drenada!", + "regainHealth": "{{pokemonName}} recuperou\npontos de saúde!" } as const; diff --git a/src/locales/pt_BR/biome.ts b/src/locales/pt_BR/biome.ts index 4ae93a1defb..6ba9c7e47f1 100644 --- a/src/locales/pt_BR/biome.ts +++ b/src/locales/pt_BR/biome.ts @@ -4,8 +4,8 @@ export const biome: SimpleTranslationEntries = { "unknownLocation": "Em algum lugar do qual você não se lembra", "TOWN": "Cidade", "PLAINS": "Planície", - "GRASS": "Campo de Grama", - "TALL_GRASS": "Campo de Grama Alta", + "GRASS": "Grama", + "TALL_GRASS": "Grama Alta", "METROPOLIS": "Metrópole", "FOREST": "Floresta", "SEA": "Mar", diff --git a/src/locales/pt_BR/dialogue.ts b/src/locales/pt_BR/dialogue.ts index faabab3077d..cf5ff6ccf4f 100644 --- a/src/locales/pt_BR/dialogue.ts +++ b/src/locales/pt_BR/dialogue.ts @@ -1,325 +1,325 @@ -import {DialogueTranslationEntries, SimpleTranslationEntries} from "#app/plugins/i18n"; +import { DialogueTranslationEntries, SimpleTranslationEntries } from "#app/plugins/i18n"; -// Dialogue of the NPCs in the game when the player character is male (or unset) +// Diálogo dos NPCs no jogo quando o personagem do jogador é masculino (ou não definido) export const PGMdialogue: DialogueTranslationEntries = { "youngster": { "encounter": { - 1: "Hey, wanna battle?", - 2: "Are you a new trainer too?", - 3: "Hey, I haven't seen you before. Let's battle!", - 4: "I just lost, so I'm trying to find more Pokémon.\nWait! You look weak! Come on, let's battle!", - 5: "Have we met or not? I don't really remember. Well, I guess it's nice to meet you anyway!", - 6: "All right! Let's go!", - 7: "All right! Here I come! I'll show you my power!", - 8: "Haw haw haw... I'll show you how hawesome my Pokémon are!", - 9: "No need to waste time saying hello. Bring it on whenever you're ready!", - 10: "Don't let your guard down, or you may be crying when a kid beats you.", - 11: "I've raised my Pokémon with great care. You're not allowed to hurt them!", - 12: "Glad you made it! It won't be an easy job from here.", - 13: "The battles continue forever! Welcome to the world with no end!" + 1: "Eai, quer batalhar?", + 2: "Você também é um treinador novo?", + 3: "Eai, nunca te vi antes. Vamos batalhar!", + 4: "Acabei de perder, então estou tentando encontrar mais Pokémon.\nEspera! Você parece fraco! Vamos batalhar!", + 5: "A gente já se conheceu antes? Não lembro muito bem. Enfim, prazer te conhecer!", + 6: "Beleza! Vamos nessa!", + 7: "Beleza! Lá vou eu! Vou te mostrar meu poder!", + 8: "Hahaha... Vou te mostrar o quão incríveis são meus Pokémon!", + 9: "Sem perder tempo com cumprimentos. Vamos logo, quando estiver pronto!", + 10: "Não baixe a guarda, ou você pode acabar chorando quando uma criança te vencer.", + 11: "Eu criei meus Pokémon com muito cuidado. Você não tem permissão para machucá-los!", + 12: "Que bom que você chegou! Não vai ser fácil daqui pra frente.", + 13: "As batalhas continuam para sempre! Bem-vindo ao mundo sem fim!" }, "victory": { - 1: "Wow! You're strong!", - 2: "I didn't stand a chance, huh?", - 3: "I'll find you again when I'm older and beat you!", - 4: "Ugh. I don't have any more Pokémon.", - 5: "No way… NO WAY! How could I lose again…", - 6: "No! I lost!", - 7: "Whoa! You are incredible! I'm amazed and surprised!", - 8: "Could it be… How… My Pokémon and I are the strongest, though…", - 9: "I won't lose next time! Let's battle again sometime!", - 10: "Sheesh! Can't you see that I'm just a kid! It wasn't fair of you to go all out like that!", - 11: "Your Pokémon are more amazing! Trade with me!", - 12: "I got a little carried away earlier, but what job was I talking about?", - 13: "Ahaha! There it is! That's right! You're already right at home in this world!" + 1: "Uau! Você é forte!", + 2: "Eu não tive chance, né?", + 3: "Vou te encontrar de novo quando for mais velho e te vencer!", + 4: "Ugh. Não tenho mais Pokémon.", + 5: "Não acredito… NÃO ACREDITO! Como posso perder de novo…", + 6: "Não! Eu perdi!", + 7: "Whoa! Você é incrível! Estou surpreso!", + 8: "Pode ser… Como… Eu e meus Pokémon somos os mais fortes, porém…", + 9: "Não vou perder da próxima vez! Vamos batalhar de novo algum dia!", + 10: "Aff! Não vê que sou apenas uma criança? Não foi justo você ir com tudo!", + 11: "Seus Pokémon são incríveis! Troca comigo!", + 12: "Me empolguei um pouco antes, mas sobre qual trabalho eu estava falando?", + 13: "Ahaha! É isso aí! Você já está em casa nesse mundo!" } }, "lass": { "encounter": { - 1: "Let's have a battle, shall we?", - 2: "You look like a new trainer. Let's have a battle!", - 3: "I don't recognize you. How about a battle?", - 4: "Let's have a fun Pokémon battle!", - 5: "I'll show you the ropes of how to really use Pokémon!", - 6: "A serious battle starts from a serious beginning! Are you sure you're ready?", - 7: "You're only young once. And you only get one shot at a given battle. Soon, you'll be nothing but a memory.", - 8: "You'd better go easy on me, OK? Though I'll be seriously fighting!", - 9: "School is boring. I've got nothing to do. Yawn. I'm only battling to kill the time." + 1: "Vamos ter uma batalha, pode ser?", + 2: "Você parece um treinador novo. Vamos batalhar!", + 3: "Não te reconheço. Que tal uma batalha?", + 4: "Vamos ter uma batalha Pokémon divertida!", + 5: "Vou te mostrar como realmente usar Pokémon!", + 6: "Uma batalha séria começa com um começo sério! Tem certeza que está pronto?", + 7: "Você só é jovem uma vez. E só tem uma chance em cada batalha. Logo, você será apenas uma memória.", + 8: "Vai com calma comigo, tá? Mas vou lutar sério!", + 9: "A escola é chata. Não tenho nada para fazer. Yawn. Só estou batalhando para passar o tempo." }, "victory": { - 1: "That was impressive! I've got a lot to learn.", - 2: "I didn't think you'd beat me that bad…", - 3: "I hope we get to have a rematch some day.", - 4: "That was pretty amazingly fun! You've totally exhausted me…", - 5: "You actually taught me a lesson! You're pretty amazing!", - 6: "Seriously, I lost. That is, like, seriously depressing, but you were seriously cool.", - 7: "I don't need memories like this. Deleting memory…", - 8: "Hey! I told you to go easy on me! Still, you're pretty cool when you're serious.", - 9: "I'm actually getting tired of battling… There's gotta be something new to do…" + 1: "Isso foi impressionante! Tenho muito a aprender.", + 2: "Não pensei que você me venceria tão fácil…", + 3: "Espero que possamos ter uma revanche um dia.", + 4: "Isso foi incrivelmente divertido! Você me esgotou totalmente…", + 5: "Você realmente me ensinou uma lição! Você é incrível!", + 6: "Sério, eu perdi. Isso é, tipo, seriamente deprimente, mas você foi seriamente legal.", + 7: "Não preciso de memórias como essa. Deletando memória…", + 8: "Ei! Eu te disse para pegar leve comigo! Mesmo assim, você é legal quando fica sério.", + 9: "Estou realmente cansando de batalhar… Deve haver algo novo para fazer…" } }, "breeder": { "encounter": { - 1: "Obedient Pokémon, selfish Pokémon… Pokémon have unique characteristics.", - 2: "Even though my upbringing and behavior are poor, I've raised my Pokémon well.", - 3: "Hmm, do you discipline your Pokémon? Pampering them too much is no good.", + 1: "Pokémon obedientes, Pokémon egoístas… Pokémon têm características únicas.", + 2: "Embora minha criação e comportamento sejam pobres, criei meus Pokémon bem.", + 3: "Hmm, você disciplina seus Pokémon? Mimar demais não é bom." }, "victory": { - 1: "It is important to nurture and train each Pokémon's characteristics.", - 2: "Unlike my diabolical self, these are some good Pokémon.", - 3: "Too much praise can spoil both Pokémon and people.", + 1: "É importante nutrir e treinar as características de cada Pokémon.", + 2: "Ao contrário do meu lado diabólico, esses são bons Pokémon.", + 3: "Muito elogio pode estragar tanto Pokémon quanto pessoas." }, "defeat": { - 1: "You should not get angry at your Pokémon, even if you lose a battle.", - 2: "Right? Pretty good Pokémon, huh? I'm suited to raising things.", - 3: "No matter how much you love your Pokémon, you still have to discipline them when they misbehave." + 1: "Você não deve ficar com raiva dos seus Pokémon, mesmo se perder uma batalha.", + 2: "Certo? Pokémon bons, né? Eu sou adequado para criar coisas.", + 3: "Não importa o quanto você ame seus Pokémon, ainda precisa discipliná-los quando se comportam mal." } }, "breeder_female": { "encounter": { - 1: "Pokémon never betray you. They return all the love you give them.", - 2: "Shall I give you a tip for training good Pokémon?", - 3: "I have raised these very special Pokémon using a special method." + 1: "Pokémon nunca te traem. Eles retribuem todo o amor que você dá a eles.", + 2: "Quer uma dica para treinar bons Pokémon?", + 3: "Eu criei esses Pokémon muito especiais usando um método especial." }, "victory": { - 1: "Ugh… It wasn't supposed to be like this. Did I administer the wrong blend?", - 2: "How could that happen to my Pokémon… What are you feeding your Pokémon?", - 3: "If I lose, that tells you I was just killing time. It doesn't damage my ego at all." + 1: "Ugh… Não era para ser assim. Será que administrei a mistura errada?", + 2: "Como isso aconteceu com meus Pokémon… O que você está dando de comer aos seus Pokémon?", + 3: "Se eu perder, isso significa que eu estava só matando o tempo. Não machuca meu ego nem um pouco." }, "defeat": { - 1: "This proves my Pokémon have accepted my love.", - 2: "The real trick behind training good Pokémon is catching good Pokémon.", - 3: "Pokémon will be strong or weak depending on how you raise them." + 1: "Isso prova que meus Pokémon aceitaram meu amor.", + 2: "O verdadeiro truque para treinar bons Pokémon é capturar bons Pokémon.", + 3: "Pokémon serão fortes ou fracos dependendo de como você os cria." } }, "fisherman": { "encounter": { - 1: "Aack! You made me lose a bite!\nWhat are you going to do about it?", - 2: "Go away! You're scaring the Pokémon!", - 3: "Let's see if you can reel in a victory!", + 1: "Aack! Você me fez perder uma fisgada!\nO que vai fazer sobre isso?", + 2: "Saia daqui! Você está assustando os Pokémon!", + 3: "Vamos ver se você consegue fisgar uma vitória!", }, "victory": { - 1: "Just forget about it.", - 2: "Next time, I'll be reelin' in the triumph!", - 3: "Guess I underestimated the currents this time.", + 1: "Esqueça isso.", + 2: "Da próxima vez, eu vou pescar a vitória!", + 3: "Acho que subestimei as correntes dessa vez.", }, }, "fisherman_female": { "encounter": { - 1: "Woah! I've hooked a big one!", - 2: "Line's in, ready to reel in success!", - 3: "Ready to make waves!" + 1: "Uau! Peguei um grande!", + 2: "Linha lançada, pronta para pescar o sucesso!", + 3: "Pronta para fazer ondas!" }, "victory": { - 1: "I'll be back with a stronger hook.", - 2: "I'll reel in victory next time.", - 3: "I'm just sharpening my hooks for the comeback!" + 1: "Vou voltar com um anzol mais forte.", + 2: "Vou pescar a vitória na próxima vez.", + 3: "Estou só afiando meus anzóis para a revanche!" }, }, "swimmer": { "encounter": { - 1: "Time to dive in!", - 2: "Let's ride the waves of victory!", - 3: "Ready to make a splash!", + 1: "Hora de mergulhar!", + 2: "Vamos surfar nas ondas da vitória!", + 3: "Pronto para fazer um splash!", }, "victory": { - 1: "Drenched in defeat!", - 2: "A wave of defeat!", - 3: "Back to shore, I guess.", + 1: "Molhado na derrota!", + 2: "Uma onda de derrota!", + 3: "De volta à praia, eu acho.", }, }, "backpacker": { "encounter": { - 1: "Pack up, game on!", - 2: "Let's see if you can keep pace!", - 3: "Gear up, challenger!", - 4: "I've spent 20 years trying to find myself… But where am I?" + 1: "Prepare-se, vamos começar!", + 2: "Vamos ver se você consegue acompanhar!", + 3: "Prepare-se, desafiante!", + 4: "Passei 20 anos tentando me encontrar… Mas onde estou?" }, "victory": { - 1: "Tripped up this time!", - 2: "Oh, I think I'm lost.", - 3: "Dead end!", - 4: "Wait up a second! Hey! Don't you know who I am?" + 1: "Dessa vez tropecei!", + 2: "Ah, acho que estou perdido.", + 3: "Caminho sem saída!", + 4: "Espere um segundo! Ei! Você não sabe quem eu sou?" }, }, "ace_trainer": { "encounter": { - 1: "You seem quite confident.", - 2: "Your Pokémon… Show them to me…", - 3: "Because I'm an Ace Trainer, people think I'm strong.", - 4: "Are you aware of what it takes to be an Ace Trainer?" + 1: "Você parece bastante confiante.", + 2: "Seus Pokémon… Mostre-os para mim…", + 3: "Como sou um Treinador Ace, as pessoas acham que sou forte.", + 4: "Você sabe o que é preciso para ser um Treinador Ace?" }, "victory": { - 1: "Yes… You have good Pokémon…", - 2: "What?! But I'm a battling genius!", - 3: "Of course, you are the main character!", - 4: "OK! OK! You could be an Ace Trainer!" + 1: "Sim… Você tem bons Pokémon…", + 2: "O quê?! Mas sou um gênio das batalhas!", + 3: "Claro, você é o personagem principal!", + 4: "OK! OK! Você poderia ser um Treinador Ace!" }, "defeat": { - 1: "I am devoting my body and soul to Pokémon battles!", - 2: "All within my expectations… Nothing to be surprised about…", - 3: "I thought I'd grow up to be a frail person who looked like they would break if you squeezed them too hard.", - 4: "Of course I'm strong and don't lose. It's important that I win gracefully." + 1: "Estou dedicando corpo e alma às batalhas de Pokémon!", + 2: "Tudo dentro das minhas expectativas… Nada para se surpreender…", + 3: "Eu achava que cresceria para ser uma pessoa frágil que parecia que quebraria se você apertasse muito.", + 4: "Claro que sou forte e não perco. É importante ganhar com graça." } }, "parasol_lady": { "encounter": { - 1: "Time to grace the battlefield with elegance and poise!", + 1: "Hora de embelezar o campo de batalha com elegância e postura!", }, "victory": { - 1: "My elegance remains unbroken!", + 1: "Minha elegância permanece inabalável!", } }, "twins": { "encounter": { - 1: "Get ready, because when we team up, it's double the trouble!", - 2: "Two hearts, one strategy – let's see if you can keep up with our twin power!", - 3: "Hope you're ready for double trouble, because we're about to bring the heat!" + 1: "Prepare-se, porque quando nos unimos, é o dobro do problema!", + 2: "Dois corações, uma estratégia – vamos ver se você consegue acompanhar nosso poder de gêmeos!", + 3: "Espero que esteja pronto para o dobro do problema, porque estamos prestes a causar!" }, "victory": { - 1: "We may have lost this round, but our bond remains unbreakable!", - 2: "Our twin spirit won't be dimmed for long.", - 3: "We'll come back stronger as a dynamic duo!" + 1: "Podemos ter perdido essa rodada, mas nosso vínculo permanece inquebrável!", + 2: "Nosso espírito de gêmeos não será apagado por muito tempo.", + 3: "Voltaremos mais fortes como uma dupla dinâmica!" }, "defeat": { - 1: "Twin power reigns supreme!", - 2: "Two hearts, one triumph!", - 3: "Double the smiles, double the victory dance!" + 1: "O poder dos gêmeos reina supremo!", + 2: "Dois corações, um triunfo!", + 3: "Dobro de sorrisos, dobro da dança da vitória!" } }, "cyclist": { "encounter": { - 1: "Get ready to eat my dust!", - 2: "Gear up, challenger! I'm about to leave you in the dust!", - 3: "Pedal to the metal, let's see if you can keep pace!" + 1: "Prepare-se para comer poeira!", + 2: "Prepare-se, desafiante! Estou prestes a te deixar para trás!", + 3: "Pé no pedal, vamos ver se você consegue acompanhar!" }, "victory": { - 1: "Spokes may be still, but determination pedals on.", - 2: "Outpaced!", - 3: "The road to victory has many twists and turns yet to explore." + 1: "Os raios podem estar parados, mas a determinação continua a pedalar.", + 2: "Fui mais rápido!", + 3: "O caminho para a vitória tem muitas curvas e voltas para explorar." }, }, "black_belt": { "encounter": { - 1: "I praise your courage in challenging me! For I am the one with the strongest kick!", - 2: "Oh, I see. Would you like to be cut to pieces? Or do you prefer the role of punching bag?" + 1: "Elogio sua coragem ao me desafiar! Pois eu sou o que tem o chute mais forte!", + 2: "Oh, entendo. Você gostaria de ser cortado em pedaços? Ou prefere o papel de saco de pancadas?" }, "victory": { - 1: "Oh. The Pokémon did the fighting. My strong kick didn't help a bit.", - 2: "Hmmm… If I was going to lose anyway, I was hoping to get totally messed up in the process." + 1: "Oh. Os Pokémon fizeram a luta. Meu chute forte não ajudou em nada.", + 2: "Hmmm… Se eu ia perder de qualquer maneira, esperava ficar totalmente destruído no processo." }, }, "battle_girl": { "encounter": { - 1: "You don't have to try to impress me. You can lose against me.", + 1: "Você não precisa tentar me impressionar. Você pode perder contra mim.", }, "victory": { - 1: "It's hard to say good-bye, but we are running out of time…", + 1: "É difícil dizer adeus, mas estamos ficando sem tempo…", }, }, "hiker": { "encounter": { - 1: "My middle-age spread has given me as much gravitas as the mountains I hike!", - 2: "I inherited this big-boned body from my parents… I'm like a living mountain range…", + 1: "Minha barriga de meia-idade me deu tanta gravidade quanto as montanhas que eu escalo!", + 2: "Herdei esse corpo ossudo dos meus pais… Sou como uma cadeia de montanhas viva…", }, "victory": { - 1: "At least I cannot lose when it comes to BMI!", - 2: "It's not enough… It's never enough. My bad cholesterol isn't high enough…" + 1: "Pelo menos não posso perder quando se trata de IMC!", + 2: "Não é suficiente… Nunca é suficiente. Meu colesterol ruim não está alto o suficiente…" }, }, "ranger": { "encounter": { - 1: "When I am surrounded by nature, most other things cease to matter.", - 2: "When I'm living without nature in my life, sometimes I'll suddenly feel an anxiety attack coming on." + 1: "Quando estou cercado pela natureza, a maioria das outras coisas deixa de importar.", + 2: "Quando estou vivendo sem natureza na minha vida, às vezes sinto uma crise de ansiedade se aproximando." }, "victory": { - 1: "It doesn't matter to the vastness of nature whether I win or lose…", - 2: "Something like this is pretty trivial compared to the stifling feelings of city life." + 1: "Não importa para a vastidão da natureza se eu ganhar ou perder…", + 2: "Algo assim é bastante trivial comparado aos sentimentos sufocantes da vida na cidade." }, "defeat": { - 1: "I won the battle. But victory is nothing compared to the vastness of nature…", - 2: "I'm sure how you feel is not so bad if you compare it to my anxiety attacks…" + 1: "Ganhei a batalha. Mas a vitória não é nada comparada à vastidão da natureza…", + 2: "Tenho certeza de que como você se sente não é tão ruim se comparar aos meus ataques de ansiedade…" } }, "scientist": { "encounter": { - 1: "My research will lead this world to peace and joy.", + 1: "Minha pesquisa levará este mundo à paz e alegria.", }, "victory": { - 1: "I am a genius… I am not supposed to lose against someone like you…", + 1: "Sou um gênio… Não devo perder para alguém como você…", }, }, "school_kid": { "encounter": { - 1: "…Heehee. I'm confident in my calculations and analysis.", - 2: "I'm gaining as much experience as I can because I want to be a Gym Leader someday." + 1: "…Heehee. Estou confiante nos meus cálculos e análises.", + 2: "Estou ganhando o máximo de experiência que posso porque quero ser um Líder de Ginásio um dia." }, "victory": { - 1: "Ohhhh… Calculation and analysis are perhaps no match for chance…", - 2: "Even difficult, trying experiences have their purpose, I suppose." + 1: "Ohhhh… Cálculo e análise talvez não sejam páreo para o acaso…", + 2: "Até experiências difíceis e desafiadoras têm seu propósito, eu acho." } }, "artist": { "encounter": { - 1: "I used to be popular, but now I am all washed up.", + 1: "Eu costumava ser popular, mas agora estou acabado.", }, "victory": { - 1: "As times change, values also change. I realized that too late.", + 1: "À medida que os tempos mudam, os valores também mudam. Percebi isso tarde demais.", }, }, "guitarist": { "encounter": { - 1: "Get ready to feel the rhythm of defeat as I strum my way to victory!", + 1: "Prepare-se para sentir o ritmo da derrota enquanto eu toco minha vitória!", }, "victory": { - 1: "Silenced for now, but my melody of resilience will play on.", + 1: "Silenciado por agora, mas minha melodia de resiliência continuará a tocar.", }, }, "worker": { "encounter": { - 1: "It bothers me that people always misunderstand me. I'm a lot more pure than everyone thinks.", + 1: "Me incomoda que as pessoas sempre me entendam mal. Sou muito mais puro do que todos pensam.", }, "victory": { - 1: "I really don't want my skin to burn, so I want to stay in the shade while I work.", + 1: "Eu realmente não quero que minha pele queime, então quero ficar na sombra enquanto trabalho.", }, }, "worker_female": { "encounter": { - 1: `It bothers me that people always misunderstand me. - $I'm a lot more pure than everyone thinks.` + 1: `Me incomoda que as pessoas sempre me entendam mal. + $Sou muito mais pura do que todos pensam.` }, "victory": { - 1: "I really don't want my skin to burn, so I want to stay in the shade while I work." + 1: "Eu realmente não quero que minha pele queime, então quero ficar na sombra enquanto trabalho." }, "defeat": { - 1: "My body and mind aren't necessarily always in sync." + 1: "Meu corpo e mente nem sempre estão necessariamente em sincronia." } }, "worker_double": { "encounter": { - 1: "I'll show you we can break you. We've been training in the field!", + 1: "Vou te mostrar que podemos te quebrar. Estamos treinando no campo!", }, "victory": { - 1: "How strange… How could this be… I shouldn't have been outmuscled.", + 1: "Que estranho… Como isso pode ser… Não deveria ter sido superado.", }, }, "hex_maniac": { "encounter": { - 1: "I normally only ever listen to classical music, but if I lose, I think I shall try a bit of new age!", - 2: "I grow stronger with each tear I cry." + 1: "Normalmente, só escuto música clássica, mas se eu perder, acho que vou tentar um pouco de new age!", + 2: "Eu fico mais forte a cada lágrima que derramo." }, "victory": { - 1: "Is this the dawning of the age of Aquarius?", - 2: "Now I can get even stronger. I grow with every grudge." + 1: "É o início da era de Aquário?", + 2: "Agora posso ficar ainda mais forte. Cresço com cada rancor." }, "defeat": { - 1: "New age simply refers to twentieth century classical composers, right?", - 2: "Don't get hung up on sadness or frustration. You can use your grudges to motivate yourself." + 1: "New age se refere simplesmente aos compositores clássicos do século XX, certo?", + 2: "Não fique preso na tristeza ou frustração. Você pode usar seus rancores para se motivar." } }, "psychic": { "encounter": { - 1: "Hi! Focus!", + 1: "Oi! Concentre-se!", }, "victory": { 1: "Eeeeek!", @@ -327,967 +327,956 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "officer": { "encounter": { - 1: "Brace yourself, because justice is about to be served!", - 2: "Ready to uphold the law and serve justice on the battlefield!" + 1: "Prepare-se, porque a justiça está prestes a ser servida!", + 2: "Pronto para defender a lei e servir a justiça no campo de batalha!" }, "victory": { - 1: "The weight of justice feels heavier than ever…", - 2: "The shadows of defeat linger in the precinct." + 1: "O peso da justiça parece mais pesado do que nunca…", + 2: "As sombras da derrota pairam no distrito." } }, "beauty": { "encounter": { - 1: "My last ever battle… That's the way I'd like us to view this match…", + 1: "Minha última batalha… É assim que eu gostaria que víssemos esta partida…", }, "victory": { - 1: "It's been fun… Let's have another last battle again someday…", + 1: "Foi divertido… Vamos ter outra última batalha algum dia…", }, }, "baker": { "encounter": { - 1: "Hope you're ready to taste defeat!" + 1: "Espero que esteja pronto para saborear a derrota!" }, "victory": { - 1: "I'll bake a comeback." + 1: "Vou assar uma revanche." }, }, "biker": { "encounter": { - 1: "Time to rev up and leave you in the dust!" + 1: "Hora de acelerar e te deixar na poeira!" }, "victory": { - 1: "I'll tune up for the next race." + 1: "Vou me ajustar para a próxima corrida." }, }, "firebreather": { "encounter": { - 1: "My flames shall devour you!", - 2: "My soul is on fire. I'll show you how hot it burns!", - 3: "Step right up and take a look!" + 1: "Minhas chamas irão te consumir!", + 2: "Minha alma está pegando fogo. Irei te mostrar como queima!", + 3: "Cola aqui e dá uma olhada!" }, "victory": { - 1: "I burned down to ashes...", - 2: "Yow! That's hot!", - 3: "Ow! I scorched the tip of my nose!" + 1: "Fui reduzido a cinzas…", + 2: "Uau! Isso foi quente!", + 3: "Ai! Queimei minha língua!" }, }, "brock": { "encounter": { - 1: "My expertise on Rock-type Pokémon will take you down! Come on!", - 2: "My rock-hard willpower will overwhelm you!", - 3: "Allow me to show you the true strength of my Pokémon!" + 1: "Minha especialidade em Pokémon do tipo Pedra vai te derrubar! Vamos lá!", + 2: "Minha vontade firme como pedra vai te sobrecarregar!", + 3: "Permita-me mostrar a verdadeira força dos meus Pokémon!" }, "victory": { - 1: "Your Pokémon's strength have overcome my rock-hard defenses!", - 2: "The world is huge! I'm glad to have had a chance to battle you.", - 3: "Perhaps I should go back to pursuing my dream as a Pokémon Breeder…" + 1: "A força dos seus Pokémon superou minhas defesas de pedra!", + 2: "O mundo é enorme! Estou feliz por ter tido a chance de batalhar com você.", + 3: "Talvez eu deva voltar a perseguir meu sonho de ser Criador de Pokémon…" }, "defeat": { - 1: "The best offense is a good defense!\nThat's my way of doing things!", - 2: "Come study rocks with me next time to better learn how to fight them!", - 3: "Hah, all my traveling around the regions is paying off!" + 1: "A melhor defesa é um bom ataque!\nEssa é a minha maneira de fazer as coisas!", + 2: "Venha estudar rochas comigo da próxima vez para aprender melhor a combatê-las!", + 3: "Hah, todas as minhas viagens pelas regiões estão valendo a pena!" } }, "misty": { "encounter": { - 1: "My policy is an all out offensive with Water-type Pokémon!", - 2: "Hiya, I'll show you the strength of my aquatic Pokémon!", - 3: "My dream was to go on a journey and battle powerful trainers…\nWill you be a sufficient challenge?" + 1: "Minha política é um ataque total com Pokémon do tipo Água!", + 2: "Oi, vou mostrar a força dos meus Pokémon aquáticos!", + 3: "Meu sonho era viajar e batalhar com treinadores poderosos…\nVocê será um desafio suficiente?" }, "victory": { - 1: "You really are strong… I'll admit that you are skilled…", - 2: "Grrr… You know you just got lucky, right?!", - 3: "Wow, you're too much! I can't believe you beat me!" + 1: "Você realmente é forte… Vou admitir que você é habilidoso…", + 2: "Grrr… Você sabe que só teve sorte, certo?!", + 3: "Uau, você é demais! Não acredito que me venceu!" }, "defeat": { - 1: "Was the mighty Misty too much for you?", - 2: "I hope you saw my Pokémon's elegant swimming techniques!", - 3: "Your Pokémon were no match for my pride and joys!" + 1: "A poderosa Misty foi demais para você?", + 2: "Espero que você tenha visto as técnicas de natação elegantes dos meus Pokémon!", + 3: "Seus Pokémon não foram páreo para meus orgulhos e alegrias!" } }, "lt_surge": { "encounter": { - 1: "My Electric Pokémon saved me during the war! I'll show you how!", - 2: "Ten-hut! I'll shock you into surrender!", - 3: "I'll zap you just like I do to all my enemies in battle!" + 1: "Meus Pokémon Elétricos me salvaram durante a guerra! Vou te mostrar como!", + 2: "Em sentido! Vou te chocar até você se render!", + 3: "Vou te eletrizar como faço com todos os meus inimigos na batalha!" }, "victory": { - 1: "Whoa! Your team's the real deal, kid!", - 2: "Aaargh, you're strong! Even my electric tricks lost against you.", - 3: "That was an absolutely shocking loss!" + 1: "Whoa! Seu time é de verdade, garoto!", + 2: "Aaargh, você é forte! Até meus truques elétricos perderam para você.", + 3: "Isso foi uma derrota absolutamente chocante!" }, "defeat": { - 1: "Oh yeah! When it comes to Electric-type Pokémon, I'm number one in the world!", - 2: "Hahaha! That was an electrifying battle, kid!", - 3: "A Pokémon battle is war, and I have showed you first-hand combat!" + 1: "Oh sim! Quando se trata de Pokémon do tipo Elétrico, sou o número um do mundo!", + 2: "Hahaha! Foi uma batalha eletrizante, garoto!", + 3: "Uma batalha de Pokémon é guerra, e eu te mostrei combate em primeira mão!" } }, "erika": { "encounter": { - 1: "Ah, the weather is lovely here…\nOh, a battle? Very well then.", - 2: "My Pokémon battling skills rival that of my flower arranging skills.", - 3: "Oh, I hope the pleasant aroma of my Pokémon doesn't put me to sleep again…", - 4: "Seeing flowers in a garden is so soothing." + 1: "Ah, o tempo está adorável aqui…\nOh, uma batalha? Muito bem então.", + 2: "Minhas habilidades de batalha Pokémon rivalizam com minhas habilidades de arranjo de flores.", + 3: "Oh, espero que o aroma agradável dos meus Pokémon não me faça dormir de novo…", + 4: "Ver flores em um jardim é tão calmante." }, "victory": { - 1: "Oh! I concede defeat.", - 2: "That match was most delightful.", - 3: "Ah, it appears it is my loss…", - 4: "Oh, my goodness." + 1: "Oh! Eu concedo a derrota.", + 2: "Aquela partida foi muito agradável.", + 3: "Ah, parece que perdi…", + 4: "Oh, meu Deus." }, "defeat": { - 1: "I was afraid I would doze off…", - 2: "Oh my, it seems my Grass Pokémon overwhelmed you.", - 3: "That battle was such a soothing experience.", - 4: "Oh… Is that all?" + 1: "Tinha medo de adormecer…", + 2: "Oh, meu Deus, parece que meus Pokémon de Grama te dominaram.", + 3: "Essa batalha foi uma experiência tão calmante.", + 4: "Oh… É só isso?" } }, "janine": { "encounter": { - 1: "I am mastering the art of poisonous attacks.\nI shall spar with you today!", - 2: "Father trusts that I can hold my own.\nI will prove him right!", - 3: "My ninja techniques are only second to my Father's!\nCan you keep up?" + 1: "Estou dominando a arte dos ataques venenosos.\nVou lutar com você hoje!", + 2: "Meu pai confia que posso me defender.\nVou provar que ele está certo!", + 3: "Minhas técnicas de ninja só perdem para as do meu pai!\nVocê consegue acompanhar?" }, "victory": { - 1: "Even now, I still need training… I understand.", - 2: "Your battle technique has outmatched mine.", - 3: "I'm going to really apply myself and improve my skills." + 1: "Ainda preciso de treinamento… Entendi.", + 2: "Sua técnica de batalha superou a minha.", + 3: "Vou me aplicar de verdade e melhorar minhas habilidades." }, "defeat": { - 1: "Fufufu… the poison has sapped all your strength to battle.", - 2: "Ha! You didn't stand a chance against my superior ninja skills!", - 3: "Father's faith in me has proven to not be misplaced." + 1: "Fufufu… o veneno drenou todas as suas forças para lutar.", + 2: "Ha! Você não teve chance contra minhas habilidades superiores de ninja!", + 3: "A fé do meu pai em mim não foi mal colocada." } }, "sabrina": { "encounter": { - 1: "Through my psychic ability, I had a vision of your arrival!", - 2: "I dislike fighting, but if you wish, I will show you my powers!", - 3: "I can sense great ambition in you. I shall see if it not unfounded." + 1: "Através da minha habilidade psíquica, tive uma visão da sua chegada!", + 2: "Não gosto de lutar, mas se você quiser, vou mostrar meus poderes!", + 3: "Posso sentir grande ambição em você. Vou ver se não é infundada." }, "victory": { - 1: "Your power… It far exceeds what I foresaw…", - 2: "I failed to accurately predict your power.", - 3: "Even with my immense psychic powers, I cannot sense another as strong as you." + 1: "Seu poder… Ele supera o que eu previa…", + 2: "Não consegui prever seu poder com precisão.", + 3: "Mesmo com meus imensos poderes psíquicos, não consigo sentir outro tão forte quanto você." }, "defeat": { - 1: "This victory… It is exactly as I foresaw in my visions!", - 2: "Perhaps it was another I sensed a great desire in…", - 3: "Hone your abilities before recklessly charging into battle.\nYou never know what the future may hold if you do…" + 1: "Essa vitória… É exatamente como previ nas minhas visões!", + 2: "Talvez fosse outra pessoa que eu sentisse um grande desejo…", + 3: "Aprimore suas habilidades antes de entrar em batalha precipitadamente.\nVocê nunca sabe o que o futuro pode reservar se fizer isso…" } }, "blaine": { "encounter": { - 1: "Hah! Hope you brought a Burn Heal!", - 2: "My fiery Pokémon will incinerate all challengers!", - 3: "Get ready to play with fire!" + 1: "Hah! Espero que tenha trazido uma Cura de Queimadura!", + 2: "Meus Pokémon de Fogo vão incinerar todos os desafiantes!", + 3: "Prepare-se para brincar com fogo!" }, "victory": { - 1: "I have burned down to nothing! Not even ashes remain!", - 2: "Didn't I stoke the flames high enough?", - 3: "I'm all burned out… But this makes my motivation to improve burn even hotter!" + 1: "Queimei até não restar nada! Nem cinzas sobraram!", + 2: "Não acendi as chamas alto o suficiente?", + 3: "Estou completamente exausto… Mas isso faz minha motivação para melhorar queimar ainda mais!" }, "defeat": { - 1: "My raging inferno cannot be quelled!", - 2: "My Pokémon have been powered up with the heat from this victory!", - 3: "Hah! My passion burns brighter than yours!" + 1: "Meu inferno ardente não pode ser apagado!", + 2: "Meus Pokémon foram fortalecidos com o calor desta vitória!", + 3: "Hah! Minha paixão queima mais do que a sua!" } }, "giovanni": { "encounter": { - 1: "I, the leader of Team Rocket, will make you feel a world of pain!", - 2: "My training here will be vital before I am to face my old associates again.", - 3: "I do not think you are prepared for the level of failure you are about to experience!" + 1: "Eu, o líder da Equipe Rocket, vou te fazer sentir um mundo de dor!", + 2: "Meu treinamento aqui será vital antes de enfrentar meus antigos associados novamente.", + 3: "Não acho que você está preparado para o nível de fracasso que está prestes a experimentar!" }, "victory": { - 1: "WHAT! Me, lose?! There is nothing I wish to say to you!", - 2: "Hmph… You could never understand what I hope to achieve.", - 3: "This defeat is merely delaying the inevitable.\nI will rise Team Rocket from the ashes in due time." + 1: "O QUE! Eu, perder?! Não tenho nada a dizer a você!", + 2: "Hmph… Você nunca poderia entender o que espero alcançar.", + 3: "Esta derrota está apenas adiando o inevitável.\nVou ressurgir a Equipe Rocket das cinzas a tempo." }, "defeat": { - 1: "Not being able to measure your own strength shows that you are still but a child.", - 2: "Do not try to interfere with me again.", - 3: "I hope you understand how foolish challenging me was." + 1: "Não ser capaz de medir sua própria força mostra que você ainda é apenas uma criança.", + 2: "Não tente interferir comigo novamente.", + 3: "Espero que entenda o quão tolo foi me desafiar." } }, "roxanne": { "encounter": { - 1: "Would you kindly demonstrate how you battle?", - 2: "You can learn many things by battling many trainers.", - 3: "Oh, you caught me strategizing.\nWould you like to battle?" + 1: "Você poderia gentilmente demonstrar como batalha?", + 2: "Você pode aprender muitas coisas batalhando com muitos treinadores.", + 3: "Oh, você me pegou estrategizando.\nGostaria de batalhar?" }, "victory": { - 1: "Oh, I appear to have lost.\nI understand.", - 2: "It seems that I still have so much more to learn when it comes to battle.", - 3: "I'll take what I learned here today to heart." + 1: "Oh, parece que perdi.\nEu entendo.", + 2: "Parece que ainda tenho muito mais a aprender quando se trata de batalhas.", + 3: "Vou levar o que aprendi aqui hoje a sério." }, "defeat": { - 1: "I have learned many things from our battle.\nI hope you have too.", - 2: "I look forward to battling you again.\nI hope you'll use what you've learned here.", - 3: "I won due to everything I have learned." + 1: "Aprendi muitas coisas com nossa batalha.\nEspero que você também tenha aprendido.", + 2: "Espero batalhar com você novamente.\nEspero que use o que aprendeu aqui.", + 3: "Venci devido a tudo o que aprendi." } }, "brawly": { "encounter": { - 1: "Oh man, a challenger!\nLet's see what you can do!", - 2: "You seem like a big splash.\nLet's battle!", - 3: "Time to create a storm!\nLet's go!" + 1: "Oh cara, um desafiante!\nVamos ver o que você pode fazer!", + 2: "Você parece um grande onda.\nVamos batalhar!", + 3: "Hora de criar uma tempestade!\nVamos!" }, "victory": { - 1: "Oh woah, you've washed me out!", - 2: "You surfed my wave and crashed me down!", - 3: "I feel like I'm lost in Granite Cave!" + 1: "Oh woah, você me derrotou!", + 2: "Você surfou minha onda e me derrubou!", + 3: "Sinto-me perdido na Caverna Granito!" }, "defeat": { - 1: "Haha, I surfed the big wave!\nChallenge me again sometime.", - 2: "Surf with me again some time!", - 3: "Just like the tides come in and out, I hope you return to challenge me again." + 1: "Haha, eu surfei a grande onda!\nDesafie-me novamente algum dia.", + 2: "Surfe comigo novamente algum dia!", + 3: "Assim como as marés vão e vêm, espero que você volte para me desafiar novamente." } }, "wattson": { "encounter": { - 1: "Time to get shocked!\nWahahahaha!", - 2: "I'll make sparks fly!\nWahahahaha!", - 3: "I hope you brought Paralyz Heal!\nWahahahaha!" + 1: "Hora de levar um choque!\nWahahahaha!", + 2: "Vou fazer faíscas voarem!\nWahahahaha!", + 3: "Espero que tenha trazido Cura de Paralisia!\nWahahahaha!" }, "victory": { - 1: "Seems like I'm out of charge!\nWahahahaha!", - 2: "You've completely grounded me!\nWahahahaha!", - 3: "Thanks for the thrill!\nWahahahaha!" + 1: "Parece que estou sem carga!\nWahahahaha!", + 2: "Você me aterrissou completamente!\nWahahahaha!", + 3: "Obrigado pela emoção!\nWahahahaha!" }, "defeat": { - 1: "Recharge your batteries and challenge me again sometime!\nWahahahaha!", - 2: "I hope you found our battle electrifying!\nWahahahaha!", - 3: "Aren't you shocked I won?\nWahahahaha!" + 1: "Você está totalmente carregado agora!\nWahahahaha!", + 2: "Espero ver você faíscando em batalhas futuras!\nWahahahaha!", + 3: "Wahahahaha! Que batalha eletrizante!" } }, "flannery": { "encounter": { - 1: "Nice to meet you! Wait, no…\nI will crush you!", - 2: "I've only been a leader for a little while, but I'll smoke you!", - 3: "It's time to demonstrate the moves my grandfather has taught me! Let's battle!" + 1: "Meus Pokémon de fogo estão prontos para queimar a concorrência!\nVamos nessa!", + 2: "Prepare-se para sentir o calor da minha determinação!\nNão vou segurar nada!", + 3: "Minhas habilidades vão incinerar você!\nPrepare-se para a batalha mais quente da sua vida!" }, "victory": { - 1: "You remind me of my grandfather…\nNo wonder I lost.", - 2: "Am I trying too hard?\nI should relax, can't get too heated.", - 3: "Losing isn't going to smother me out.\nTime to reignite training!" + 1: "Essa derrota só faz minha determinação queimar mais!", + 2: "Essa perda não apagará minhas chamas!\nEstarei de volta mais forte!", + 3: "Vou usar essa experiência para reacender meu espírito competitivo!" }, "defeat": { - 1: "I hope I've made my grandfather proud…\nLet's battle again some time.", - 2: "I…I can't believe I won!\nDoing things my way worked!", - 3: "Let's exchange burning hot moves again soon!" + 1: "Minhas chamas nunca se apagarão!\nSou muito apaixonada por isso!", + 2: "Você foi incrível!\nVamos fazer isso de novo algum dia!", + 3: "Que batalha ardente!\nMal posso esperar pela próxima!" } }, "norman": { "encounter": { - 1: "I'm surprised you managed to get here.\nLet's battle.", - 2: "I'll do everything in my power as a Gym Leader to win.\nLet's go!", - 3: "You better give this your all.\nIt's time to battle!" + 1: "Você está pronto para enfrentar a força pura do meu time?\nVou te mostrar o poder do equilíbrio!", + 2: "Minha experiência em batalha vai fazer você suar!\nPrepare-se!", + 3: "Treinei meu time rigorosamente.\nVamos ver se você consegue igualar!" }, "victory": { - 1: "I lost to you…?\nRules are rules, though.", - 2: "Was moving from Olivine a mistake…?", - 3: "I can't believe it.\nThat was a great match." + 1: "Parece que subestimei você.\nFoi uma batalha dura.", + 2: "Você é forte, mas ainda há muito para aprender.", + 3: "Essa derrota não abalará minha determinação.\nEstarei de volta mais forte!" }, "defeat": { - 1: "We both tried our best.\nI hope we can battle again soon.", - 2: "You should try challenging my kid instead.\nYou might learn something!", - 3: "Thank you for the excellent battle.\nBetter luck next time." + 1: "Você lutou bravamente!\nEspero batalhar com você novamente.", + 2: "Sua força é incrível!\nNão posso esperar pela nossa próxima batalha.", + 3: "Foi uma honra batalhar com você!\nAté a próxima!" } }, "winona": { "encounter": { - 1: "I've been soaring the skies looking for prey…\nAnd you're my target!", - 2: "No matter how our battle is, my Flying Pokémon and I will triumph with grace. Let's battle!", - 3: "I hope you aren't scared of heights.\nLet's ascend!" + 1: "Tenho sobrevoado os céus em busca de presas...\nE você é meu alvo!", + 2: "Não importa como será nossa batalha, meus Pokémon Voadores e eu triunfaremos com graça. Vamos batalhar!", + 3: "Espero que você não tenha medo de altura.\nVamos subir!" }, "victory": { - 1: "You're the first Trainer I've seen with more grace than I.\nExcellently played.", - 2: "Oh, my Flying Pokémon have plummeted!\nVery well.", - 3: "Though I may have fallen, my Pokémon will continue to fly!" + 1: "Você é o primeiro Treinador que vejo com mais graça do que eu.\nJogou excelentemente.", + 2: "Oh, meus Pokémon Voadores despencaram!\nMuito bem.", + 3: "Embora eu tenha caído, meus Pokémon continuarão a voar!" }, "defeat": { - 1: "My Flying Pokémon and I will forever dance elegantly!", - 2: "I hope you enjoyed our show.\nOur graceful dance is finished.", - 3: "Won't you come see our elegant choreography again?" + 1: "Meus Pokémon Voadores e eu sempre dançaremos com elegância!", + 2: "Espero que tenha gostado do nosso show.\nNossa dança graciosa terminou.", + 3: "Você não quer ver nossa coreografia elegante novamente?" } }, "tate": { "encounter": { - 1: "Hehehe…\nWere you surprised to see me without my sister?", - 2: "I can see what you're thinking…\nYou want to battle!", - 3: "How can you defeat someone…\nWho knows your every move?" + 1: "Hehehe... Ficou surpreso de me ver sem minha irmã?", + 2: "Posso ver o que você está pensando...\nVocê quer batalhar!", + 3: "Como você pode derrotar alguém...\nQue sabe todos os seus movimentos?" }, "victory": { - 1: "It can't be helped…\nI miss Liza…", - 2: "Your bond with your Pokémon was stronger than mine.", - 3: "If I were with Liza, we would have won.\nWe can finish each other's thoughts!" + 1: "Não pode ser ajudado...\nSinto falta da Liza...", + 2: "Seu vínculo com seus Pokémon era mais forte que o meu.", + 3: "Se eu estivesse com Liza, teríamos vencido.\nConseguimos completar os pensamentos um do outro!" }, "defeat": { - 1: "My Pokémon and I are superior!", - 2: "If you can't even defeat me, you'll never be able to defeat Liza either.", - 3: "It's all thanks to my strict training with Liza.\nI can make myself one with Pokémon." + 1: "Meus Pokémon e eu somos superiores!", + 2: "Se você não consegue nem me derrotar, nunca será capaz de derrotar Liza também.", + 3: "Tudo graças ao meu treinamento rigoroso com Liza.\nPosso me tornar um com os Pokémon." } }, "liza": { "encounter": { - 1: "Fufufu…\nWere you surprised to see me without my brother?", - 2: "I can determine what you desire…\nYou want to battle, don't you?", - 3: "How can you defeat someone…\nWho's one with their Pokémon?" + 1: "Fufufu... Ficou surpreso de me ver sem meu irmão?", + 2: "Posso determinar o que você deseja...\nVocê quer batalhar, não quer?", + 3: "Como você pode derrotar alguém...\nQue é um com seus Pokémon?" }, "victory": { - 1: "It can't be helped…\nI miss Tate…", - 2: "Your bond with your Pokémon…\nIt's stronger than mine.", - 3: "If I were with Tate, we would have won.\nWe can finish each other's sentences!" + 1: "Não pode ser ajudado...\nSinto falta do Tate...", + 2: "Seu vínculo com seus Pokémon...\nÉ mais forte que o meu.", + 3: "Se eu estivesse com Tate, teríamos vencido.\nPodemos terminar as frases um do outro!" }, "defeat": { - 1: "My Pokémon and I are victorious.", - 2: "If you can't even defeat me, you'll never be able to defeat Tate either.", - 3: "It's all thanks to my strict training with Tate.\nI can synchronize myself with my Pokémon." + 1: "Meus Pokémon e eu somos vitoriosos.", + 2: "Se você não consegue nem me derrotar, nunca será capaz de derrotar Tate também.", + 3: "Tudo graças ao meu treinamento rigoroso com Tate.\nPosso me sincronizar com meus Pokémon." } }, "juan": { "encounter": { - 1: "Now's not the time to act coy.\nLet's battle!", - 2: "Ahahaha, You'll be witness to my artistry with Water Pokémon!", - 3: "A typhoon approaches!\nWill you be able to test me?", - 4: "Please, you shall bear witness to our artistry.\nA grand illusion of water sculpted by my Pokémon and myself!" + 1: "Agora não é hora de agir timidamente.\nVamos batalhar!", + 2: "Ahahaha, você será testemunha da minha arte com Pokémon de Água!", + 3: "Um tufão se aproxima!\nVocê será capaz de me testar?", + 4: "Por favor, você será testemunha da nossa arte.\nUma grande ilusão de água esculpida por meus Pokémon e por mim!" }, "victory": { - 1: "You may be a genius who can take on Wallace!", - 2: "I focused on elegance while you trained.\nIt's only natural that you defeated me.", - 3: "Ahahaha!\nVery well, You have won this time.", - 4: "From you, I sense the brilliant shine of skill that will overcome all." + 1: "Você pode ser um gênio que pode enfrentar Wallace!", + 2: "Eu me concentrei na elegância enquanto você treinava.\nÉ natural que você me derrotasse.", + 3: "Ahahaha!\nMuito bem, você venceu desta vez.", + 4: "De você, sinto o brilho brilhante da habilidade que superará tudo." }, "defeat": { - 1: "My Pokémon and I have sculpted an illusion of Water and come out victorious.", - 2: "Ahahaha, I have won, and you have lost.", - 3: "Shall I loan you my outfit? It may help you battle!\nAhahaha, I jest!", - 4: "I'm the winner! Which is to say, you lost." + 1: "Meus Pokémon e eu esculpimos uma ilusão de Água e saímos vitoriosos.", + 2: "Ahahaha, eu venci, e você perdeu.", + 3: "Posso emprestar meu traje? Pode te ajudar a batalhar!\nAhahaha, estou brincando!", + 4: "Eu sou o vencedor! O que quer dizer, você perdeu." } }, "crasher_wake": { "encounter": { - 1: "Crash! Crash! Watch out!\nCrasher Wake…is…heeere!", + 1: "Crash! Crash! Cuidado!\nCrasher Wake… está… aqui!", 2: "Crash! Crash! Crasher Wake!", - 3: "I'm the tidal wave of power to wash you away!" + 3: "Sou a onda de poder que vai te lavar!" }, "victory": { - 1: "That puts a grin on my face!\nGuhahaha! That was a blast!", - 2: "Hunwah! It's gone and ended!\nHow will I say this…\nI want more! I wanted to battle a lot more!", - 3: "WHAAAAT!?" + 1: "Isso coloca um sorriso no meu rosto!\nGuhahaha! Foi uma explosão!", + 2: "Hunwah! Acabou e terminou!\nComo vou dizer isso...\nQuero mais! Queria batalhar muito mais!", + 3: "O QUÊ?!" }, "defeat": { - 1: "Yeeeeah! That's right!", - 2: "I won, but I want more! I wanted to battle a lot more!", - 3: "So long!" + 1: "Siiiiim! Isso mesmo!", + 2: "Eu venci, mas quero mais! Queria batalhar muito mais!", + 3: "Até logo!" } }, "falkner": { "encounter": { - 1: "I'll show you the real power of the magnificent bird Pokémon!", - 2: "Winds, stay with me!", - 3: "Dad! I hope you're watching me battle from above!" + 1: "Vou mostrar o verdadeiro poder dos magníficos Pokémon pássaros!", + 2: "Ventos, fiquem comigo!", + 3: "Pai! Espero que esteja vendo minha batalha de cima!" }, "victory": { - 1: "I understand… I'll bow out gracefully.", - 2: "A defeat is a defeat. You are strong indeed.", - 3: "…Shoot! Yeah, I lost." + 1: "Eu entendo... Vou sair graciosamente.", + 2: "Uma derrota é uma derrota. Você é realmente forte.", + 3: "...Droga! Sim, eu perdi." }, "defeat": { - 1: "Dad! I won with your cherished bird Pokémon…", - 2: "Bird Pokémon are the best after all!", - 3: "Feels like I'm catching up to my dad!" + 1: "Pai! Venci com seus amados Pokémon pássaros...", + 2: "Pokémon pássaros são os melhores afinal!", + 3: "Sinto que estou alcançando meu pai!" } }, "nessa": { "encounter": { - 1: "No matter what kind of plan your refined mind may be plotting, my partner and I will be sure to sink it.", - 2: "I'm not here to chat. I'm here to win!", - 3: "This is a little gift from my Pokémon… I hope you can take it!" + 1: "Não importa que tipo de plano sua mente refinada possa estar tramando, meu parceiro e eu vamos afundá-lo.", + 2: "Não estou aqui para conversar. Estou aqui para vencer!", + 3: "Este é um pequeno presente dos meus Pokémon... Espero que você possa recebê-lo!" }, "victory": { - 1: "You and your Pokémon are just too much…", - 2: "How…? How can this be?!", - 3: "I was totally washed away!" + 1: "Você e seus Pokémon são demais...", + 2: "Como...? Como isso pode ser?!", + 3: "Fui totalmente arrastada!" }, "defeat": { - 1: "The raging wave crashes again!", - 2: "Time to ride the wave of victory!", + 1: "A onda furiosa ataca novamente!", + 2: "Hora de surfar na onda da vitória!", 3: "Ehehe!" } }, "melony": { "encounter": { - 1: "I'm not going to hold back!", - 2: "All righty, I suppose we should get started.", - 3: "I'll freeze you solid!" + 1: "Não vou me segurar!", + 2: "Tudo bem, acho que devemos começar.", + 3: "Vou congelar você completamente!" }, "victory": { - 1: "You… You're pretty good, huh?", - 2: "If you find Gordie around, be sure to give him a right trashing, would you?", - 3: "I think you took breaking the ice a little too literally…" + 1: "Você... Você é muito bom, hein?", + 2: "Se você encontrar Gordie por aí, certifique-se de dar uma boa surra nele, ok?", + 3: "Acho que você levou a quebra de gelo um pouco literalmente demais..." }, "defeat": { - 1: "Now do you see how severe battles can be?", - 2: "Hee! Looks like I went and won again!", - 3: "Are you holding back?" + 1: "Agora você vê como as batalhas podem ser severas?", + 2: "Hee! Parece que ganhei de novo!", + 3: "Você está segurando?" } }, "marlon": { "encounter": { - 1: "You look strong! Shoots! Let's start!", - 2: "I'm strong like the ocean's wide. You're gonna get swept away, fo' sho'.", - 3: "Oh ho, so I'm facing you! That's off the wall." + 1: "Você parece forte! Vamos começar!", + 2: "Sou forte como a amplitude do oceano. Você vai ser varrido, com certeza.", + 3: "Oh ho, então estou enfrentando você! Isso é fora do comum." }, "victory": { - 1: "You totally rocked that! You're raising some wicked Pokémon. You got this Trainer thing down!", - 2: "You don't just look strong, you're strong fo' reals! Eh, I was swept away, too!", - 3: "You're strong as a gnarly wave!" + 1: "Você foi incrível! Está criando alguns Pokémon incríveis. Você dominou a coisa de Treinador!", + 2: "Você não apenas parece forte, você é forte de verdade! Eh, eu também fui varrido!", + 3: "Você é forte como uma onda impressionante!" }, "defeat": { - 1: "You're tough, but it's not enough to sway the sea, 'K!", - 2: "Hee! Looks like I went and won again!", - 3: "Sweet, sweet victory!" + 1: "Você é forte, mas não é o suficiente para mudar o mar, ok!", + 2: "Hee! Parece que ganhei de novo!", + 3: "Doce, doce vitória!" } }, "shauntal": { "encounter": { - 1: "Excuse me. You're a challenger, right?\nI'm the Elite Four's Ghost-type Pokémon user, Shauntal, and I shall be your opponent.", - 2: "I absolutely love writing about Trainers who come here and the Pokémon they train.\nCould I use you and your Pokémon as a subject?", - 3: "Every person who works with Pokémon has a story to tell.\nWhat story is about to be told?" + 1: "Com licença. Você é um desafiante, certo?\nSou a usuária de Pokémon do tipo Fantasma da Elite Four, Shauntal, e serei sua oponente.", + 2: "Adoro escrever sobre Treinadores que vêm aqui e os Pokémon que treinam.\nPosso usar você e seus Pokémon como tema?", + 3: "Cada pessoa que trabalha com Pokémon tem uma história para contar.\nQue história está prestes a ser contada?" }, "victory": { - 1: "Wow. I'm dumbstruck!", - 2: "S-sorry! First, I must apologize to my Pokémon…\n\nI'm really sorry you had a bad experience because of me!", - 3: "Even in light of that, I'm still one of the Elite Four!" + 1: "Uau. Estou sem palavras!", + 2: "D-desculpe! Primeiro, preciso me desculpar com meus Pokémon...\n\nLamento muito que você tenha tido uma experiência ruim por minha causa!", + 3: "Mesmo com isso, ainda sou uma das Elite Four!" }, "defeat": { 1: "Eheh.", - 2: "That gave me excellent material for my next novel!", - 3: "And so, another tale ends…" + 2: "Isso me deu um excelente material para meu próximo romance!", + 3: "E assim, outra história termina..." } }, "marshal": { "encounter": { - 1: "My mentor, Alder, sees your potential as a Trainer and is taking an interest in you.\nIt is my intention to test you--to take you to the limits of your strength. Kiai!", - 2: "Victory, decisive victory, is my intention! Challenger, here I come!", - 3: "In myself, I seek to develop the strength of a fighter and shatter any weakness in myself!\nPrevailing with the force of my convictions!" + 1: "Meu mentor, Alder, vê seu potencial como Treinador e está interessado em você.\nMeu objetivo é testá-lo—levar você aos limites da sua força. Kiai!", + 2: "Vitória, vitória decisiva, é meu objetivo! Desafiante, aqui vou eu!", + 3: "Em mim mesmo, procuro desenvolver a força de um lutador e eliminar qualquer fraqueza em mim!\nPrevalecendo com a força de minhas convicções!" }, "victory": { - 1: "Whew! Well done!", - 2: "As your battles continue, aim for even greater heights!", - 3: "The strength shown by you and your Pokémon has deeply impressed me…" + 1: "Ufa! Bem feito!", + 2: "À medida que suas batalhas continuarem, mire em alturas ainda maiores!", + 3: "A força demonstrada por você e seus Pokémon me impressionou profundamente..." }, "defeat": { 1: "Hmm.", - 2: "That was good battle.", + 2: "Isso foi uma boa batalha.", 3: "Haaah! Haaah! Haiyaaaah!" } }, "cheren": { "encounter": { - 1: "You remind me of an old friend. That makes me excited about this Pokémon battle!", - 2: `Pokémon battles have no meaning if you don't think why you battle. - $Or better said, it makes battling together with Pokémon meaningless.`, - 3: "My name's Cheren! I'm a Gym Leader and a teacher! Pleasure to meet you." + 1: "Você me lembra um velho amigo. Isso me deixa animado para essa batalha Pokémon!", + 2: "As batalhas Pokémon não têm sentido se você não pensa por que você batalha.\nOu melhor, isso torna as batalhas junto com Pokémon sem sentido.", + 3: "Meu nome é Cheren! Sou um Líder de Ginásio e professor! Prazer em conhecê-lo." }, "victory": { - 1: "Thank you! I saw what was missing in me.", - 2: "Thank you! I feel like I saw a little of the way toward my ideals.", - 3: "Hmm… This is problematic." + 1: "Obrigado! Vi o que estava faltando em mim.", + 2: "Obrigado! Sinto que vi um pouco do caminho em direção aos meus ideais.", + 3: "Hmm... Isso é problemático." }, "defeat": { - 1: "As a Gym Leader, I aim to be a wall for you to overcome.", - 2: "All right!", - 3: "I made it where I am because Pokémon were by my side.\nPerhaps we need to think about why Pokémon help us not in terms of Pokémon and Trainers but as a relationship between living beings." + 1: "Como Líder de Ginásio, meu objetivo é ser um obstáculo para você superar.", + 2: "Tudo bem!", + 3: "Cheguei onde estou porque os Pokémon estavam ao meu lado.\nTalvez precisemos pensar por que os Pokémon nos ajudam, não em termos de Pokémon e Treinadores, mas como uma relação entre seres vivos." } }, "chili": { "encounter": { - 1: "Yeeeeooow! Time to play with FIRE!! I'm the strongest of us brothers!", - 2: "Ta-da! The Fire-type scorcher Chili--that's me--will be your opponent!", - 3: "I'm going to show you what me and my blazing Fire types can do!" + 1: "Yeeeeooow! Hora de brincar com FOGO!! Sou o mais forte dos nossos irmãos!", + 2: "Ta-da! O incendiário do tipo Fogo Chili—sou eu—será seu oponente!", + 3: "Vou mostrar o que eu e meus tipos de Fogo podemos fazer!" }, "victory": { - 1: "You got me. I am… burned… out…", - 2: "Whoa ho! You're on fire!", - 3: "Augh! You got me!" + 1: "Você me pegou. Estou... queimado...", + 2: "Uau! Você está pegando fogo!", + 3: "Augh! Você me pegou!" }, "defeat": { - 1: "I'm on fire! Play with me, and you'll get burned!", - 2: "When you play with fire, you get burned!", - 3: "I mean, c'mon, your opponent was me! You didn't have a chance!" + 1: "Estou pegando fogo! Jogue comigo, e você se queimará!", + 2: "Quando você brinca com fogo, você se queima!", + 3: "Quero dizer, vamos lá, seu oponente era eu! Você não tinha chance!" } }, "cilan": { "encounter": { - 1: `Nothing personal... No hard feelings... Me and my Grass-type Pokémon will... - $Um... We're gonna battle come what may.`, - 2: "So, um, if you're OK with me, I'll, um, put everything I've got into being, er, you know, your opponent.", - 3: "OK… So, um, I'm Cilan, I like Grass-type Pokémon." + 1: "Nada pessoal... Sem ressentimentos... Eu e meus Pokémon do tipo Grama vamos...\nUm... Vamos batalhar, aconteça o que acontecer.", + 2: "Então, hum, se você está bem comigo, vou, hum, colocar tudo o que tenho em ser, er, você sabe, seu oponente.", + 3: "OK… Então, hum, eu sou o Cilan, gosto de Pokémon do tipo Grama." }, "victory": { - 1: "Er… Is it over now?", - 2: `…What a surprise. You are very strong, aren't you? - $I guess my brothers wouldn't have been able to defeat you either…`, - 3: "…Huh. Looks like my timing was, um, off?" + 1: "Er... Acabou agora?", + 2: "…Que surpresa. Você é muito forte, não é?\nAcho que meus irmãos também não teriam sido capazes de te derrotar...", + 3: "…Huh. Parece que meu timing estava, hum, errado?" }, "defeat": { - 1: "Huh? Did I win?", - 2: `I guess… - $I suppose I won, because I've been competing with my brothers Chili and Cress, and we all were able to get tougher.`, - 3: "It…it was quite a thrilling experience…" + 1: "Huh? Ganhei?", + 2: "Acho...\nSuponho que ganhei, porque competi com meus irmãos Chili e Cress, e todos conseguimos ficar mais fortes.", + 3: "Foi... uma experiência bastante emocionante..." } }, "roark": { "encounter": { - 1: "I need to see your potential as a Trainer. And, I'll need to see the toughness of the Pokémon that battle with you!", - 2: "Here goes! These are my rocking Pokémon, my pride and joy!", - 3: "Rock-type Pokémon are simply the best!", - 4: "I need to see your potential as a Trainer. And, I'll need to see the toughness of the Pokémon that battle with you!" + 1: "Preciso ver seu potencial como Treinador. E, vou precisar ver a dureza dos Pokémon que batalham com você!", + 2: "Vamos lá! Estes são meus Pokémon de pedra, meu orgulho e alegria!", + 3: "Pokémon do tipo Pedra são simplesmente os melhores!", + 4: "Preciso ver seu potencial como Treinador. E, vou precisar ver a dureza dos Pokémon que batalham com você!" }, "victory": { - 1: "W-what? That can't be! My buffed-up Pokémon!", - 2: "…We lost control there. Next time I'd like to challenge you to a Fossil-digging race underground.", - 3: "With skill like yours, it's natural for you to win.", - 4: "Wh-what?! It can't be! Even that wasn't enough?", - 5: "I blew it." + 1: "O-o que? Isso não pode ser! Meus Pokémon fortificados!", + 2: "...Perdemos o controle. Da próxima vez, gostaria de desafiá-lo a uma corrida de escavação de fósseis no subsolo.", + 3: "Com habilidade como a sua, é natural que você vença.", + 4: "O-o que?! Não pode ser! Nem isso foi suficiente?", + 5: "Eu estraguei tudo." }, "defeat": { - 1: "See? I'm proud of my rocking battle style!", - 2: "Thanks! The battle gave me confidence that I may be able to beat my dad!", - 3: "I feel like I just smashed through a really stubborn boulder!" + 1: "Veja? Estou orgulhoso do meu estilo de batalha rochoso!", + 2: "Obrigado! A batalha me deu confiança de que talvez eu consiga vencer meu pai!", + 3: "Sinto como se tivesse acabado de quebrar uma pedra muito teimosa!" } }, "morty": { "encounter": { - 1: `With a little more, I could see a future in which I meet the legendary Pokémon. - $You're going to help me reach that level!`, - 2: `It's said that a rainbow-hued Pokémon will come down to appear before a truly powerful Trainer. - $I believed that tale, so I have secretly trained here all my life. As a result, I can now see what others cannot. - $I see a shadow of the person who will make the Pokémon appear. - $I believe that person is me! You're going to help me reach that level!`, - 3: "Whether you choose to believe or not, mystic power does exist.", - 4: "You can bear witness to the fruits of my training.", - 5: "You must make your soul one with that of Pokémon. Can you do this?", - 6: "Say, do you want to be part of my training?" + 1: "Com um pouco mais, eu poderia ver um futuro em que encontro o Pokémon lendário.\nVocê vai me ajudar a alcançar esse nível!", + 2: "Dizem que um Pokémon com cores de arco-íris aparecerá diante de um Treinador verdadeiramente poderoso.\nAcreditei nessa história, então treinei secretamente aqui a vida toda. Como resultado, agora posso ver o que os outros não podem.\nVejo uma sombra da pessoa que fará o Pokémon aparecer.\nAcredito que essa pessoa sou eu! Você vai me ajudar a alcançar esse nível!", + 3: "Quer você escolha acreditar ou não, o poder místico existe.", + 4: "Você pode testemunhar os frutos do meu treinamento.", + 5: "Você deve fazer sua alma se tornar uma com a dos Pokémon. Você pode fazer isso?", + 6: "Diga, você quer fazer parte do meu treinamento?" }, "victory": { - 1: "I'm not good enough yet…", - 2: `I see… Your journey has taken you to far-away places and you have witnessed much more than I. - $I envy you for that…`, - 3: "How is this possible…", - 4: `I don't think our potentials are so different. - $But you seem to have something more than that… So be it.`, - 5: "Guess I need more training.", - 6: "That's a shame." + 1: "Ainda não sou bom o suficiente...", + 2: "Eu vejo... Sua jornada o levou a lugares distantes e você testemunhou muito mais do que eu.\nEu invejo você por isso...", + 3: "Como isso é possível...", + 4: "Não acho que nossos potenciais sejam tão diferentes.\nMas você parece ter algo mais do que isso... Que seja.", + 5: "Acho que preciso de mais treinamento.", + 6: "Isso é uma pena." }, "defeat": { - 1: "I moved… one step ahead again.", - 2: "Fufufu…", - 3: "Wh-what?! It can't be! Even that wasn't enough?", - 4: "I feel like I just smashed through a really stubborn boulder!", + 1: "Eu me movi... mais um passo adiante.", + 2: "Fufufu...", + 3: "O-o que?! Não pode ser! Nem isso foi suficiente?", + 4: "Sinto como se tivesse acabado de quebrar uma pedra muito teimosa!", 5: "Ahahahah!", - 6: "I knew I would win!" + 6: "Eu sabia que venceria!" } }, "crispin": { "encounter": { - 1: "I wanna win, so that's exactly what I'll do!", - 2: "I battle because I wanna battle! And you know what? That's how it should be!" + 1: "Quero vencer, então é exatamente isso que vou fazer!", + 2: "Eu batalho porque quero batalhar! E sabe de uma coisa? É assim que deve ser!" }, "victory": { - 1: "I wanted to win…but I lost!", - 2: "I lost…'cause I couldn't win!" + 1: "Queria vencer... mas perdi!", + 2: "Eu perdi... porque não consegui vencer!" }, "defeat": { - 1: "Hey, wait a sec. Did I just win? I think I just won! Talk about satisfying!", - 2: "Wooo! That was amazing!" + 1: "Ei, espere um segundo. Eu acabei de vencer? Acho que acabei de vencer! Que satisfação!", + 2: "Uooo! Isso foi incrível!" } }, "amarys": { "encounter": { - 1: `I want to be the one to help a certain person. That being the case, I cannot afford to lose. - $… Our battle starts now.`, + 1: "Quero ser a pessoa a ajudar alguém em particular. Sendo assim, não posso me dar ao luxo de perder.\n... Nossa batalha começa agora." }, "victory": { - 1: "I am… not enough, I see." + 1: "Eu sou... não o suficiente, eu vejo." }, "defeat": { - 1: "Victory belongs to me. Well fought." + 1: "A vitória pertence a mim. Bem lutado." } }, "lacey": { "encounter": { - 1: "I'll be facing you with my usual party as a member of the Elite Four." + 1: "Vou enfrentar você com meu time usual como membro da Elite Four." }, "victory": { - 1: "That was a great battle!" + 1: "Foi uma excelente batalha. Estou ansiosa para o próximo desafio." }, "defeat": { - 1: "Let's give your Pokémon a nice round of applause for their efforts!" + 1: "Fufufu... Nada mal.\nDesafiantes que derrotam a Elite Four são dignos de notar." } }, "drayton": { "encounter": { - 1: `Man, I love chairs. Don't you love chairs? What lifesavers. - $I don't get why everyone doesn't just sit all the time. Standing up's tiring work!`, + 1: `Cara, eu amo cadeiras. Você não ama cadeiras? Que salva-vidas. + $Não entendo por que todo mundo não fica sentado o tempo todo. Ficar de pé é cansativo!`, }, "victory": { - 1: "Guess I should've expected that!" + 1: "Acho que deveria ter esperado por isso!" }, "defeat": { - 1: "Heh heh! Don't mind me, just scooping up a W over here. I get it if you're upset, but don't go full Kieran on me, OK?" + 1: "Heh heh! Não ligue para mim, só pegando uma vitória aqui. Entendo se você estiver chateado, mas não vá dar uma de Kieran comigo, OK?" } }, "ramos": { "encounter": { - 1: `Did yeh enjoy the garden playground I made with all these sturdy plants o' mine? - $Their strength is a sign o' my strength as a gardener and a Gym Leader! Yeh sure yer up to facing all that?`, + 1: `Você gostou do jardim de diversão que fiz com todas essas plantas resistentes minhas? + $A força delas é um sinal da minha força como jardineiro e Líder de Ginásio! Você tem certeza de que está pronto para enfrentar tudo isso?`, }, "victory": { - 1: "Yeh believe in yer Pokémon… And they believe in yeh, too… It was a fine battle, sprout." + 1: "Você acredita nos seus Pokémon... E eles acreditam em você também... Foi uma boa batalha, broto." }, "defeat": { - 1: "Hohoho… Indeed. Frail little blades o' grass'll break through even concrete." + 1: "Hohoho... De fato. Pequenas lâminas frágeis de grama conseguem quebrar até mesmo concreto." } }, "viola": { "encounter": { - 1: `Whether it's the tears of frustration that follow a loss or the blossoming of joy that comes with victory… - $They're both great subjects for my camera! Fantastic! This'll be just fantastic! - $Now come at me!`, - 2: "My lens is always focused on victory--I won't let anything ruin this shot!" + 1: `Seja as lágrimas de frustração que seguem uma derrota ou o florescer da alegria que vem com a vitória… + $Ambos são ótimos temas para minha câmera! Fantástico! Isso vai ser simplesmente fantástico! + $Agora venha para cima de mim!`, + 2: "Minha lente está sempre focada na vitória – não vou deixar nada estragar esta foto!" }, "victory": { - 1: "You and your Pokémon have shown me a whole new depth of field! Fantastic! Just fantastic!", - 2: `The world you see through a lens, and the world you see with a Pokémon by your side… - $The same world can look entirely different depending on your view.` + 1: "Você e seus Pokémon me mostraram uma nova profundidade de campo! Fantástico! Simplesmente fantástico!", + 2: `O mundo que você vê através de uma lente, e o mundo que você vê com um Pokémon ao seu lado… + $O mesmo mundo pode parecer completamente diferente dependendo do seu ponto de vista.` }, "defeat": { - 1: "The photo from the moment of my victory will be a real winner, all right!", - 2: "Yes! I took some great photos!" + 1: "A foto do momento da minha vitória vai ser um verdadeiro sucesso!", + 2: "Sim! Tirei ótimas fotos!" } }, "candice": { "encounter": { - 1: `You want to challenge Candice? Sure thing! I was waiting for someone tough! - $But I should tell you, I'm tough because I know how to focus.`, - 2: `Pokémon, fashion, romance… It's all about focus! - $I'll show you just what I mean. Get ready to lose!` + 1: `Você quer desafiar a Candice? Com certeza! Eu estava esperando por alguém forte! + $Mas devo te avisar, sou forte porque sei como focar.`, + 2: `Pokémon, moda, romance… É tudo uma questão de foco! + $Vou te mostrar exatamente o que quero dizer. Prepare-se para perder!` }, "victory": { - 1: "I must say, I'm warmed up to you! I might even admire you a little.", - 2: `Wow! You're great! You've earned my respect! - $I think your focus and will bowled us over totally. ` + 1: "Devo dizer, estou aquecida para você! Posso até te admirar um pouco.", + 2: `Uau! Você é ótimo! Ganhou meu respeito! + $Acho que seu foco e vontade nos derrubaram totalmente.` }, "defeat": { - 1: "I sensed your will to win, but I don't lose!", - 2: "See? Candice's focus! My Pokémon's focus is great, too!" + 1: "Eu senti sua vontade de vencer, mas eu não perco!", + 2: "Viu? O foco da Candice! O foco dos meus Pokémon também é ótimo!" } }, "gardenia": { "encounter": { - 1: "You have a winning aura about you. So, anyway, this will be fun. Let's have our battle!" + 1: "Você tem uma aura vencedora. Então, de qualquer forma, isso vai ser divertido. Vamos ter nossa batalha!" }, "victory": { - 1: "Amazing! You're very good, aren't you?" + 1: "Incrível! Você é muito bom, não é?" }, "defeat": { - 1: "Yes! My Pokémon and I are perfectly good!" + 1: "Sim! Meus Pokémon e eu somos perfeitamente bons!" } }, "aaron": { "encounter": { - 1: "Ok! Let me take you on!" + 1: "Ok! Deixe-me enfrentar você!" }, "victory": { - 1: "Battling is a deep and complex affair…" + 1: "Batalhar é um assunto profundo e complexo..." }, "defeat": { - 1: "Victory over an Elite Four member doesn't come easily." + 1: "Vencer um membro da Elite Four não é fácil." } }, "cress": { "encounter": { - 1: "That is correct! It shall be I and my esteemed Water types that you must face in battle!" + 1: "Isso mesmo! Serei eu e meus estimados tipos Água que você deve enfrentar na batalha!" }, "victory": { - 1: "Lose? Me? I don't believe this." + 1: "Perder? Eu? Não acredito nisso." }, "defeat": { - 1: "This is the appropriate result when I'm your opponent." + 1: "Este é o resultado apropriado quando eu sou seu oponente." } }, "allister": { "encounter": { - 1: "'M Allister.\nH-here… I go…" + 1: "'M Allister.\nA-aqui... vou eu..." }, "victory": { - 1: `I nearly lost my mask from the shock… That was… - $Wow. I can see your skill for what it is.`, + 1: `Quase perdi minha máscara de tanto choque... Isso foi… + $Uau. Posso ver sua habilidade pelo que ela é.`, }, "defeat": { - 1: "Th-that was ace!" + 1: "I-isso foi incrível!" } }, "clay": { "encounter": { - 1: "Harrumph! Kept me waitin', didn't ya, kid? All right, time to see what ya can do!" + 1: "Harrumph! Me deixou esperando, não foi, garoto? Tudo bem, hora de ver o que você pode fazer!" }, "victory": { - 1: "Man oh man… It feels good to go all out and still be defeated!" + 1: "Cara, como é bom dar tudo de si e ainda assim ser derrotado!" }, "defeat": { - 1: `What's important is how ya react to losin'. - $That's why folks who use losin' as fuel to get better are tough.`, + 1: `O que importa é como você reage à derrota. + $É por isso que as pessoas que usam a derrota como combustível para melhorar são duras.`, } }, "kofu": { "encounter": { - 1: "I'mma serve you a full course o' Water-type Pokémon! Don't try to eat 'em, though!" + 1: "Vou te servir um prato completo de Pokémon do tipo Água! Mas não tente comê-los!" }, "victory": { - 1: "Vaultin' Veluza! Yer a lively one, aren't ya! A little TOO lively, if I do say so myself!" + 1: "Vaultin' Veluza! Você é animado, não é! Um pouco ANIMADO DEMAIS, se me permite dizer!" }, "defeat": { - 1: "You come back to see me again now, ya hear?" + 1: "Volte para me ver novamente, ouviu?" } }, "tulip": { "encounter": { - 1: "Allow me to put my skills to use to make your cute little Pokémon even more beautiful!" + 1: "Permita-me usar minhas habilidades para deixar seus lindos Pokémon ainda mais bonitos!" }, "victory": { - 1: "Your strength has a magic to it that cannot be washed away." + 1: "Sua força tem uma magia que não pode ser apagada." }, "defeat": { - 1: "You know, in my line of work, people who lack talent in one area or the other often fade away quickly—never to be heard of again." + 1: "Você sabe, na minha linha de trabalho, pessoas que carecem de talento em uma área ou outra frequentemente desaparecem rapidamente - nunca mais se ouve falar delas." } }, "sidney": { "encounter": { - 1: `I like that look you're giving me. I guess you'll give me a good match. - $That's good! Looking real good! All right! - $You and me, let's enjoy a battle that can only be staged here!`, + 1: `Gostei desse olhar que você me deu. Acho que você vai ser um bom desafio. + $Isso é ótimo! Parece muito bom! Vamos nessa! + $Você e eu, vamos curtir uma batalha que só pode acontecer aqui!`, }, "victory": { - 1: "Well, how do you like that? I lost! Eh, it was fun, so it doesn't matter." + 1: "E aí, gostou? Eu perdi! Mas foi divertido, então não importa." }, "defeat": { - 1: "No hard feelings, alright?" + 1: "Sem ressentimentos, beleza?" } }, "phoebe": { "encounter": { - 1: `While I trained, I gained the ability to commune with Ghost-type Pokémon. - $Yes, the bond I developed with Pokémon is extremely tight. - $So, come on, just try and see if you can even inflict damage on my Pokémon!`, + 1: `Enquanto treinava, adquiri a habilidade de me comunicar com Pokémon do tipo Fantasma. + $Sim, o vínculo que desenvolvi com os Pokémon é extremamente forte. + $Então, vamos lá, tente ver se você consegue até mesmo causar dano aos meus Pokémon!`, }, "victory": { - 1: "Oh, darn. I've gone and lost." + 1: "Ah, droga. Eu perdi." }, "defeat": { - 1: "I look forward to battling you again sometime!" + 1: "Estou ansiosa para batalhar com você de novo algum dia!" } }, "glacia": { "encounter": { - 1: `All I have seen are challenges by weak Trainers and their Pokémon. - $What about you? It would please me to no end if I could go all out against you!`, + 1: `Tudo o que vi foram desafios de Treinadores fracos e seus Pokémon. + $E você? Ficaria extremamente satisfeita se pudesse dar tudo de mim contra você!`, }, "victory": { - 1: `You and your Pokémon… How hot your spirits burn! - $The all-consuming heat overwhelms. - $It's no surprise that my icy skills failed to harm you.`, + 1: `Você e seus Pokémon… Como seus espíritos queimam! + $O calor consumido é esmagador. + $Não é surpresa que minhas habilidades geladas falharam em te machucar.`, }, "defeat": { - 1: "A fiercely passionate battle, indeed." + 1: "Uma batalha intensamente apaixonada, sem dúvida." } }, "drake": { "encounter": { - 1: `For us to battle with Pokémon as partners, do you know what it takes? Do you know what is needed? - $If you don't, then you will never prevail over me!`, + 1: `Para nós, batalhar com Pokémon como parceiros, você sabe o que é necessário? Você sabe o que precisa? + $Se não souber, nunca prevalecerá contra mim!`, }, "victory": { - 1: "Superb, it should be said." + 1: "Excelente, deve-se dizer." }, "defeat": { - 1: "I gave my all for that battle!" + 1: "Dei meu máximo nessa batalha!" } }, "wallace": { "encounter": { - 1: `There's something about you… A difference in your demeanor. - $I think I sense that in you. Now, show me. Show me the power you wield with your Pokémon. - $And I, in turn, shall present you with a performance of illusions in water by me and my Pokémon!`, + 1: `Há algo em você… Uma diferença na sua postura. + $Acho que sinto isso em você. Agora, me mostre. Mostre-me o poder que você tem com seus Pokémon. + $E eu, por minha vez, apresentarei uma performance de ilusões na água com meus Pokémon!`, }, "victory": { - 1: `Bravo. I realize now your authenticity and magnificence as a Pokémon Trainer. - $I find much joy in having met you and your Pokémon. You have proven yourself worthy.`, + 1: `Bravo. Agora percebo sua autenticidade e magnificência como Treinador de Pokémon. + $Tenho muita alegria em ter conhecido você e seus Pokémon. Você se mostrou digno.`, }, "defeat": { - 1: "A grand illusion!" + 1: "Uma grande ilusão!" } }, "lorelei": { "encounter": { - 1: `No one can best me when it comes to icy Pokémon! Freezing moves are powerful! - $Your Pokémon will be at my mercy when they are frozen solid! Hahaha! Are you ready?`, + 1: `Ninguém me supera quando se trata de Pokémon gelados! Movimentos congelantes são poderosos! + $Seus Pokémon estarão à minha mercê quando estiverem congelados! Hahaha! Está pronto?`, }, "victory": { - 1: "How dare you!" + 1: "Como ousa!" }, "defeat": { - 1: "There's nothing you can do once you're frozen." + 1: "Não há nada que você possa fazer quando está congelado." } }, "will": { "encounter": { - 1: `I have trained all around the world, making my psychic Pokémon powerful. - $I can only keep getting better! Losing is not an option!`, + 1: `Treinei por todo o mundo, tornando meus Pokémon psíquicos poderosos. + $Eu só posso melhorar! Perder não é uma opção!`, }, "victory": { - 1: "I… I can't… believe it…" + 1: "Eu… Eu não… acredito…" }, "defeat": { - 1: "That was close. I wonder what it is that you lack." + 1: "Isso foi por pouco. Me pergunto o que você está faltando." } }, "malva": { "encounter": { - 1: `I feel like my heart might just burst into flames. - $I'm burning up with my hatred for you, runt!`, + 1: `Sinto que meu coração pode explodir em chamas. + $Estou ardendo de ódio por você, pirralho!`, }, "victory": { - 1: "What news… So a new challenger has defeated Malva!" + 1: "Que novidade… Um novo desafiador derrotou Malva!" }, "defeat": { - 1: "I am delighted! Yes, delighted that I could squash you beneath my heel." + 1: "Estou encantada! Sim, encantada por poder esmagar você sob meu calcanhar." } }, "hala": { "encounter": { - 1: "Old Hala is here to make you holler!" + 1: "O velho Hala está aqui para fazer você gritar!" }, "victory": { - 1: "I could feel the power you gained on your journey." + 1: "Pude sentir o poder que você ganhou na sua jornada." }, "defeat": { - 1: "Haha! What a delightful battle!" + 1: "Haha! Que batalha deliciosa!" } }, "molayne": { "encounter": { - 1: `I gave the captain position to my cousin Sophocles, but I'm confident in my ability. - $My strength is like that of a supernova!`, + 1: `Dei a posição de capitão ao meu primo Sophocles, mas estou confiante na minha habilidade. + $Minha força é como a de uma supernova!`, }, "victory": { - 1: "I certainly found an interesting Trainer to face!" + 1: "Certamente encontrei um Treinador interessante para enfrentar!" }, "defeat": { - 1: "Ahaha. What an interesting battle." + 1: "Ahaha. Que batalha interessante." } }, "rika": { "encounter": { - 1: "I'd say I'll go easy on you, but… I'd be lying! Think fast!" + 1: "Eu diria que vou pegar leve com você, mas… estaria mentindo! Pense rápido!" }, "victory": { - 1: "Not bad, kiddo." + 1: "Nada mal, garoto." }, "defeat": { - 1: "Nahahaha! You really are something else, kiddo!" + 1: "Nahahaha! Você realmente é algo mais, garoto!" } }, "bruno": { "encounter": { - 1: "We will grind you down with our superior power! Hoo hah!" + 1: "Nós vamos te triturar com nosso poder superior! Hoo hah!" }, "victory": { - 1: "Why? How could I lose?" + 1: "Por quê? Como eu poderia perder?" }, "defeat": { - 1: "You can challenge me all you like, but the results will never change!" + 1: "Você pode me desafiar o quanto quiser, mas os resultados nunca vão mudar!" } }, "bugsy": { "encounter": { - 1: "I'm Bugsy! I never lose when it comes to bug Pokémon!" + 1: "Sou Bugsy! Eu nunca perco quando se trata de Pokémon do tipo Inseto!" }, "victory": { - 1: "Whoa, amazing! You're an expert on Pokémon!\nMy research isn't complete yet. OK, you win." + 1: "Uau, incrível! Você é um especialista em Pokémon!\nMinha pesquisa ainda não está completa. OK, você venceu." }, "defeat": { - 1: "Thanks! Thanks to our battle, I was also able to make progress in my research!" + 1: "Obrigado! Graças à nossa batalha, eu também pude fazer progressos na minha pesquisa!" } }, "koga": { "encounter": { - 1: "Fwahahahaha! Pokémon are not merely about brute force--you shall see soon enough!" + 1: "Fwahahahaha! Pokémon não são apenas sobre força bruta--você verá em breve!" }, "victory": { - 1: "Ah! You've proven your worth!" + 1: "Ah! Você provou seu valor!" }, "defeat": { - 1: "Have you learned to fear the techniques of the ninja?" + 1: "Você aprendeu a temer as técnicas do ninja?" } }, "bertha": { "encounter": { - 1: "Well, would you show this old lady how much you've learned?" + 1: "Bem, você mostraria a esta velha senhora o quanto aprendeu?" }, "victory": { - 1: `Well! Dear child, I must say, that was most impressive. - $Your Pokémon believed in you and did their best to earn you the win. - $Even though I've lost, I find myself with this silly grin!`, + 1: `Bem! Querida criança, devo dizer, isso foi muito impressionante. + $Seus Pokémon acreditaram em você e fizeram o melhor para te dar a vitória. + $Mesmo tendo perdido, me encontro com esse sorriso bobo!`, }, "defeat": { - 1: "Hahahahah! Looks like this old lady won!" + 1: "Hahahahah! Parece que esta velha senhora ganhou!" } }, "lenora": { "encounter": { - 1: "Well then, challenger, I'm going to research how you battle with the Pokémon you've so lovingly raised!" + 1: "Bem, desafiador, vou pesquisar como você batalha com os Pokémon que criou com tanto carinho!" }, "victory": { - 1: "My theory about you was correct. You're more than just talented… You're motivated! I salute you!" + 1: "Minha teoria sobre você estava correta. Você é mais do que talentoso… Você é motivado! Eu te saúdo!" }, "defeat": { - 1: "Ah ha ha! If you lose, make sure to analyze why, and use that knowledge in your next battle!" + 1: "Ah ha ha! Se você perder, certifique-se de analisar o porquê e use esse conhecimento na próxima batalha!" } }, "siebold": { "encounter": { - 1: "As long as I am alive, I shall strive onward to seek the ultimate cuisine... and the strongest opponents in battle!" + 1: "Enquanto eu estiver vivo, continuarei em busca da culinária suprema... e dos oponentes mais fortes em batalha!" }, "victory": { - 1: "I shall store my memory of you and your Pokémon forever away within my heart." + 1: "Guardarei minha memória de você e seus Pokémon para sempre em meu coração." }, "defeat": { - 1: `Our Pokémon battle was like food for my soul. It shall keep me going. - $That is how I will pay my respects to you for giving your all in battle!`, + 1: `Nossa batalha Pokémon foi como alimento para minha alma. Isso vai me manter em frente. + $É assim que vou prestar meus respeitos a você por dar tudo de si na batalha!`, } }, "roxie": { "encounter": { - 1: "Get ready! I'm gonna knock some sense outta ya!" + 1: "Prepare-se! Vou arrancar algum senso de você!" }, "victory": { - 1: "Wild! Your reason's already more toxic than mine!" + 1: "Selvagem! Sua razão já é mais tóxica que a minha!" }, "defeat": { - 1: "Hey, c'mon! Get serious! You gotta put more out there!" + 1: "Ei, vamos lá! Seja sério! Você tem que dar mais de si!" } }, "olivia": { "encounter": { - 1: "No introduction needed here. Time to battle me, Olivia!" + 1: "Não precisa de introdução aqui. Hora de batalhar comigo, Olivia!" }, "victory": { - 1: "Really lovely… Both you and your Pokémon…" + 1: "Realmente encantador… Tanto você quanto seus Pokémon…" }, "defeat": { 1: "Mmm-hmm." @@ -1295,224 +1284,224 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "poppy": { "encounter": { - 1: "Oooh! Do you wanna have a Pokémon battle with me?" + 1: "Oooh! Você quer ter uma batalha Pokémon comigo?" }, "victory": { 1: "Uagh?! Mmmuuuggghhh…" }, "defeat": { - 1: `Yaaay! I did it! I de-feet-ed you! You can come for… For… An avenge match? - $Come for an avenge match anytime you want!`, + 1: `Yaaay! Eu consegui! Eu der-ro-tei você! Você pode vir para… Para… Uma revanche? + $Venha para uma revanche quando quiser!`, } }, "agatha": { "encounter": { - 1: "Pokémon are for battling! I'll show you how a real Trainer battles!" + 1: "Pokémon são para batalhas! Vou te mostrar como um verdadeiro Treinador batalha!" }, "victory": { - 1: "Oh my! You're something special, child!" + 1: "Oh meu! Você é algo especial, criança!" }, "defeat": { - 1: "Bahaha. That's how a proper battle's done!" + 1: "Bahaha. É assim que uma batalha adequada é feita!" } }, "flint": { "encounter": { - 1: "Hope you're warmed up, cause here comes the Big Bang!" + 1: "Espero que você esteja aquecido, porque aqui vem o Big Bang!" }, "victory": { - 1: "Incredible! Your moves are so hot, they make mine look lukewarm!" + 1: "Incrível! Seus movimentos são tão quentes que fazem os meus parecerem mornos!" }, "defeat": { - 1: "Huh? Is that it? I think you need a bit more passion." + 1: "Huh? Isso é tudo? Acho que você precisa de um pouco mais de paixão." } }, "grimsley": { "encounter": { - 1: "The winner takes everything, and there's nothing left for the loser." + 1: "O vencedor leva tudo, e não sobra nada para o perdedor." }, "victory": { - 1: "When one loses, they lose everything… The next thing I'll look for will be victory, too!" + 1: "Quando se perde, perde-se tudo… A próxima coisa que vou procurar será a vitória, também!" }, "defeat": { - 1: "If somebody wins, the person who fought against that person will lose." + 1: "Se alguém vence, a pessoa que lutou contra essa pessoa perde." } }, "caitlin": { "encounter": { - 1: `It's me who appeared when the flower opened up. You who have been waiting… - $You look like a Pokémon Trainer with refined strength and deepened kindness. - $What I look for in my opponent is superb strength… - $Please unleash your power to the fullest!`, + 1: `Sou eu que apareci quando a flor se abriu. Você que estava esperando… + $Você parece um Treinador de Pokémon com força refinada e bondade profunda. + $O que eu procuro no meu oponente é uma força soberba… + $Por favor, libere seu poder ao máximo!`, }, "victory": { - 1: "My Pokémon and I learned so much! I offer you my thanks." + 1: "Meus Pokémon e eu aprendemos muito! Agradeço a você." }, "defeat": { - 1: "I aspire to claim victory with elegance and grace." + 1: "Aspiro a reivindicar a vitória com elegância e graça." } }, "diantha": { "encounter": { - 1: `Battling against you and your Pokémon, all of you brimming with hope for the future… - $Honestly, it just fills me up with energy I need to keep facing each new day! It does!`, + 1: `Batalhar contra você e seus Pokémon, todos vocês cheios de esperança para o futuro… + $Honestamente, isso apenas me enche da energia que preciso para continuar enfrentando cada novo dia! Sim!`, }, "victory": { - 1: "Witnessing the noble spirits of you and your Pokémon in battle has really touched my heart…" + 1: "Testemunhar os espíritos nobres de você e seus Pokémon em batalha realmente tocou meu coração…" }, "defeat": { - 1: "Oh, fantastic! What did you think? My team was pretty cool, right?" + 1: "Oh, fantástico! O que achou? Minha equipe foi bem legal, né?" } }, "wikstrom": { "encounter": { - 1: `Well met, young challenger! Verily am I the famed blade of hardened steel, Duke Wikstrom! - $Let the battle begin! En garde!`, + 1: `Bem encontrado, jovem desafiador! Verdadeiramente sou a lâmina famosa de aço endurecido, Duque Wikstrom! + $Que a batalha comece! En garde!`, }, "victory": { - 1: "Glorious! The trust that you share with your honorable Pokémon surpasses even mine!" + 1: "Glorioso! A confiança que você compartilha com seu honrado Pokémon supera até mesmo a minha!" }, "defeat": { - 1: `What manner of magic is this? My heart, it doth hammer ceaselessly in my breast! - $Winning against such a worthy opponent doth give my soul wings--thus do I soar!`, + 1: `Que tipo de magia é essa? Meu coração bate incessantemente no meu peito! + $Vencer contra um oponente tão digno dá asas à minha alma--assim eu voo!`, } }, "acerola": { "encounter": { - 1: "Battling is just plain fun! Come on, I can take you!" + 1: "Batalhar é simplesmente divertido! Vamos lá, eu posso te derrotar!" }, "victory": { - 1: "I'm… I'm speechless! How did you do it?!" + 1: "Eu… Estou sem palavras! Como você conseguiu?!" }, "defeat": { - 1: "Ehaha! What an amazing victory!" + 1: "Ehaha! Que vitória incrível!" } }, "larry_elite": { "encounter": { - 1: `Hello there… It's me, Larry. - $I serve as a member of the Elite Four too, yes… Unfortunately for me.`, + 1: `Olá… Sou eu, Larry. + $Eu também sou membro da Elite Four, sim… Infelizmente para mim.`, }, "victory": { - 1: "Well, that took the wind from under our wings…" + 1: "Bem, isso tirou o vento debaixo das nossas asas…" }, "defeat": { - 1: "It's time for a meeting with the boss." + 1: "É hora de uma reunião com o chefe." } }, "lance": { "encounter": { - 1: "I've been waiting for you. Allow me to test your skill.", - 2: "I thought that you would be able to get this far. Let's get this started." + 1: "Estive esperando por você. Permita-me testar suas habilidades.", + 2: "Achei que você conseguiria chegar tão longe. Vamos começar." }, "victory": { - 1: "You got me. You are magnificent!", - 2: "I never expected another trainer to beat me… I'm surprised." + 1: "Você me pegou. Você é magnífico!", + 2: "Nunca esperei que outro treinador me derrotasse… Estou surpreso." }, "defeat": { - 1: "That was close. Want to try again?", - 2: "It's not that you are weak. Don't let it bother you." + 1: "Isso foi por pouco. Quer tentar de novo?", + 2: "Não é que você seja fraco. Não se incomode com isso." } }, "karen": { "encounter": { - 1: "I am Karen. Would you care for a showdown with my Dark-type Pokémon?", - 2: "I am unlike those you've already met.", - 3: "You've assembled a charming team. Our battle should be a good one." + 1: "Eu sou Karen. Você gostaria de um duelo com meus Pokémon do tipo Sombrio?", + 2: "Sou diferente daqueles que você já conheceu.", + 3: "Você montou uma equipe charmosa. Nossa batalha deve ser boa." }, "victory": { - 1: "No! I can't win. How did you become so strong?", - 2: "I will not stray from my chosen path.", - 3: "The Champion is looking forward to meeting you." + 1: "Não! Eu não posso vencer. Como você ficou tão forte?", + 2: "Não me desviarei do meu caminho escolhido.", + 3: "O Campeão está ansioso para te conhecer." }, "defeat": { - 1: "That's about what I expected.", - 2: "Well, that was relatively entertaining.", - 3: "Come visit me anytime." + 1: "Isso era o que eu esperava.", + 2: "Bem, isso foi relativamente divertido.", + 3: "Venha me visitar a qualquer momento." } }, "milo": { "encounter": { - 1: `Sure seems like you understand Pokémon real well. - $This is gonna be a doozy of a battle! - $I'll have to Dynamax my Pokémon if I want to win!`, + 1: `Parece que você entende bem os Pokémon. + $Isso vai ser uma batalha e tanto! + $Vou ter que usar a Dynamax no meu Pokémon se eu quiser vencer!`, }, "victory": { - 1: "The power of Grass has wilted… What an incredible Challenger!" + 1: "O poder da Grama murchou… Que desafiador incrível!" }, "defeat": { - 1: "This'll really leave you in shock and awe." + 1: "Isso realmente vai te deixar em choque e admiração." } }, "lucian": { "encounter": { - 1: `Just a moment, please. The book I'm reading has nearly reached its thrilling climax… - $The hero has obtained a mystic sword and is about to face their final trial… Ah, never mind. - $Since you've made it this far, I'll put that aside and battle you. - $Let me see if you'll achieve as much glory as the hero of my book!,` + 1: `Só um momento, por favor. O livro que estou lendo está quase no clímax emocionante… + $O herói obteve uma espada mística e está prestes a enfrentar sua prova final… Ah, tanto faz. + $Já que você chegou tão longe, vou deixar isso de lado e batalhar com você. + $Deixe-me ver se você alcançará tanta glória quanto o herói do meu livro!`, }, "victory": { - 1: "I see… It appears you've put me in checkmate." + 1: "Eu vejo… Parece que você me colocou em xeque-mate." }, "defeat": { - 1: "I have a reputation to uphold." + 1: "Tenho uma reputação a manter." } }, "drasna": { "encounter": { - 1: `You must be a strong Trainer. Yes, quite strong indeed… - $That's just wonderful news! Facing opponents like you and your team will make my Pokémon grow like weeds!` + 1: `Você deve ser um Treinador forte. Sim, bastante forte… + $Isso é uma notícia maravilhosa! Enfrentar oponentes como você e sua equipe fará meus Pokémon crescerem como ervas daninhas!`, }, "victory": { - 1: "Oh, dear me. That sure was a quick battle… I do hope you'll come back again sometime!" + 1: "Oh, meu Deus. Isso foi uma batalha rápida… Espero que você volte novamente algum dia!" }, "defeat": { - 1: "How can this be?" + 1: "Como isso é possível?" } }, "kahili": { "encounter": { - 1: "So, here you are… Why don't we see who the winds favor today, you… Or me?" + 1: "Então, aqui está você… Por que não vemos para quem os ventos favorecem hoje, você… ou eu?" }, "victory": { - 1: "It's frustrating to me as a member of the Elite Four, but it seems your strength is the real deal." + 1: "É frustrante para mim como membro da Elite Four, mas parece que sua força é real." }, "defeat": { - 1: "That was an ace!" + 1: "Essa foi uma jogada de mestre!" } }, "hassel": { "encounter": { - 1: "Prepare to learn firsthand how the fiery breath of ferocious battle feels!" + 1: "Prepare-se para aprender em primeira mão como é a respiração ardente de uma batalha feroz!" }, "victory": { - 1: `Fortune smiled on me this time, but… - $Judging from how the match went, who knows if I will be so lucky next time.`, + 1: `A sorte sorriu para mim desta vez, mas… + $Julgando pelo andamento da luta, quem sabe se serei tão sortudo na próxima vez.`, }, "defeat": { - 1: "That was an ace!" + 1: "Essa foi uma jogada de mestre!" } }, "blue": { "encounter": { - 1: "You must be pretty good to get this far." + 1: "Você deve ser muito bom para chegar tão longe." }, "victory": { - 1: "I've only lost to him and now to you… Him? Hee, hee…" + 1: "Só perdi para ele e agora para você… Ele? Hee, hee…" }, "defeat": { - 1: "See? My power is what got me here." + 1: "Viu? Meu poder é o que me trouxe até aqui." } }, "piers": { "encounter": { - 1: "Get ready for a mosh pit with me and my party! Spikemuth, it's time to rock!" + 1: "Prepare-se para uma mosh pit comigo e minha galera! Spikemuth, é hora de roquear!" }, "victory": { - 1: "Me an' my team gave it our best. Let's meet up again for a battle some time…" + 1: "Eu e minha equipe demos o nosso melhor. Vamos nos encontrar novamente para uma batalha algum dia…" }, "defeat": { - 1: "My throat's ragged from shoutin'… But 'at was an excitin' battle!" + 1: "Minha garganta está desgastada de tanto gritar… Mas essa foi uma batalha empolgante!" } }, "red": { @@ -1528,696 +1517,694 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "jasmine": { "encounter": { - 1: "Oh… Your Pokémon are impressive. I think I will enjoy this." + 1: "Oh… Seus Pokémon são impressionantes. Acho que vou gostar disso." }, "victory": { - 1: "You are truly strong. I'll have to try much harder, too." + 1: "Você é realmente forte. Vou ter que me esforçar muito mais também." }, "defeat": { - 1: "I never expected to win." + 1: "Eu nunca esperei ganhar." } }, "lance_champion": { "encounter": { - 1: "I am still the Champion. I won't hold anything back." + 1: "Ainda sou o Campeão. Não vou segurar nada." }, "victory": { - 1: "This is the emergence of a new Champion." + 1: "Esta é a emergência de um novo Campeão." }, "defeat": { - 1: "I successfully defended my Championship." + 1: "Defendi com sucesso meu Campeonato." } }, "steven": { "encounter": { - 1: `Tell me… What have you seen on your journey with your Pokémon? - $What have you felt, meeting so many other Trainers out there? - $Traveling this rich land… Has it awoken something inside you? - $I want you to come at me with all that you've learned. - $My Pokémon and I will respond in turn with all that we know!`, + 1: `Diga-me… O que você viu na sua jornada com seus Pokémon? + $O que você sentiu, encontrando tantos outros Treinadores por aí? + $Viajar por esta terra rica… Isso despertou algo dentro de você? + $Quero que você venha até mim com tudo o que aprendeu. + $Meus Pokémon e eu responderemos com tudo o que sabemos!`, }, "victory": { - 1: "So I, the Champion, fall in defeat…" + 1: "Então eu, o Campeão, caio em derrota…" }, "defeat": { - 1: "That was time well spent! Thank you!" + 1: "Esse tempo foi bem gasto! Obrigado!" } }, "cynthia": { "encounter": { - 1: "I, Cynthia, accept your challenge! There won't be any letup from me!" + 1: "Eu, Cynthia, aceito seu desafio! Não haverá nenhuma trégua da minha parte!" }, "victory": { - 1: "No matter how fun the battle is, it will always end sometime…" + 1: "Não importa o quão divertida a batalha seja, ela sempre terminará algum dia…" }, "defeat": { - 1: "Even if you lose, never lose your love of Pokémon." + 1: "Mesmo que você perca, nunca perca o amor pelos Pokémon." } }, "iris": { "encounter": { - 1: `Know what? I really look forward to having serious battles with strong Trainers! - $I mean, come on! The Trainers who make it here are Trainers who desire victory with every fiber of their being! - #And they are battling alongside Pokémon that have been through countless difficult battles! - $If I battle with people like that, not only will I get stronger, my Pokémon will, too! - $And we'll get to know each other even better! OK! Brace yourself! - $I'm Iris, the Pokémon League Champion, and I'm going to defeat you!`, + 1: `Sabe de uma coisa? Estou realmente ansiosa para ter batalhas sérias com Treinadores fortes! + $Quero dizer, vamos lá! Os Treinadores que chegam aqui são Treinadores que desejam a vitória com todas as fibras do seu ser! + #E eles estão batalhando ao lado de Pokémon que passaram por inúmeras batalhas difíceis! + $Se eu batalhar com pessoas assim, não só eu ficarei mais forte, meus Pokémon também! + $E nós vamos nos conhecer ainda melhor! OK! Prepare-se! + $Sou Iris, a Campeã da Liga Pokémon, e vou te derrotar!`, }, "victory": { - 1: "Aghhhh… I did my best, but we lost…" + 1: "Aghhhh… Eu dei o meu melhor, mas nós perdemos…" }, "defeat": { - 1: "Yay! We won!" + 1: "Yay! Nós vencemos!" } }, "hau": { "encounter": { - 1: `I wonder if a Trainer battles differently depending on whether they're from a warm region or a cold region. - $Let's test it out!`, + 1: `Eu me pergunto se um Treinador batalha de maneira diferente dependendo se ele é de uma região quente ou fria. + $Vamos testar isso!`, }, "victory": { - 1: "That was awesome! I think I kinda understand your vibe a little better now!" + 1: "Isso foi incrível! Acho que entendi um pouco melhor seu estilo agora!" }, "defeat": { - 1: "Ma-an, that was some kinda battle!" + 1: "Cara, essa foi uma batalha e tanto!" } }, "geeta": { "encounter": { - 1: `I decided to throw my hat in the ring once more. - $Come now… Show me the fruits of your training.`, + 1: `Decidi entrar na batalha mais uma vez. + $Venha agora… Mostre-me os frutos do seu treinamento.`, }, "victory": { - 1: "I eagerly await news of all your achievements!" + 1: "Estou ansiosa para notícias de todas as suas conquistas!" }, "defeat": { - 1: "What's the matter? This isn't all, is it?" + 1: "Qual o problema? Isso é tudo?" } }, "nemona": { "encounter": { - 1: "Yesss! I'm so psyched! Time for us to let loose!" + 1: "Yesss! Estou tão empolgada! Hora de soltar tudo!" }, "victory": { - 1: "Well, that stinks, but I still had fun! I'll getcha next time!" + 1: "Bem, isso foi ruim, mas ainda me diverti! Eu te pego na próxima!" }, "defeat": { - 1: "Well, that was a great battle! Fruitful for sure." + 1: "Bem, essa foi uma ótima batalha! Frutífera, com certeza." } }, "leon": { "encounter": { - 1: "We're gonna have an absolutely champion time!" + 1: "Vamos ter um tempo absolutamente campeão!" }, "victory": { - 1: `My time as Champion is over… - $But what a champion time it's been! - $Thank you for the greatest battle I've ever had!`, + 1: `Meu tempo como Campeão acabou… + $Mas que tempo campeão foi! + $Obrigado pela melhor batalha que já tive!`, }, "defeat": { - 1: "An absolute champion time, that was!" + 1: "Um tempo absolutamente campeão, foi!" } }, "whitney": { "encounter": { - 1: "Hey! Don't you think Pokémon are, like, super cute?" + 1: "Eai! Você não acha que os Pokémon são, tipo, super fofos?" }, "victory": { - 1: "Waaah! Waaah! You're so mean!" + 1: "Waaah! Waaah! Você é tão mau!" }, "defeat": { - 1: "And that's that!" + 1: "E é isso!" } }, "chuck": { "encounter": { - 1: "Hah! You want to challenge me? Are you brave or just ignorant?" + 1: "Hah! Você quer me desafiar? É corajoso ou apenas ignorante?" }, "victory": { - 1: "You're strong! Would you please make me your apprentice?" + 1: "Você é forte! Por favor, me faça seu aprendiz?" }, "defeat": { - 1: "There. Do you realize how much more powerful I am than you?" + 1: "Aí está. Você percebe o quanto sou mais poderoso que você?" } }, "katy": { "encounter": { - 1: "Don't let your guard down unless you would like to find yourself knocked off your feet!" + 1: "Não baixe a guarda, a menos que queira se ver jogado no chão!" }, "victory": { - 1: "All of my sweet little Pokémon dropped like flies!" + 1: "Todos os meus adoráveis Pokémon caíram como moscas!" }, "defeat": { - 1: "Eat up, my cute little Vivillon!" + 1: "Coma, meu adorável Vivillon!" } }, "pryce": { "encounter": { - 1: "Youth alone does not ensure victory! Experience is what counts." + 1: "A juventude sozinha não garante a vitória! Experiência é o que conta." }, "victory": { - 1: "Outstanding! That was perfect. Try not to forget what you feel now." + 1: "Excelente! Isso foi perfeito. Tente não esquecer o que sente agora." }, "defeat": { - 1: "Just as I envisioned." + 1: "Exatamente como eu imaginei." } }, "clair": { "encounter": { - 1: "Do you know who I am? And you still dare to challenge me?" + 1: "Você sabe quem eu sou? E ainda se atreve a me desafiar?" }, "victory": { - 1: "I wonder how far you can get with your skill level. This should be fascinating." + 1: "Eu me pergunto até onde você pode ir com seu nível de habilidade. Isso deve ser fascinante." }, "defeat": { - 1: "That's that." + 1: "E é isso." } }, "maylene": { "encounter": { - 1: `I've come to challenge you now, and I won't hold anything back. - $Please prepare yourself for battle!`, + 1: `Vim desafiá-lo agora e não vou segurar nada. + $Por favor, prepare-se para a batalha!`, }, "victory": { - 1: "I admit defeat…" + 1: "Eu admito a derrota…" }, "defeat": { - 1: "That was awesome." + 1: "Isso foi incrível." } }, "fantina": { "encounter": { - 1: `You shall challenge me, yes? But I shall win. - $That is what the Gym Leader of Hearthome does, non?`, + 1: `Você vai me desafiar, não é? Mas eu vou ganhar. + $É o que a Líder do Ginásio de Hearthome faz, não?`, }, "victory": { - 1: "You are so fantastically strong. I know why I have lost." + 1: "Você é tão incrivelmente forte. Sei porque perdi." }, "defeat": { - 1: "I am so, so, very happy!" + 1: "Estou tão, tão, muito feliz!" } }, "byron": { "encounter": { - 1: `Trainer! You're young, just like my son, Roark. - $With more young Trainers taking charge, the future of Pokémon is bright! - $So, as a wall for young people, I'll take your challenge!`, + 1: `Treinador! Você é jovem, assim como meu filho, Roark. + $Com mais Treinadores jovens assumindo o comando, o futuro dos Pokémon é brilhante! + $Então, como uma parede para os jovens, aceitarei seu desafio!`, }, "victory": { - 1: "Hmm! My sturdy Pokémon--defeated!" + 1: "Hmm! Meus Pokémon robustos--derrotados!" }, "defeat": { - 1: "Gwahahaha! How were my sturdy Pokémon?!" + 1: "Gwahahaha! Como foram meus Pokémon robustos?!" } }, "olympia": { "encounter": { - 1: "An ancient custom deciding one's destiny. The battle begins!" + 1: "Um costume antigo decidindo o destino de alguém. A batalha começa!" }, "victory": { - 1: "Create your own path. Let nothing get in your way. Your fate, your future." + 1: "Crie seu próprio caminho. Não deixe nada te atrapalhar. Seu destino, seu futuro." }, "defeat": { - 1: "Our path is clear now." + 1: "Nosso caminho está claro agora." } }, "volkner": { "encounter": { - 1: `Since you've come this far, you must be quite strong… - $I hope you're the Trainer who'll make me remember how fun it is to battle!`, + 1: `Já que você chegou tão longe, deve ser bastante forte… + $Espero que você seja o Treinador que me faça lembrar como é divertido batalhar!`, }, "victory": { - 1: `You've got me beat… - $Your desire and the noble way your Pokémon battled for you… - $I even felt thrilled during our match. That was a very good battle.`, + 1: `Você me venceu… + $Seu desejo e a maneira nobre como seus Pokémon batalharam por você… + $Eu até me senti emocionado durante nossa luta. Foi uma batalha muito boa.`, }, "defeat": { - 1: `It was not shocking at all… - $That is not what I wanted!`, + 1: `Não foi nada chocante… + $Isso não é o que eu queria!`, } }, "burgh": { "encounter": { - 1: `M'hm… If I win this battle, I feel like I can draw a picture unlike any before it. - $OK! I can hear my battle muse loud and clear. Let's get straight to it!`, - 2: `Of course, I'm really proud of all of my Pokémon! - $Well now… Let's get right to it!` + 1: `M'hm… Se eu ganhar esta batalha, sinto que posso desenhar um quadro diferente de qualquer outro. + $OK! Posso ouvir minha musa da batalha claramente. Vamos direto ao ponto!`, + 2: `Claro, estou realmente orgulhoso de todos os meus Pokémon! + $Bem agora… Vamos direto ao ponto!` }, "victory": { - 1: "Is it over? Has my muse abandoned me?", - 2: "Hmm… It's over! You're incredible!" + 1: "Acabou? Minha musa me abandonou?", + 2: "Hmm… Acabou! Você é incrível!" }, "defeat": { - 1: "Wow… It's beautiful somehow, isn't it…", - 2: `Sometimes I hear people say something was an ugly win. - $I think if you're trying your best, any win is beautiful.` + 1: "Uau… É bonito de alguma forma, não é…", + 2: `Às vezes ouço as pessoas dizerem que foi uma vitória feia. + $Acho que se você está dando o seu melhor, qualquer vitória é bonita.` } }, "elesa": { "encounter": { - 1: `C'est fini! When I'm certain of that, I feel an electric jolt run through my body! - $I want to feel the sensation, so now my beloved Pokémon are going to make your head spin!`, + 1: `C'est fini! Quando tenho certeza disso, sinto um choque elétrico percorrer meu corpo! + $Quero sentir essa sensação, então agora meus amados Pokémon vão fazer sua cabeça girar!`, }, "victory": { - 1: "I meant to make your head spin, but you shocked me instead." + 1: "Eu queria fazer sua cabeça girar, mas você me surpreendeu." }, "defeat": { - 1: "That was unsatisfying somehow… Will you give it your all next time?" + 1: "Isso foi insatisfatório de alguma forma… Você dará tudo de si na próxima vez?" } }, "skyla": { "encounter": { - 1: `It's finally time for a showdown! That means the Pokémon battle that decides who's at the top, right? - $I love being on the summit! 'Cause you can see forever and ever from high places! - $So, how about you and I have some fun?`, + 1: `Finalmente é hora do confronto! Isso significa a batalha Pokémon que decide quem está no topo, certo? + $Eu amo estar no topo! Porque você pode ver para sempre e sempre de lugares altos! + $Então, que tal nós nos divertirmos?`, }, "victory": { - 1: "Being your opponent in battle is a new source of strength to me. Thank you!" + 1: "Ser seu oponente na batalha é uma nova fonte de força para mim. Obrigada!" }, "defeat": { - 1: "Win or lose, you always gain something from a battle, right?" + 1: "Ganhar ou perder, você sempre ganha algo com uma batalha, certo?" } }, "brycen": { "encounter": { - 1: `There is also strength in being with other people and Pokémon. - $Receiving their support makes you stronger. I'll show you this power!`, + 1: `Há também força em estar com outras pessoas e Pokémon. + $Receber o apoio deles te fortalece. Vou te mostrar esse poder!`, }, "victory": { - 1: "The wonderful combination of you and your Pokémon! What a beautiful friendship!" + 1: "A maravilhosa combinação de você e seus Pokémon! Que amizade linda!" }, "defeat": { - 1: "Extreme conditions really test you and train you!" + 1: "Condições extremas realmente testam e treinam você!" } }, "drayden": { "encounter": { - 1: `What I want to find is a young Trainer who can show me a bright future. - $Let's battle with everything we have: your skill, my experience, and the love we've raised our Pokémon with!`, + 1: `O que eu quero encontrar é um jovem Treinador que possa me mostrar um futuro brilhante. + $Vamos batalhar com tudo o que temos: sua habilidade, minha experiência e o amor com que criamos nossos Pokémon!`, }, "victory": { - 1: "This intense feeling that floods me after a defeat… I don't know how to describe it." + 1: "Esse sentimento intenso que me invade após uma derrota… Não sei como descrevê-lo." }, "defeat": { - 1: "Harrumph! I know your ability is greater than that!" + 1: "Harrumph! Sei que sua habilidade é maior que isso!" } }, "grant": { "encounter": { - 1: `There is only one thing I wish for. - $That by surpassing one another, we find a way to even greater heights.`, + 1: `Só há uma coisa que desejo. + $Que, superando um ao outro, encontremos um caminho para alturas ainda maiores.`, }, "victory": { - 1: "You are a wall that I am unable to surmount!" + 1: "Você é uma parede que não consigo superar!" }, "defeat": { - 1: `Do not give up. - $That is all there really is to it. - $The most important lessons in life are simple.`, + 1: `Não desista. + $Isso é tudo o que realmente importa. + $As lições mais importantes da vida são simples.`, } }, "korrina": { "encounter": { - 1: "Time for Lady Korrina's big appearance!" + 1: "Hora da grande aparição de Lady Korrina!" }, "victory": { - 1: "It's your very being that allows your Pokémon to evolve!" + 1: "É o seu próprio ser que permite que seus Pokémon evoluam!" }, "defeat": { - 1: "What an explosive battle!" + 1: "Que batalha explosiva!" } }, "clemont": { "encounter": { - 1: "Oh! I'm glad that we got to meet!" + 1: "Oh! Estou feliz por termos nos encontrado!" }, "victory": { - 1: "Your passion for battle inspires me!" + 1: "Sua paixão pela batalha me inspira!" }, "defeat": { - 1: "Looks like my Trainer-Grow-Stronger Machine, Mach 2 is really working!" + 1: "Parece que minha Máquina Treinadora-Crescer-Forte, Mach 2 está realmente funcionando!" } }, "valerie": { "encounter": { - 1: `Oh, if it isn't a young Trainer… It is lovely to get to meet you like this. - $Then I suppose you have earned yourself the right to a battle, as a reward for your efforts. - $The elusive Fairy may appear frail as the breeze and delicate as a bloom, but it is strong.`, + 1: `Oh, se não é um jovem Treinador… É adorável conhecê-lo assim. + $Então, suponho que você ganhou o direito a uma batalha, como recompensa por seus esforços. + $O elusivo Fada pode parecer frágil como a brisa e delicado como uma flor, mas é forte.`, }, "victory": { - 1: "I hope that you will find things worth smiling about tomorrow…" + 1: "Espero que você encontre coisas para sorrir amanhã…" }, "defeat": { - 1: "Oh goodness, what a pity…" + 1: "Oh meu Deus, que pena…" } }, "wulfric": { "encounter": { - 1: `You know what? We all talk big about what you learn from battling and bonds and all that… - $But really, I just do it 'cause it's fun. - $Who cares about the grandstanding? Let's get to battling!`, + 1: `Sabe de uma coisa? Todos falamos muito sobre o que você aprende com as batalhas e os laços e tudo mais… + $Mas realmente, eu só faço isso porque é divertido. + $Quem se importa com o grandioso? Vamos batalhar!`, }, "victory": { - 1: "Outstanding! I'm tough as an iceberg, but you smashed me through and through!" + 1: "Incrível! Sou duro como um iceberg, mas você me quebrou por completo!" }, "defeat": { - 1: "Tussle with me and this is what happens!" + 1: "Lute comigo e é isso que acontece!" } }, "kabu": { "encounter": { - 1: `Every Trainer and Pokémon trains hard in pursuit of victory. - $But that means your opponent is also working hard to win. - $In the end, the match is decided by which side is able to unleash their true potential.`, + 1: `Todo Treinador e Pokémon treina duro em busca da vitória. + $Mas isso significa que seu oponente também está se esforçando para vencer. + $No final, a partida é decidida por qual lado é capaz de liberar seu verdadeiro potencial.`, }, "victory": { - 1: "I'm glad I could battle you today!" + 1: "Estou feliz por poder lutar com você hoje!" }, "defeat": { - 1: "That's a great way for me to feel my own growth!" + 1: "É uma ótima maneira de sentir meu próprio crescimento!" } }, "bea": { "encounter": { - 1: `Do you have an unshakable spirit that won't be moved, no matter how you are attacked? - $I think I'll just test that out, shall I?`, + 1: `Você tem um espírito inabalável que não será movido, não importa como você seja atacado? + $Acho que vou testar isso, certo?`, }, "victory": { - 1: "I felt the fighting spirit of your Pokémon as you led them in battle." + 1: "Senti o espírito de luta de seus Pokémon enquanto você os liderava na batalha." }, "defeat": { - 1: "That was the best sort of match anyone could ever hope for." + 1: "Essa foi a melhor partida que alguém poderia esperar." } }, "opal": { "encounter": { - 1: "Let me have a look at how you and your partner Pokémon behave!" + 1: "Deixe-me ver como você e seu Pokémon parceiro se comportam!" }, "victory": { - 1: "Your pink is still lacking, but you're an excellent Trainer with excellent Pokémon." + 1: "Seu rosa ainda está faltando, mas você é um Treinador excelente com Pokémon excelentes." }, "defeat": { - 1: "Too bad for you, I guess." + 1: "Muito ruim para você, eu acho." } }, "bede": { "encounter": { - 1: "I suppose I should prove beyond doubt just how pathetic you are and how strong I am." + 1: "Suponho que devo provar além de qualquer dúvida o quão patético você é e quão forte eu sou." }, "victory": { - 1: "I see… Well, that's fine. I wasn't really trying all that hard anyway." + 1: "Eu vejo… Bem, tudo bem. Eu não estava me esforçando muito de qualquer maneira." }, "defeat": { - 1: "Not a bad job, I suppose." + 1: "Bom trabalho, eu suponho." } }, "gordie": { "encounter": { - 1: "So, let's get this over with." + 1: "Então, vamos acabar com isso." }, "victory": { - 1: "I just want to climb into a hole… Well, I guess it'd be more like falling from here." + 1: "Eu só quero me enterrar em um buraco… Bem, acho que seria mais como cair daqui." }, "defeat": { - 1: "Battle like you always do, victory will follow!" + 1: "Batalhe como sempre faz, a vitória seguirá!" } }, "marnie": { "encounter": { - 1: `The truth is, when all's said and done… I really just wanna become Champion for myself! - $So don't take it personal when I kick your butt!`, + 1: `A verdade é que, quando tudo está dito e feito… Eu realmente só quero me tornar Campeã por mim mesma! + $Então, não leve para o pessoal quando eu chutar seu traseiro!`, }, "victory": { - 1: "OK, so I lost… But I got to see a lot of the good points of you and your Pokémon!" + 1: "OK, então eu perdi… Mas consegui ver muitos dos pontos bons de você e seus Pokémon!" }, "defeat": { - 1: "Hope you enjoyed our battle tactics." + 1: "Espero que você tenha gostado das nossas táticas de batalha." } }, "raihan": { "encounter": { - 1: "I'm going to defeat the Champion, win the whole tournament, and prove to the world just how strong the great Raihan really is!" + 1: "Vou derrotar o Campeão, vencer todo o torneio e provar ao mundo o quão forte o grande Raihan realmente é!" }, "victory": { - 1: `I look this good even when I lose. - $It's a real curse. - $Guess it's time for another selfie!`, + 1: `Eu pareço bem mesmo quando perco. + $É uma verdadeira maldição. + $Acho que é hora de mais uma selfie!`, }, "defeat": { - 1: "Let's take a selfie to remember this." + 1: "Vamos tirar uma selfie para lembrar disso." } }, "brassius": { "encounter": { - 1: "I assume you are ready? Let our collaborative work of art begin!" + 1: "Pressuponho que você está pronto? Que nossa obra de arte colaborativa comece!" }, "victory": { - 1: "Ahhh…vant-garde!" + 1: "Ahhh…avant-garde!" }, "defeat": { - 1: "I will begin on a new piece at once!" + 1: "Começarei uma nova peça imediatamente!" } }, "iono": { "encounter": { - 1: `How're ya feelin' about this battle? + 1: `Como você está se sentindo sobre esta batalha? $... - $Let's get this show on the road! How strong is our challenger? - $I 'unno! Let's find out together!`, + $Vamos começar o show! Quão forte é o nosso desafiador? + $Eu não sei! Vamos descobrir juntos!`, }, "victory": { - 1: "You're as flashy and bright as a 10,000,000-volt Thunderbolt, friendo!" + 1: "Você é tão chamativo e brilhante quanto um Raio do Trovão de 10.000.000 volts, amigo!" }, "defeat": { - 1: "Your eyeballs are MINE!" + 1: "Seus olhos são MEUS!" } }, "larry": { "encounter": { - 1: "When all's said and done, simplicity is strongest." + 1: "Quando tudo está dito e feito, a simplicidade é mais forte." }, "victory": { - 1: "A serving of defeat, huh?" + 1: "Uma porção de derrota, hein?" }, "defeat": { - 1: "I'll call it a day." + 1: "Vou encerrar o dia." } }, "ryme": { "encounter": { - 1: "Come on, baby! Rattle me down to the bone!" + 1: "Vamos lá, baby! Me agite até os ossos!" }, "victory": { - 1: "You're cool, my friend—you move my SOUL!" + 1: "Você é legal, meu amigo—você move minha ALMA!" }, "defeat": { - 1: "Later, baby!" + 1: "Até mais, baby!" } }, "grusha": { "encounter": { - 1: "All I need to do is make sure the power of my Pokémon chills you to the bone!" + 1: "Tudo o que preciso fazer é garantir que o poder do meu Pokémon te arrependa até os ossos!" }, "victory": { - 1: "Your burning passion… I kinda like it, to be honest." + 1: "Sua paixão ardente... Eu meio que gosto, para ser honesto." }, "defeat": { - 1: "Things didn't heat up for you." + 1: "As coisas não esquentaram para você." } }, "marnie_elite": { "encounter": { - 1: "You've made it this far, huh? Let's see if you can handle my Pokémon!", - 2: "I'll give it my best shot, but don't think I'll go easy on you!" + 1: "Você chegou até aqui, hein? Vamos ver se você pode lidar com meus Pokémon!", + 2: "Vou dar o meu melhor, mas não pense que vou pegar leve com você!" }, "victory": { - 1: "I can't believe I lost... But you deserved that win. Well done!", - 2: "Looks like I've still got a lot to learn. Great battle, though!" + 1: "Não acredito que perdi... Mas você mereceu essa vitória. Bem feito!", + 2: "Parece que ainda tenho muito a aprender. Grande batalha, porém!" }, "defeat": { - 1: "You put up a good fight, but I've got the edge! Better luck next time!", - 2: "Seems like my training's paid off. Thanks for the battle!" + 1: "Você lutou bem, mas eu tenho a vantagem! Melhor sorte na próxima vez!", + 2: "Parece que meu treinamento valeu a pena. Obrigado pela batalha!" } }, "nessa_elite": { "encounter": { - 1: "The tides are turning in my favor. Ready to get swept away?", - 2: "Let's make some waves with this battle! I hope you're prepared!" + 1: "As marés estão mudando a meu favor. Pronto para ser levado pela corrente?", + 2: "Vamos fazer ondas com esta batalha! Espero que esteja preparado!" }, "victory": { - 1: "You navigated those waters perfectly... Well done!", - 2: "Looks like my currents were no match for you. Great job!" + 1: "Você navegou nessas águas perfeitamente... Bem feito!", + 2: "Parece que minhas correntes não foram páreo para você. Bom trabalho!" }, "defeat": { - 1: "Water always finds a way. That was a refreshing battle!", - 2: "You fought well, but the ocean's power is unstoppable!" + 1: "A água sempre encontra um caminho. Essa foi uma batalha refrescante!", + 2: "Você lutou bem, mas o poder do oceano é imparável!" } }, "bea_elite": { "encounter": { - 1: "Prepare yourself! My fighting spirit burns bright!", - 2: "Let's see if you can keep up with my relentless pace!" + 1: "Prepare-se! Meu espírito de luta brilha intensamente!", + 2: "Vamos ver se você consegue acompanhar meu ritmo implacável!" }, "victory": { - 1: "Your strength... It's impressive. You truly deserve this win.", - 2: "I've never felt this intensity before. Amazing job!" + 1: "Sua força... É impressionante. Você realmente merece essa vitória.", + 2: "Nunca senti essa intensidade antes. Trabalho incrível!" }, "defeat": { - 1: "Another victory for my intense training regimen! Well done!", - 2: "You've got strength, but I trained harder. Great battle!" + 1: "Outra vitória para meu rigoroso regime de treinamento! Bem feito!", + 2: "Você tem força, mas eu treinei mais. Grande batalha!" } }, "allister_elite": { "encounter": { - 1: "Shadows fall... Are you ready to face your fears?", - 2: "Let's see if you can handle the darkness that I command." + 1: "As sombras caem... Você está pronto para enfrentar seus medos?", + 2: "Vamos ver se você pode lidar com a escuridão que eu comando." }, "victory": { - 1: "You've dispelled the shadows... For now. Well done.", - 2: "Your light pierced through my darkness. Great job." + 1: "Você dissipou as sombras... Por enquanto. Bem feito.", + 2: "Sua luz atravessou minha escuridão. Ótimo trabalho." }, "defeat": { - 1: "The shadows have spoken... Your strength isn't enough.", - 2: "Darkness triumphs... Maybe next time you'll see the light." + 1: "As sombras falaram... Sua força não é suficiente.", + 2: "A escuridão triunfa... Talvez na próxima vez você veja a luz." } }, "raihan_elite": { "encounter": { - 1: "Storm's brewing! Let's see if you can weather this fight!", - 2: "Get ready to face the eye of the storm!" + 1: "Tempestade se formando! Vamos ver se você aguenta essa luta!", + 2: "Prepare-se para enfrentar o olho da tempestade!" }, "victory": { - 1: "You've bested the storm... Incredible job!", - 2: "You rode the winds perfectly... Great battle!" + 1: "Você enfrentou a tempestade... Trabalho incrível!", + 2: "Você navegou nos ventos perfeitamente... Grande batalha!" }, "defeat": { - 1: "Another storm weathered, another victory claimed! Well fought!", - 2: "You got caught in my storm! Better luck next time!" + 1: "Outra tempestade enfrentada, outra vitória conquistada! Bem lutado!", + 2: "Você foi pego na minha tempestade! Melhor sorte na próxima vez!" } }, "rival": { "encounter": { - 1: `@c{smile}Hey, I was looking for you! I knew you were eager to get going but I expected at least a goodbye… - $@c{smile_eclosed}So you're really pursuing your dream after all?\n I almost can't believe it. - $@c{serious_smile_fists}Since we're here, how about a battle?\nAfter all, I want to make sure you're ready. - $@c{serious_mopen_fists}Don't hold back, I want you to give me everything you've got!` + 1: `@c{smile}Eai, estava procurando você! Sabia que você estava ansioso para começar, mas esperava pelo menos um tchau… + $@c{smile_eclosed}Então você está realmente perseguindo seu sonho, hein?\n Quase não consigo acreditar. + $@c{serious_smile_fists}Já que estamos aqui, que tal uma batalha?\nAfinal, quero ter certeza de que você está pronto. + $@c{serious_mopen_fists}Não se segure, quero que você dê tudo de si!` }, "victory": { - 1: `@c{shock}Wow… You cleaned me out.\nAre you actually a beginner? - $@c{smile}Maybe it was a bit of luck but…\nWho knows you might just be able to go all the way. - $By the way, the professor asked me to give you these items. They look pretty cool. - $@c{serious_smile_fists}Good luck out there!` + 1: `@c{shock}Caramba… Você me limpou.\nVocê é mesmo um novato? + $@c{smile}Talvez tenha sido um pouco de sorte, mas…\nQuem sabe você consiga chegar até o fim. + $Aliás, o professor me pediu para te dar esses itens. Eles parecem bem legais. + $@c{serious_smile_fists}Boa sorte lá fora!` }, }, "rival_female": { "encounter": { - 1: `@c{smile_wave}There you are! I've been looking everywhere for you!\n@c{angry_mopen}Did you forget to say goodbye to your best friend? - $@c{smile_ehalf}You're going after your dream, huh?\nThat day is really today isn't it… - $@c{smile}Anyway, I'll forgive you for forgetting me, but on one condition. @c{smile_wave_wink}You have to battle me! - $@c{angry_mopen}Give it your all! Wouldn't want your adventure to be over before it started, right?` + 1: `@c{smile_wave}Aí está você! Procurei você em todo lugar!\n@c{angry_mopen}Esqueceu de se despedir da sua melhor amiga? + $@c{smile_ehalf}Você está indo atrás do seu sonho, né?\nEsse dia realmente chegou, não é… + $@c{smile}Enfim, vou te perdoar por ter me esquecido, mas com uma condição. @c{smile_wave_wink}Você tem que lutar comigo! + $@c{angry_mopen}Dê o seu melhor! Não quer que sua aventura acabe antes de começar, né?` }, "victory": { - 1: `@c{shock}You just started and you're already this strong?!@d{96}\n@c{angry}You totally cheated, didn't you? - $@c{smile_wave_wink}Just kidding!@d{64} @c{smile_eclosed}I lost fair and square… I have a feeling you're going to do really well out there. - $@c{smile}By the way, the professor wanted me to give you some items. Hopefully they're helpful! - $@c{smile_wave}Do your best like always! I believe in you!` + 1: `@c{shock}Você acabou de começar e já está tão forte?!@d{96}\n@c{angry}Você trapaceou, não foi? + $@c{smile_wave_wink}Brincadeirinha!@d{64} @c{smile_eclosed}Eu perdi de forma justa… Tenho a sensação de que você vai se sair muito bem lá fora. + $@c{smile}Aliás, o professor pediu para eu te dar alguns itens. Espero que sejam úteis! + $@c{smile_wave}Dê o seu melhor, como sempre! Eu acredito em você!` }, }, "rival_2": { "encounter": { - 1: `@c{smile}Hey, you're here too?\n@c{smile_eclosed}Still a perfect record, huh…? - $@c{serious_mopen_fists}I know it kind of looks like I followed you here, but that's mostly not true. - $@c{serious_smile_fists}Honestly though, I've been itching for a rematch since you beat me back at home. - $I've been doing a lot of my own training so I'll definitely put up a fight this time. - $@c{serious_mopen_fists}Don't hold back, just like before!\nLet's go!` + 1: `@c{smile}Eai, você também está aqui?\n@c{smile_eclosed}Ainda com um recorde perfeito, hein…? + $@c{serious_mopen_fists}Sei que parece que eu te segui até aqui, mas isso não é totalmente verdade. + $@c{serious_smile_fists}Sinceramente, tenho estado ansioso por uma revanche desde que você me venceu em casa. + $Tenho treinado bastante, então vou dar uma luta difícil desta vez. + $@c{serious_mopen_fists}Não se segure, assim como antes!\nVamos lá!` }, "victory": { - 1: `@c{neutral_eclosed}Oh. I guess I was overconfident. - $@c{smile}That's alright, though. I figured this might happen.\n@c{serious_mopen_fists}It just means I need to try harder for next time!\n - $@c{smile}Oh, not that you really need the help, but I had an extra one of these lying around and figured you might want it.\n - $@c{serious_smile_fists}Don't expect another one after this, though!\nI can't keep giving my opponent an advantage after all. - $@c{smile}Anyway, take care!` + 1: `@c{neutral_eclosed}Ah. Acho que fui confiante demais. + $@c{smile}Tudo bem, no entanto. Eu imaginei que isso poderia acontecer.\n@c{serious_mopen_fists}Isso só significa que preciso me esforçar mais para a próxima vez!\n + $@c{smile}Ah, não que você precise realmente de ajuda, mas eu tinha um extra desses itens e pensei que você poderia querer. + $@c{serious_smile_fists}Não espere outro depois deste!\nNão posso continuar dando vantagem ao meu oponente. + $@c{smile}Enfim, cuide-se!` }, }, "rival_2_female": { "encounter": { - 1: `@c{smile_wave}Oh, fancy meeting you here. Looks like you're still undefeated. @c{angry_mopen}Huh… Not bad! - $@c{angry_mopen}I know what you're thinking, and no, I wasn't creeping on you. @c{smile_eclosed}I just happened to be in the area. - $@c{smile_ehalf}I'm happy for you but I just want to let you know that it's OK to lose sometimes. - $@c{smile}We learn from our mistakes, often more than we would if we kept succeeding. - $@c{angry_mopen}In any case, I've been training hard for our rematch, so you'd better give it your all!` + 1: `@c{smile_wave}Oh, que surpresa te encontrar aqui. Parece que você ainda está invicto. @c{angry_mopen}Hum… Nada mal! + $@c{angry_mopen}Eu sei o que você está pensando, e não, eu não estava te espionando. @c{smile_eclosed}Acontece que eu estava na área. + $@c{smile_ehalf}Estou feliz por você, mas só quero te avisar que está tudo bem perder às vezes. + $@c{smile}Aprendemos com nossos erros, muitas vezes mais do que se continuássemos vencendo. + $@c{angry_mopen}De qualquer forma, tenho treinado duro para nossa revanche, então é melhor você dar o seu melhor!` }, "victory": { - 1: `@c{neutral}I… wasn't supposed to lose that time… - $@c{smile}Aw well. That just means I'll have to train even harder for next time! - $@c{smile_wave}I also got you another one of these!\n@c{smile_wave_wink}No need to thank me~. - $@c{angry_mopen}This is the last one, though! You won't be getting anymore freebies from me after this! - $@c{smile_wave}Keep at it!` + 1: `@c{neutral}Eu… não era para eu perder dessa vez… + $@c{smile}Ah bem. Isso só significa que vou ter que treinar ainda mais para a próxima vez! + $@c{smile_wave}Também consegui mais um desses para você!\n@c{smile_wave_wink}Não precisa me agradecer~. + $@c{angry_mopen}Este é o último, hein! Você não vai ganhar mais nenhum presente de mim depois desse! + $@c{smile_wave}Continue assim!` }, "defeat": { - 1: "It's OK to lose sometimes…" + 1: "Está tudo bem perder às vezes…" } }, "rival_3": { "encounter": { - 1: `@c{smile}Hey, look who it is! It's been a while.\n@c{neutral}You're… still undefeated? Huh. - $@c{neutral_eclosed}Things have been kind of… strange.\nIt's not the same back home without you. - $@c{serious}I know it's selfish, but I need to get this off my chest.\n@c{neutral_eclosed}I think you're in over your head here. - $@c{serious}Never losing once is just unrealistic.\nWe need to lose sometimes in order to grow. - $@c{neutral_eclosed}You've had a great run but there's still so much ahead, and it only gets harder. @c{neutral}Are you prepared for that? - $@c{serious_mopen_fists}If so, prove it to me.` + 1: `@c{smile}Eai, olha quem é! Faz um tempo.\n@c{neutral}Você… ainda está invicto? Hum. + $@c{neutral_eclosed}As coisas têm sido meio… estranhas.\nNão é a mesma coisa em casa sem você. + $@c{serious}Eu sei que é egoísta, mas preciso desabafar.\n@c{neutral_eclosed}Acho que você está se metendo em algo grande demais aqui. + $@c{serious}Nunca perder é irrealista.\nPrecisamos perder às vezes para crescer. + $@c{neutral_eclosed}Você teve uma grande jornada, mas ainda há muito pela frente, e só vai ficar mais difícil. @c{neutral}Você está preparado para isso? + $@c{serious_mopen_fists}Se sim, prove para mim.` }, "victory": { - 1: "@c{angry_mhalf}This is ridiculous… I've hardly stopped training…\nHow are we still so far apart?" + 1: "@c{angry_mhalf}Isso é ridículo… Eu mal parei de treinar…\nComo ainda estamos tão distantes?" }, }, "rival_3_female": { "encounter": { - 1: `@c{smile_wave}Long time no see! Still haven't lost, huh.\n@c{angry}You're starting to get on my nerves. @c{smile_wave_wink}Just kidding! - $@c{smile_ehalf}But really, don't you miss home by now? Or… me?\nI… I mean, we've really missed you. - $@c{smile_eclosed}I support you in your dream and everything, but the reality is you're going to lose sooner or later. - $@c{smile}And when you do, I'll be there for you like always.\n@c{angry_mopen}Now, let me show you how strong I've become!` + 1: `@c{smile_wave}Quanto tempo! Ainda não perdeu, né.\n@c{angry}Você está começando a me irritar. @c{smile_wave_wink}Brincadeirinha! + $@c{smile_ehalf}Mas sério, você não sente saudades de casa? Ou… de mim?\nEu… Eu quero dizer, sentimos muito a sua falta. + $@c{smile_eclosed}Eu apoio o seu sonho e tudo mais, mas a realidade é que você vai perder mais cedo ou mais tarde. + $@c{smile}E quando isso acontecer, estarei lá para você, como sempre.\n@c{angry_mopen}Agora, deixe-me mostrar o quão forte eu me tornei!` }, "victory": { - 1: "@c{shock}After all that… it wasn't enough…?\nYou'll never come back at this rate…" - + 1: "@c{shock}Depois de tudo isso… não foi o suficiente…?\nVocê nunca vai voltar a esse ritmo…" }, "defeat": { - 1: "You gave it your best, now let's go home." + 1: "Você deu o seu melhor, agora vamos para casa." } }, "rival_4": { "encounter": { - 1: `@c{neutral}Hey. - $I won't mince words or pleasantries with you.\n@c{neutral_eclosed}I'm here to win, plain and simple. - $@c{serious_mhalf_fists}I've learned to maximize my potential by putting all my time into training. - $@c{smile}You get a lot of extra time when you cut out the unnecessary sleep and social interaction. - $@c{serious_mopen_fists}None of that matters anymore, not until I win. - $@c{neutral_eclosed}I've even reached the point where I don't lose anymore.\n@c{smile_eclosed}I suppose your philosophy wasn't so wrong after all. - $@c{angry_mhalf}Losing is for the weak, and I'm not weak anymore. - $@c{serious_mopen_fists}Prepare yourself.` + 1: `@c{neutral}Oi. + $Não vou enrolar com você.\n@c{neutral_eclosed}Estou aqui para vencer, simples assim. + $@c{serious_mhalf_fists}Aprendi a maximizar meu potencial dedicando todo o meu tempo ao treino. + $@c{smile}Você ganha muito tempo extra quando corta o sono e a interação social desnecessários. + $@c{serious_mopen_fists}Nada disso importa mais, não até eu vencer. + $@c{neutral_eclosed}Cheguei ao ponto de não perder mais.\n@c{smile_eclosed}Acho que sua filosofia não estava tão errada afinal. + $@c{angry_mhalf}Perder é para os fracos, e eu não sou mais fraco. + $@c{serious_mopen_fists}Prepare-se.` }, "victory": { - 1: "@c{neutral}What…@d{64} What are you?" + 1: "@c{neutral}O que…@d{64} O que você é?" }, }, "rival_4_female": { "encounter": { - 1: `@c{neutral}It's me! You didn't forget about me again… did you? - $@c{smile}You should be proud of how far you made it. Congrats!\nBut it looks like it's the end of your journey. - $@c{smile_eclosed}You've awoken something in me I never knew was there.\nIt seems like all I do now is train. - $@c{smile_ehalf}I hardly even eat or sleep now, I just train my Pokémon all day, getting stronger every time. - $@c{neutral}In fact, I… hardly recognize myself. - $And now, I've finally reached peak performance.\nI don't think anyone could beat me now. - $And you know what? It's all because of you.\n@c{smile_ehalf}I don't know whether to thank you or hate you. - $@c{angry_mopen}Prepare yourself.` + 1: `@c{neutral}Sou eu! Você não esqueceu de mim de novo… esqueceu? + $@c{smile}Você deveria se orgulhar de até onde chegou. Parabéns!\nMas parece que é o fim da sua jornada. + $@c{smile_eclosed}Você despertou algo em mim que eu nunca soube que existia.\nParece que agora tudo o que faço é treinar. + $@c{smile_ehalf}Eu mal como ou durmo agora, só treino meus Pokémon o dia todo, ficando mais forte a cada vez. + $@c{neutral}Na verdade, eu… mal me reconheço. + $E agora, finalmente atingi o desempenho máximo.\nNão acho que alguém poderia me vencer agora. + $E sabe de uma coisa? É tudo por sua causa.\n@c{smile_ehalf}Eu não sei se te agradeço ou te odeio. + $@c{angry_mopen}Prepare-se.` }, "victory": { - 1: "@c{neutral}What…@d{64} What are you?" - + 1: "@c{neutral}O que…@d{64} O que você é?" }, "defeat": { - 1: "$@c{smile}You should be proud of how far you made it." + 1: "$@c{smile}Você deveria se orgulhar de até onde chegou." } }, "rival_5": { @@ -2234,7 +2221,6 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "victory": { 1: "@c{neutral}…" - }, "defeat": { 1: "$@c{smile_ehalf}…" @@ -2242,210 +2228,211 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "rival_6": { "encounter": { - 1: `@c{smile_eclosed}We meet again. - $@c{neutral}I've had some time to reflect on all this.\nThere's a reason this all seems so strange. - $@c{neutral_eclosed}Your dream, my drive to beat you…\nIt's all a part of something greater. - $@c{serious}This isn't about me, or about you… This is about the world, @c{serious_mhalf_fists}and it's my purpose to push you to your limits. - $@c{neutral_eclosed}Whether I've fulfilled that purpose I can't say, but I've done everything in my power. - $@c{neutral}This place we ended up in is terrifying… Yet somehow I feel unphased, like I've been here before. - $@c{serious_mhalf_fists}You feel the same, don't you? - $@c{serious}…and it's like something here is speaking to me.\nThis is all the world's known for a long time now. - $Those times we cherished together that seem so recent are nothing but a distant memory. - $@c{neutral_eclosed}Who can say whether they were ever even real in the first place. - $@c{serious_mopen_fists}You need to keep pushing, because if you don't, it will never end. You're the only one who can do this. - $@c{serious_smile_fists}I hardly know what any of this means, I just know that it's true. - $@c{serious_mopen_fists}If you can't defeat me here and now, you won't stand a chance.` + 1: `@c{smile_eclosed}Nos encontramos de novo. + $@c{neutral}Tive um tempo para refletir sobre tudo isso.\nHá uma razão para tudo isso parecer tão estranho. + $@c{neutral_eclosed}Seu sonho, minha vontade de te vencer…\nTudo faz parte de algo maior. + $@c{serious}Isso não é sobre mim, nem sobre você… É sobre o mundo, @c{serious_mhalf_fists}e é meu propósito te levar ao limite. + $@c{neutral_eclosed}Se cumpri esse propósito, não posso dizer, mas fiz tudo ao meu alcance. + $@c{neutral}Este lugar em que acabamos é assustador… Mas de alguma forma me sinto indiferente, como se já tivesse estado aqui antes. + $@c{serious_mhalf_fists}Você sente o mesmo, não sente? + $@c{serious}…é como se algo aqui estivesse falando comigo.\nIsso é tudo o que o mundo conhece há muito tempo. + $Aqueles momentos que apreciamos juntos que parecem tão recentes não passam de uma memória distante. + $@c{neutral_eclosed}Quem pode dizer se eles foram realmente reais em primeiro lugar. + $@c{serious_mopen_fists}Você precisa continuar empurrando, porque se não o fizer, isso nunca vai acabar. Você é o único que pode fazer isso. + $@c{serious_smile_fists}Eu mal sei o que tudo isso significa, só sei que é verdade. + $@c{serious_mopen_fists}Se você não pode me derrotar aqui e agora, você não terá chance.` }, "victory": { - 1: `@c{smile_eclosed}It looks like my work is done here. - $I want you to promise me one thing.\n@c{smile}After you heal the world, please come home.` + 1: `@c{smile_eclosed}Parece que meu trabalho aqui está feito. + $Quero que você me prometa uma coisa.\n@c{smile}Depois que curar o mundo, por favor, volte para casa.` }, }, "rival_6_female": { "encounter": { - 1: `@c{smile_ehalf}So it's just us again. - $@c{smile_eclosed}You know, I keep going around and around in my head… - $@c{smile_ehalf}There's something to all this, why everything seems so strange now… - $@c{smile}You have your dream, and I have this ambition in me… - $I just can't help but feel there's a greater purpose to all this, to what we're doing, you and I. - $@c{smile_eclosed}I think I'm supposed to push you… to your limits. - $@c{smile_ehalf}I'm not sure if I've been doing a good job at that, but I've tried my best up to now. - $It's something about this strange and dreadful place… Everything seems so clear… - $This… is all the world's known for a long time now. - $@c{smile_eclosed}It's like I can barely remember the memories we cherished together. - $@c{smile_ehalf}Were they even real? They seem so far away now… - $@c{angry_mopen}You need to keep pushing, because if you don't, it will never end. You're the only one who can do this. - $@c{smile_ehalf}I… don't know what all this means… but I feel it's true. - $@c{neutral}If you can't defeat me here and now, you won't stand a chance.` + 1: `@c{smile_ehalf}Então somos só nós de novo. + $@c{smile_eclosed}Sabe, continuo pensando nisso… + $@c{smile_ehalf}Há algo nisso tudo, por que tudo parece tão estranho agora… + $@c{smile}Você tem seu sonho, e eu tenho essa ambição em mim… + $Não consigo evitar sentir que há um propósito maior em tudo isso, no que estamos fazendo, você e eu. + $@c{smile_eclosed}Acho que devo te levar ao limite. + $@c{smile_ehalf}Não tenho certeza se estou fazendo um bom trabalho nisso, mas tentei meu melhor até agora. + $Há algo neste lugar estranho e terrível… Tudo parece tão claro… + $Isso… é tudo o que o mundo conhece há muito tempo. + $@c{smile_eclosed}É como se eu mal pudesse lembrar das memórias que apreciamos juntos. + $@c{smile_ehalf}Elas foram reais? Elas parecem tão distantes agora… + $@c{angry_mopen}Você precisa continuar empurrando, porque se não o fizer, isso nunca vai acabar. Você é o único que pode fazer isso. + $@c{smile_ehalf}Eu… não sei o que tudo isso significa… mas sinto que é verdade. + $@c{neutral}Se você não pode me derrotar aqui e agora, você não terá chance.` }, "victory": { - 1: `@c{smile_ehalf}I… I think I fulfilled my purpose… - $@c{smile_eclosed}Promise me… After you heal the world… Please… come home safe. - $@c{smile_ehalf}…Thank you.` - + 1: `@c{smile_ehalf}Eu… acho que cumpri meu propósito… + $@c{smile_eclosed}Prometa-me… Depois que curar o mundo… Por favor… volte para casa. + $@c{smile_ehalf}…Obrigada.` }, }, }; -// Dialogue of the NPCs in the game when the player character is female. For languages that do not have gendered pronouns, this can be set to PGMdialogue. +// Diálogo dos NPCs no jogo quando o personagem do jogador é feminino. Para idiomas que não possuem pronomes de gênero, isso pode ser definido como PGMdialogue. export const PGFdialogue: DialogueTranslationEntries = PGMdialogue; -// Dialogue of the endboss of the game when the player character is male (Or unset) +// Diálogo do chefe final do jogo quando o personagem do jogador é masculino (ou não definido) export const PGMbattleSpecDialogue: SimpleTranslationEntries = { - "encounter": `It appears the time has finally come once again.\nYou know why you have come here, do you not? - $You were drawn here, because you have been here before.\nCountless times. - $Though, perhaps it can be counted.\nTo be precise, this is in fact your 5,643,853rd cycle. - $Each cycle your mind reverts to its former state.\nEven so, somehow, remnants of your former selves remain. - $Until now you have yet to succeed, but I sense a different presence in you this time.\n - $You are the only one here, though it is as if there is… another. - $Will you finally prove a formidable challenge to me?\nThe challenge I have longed for for millennia? - $We begin.`, - "firstStageWin": `I see. The presence I felt was indeed real.\nIt appears I no longer need to hold back. - $Do not disappoint me.`, - "secondStageWin": "…Magnificent." + "encounter": `Parece que a hora finalmente chegou novamente.\nVocê sabe por que veio aqui, não sabe? + $Você foi atraído para cá, porque já esteve aqui antes.\nInúmeras vezes. + $Embora, talvez isso possa ser contado.\nPara ser preciso, este é de fato o seu 5.643.853º ciclo. + $A cada ciclo, sua mente retorna ao seu estado anterior.\nMesmo assim, de alguma forma, vestígios de seus antigos "eus" permanecem. + $Até agora, você ainda não conseguiu, mas sinto uma presença diferente em você desta vez.\n + $Você é o único aqui, embora pareça haver... outro. + $Você finalmente vai se mostrar um desafio formidável para mim?\nO desafio que anseio há milênios? + $Vamos começar.`, + "firstStageWin": `Entendo. A presença que senti era realmente real.\nParece que não preciso mais me segurar. + $Não me decepcione.`, + "secondStageWin": "…Magnífico." }; -// Dialogue of the endboss of the game when the player character is female. For languages that do not have gendered pronouns, this can be set to PGMbattleSpecDialogue. +// Diálogo do chefe final do jogo quando o personagem do jogador é feminino. Para idiomas que não possuem pronomes de gênero, isso pode ser definido como PGMbattleSpecDialogue. export const PGFbattleSpecDialogue: SimpleTranslationEntries = PGMbattleSpecDialogue; -// Dialogue that does not fit into any other category (e.g. tutorial messages, or the end of the game). For when the player character is male +// Diálogo que não se enquadra em nenhuma outra categoria (por exemplo, mensagens de tutorial ou o final do jogo). Para quando o personagem do jogador é masculino export const PGMmiscDialogue: SimpleTranslationEntries = { "ending": - `@c{smile}Oh? You won?@d{96} @c{smile_eclosed}I guess I should've known.\nBut, you're back now. - $@c{smile}It's over.@d{64} You ended the loop. - $@c{serious_smile_fists}You fulfilled your dream too, didn't you?\nYou didn't lose even once. - $@c{neutral}I'm the only one who'll remember what you did.@d{96}\nI guess that's okay, isn't it? - $@c{serious_smile_fists}Your legend will always live on in our hearts. - $@c{smile_eclosed}Anyway, I've had about enough of this place, haven't you? Let's head home. - $@c{serious_smile_fists}Maybe when we get back, we can have another battle?\nIf you're up to it.`, + `@c{smile}Oh? Você venceu?@d{96} @c{smile_eclosed}Acho que eu deveria saber.\nMas, você está de volta agora. + $@c{smile}Acabou.@d{64} Você quebrou o ciclo. + $@c{serious_smile_fists}Você também realizou seu sonho, não é?\nVocê não perdeu nenhuma vez. + $@c{neutral}Eu sou o único que vai lembrar o que você fez.@d{96}\nAcho que está tudo bem, não é? + $@c{serious_smile_fists}Sua lenda sempre viverá em nossos corações. + $@c{smile_eclosed}Enfim, já tive o suficiente deste lugar, não é? Vamos para casa. + $@c{serious_smile_fists}Talvez quando voltarmos, possamos ter outra batalha?\nSe você estiver disposto.`, "ending_female": - `@c{shock}You're back?@d{32} Does that mean…@d{96} you won?!\n@c{smile_ehalf}I should have known you had it in you. - $@c{smile_eclosed}Of course… I always had that feeling.\n@c{smile}It's over now, right? You ended the loop. - $@c{smile_ehalf}You fulfilled your dream too, didn't you?\nYou didn't lose even once. - $I'll be the only one to remember what you did.\n@c{angry_mopen}I'll try not to forget! - $@c{smile_wave_wink}Just kidding!@d{64} @c{smile}I'd never forget.@d{32}\nYour legend will live on in our hearts. - $@c{smile_wave}Anyway,@d{64} it's getting late…@d{96} I think?\nIt's hard to tell in this place. - $Let's go home. @c{smile_wave_wink}Maybe tomorrow, we can have another battle, for old time's sake?`, + `@c{shock}Você está de volta?@d{32} Isso significa que…@d{96} você venceu?!\n@c{smile_ehalf}Eu deveria saber que você conseguiria. + $@c{smile_eclosed}Claro… Eu sempre tive essa sensação.\n@c{smile}Acabou agora, certo? Você quebrou o ciclo. + $@c{smile_ehalf}Você também realizou seu sonho, não foi?\nVocê não perdeu nenhuma vez. + $Eu serei a única a lembrar o que você fez.\n@c{angry_mopen}Eu tentarei não esquecer! + $@c{smile_wave_wink}Brincadeirinha!@d{64} @c{smile}Eu nunca esqueceria.@d{32}\nSua lenda viverá em nossos corações. + $@c{smile_wave}De qualquer forma,@d{64} está ficando tarde…@d{96} Eu acho?\nÉ difícil dizer neste lugar. + $Vamos para casa. @c{smile_wave_wink}Talvez amanhã possamos ter outra batalha, pelos velhos tempos?`, }; -// Dialogue that does not fit into any other category (e.g. tutorial messages, or the end of the game). For when the player character is female. For languages that do not have gendered pronouns, this can be set to PGMmiscDialogue. + +// Diálogo que não se enquadra em nenhuma outra categoria (por exemplo, mensagens de tutorial ou o final do jogo). Para quando o personagem do jogador é feminino. Para idiomas que não possuem pronomes de gênero, isso pode ser definido como PGMmiscDialogue. export const PGFmiscDialogue: SimpleTranslationEntries = PGMmiscDialogue; -// Dialogue of the named double battles in the game. For when the player is male (or unset). +// Diálogo das batalhas duplas nomeadas no jogo. Para quando o jogador é masculino (ou não definido). export const PGMdoubleBattleDialogue: DialogueTranslationEntries = { "blue_red_double": { "encounter": { - 1: `Blue: Hey Red, let's show them what we're made of! + 1: `Blue: Ei Red, vamos mostrar do que somos feitos! $Red: ... - $Blue: This is Pallet Town Power!`, + $Blue: Este é o poder da Cidade de Pallet!`, }, "victory": { - 1: `Blue: That was a great battle! + 1: `Blue: Essa foi uma ótima batalha! $Red: ...`, }, }, "red_blue_double": { "encounter": { 1: `Red: ...! - $Blue: He never talks much. - $Blue: But dont let that fool you! He is a champ after all!`, + $Blue: Ele nunca fala muito. + $Blue: Mas não se deixe enganar! Ele é um campeão, afinal!`, }, "victory": { 1: `Red: ...! - $Blue: Next time we will beat you!`, + $Blue: Da próxima vez, vamos vencer você!`, }, }, "tate_liza_double": { "encounter": { - 1: `Tate: Are you suprised? - $Liza: We are two gym leaders at once! - $Tate: We are twins! - $Liza: We dont need to talk to understand each other! - $Tate: Twice the power... - $Liza: Can you handle it?`, + 1: `Tate: Está surpreso? + $Liza: Somos dois líderes de ginásio ao mesmo tempo! + $Tate: Somos gêmeos! + $Liza: Não precisamos falar para nos entender! + $Tate: Duas vezes o poder... + $Liza: Você consegue lidar com isso?`, }, "victory": { - 1: `Tate: What? Our combination was perfect! - $Liza: Looks like we need to train more...`, + 1: `Tate: O quê? Nossa combinação foi perfeita! + $Liza: Parece que precisamos treinar mais...`, }, }, "liza_tate_double": { "encounter": { - 1: `Liza: Hihihi... Are you suprised? - $Tate: Yes, we are really two gym leaders at once! - $Liza: This is my twin brother Tate! - $Tate: And this is my twin sister Liza! - $Liza: Don't you think we are a perfect combination?` + 1: `Liza: Hihihi... Está surpreso? + $Tate: Sim, somos realmente dois líderes de ginásio ao mesmo tempo! + $Liza: Este é meu irmão gêmeo Tate! + $Tate: E esta é minha irmã gêmea Liza! + $Liza: Não acha que somos uma combinação perfeita?` }, "victory": { - 1: `Liza: Are we... - $Tate: ...not as strong as we thought?`, + 1: `Liza: Nós somos... + $Tate: ...não tão fortes quanto pensávamos?`, }, }, "wallace_steven_double": { "encounter": { - 1: `Steven: Wallace, let's show them the power of the champions! - $Wallace: We will show you the power of Hoenn! - $Steven: Let's go!`, + 1: `Steven: Wallace, vamos mostrar a eles o poder dos campeões! + $Wallace: Vamos mostrar o poder de Hoenn! + $Steven: Vamos lá!`, }, "victory": { - 1: `Steven: That was a great battle! - $Wallace: We will win next time!`, + 1: `Steven: Essa foi uma ótima batalha! + $Wallace: Vamos vencer da próxima vez!`, }, }, "steven_wallace_double": { "encounter": { - 1: `Steven: Do you have any rare pokémon? - $Wallace: Steven... We are here for a battle, not to show off our pokémon. - $Steven: Oh... I see... Let's go then!`, + 1: `Steven: Você tem algum Pokémon raro? + $Wallace: Steven... Estamos aqui para uma batalha, não para mostrar nossos Pokémon. + $Steven: Ah... Entendi... Vamos lá então!`, }, "victory": { - 1: `Steven: Now that we are done with the battle, let's show off our pokémon! + 1: `Steven: Agora que terminamos a batalha, vamos mostrar nossos Pokémon! $Wallace: Steven...`, }, }, "alder_iris_double": { "encounter": { - 1: `Alder: We are the strongest trainers in Unova! - $Iris: Fights against strong trainers are the best!`, + 1: `Alder: Somos os treinadores mais fortes de Unova! + $Iris: Lutas contra treinadores fortes são as melhores!`, }, "victory": { - 1: `Alder: Wow! You are super strong! - $Iris: We will win next time!`, + 1: `Alder: Uau! Você é super forte! + $Iris: Vamos vencer da próxima vez!`, }, }, "iris_alder_double": { "encounter": { - 1: `Iris: Welcome Challenger! I am THE Unova Champion! - $Alder: Iris, aren't you a bit too excited?`, + 1: `Iris: Bem-vindo, Desafiante! Eu sou A Campeã de Unova! + $Alder: Iris, você não está um pouco empolgada demais?`, }, "victory": { - 1: `Iris: A loss like this is not easy to take... - $Alder: But we will only get stronger with every loss!`, + 1: `Iris: Uma derrota como essa não é fácil de engolir... + $Alder: Mas só ficaremos mais fortes a cada derrota!`, }, }, "piers_marnie_double": { "encounter": { - 1: `Marnie: Brother, let's show them the power of Spikemuth! - $Piers: We bring darkness!`, + 1: `Marnie: Irmão, vamos mostrar a eles o poder de Spikemuth! + $Piers: Nós trazemos a escuridão!`, }, "victory": { - 1: `Marnie: You brought light to our darkness! - $Piers: Its too bright...`, + 1: `Marnie: Você trouxe luz para nossa escuridão! + $Piers: Está muito claro...`, }, }, "marnie_piers_double": { "encounter": { - 1: `Piers: Ready for a concert? - $Marnie: Brother... They are here to fight, not to sing...`, + 1: `Piers: Prontos para um show? + $Marnie: Irmão... Eles estão aqui para lutar, não para cantar...`, }, "victory": { - 1: `Piers: Now that was a great concert! - $Marnie: Brother...`, + 1: `Piers: Agora esse foi um ótimo show! + $Marnie: Irmão...`, }, }, }; + // Dialogue of the named double battles in the game. For when the player is female. For languages that do not have gendered pronouns, this can be set to PGMdoubleBattleDialogue. export const PGFdoubleBattleDialogue: DialogueTranslationEntries = PGMdoubleBattleDialogue; diff --git a/src/locales/pt_BR/game-stats-ui-handler.ts b/src/locales/pt_BR/game-stats-ui-handler.ts index 64e4e2af5e3..dc03ba64cf2 100644 --- a/src/locales/pt_BR/game-stats-ui-handler.ts +++ b/src/locales/pt_BR/game-stats-ui-handler.ts @@ -1,44 +1,44 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const gameStatsUiHandler: SimpleTranslationEntries = { - "stats": "Stats", - "playTime": "Play Time", - "totalBattles": "Total Battles", - "starters": "Starters", - "shinyStarters": "Shiny Starters", - "speciesSeen": "Species Seen", - "speciesCaught": "Species Caught", - "ribbonsOwned": "Ribbons Owned", - "classicRuns": "Classic Runs", - "classicWins": "Classic Wins", - "dailyRunAttempts": "Daily Run Attempts", - "dailyRunWins": "Daily Run Wins", - "endlessRuns": "Endless Runs", - "highestWaveEndless": "Highest Wave (Endless)", - "highestMoney": "Highest Money", - "highestDamage": "Highest Damage", - "highestHPHealed": "Highest HP Healed", - "pokemonEncountered": "Pokémon Encountered", - "pokemonDefeated": "Pokémon Defeated", - "pokemonCaught": "Pokémon Caught", - "eggsHatched": "Eggs Hatched", - "subLegendsSeen": "Sub-Legends Seen", - "subLegendsCaught": "Sub-Legends Caught", - "subLegendsHatched": "Sub-Legends Hatched", - "legendsSeen": "Legends Seen", - "legendsCaught": "Legends Caught", - "legendsHatched": "Legends Hatched", - "mythicalsSeen": "Mythicals Seen", - "mythicalsCaught": "Mythicals Caught", - "mythicalsHatched": "Mythicals Hatched", - "shiniesSeen": "Shinies Seen", - "shiniesCaught": "Shinies Caught", - "shiniesHatched": "Shinies Hatched", - "pokemonFused": "Pokémon Fused", - "trainersDefeated": "Trainers Defeated", - "eggsPulled": "Eggs Pulled", - "rareEggsPulled": "Rare Eggs Pulled", - "epicEggsPulled": "Epic Eggs Pulled", - "legendaryEggsPulled": "Legendary Eggs Pulled", - "manaphyEggsPulled": "Manaphy Eggs Pulled", + "stats": "Estatísticas", + "playTime": "Tempo de Jogo", + "totalBattles": "Total de Batalhas", + "starters": "Iniciais", + "shinyStarters": "Iniciais Shiny", + "speciesSeen": "Espécies Vistas", + "speciesCaught": "Capturadas", + "ribbonsOwned": "Fitas Obtidas", + "classicRuns": "Jogos Clássicos", + "classicWins": "Vitórias Clássicas", + "dailyRunAttempts": "Jogos de Desafio Diário", + "dailyRunWins": "Vitórias de Desafio Diário", + "endlessRuns": "Jogos Infinitos", + "highestWaveEndless": "Maior Onda (Infinito)", + "highestMoney": "Maior Dinheiro", + "highestDamage": "Maior Dano", + "highestHPHealed": "Maior PS Curado", + "pokemonEncountered": "Pokémon Encontrados", + "pokemonDefeated": "Pokémon Derrotados", + "pokemonCaught": "Pokémon Capturados", + "eggsHatched": "Ovos Chocados", + "subLegendsSeen": "Sub-Lendários Vistos", + "subLegendsCaught": "Sub-Lend. Capturados", + "subLegendsHatched": "Sub-Lendários Chocados", + "legendsSeen": "Lendários Vistos", + "legendsCaught": "Lendários Capturados", + "legendsHatched": "Legendários Chocados", + "mythicalsSeen": "Míticos Vistos", + "mythicalsCaught": "Míticos Capturados", + "mythicalsHatched": "Míticos Chocados", + "shiniesSeen": "Shinies Vistos", + "shiniesCaught": "Shinies Capturados", + "shiniesHatched": "Shinies Chocados", + "pokemonFused": "Pokémon Fundidos", + "trainersDefeated": "Treinadores Derrotados", + "eggsPulled": "Ovos Ganhos", + "rareEggsPulled": "Ovos Raros Ganhos", + "epicEggsPulled": "Ovos Épicos Ganhos", + "legendaryEggsPulled": "Ovos Lendários Ganhos", + "manaphyEggsPulled": "Ovos de Manaphy Ganhos", } as const; diff --git a/src/locales/pt_BR/modifier-type.ts b/src/locales/pt_BR/modifier-type.ts index 2deabc7836b..e023fc7a6ba 100644 --- a/src/locales/pt_BR/modifier-type.ts +++ b/src/locales/pt_BR/modifier-type.ts @@ -209,8 +209,8 @@ export const modifierType: ModifierTypeTranslationEntries = { "LEFTOVERS": { name: "Sobras", description: "Cura 1/16 dos PS máximos de um Pokémon a cada turno" }, "SHELL_BELL": { name: "Concha-Sino", description: "Cura 1/8 do dano causado por um Pokémon" }, - "TOXIC_ORB": { name: "Toxic Orb", description: "It's a bizarre orb that exudes toxins when touched and will badly poison the holder during battle" }, - "FLAME_ORB": { name: "Flame Orb", description: "It's a bizarre orb that gives off heat when touched and will affect the holder with a burn during battle" }, + "TOXIC_ORB": { name: "Esfera Tóxica", description: "Uma esfera estranha que exala toxinas quando tocada e envenena seriamente quem a segurar" }, + "FLAME_ORB": { name: "Esfera da Chama", description: "Uma esfera estranha que aquece quando tocada e queima quem a segurar" }, "BATON": { name: "Bastão", description: "Permite passar mudanças de atributo ao trocar Pokémon, ignorando armadilhas" }, diff --git a/src/locales/pt_BR/move.ts b/src/locales/pt_BR/move.ts index f824eccdf6b..dfe7192c011 100644 --- a/src/locales/pt_BR/move.ts +++ b/src/locales/pt_BR/move.ts @@ -1943,7 +1943,7 @@ export const move: MoveTranslationEntries = { }, "electroBall": { name: "Electro Ball", - effect: "O usuário arremessa uma orbe elétrica no alvo. Quanto mais rápido for o usuário comparado ao alvo, maior será o poder do movimento." + effect: "O usuário arremessa uma esfera elétrica no alvo. Quanto mais rápido for o usuário comparado ao alvo, maior será o poder do movimento." }, "soak": { name: "Soak", @@ -2495,7 +2495,7 @@ export const move: MoveTranslationEntries = { }, "allOutPummelingPhysical": { name: "All-Out Pummeling", - effect: "Utilizando o Poder Z, o usuário cria e arremessa um orbe de energia no alvo com força total. Seu poder varia dependendo do movimento original." + effect: "Utilizando o Poder Z, o usuário cria e arremessa uma esfera de energia no alvo com força total. Seu poder varia dependendo do movimento original." }, "allOutPummelingSpecial": { name: "All-Out Pummeling", diff --git a/src/locales/pt_BR/trainers.ts b/src/locales/pt_BR/trainers.ts index ee6a78dd926..5d624c60ad5 100644 --- a/src/locales/pt_BR/trainers.ts +++ b/src/locales/pt_BR/trainers.ts @@ -48,7 +48,7 @@ export const trainerClasses: SimpleTranslationEntries = { "depot_agent": "Ferroviário", "doctor": "Doutor", "doctor_female": "Doutora", - "firebreather": "Firebreather", + "firebreather": "Cospe-Fogo", "fishermen": "Pescador", "fishermen_female": "Pescadora", "gentleman": "Cavalheiro", From 145a79f8ef7ba1f2bf332149a754dc199a7bf7d7 Mon Sep 17 00:00:00 2001 From: sodam <66295123+sodaMelon@users.noreply.github.com> Date: Tue, 4 Jun 2024 00:38:37 +0900 Subject: [PATCH 020/129] [Localization] Fixed typo in Korean tutorial (#1757) --- src/locales/ko/tutorial.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/locales/ko/tutorial.ts b/src/locales/ko/tutorial.ts index 5056ef57dd3..70702aa1698 100644 --- a/src/locales/ko/tutorial.ts +++ b/src/locales/ko/tutorial.ts @@ -29,8 +29,8 @@ export const tutorial: SimpleTranslationEntries = { $종류는 소모품, 포켓몬의 지닌 도구, 영구적 패시브 아이템에 이르기까지 다양합니다. $대부분의 소모되지 않는 도구는 효과가 누적됩니다. $진화용과 같은 일부분의 아이템은 사용할 수 있는 경우에만 등장합니다. - $지닌 도구 건내주기 기능을 사용해 포켓몬끼리 도구를 옮겨 지닐 수도 있습니다. - $지닌 도구가 있다면 아이템 선택 화면 오른쪽 하단에 건내주기 기능이 표시됩니다. + $지닌 도구 건네주기 기능을 사용해 포켓몬끼리 도구를 옮겨 지닐 수도 있습니다. + $지닌 도구가 있다면 아이템 선택 화면 오른쪽 하단에 건네주기 기능이 표시됩니다. $돈으로 소모품을 구입할 수도 있으며, 웨이브 진행에 따라 구입 가능한 종류가 늘어납니다. $아이템을 선택하면 다음 웨이브로 넘어가므로, 소모품 구입을 먼저 해 주세요.`, From ff0e4fbdf048f07d3f7239d5b127c384d3e8b3ee Mon Sep 17 00:00:00 2001 From: Xavion3 Date: Tue, 4 Jun 2024 05:43:52 +1000 Subject: [PATCH 021/129] Implement Pity System (#1752) * Implement Pity System * Add comments and optimised worst case slightly --- src/egg-hatch-phase.ts | 16 +++++++++++++++- src/system/game-data.ts | 13 ++++++++++++- src/ui/egg-gacha-ui-handler.ts | 14 ++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/egg-hatch-phase.ts b/src/egg-hatch-phase.ts index f13097ac7eb..320b768e1e5 100644 --- a/src/egg-hatch-phase.ts +++ b/src/egg-hatch-phase.ts @@ -497,11 +497,19 @@ export class EggHatchPhase extends Phase { const ignoredSpecies = [ Species.PHIONE, Species.MANAPHY, Species.ETERNATUS ]; - const speciesPool = Object.keys(speciesStarters) + let speciesPool = Object.keys(speciesStarters) .filter(s => speciesStarters[s] >= minStarterValue && speciesStarters[s] <= maxStarterValue) .map(s => parseInt(s) as Species) .filter(s => !pokemonPrevolutions.hasOwnProperty(s) && getPokemonSpecies(s).isObtainable() && ignoredSpecies.indexOf(s) === -1); + // If this is the 10th egg without unlocking something new, attempt to force it. + if (this.scene.gameData.unlockPity[this.egg.tier] >= 9) { + const lockedPool = speciesPool.filter(s => !this.scene.gameData.dexData[s].caughtAttr); + if (lockedPool.length) { // Skip this if everything is unlocked + speciesPool = lockedPool; + } + } + /** * Pokemon that are cheaper in their tier get a weight boost. Regionals get a weight penalty * 1 cost mons get 2x @@ -536,6 +544,12 @@ export class EggHatchPhase extends Phase { } } + if (!!this.scene.gameData.dexData[species].caughtAttr) { + this.scene.gameData.unlockPity[this.egg.tier] = Math.min(this.scene.gameData.unlockPity[this.egg.tier] + 1, 10); + } else { + this.scene.gameData.unlockPity[this.egg.tier] = 0; + } + const pokemonSpecies = getPokemonSpecies(species); ret = this.scene.addPlayerPokemon(pokemonSpecies, 1, undefined, undefined, undefined, false); diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 35b6bc1435c..433ab13fd70 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -100,6 +100,8 @@ interface SystemSaveData { eggs: EggData[]; gameVersion: string; timestamp: integer; + eggPity: integer[]; + unlockPity: integer[]; } export interface SessionSaveData { @@ -248,6 +250,8 @@ export class GameData { public voucherUnlocks: VoucherUnlocks; public voucherCounts: VoucherCounts; public eggs: Egg[]; + public eggPity: integer[]; + public unlockPity: integer[]; constructor(scene: BattleScene) { this.scene = scene; @@ -272,6 +276,8 @@ export class GameData { [VoucherType.GOLDEN]: 0 }; this.eggs = []; + this.eggPity = [0, 0, 0, 0]; + this.unlockPity = [0, 0, 0, 0]; this.initDexData(); this.initStarterData(); } @@ -290,7 +296,9 @@ export class GameData { voucherCounts: this.voucherCounts, eggs: this.eggs.map(e => new EggData(e)), gameVersion: this.scene.game.config.gameVersion, - timestamp: new Date().getTime() + timestamp: new Date().getTime(), + eggPity: this.eggPity.slice(0), + unlockPity: this.unlockPity.slice(0) }; } @@ -473,6 +481,9 @@ export class GameData { ? systemData.eggs.map(e => e.toEgg()) : []; + this.eggPity = systemData.eggPity ? systemData.eggPity.slice(0) : [0, 0, 0, 0]; + this.unlockPity = systemData.unlockPity ? systemData.unlockPity.slice(0) : [0, 0, 0, 0]; + this.dexData = Object.assign(this.dexData, systemData.dexData); this.consolidateDexData(this.dexData); this.defaultDexData = null; diff --git a/src/ui/egg-gacha-ui-handler.ts b/src/ui/egg-gacha-ui-handler.ts index 9536df7dbed..f0e55e04801 100644 --- a/src/ui/egg-gacha-ui-handler.ts +++ b/src/ui/egg-gacha-ui-handler.ts @@ -378,6 +378,20 @@ export default class EggGachaUiHandler extends MessageUiHandler { } else if (pullCount >= 10 && !tiers.filter(t => t >= EggTier.GREAT).length) { tiers[Utils.randInt(tiers.length)] = EggTier.GREAT; } + for (let i = 0; i < pullCount; i++) { + this.scene.gameData.eggPity[EggTier.GREAT] += 1; + this.scene.gameData.eggPity[EggTier.ULTRA] += 1; + this.scene.gameData.eggPity[EggTier.MASTER] += 1 + tierValueOffset; + // These numbers are roughly the 80% mark. That is, 80% of the time you'll get an egg before this gets triggered. + if (this.scene.gameData.eggPity[EggTier.MASTER] >= 412 && tiers[i] === EggTier.COMMON) { + tiers[i] = EggTier.MASTER; + } else if (this.scene.gameData.eggPity[EggTier.ULTRA] >= 59 && tiers[i] === EggTier.COMMON) { + tiers[i] = EggTier.ULTRA; + } else if (this.scene.gameData.eggPity[EggTier.GREAT] >= 9 && tiers[i] === EggTier.COMMON) { + tiers[i] = EggTier.GREAT; + } + this.scene.gameData.eggPity[tiers[i]] = 0; + } const timestamp = new Date().getTime(); From 763d2bfeeb4dfb0d8bda1d720de2352b3e9d98b6 Mon Sep 17 00:00:00 2001 From: Philippe <56865723+prateau@users.noreply.github.com> Date: Mon, 3 Jun 2024 22:26:15 +0200 Subject: [PATCH 022/129] [Move] Implement pollen puff (#1732) --- src/data/move.ts | 53 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index 594b9e73efe..1e2c5e7485b 100755 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1201,6 +1201,29 @@ export class BoostHealAttr extends HealAttr { } } +/** + * Heals the target only if it is the ally + * @extends HealAttr + * @see {@linkcode apply} + */ +export class HealOnAllyAttr extends HealAttr { + /** + * @param user {@linkcode Pokemon} using the move + * @param target {@linkcode Pokemon} target of the move + * @param move {@linkcode Move} with this attribute + * @param args N/A + * @returns true if the function succeeds + */ + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + if (user.getAlly() === target) { + super.apply(user, target, move, args); + return true; + } + + return false; + } +} + /** * Heals user as a side effect of a move that hits a target. * Healing is based on {@linkcode healRatio} * the amount of damage dealt or a stat of the target. @@ -3043,6 +3066,31 @@ export class TeraBlastCategoryAttr extends VariableMoveCategoryAttr { } } +/** + * Change the move category to status when used on the ally + * @extends VariableMoveCategoryAttr + * @see {@linkcode apply} + */ +export class StatusCategoryOnAllyAttr extends VariableMoveCategoryAttr { + /** + * @param user {@linkcode Pokemon} using the move + * @param target {@linkcode Pokemon} target of the move + * @param move {@linkcode Move} with this attribute + * @param args [0] {@linkcode Utils.IntegerHolder} The category of the move + * @returns true if the function succeeds + */ + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + const category = (args[0] as Utils.IntegerHolder); + + if (user.getAlly() === target) { + category.value = MoveCategory.STATUS; + return true; + } + + return false; + } +} + export class ShellSideArmCategoryAttr extends VariableMoveCategoryAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const category = (args[0] as Utils.IntegerHolder); @@ -6966,8 +7014,9 @@ export function initMoves() { new AttackMove(Moves.THROAT_CHOP, Type.DARK, MoveCategory.PHYSICAL, 80, 100, 15, 100, 0, 7) .partial(), new AttackMove(Moves.POLLEN_PUFF, Type.BUG, MoveCategory.SPECIAL, 90, 100, 15, -1, 0, 7) - .ballBombMove() - .partial(), + .attr(StatusCategoryOnAllyAttr) + .attr(HealOnAllyAttr, 0.5, true, false) + .ballBombMove(), new AttackMove(Moves.ANCHOR_SHOT, Type.STEEL, MoveCategory.PHYSICAL, 80, 100, 20, -1, 0, 7) .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1), new StatusMove(Moves.PSYCHIC_TERRAIN, Type.PSYCHIC, -1, 10, -1, 0, 7) From 69da96d543e7172050c33a8550787f06b37780f0 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 3 Jun 2024 19:57:47 -0400 Subject: [PATCH 023/129] Settings Refactor (#1771) --- src/configs/inputs/cfg_keyboard_qwerty.ts | 2 +- src/configs/inputs/pad_dualshock.ts | 2 +- src/configs/inputs/pad_generic.ts | 2 +- src/configs/inputs/pad_procon.ts | 2 +- src/configs/inputs/pad_unlicensedSNES.ts | 2 +- src/configs/inputs/pad_xbox360.ts | 2 +- src/inputs-controller.ts | 4 +- src/phases.ts | 6 +- src/system/game-data.ts | 82 +-- src/system/settings.ts | 279 -------- src/system/{ => settings}/settings-gamepad.ts | 53 +- .../{ => settings}/settings-keyboard.ts | 76 +- src/system/settings/settings.ts | 453 ++++++++++++ src/test/helpers/inGameManip.ts | 2 +- src/test/helpers/menuManip.ts | 2 +- src/test/rebinding_setting.test.ts | 2 +- src/ui-inputs.ts | 22 +- .../abstract-control-settings-ui-handler.ts | 660 ++++++++++++++++++ .../settings/abstract-settings-ui-handler.ts | 608 +++++----------- src/ui/settings/navigationMenu.ts | 43 +- .../settings-accessiblity-ui-handler.ts | 20 + .../settings/settings-gamepad-ui-handler.ts | 70 +- .../settings/settings-keyboard-ui-handler.ts | 65 +- src/ui/settings/settings-ui-handler.ts | 350 +--------- src/ui/ui.ts | 4 + 25 files changed, 1487 insertions(+), 1326 deletions(-) delete mode 100644 src/system/settings.ts rename src/system/{ => settings}/settings-gamepad.ts (82%) rename src/system/{ => settings}/settings-keyboard.ts (84%) create mode 100644 src/system/settings/settings.ts create mode 100644 src/ui/settings/abstract-control-settings-ui-handler.ts create mode 100644 src/ui/settings/settings-accessiblity-ui-handler.ts diff --git a/src/configs/inputs/cfg_keyboard_qwerty.ts b/src/configs/inputs/cfg_keyboard_qwerty.ts index 869b763d6c1..83472529697 100644 --- a/src/configs/inputs/cfg_keyboard_qwerty.ts +++ b/src/configs/inputs/cfg_keyboard_qwerty.ts @@ -1,5 +1,5 @@ import {Button} from "#app/enums/buttons"; -import {SettingKeyboard} from "#app/system/settings-keyboard"; +import {SettingKeyboard} from "#app/system/settings/settings-keyboard"; const cfg_keyboard_qwerty = { padID: "default", diff --git a/src/configs/inputs/pad_dualshock.ts b/src/configs/inputs/pad_dualshock.ts index b0aaedf0ab8..d58a8b2879d 100644 --- a/src/configs/inputs/pad_dualshock.ts +++ b/src/configs/inputs/pad_dualshock.ts @@ -1,4 +1,4 @@ -import {SettingGamepad} from "../../system/settings-gamepad"; +import {SettingGamepad} from "../../system/settings/settings-gamepad"; import {Button} from "../../enums/buttons"; /** diff --git a/src/configs/inputs/pad_generic.ts b/src/configs/inputs/pad_generic.ts index f209f6858bd..916f8c82add 100644 --- a/src/configs/inputs/pad_generic.ts +++ b/src/configs/inputs/pad_generic.ts @@ -1,4 +1,4 @@ -import {SettingGamepad} from "../../system/settings-gamepad"; +import {SettingGamepad} from "../../system/settings/settings-gamepad"; import {Button} from "../../enums/buttons"; /** diff --git a/src/configs/inputs/pad_procon.ts b/src/configs/inputs/pad_procon.ts index ccaaf5fc635..4b7bd8457b0 100644 --- a/src/configs/inputs/pad_procon.ts +++ b/src/configs/inputs/pad_procon.ts @@ -1,4 +1,4 @@ -import {SettingGamepad} from "#app/system/settings-gamepad"; +import {SettingGamepad} from "#app/system/settings/settings-gamepad.js"; import {Button} from "#app/enums/buttons"; /** diff --git a/src/configs/inputs/pad_unlicensedSNES.ts b/src/configs/inputs/pad_unlicensedSNES.ts index 803d30442b5..106fbc9eb8f 100644 --- a/src/configs/inputs/pad_unlicensedSNES.ts +++ b/src/configs/inputs/pad_unlicensedSNES.ts @@ -1,4 +1,4 @@ -import {SettingGamepad} from "../../system/settings-gamepad"; +import {SettingGamepad} from "../../system/settings/settings-gamepad"; import {Button} from "../../enums/buttons"; /** diff --git a/src/configs/inputs/pad_xbox360.ts b/src/configs/inputs/pad_xbox360.ts index 213ed7f89fb..645829d5be2 100644 --- a/src/configs/inputs/pad_xbox360.ts +++ b/src/configs/inputs/pad_xbox360.ts @@ -1,4 +1,4 @@ -import {SettingGamepad} from "../../system/settings-gamepad"; +import {SettingGamepad} from "../../system/settings/settings-gamepad"; import {Button} from "#app/enums/buttons"; /** diff --git a/src/inputs-controller.ts b/src/inputs-controller.ts index 2791f3b5b85..aa0f781cfef 100644 --- a/src/inputs-controller.ts +++ b/src/inputs-controller.ts @@ -19,8 +19,8 @@ import { getIconForLatestInput, swap, } from "#app/configs/inputs/configHandler"; import BattleScene from "./battle-scene"; -import {SettingGamepad} from "#app/system/settings-gamepad"; -import {SettingKeyboard} from "#app/system/settings-keyboard"; +import {SettingGamepad} from "#app/system/settings/settings-gamepad.js"; +import {SettingKeyboard} from "#app/system/settings/settings-keyboard"; export interface DeviceMapping { [key: string]: number; diff --git a/src/phases.ts b/src/phases.ts index 67f33b5e031..110c4155849 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -48,7 +48,7 @@ import { addPokeballCaptureStars, addPokeballOpenParticles } from "./field/anims import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeManualTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangePreMoveTrigger } from "./data/pokemon-forms"; import { battleSpecDialogue, getCharVariantFromDialogue, miscDialogue } from "./data/dialogue"; import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "./ui/modifier-select-ui-handler"; -import { Setting } from "./system/settings"; +import { SettingKeys } from "./system/settings/settings"; import { Tutorial, handleTutorial } from "./tutorial"; import { TerrainType } from "./data/terrain"; import { OptionSelectConfig, OptionSelectItem } from "./ui/abstact-option-select-ui-handler"; @@ -477,7 +477,7 @@ export class SelectGenderPhase extends Phase { label: i18next.t("menu:boy"), handler: () => { this.scene.gameData.gender = PlayerGender.MALE; - this.scene.gameData.saveSetting(Setting.Player_Gender, 0); + this.scene.gameData.saveSetting(SettingKeys.Player_Gender, 0); this.scene.gameData.saveSystem().then(() => this.end()); return true; } @@ -486,7 +486,7 @@ export class SelectGenderPhase extends Phase { label: i18next.t("menu:girl"), handler: () => { this.scene.gameData.gender = PlayerGender.FEMALE; - this.scene.gameData.saveSetting(Setting.Player_Gender, 1); + this.scene.gameData.saveSetting(SettingKeys.Player_Gender, 1); this.scene.gameData.saveSystem().then(() => this.end()); return true; } diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 433ab13fd70..ec4a814b643 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -13,7 +13,7 @@ import { GameModes, gameModes } from "../game-mode"; import { BattleType } from "../battle"; import TrainerData from "./trainer-data"; import { trainerConfigs } from "../data/trainer-config"; -import { Setting, setSetting, settingDefaults } from "./settings"; +import { SettingKeys, resetSettings, setSetting } from "./settings/settings"; import { achvs } from "./achv"; import EggData from "./egg-data"; import { Egg } from "../data/egg"; @@ -30,9 +30,10 @@ import { allMoves } from "../data/move"; import { TrainerVariant } from "../field/trainer"; import { OutdatedPhase, ReloadSessionPhase } from "#app/phases"; import { Variant, variantData } from "#app/data/variant"; -import {setSettingGamepad, SettingGamepad, settingGamepadDefaults} from "./settings-gamepad"; -import {setSettingKeyboard, SettingKeyboard, settingKeyboardDefaults} from "#app/system/settings-keyboard"; +import {setSettingGamepad, SettingGamepad, settingGamepadDefaults} from "./settings/settings-gamepad"; +import {setSettingKeyboard, SettingKeyboard} from "#app/system/settings/settings-keyboard"; import { TerrainChangedEvent, WeatherChangedEvent } from "#app/field/arena-events.js"; +import { Device } from "#app/enums/devices.js"; const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet necessary @@ -402,7 +403,7 @@ export class GameData { this.gender = systemData.gender; - this.saveSetting(Setting.Player_Gender, systemData.gender === PlayerGender.FEMALE ? 1 : 0); + this.saveSetting(SettingKeys.Player_Gender, systemData.gender === PlayerGender.FEMALE ? 1 : 0); const initStarterData = !systemData.starterData; @@ -568,19 +569,21 @@ export class GameData { } } - public saveSetting(setting: Setting, valueIndex: integer): boolean { + /** + * Saves a setting to localStorage + * @param setting string ideally of SettingKeys + * @param valueIndex index of the setting's option + * @returns true + */ + public saveSetting(setting: string, valueIndex: integer): boolean { let settings: object = {}; if (localStorage.hasOwnProperty("settings")) { settings = JSON.parse(localStorage.getItem("settings")); } - setSetting(this.scene, setting as Setting, valueIndex); + setSetting(this.scene, setting, valueIndex); - Object.keys(settingDefaults).forEach(s => { - if (s === setting) { - settings[s] = valueIndex; - } - }); + settings[setting] = valueIndex; localStorage.setItem("settings", JSON.stringify(settings)); @@ -653,61 +656,36 @@ export class GameData { * to update the specified setting with the new value. Finally, it saves the updated settings back * to localStorage and returns `true` to indicate success. */ - public saveGamepadSetting(setting: SettingGamepad, valueIndex: integer): boolean { - let settingsGamepad: object = {}; // Initialize an empty object to hold the gamepad settings + public saveControlSetting(device: Device, localStoragePropertyName: string, setting: SettingGamepad|SettingKeyboard, settingDefaults, valueIndex: integer): boolean { + let settingsControls: object = {}; // Initialize an empty object to hold the gamepad settings - if (localStorage.hasOwnProperty("settingsGamepad")) { // Check if 'settingsGamepad' exists in localStorage - settingsGamepad = JSON.parse(localStorage.getItem("settingsGamepad")); // Parse the existing 'settingsGamepad' from localStorage + if (localStorage.hasOwnProperty(localStoragePropertyName)) { // Check if 'settingsControls' exists in localStorage + settingsControls = JSON.parse(localStorage.getItem(localStoragePropertyName)); // Parse the existing 'settingsControls' from localStorage } - setSettingGamepad(this.scene, setting as SettingGamepad, valueIndex); // Set the gamepad setting in the current scene + if (device === Device.GAMEPAD) { + setSettingGamepad(this.scene, setting as SettingGamepad, valueIndex); // Set the gamepad setting in the current scene + } else if (device === Device.KEYBOARD) { + setSettingKeyboard(this.scene, setting as SettingKeyboard, valueIndex); // Set the keyboard setting in the current scene + } - Object.keys(settingGamepadDefaults).forEach(s => { // Iterate over the default gamepad settings + Object.keys(settingDefaults).forEach(s => { // Iterate over the default gamepad settings if (s === setting) {// If the current setting matches, update its value - settingsGamepad[s] = valueIndex; + settingsControls[s] = valueIndex; } }); - localStorage.setItem("settingsGamepad", JSON.stringify(settingsGamepad)); // Save the updated gamepad settings back to localStorage + localStorage.setItem(localStoragePropertyName, JSON.stringify(settingsControls)); // Save the updated gamepad settings back to localStorage return true; // Return true to indicate the operation was successful } /** - * Saves a keyboard setting to localStorage. - * - * @param setting - The keyboard setting to save. - * @param valueIndex - The index of the value to set for the keyboard setting. - * @returns `true` if the setting is successfully saved. - * - * @remarks - * This method initializes an empty object for keyboard settings if none exist in localStorage. - * It then updates the setting in the current scene and iterates over the default keyboard settings - * to update the specified setting with the new value. Finally, it saves the updated settings back - * to localStorage and returns `true` to indicate success. + * Loads Settings from local storage if available + * @returns true if succesful, false if not */ - public saveKeyboardSetting(setting: SettingKeyboard, valueIndex: integer): boolean { - let settingsKeyboard: object = {}; // Initialize an empty object to hold the keyboard settings - - if (localStorage.hasOwnProperty("settingsKeyboard")) { // Check if 'settingsKeyboard' exists in localStorage - settingsKeyboard = JSON.parse(localStorage.getItem("settingsKeyboard")); // Parse the existing 'settingsKeyboard' from localStorage - } - - setSettingKeyboard(this.scene, setting as SettingKeyboard, valueIndex); // Set the keyboard setting in the current scene - - Object.keys(settingKeyboardDefaults).forEach(s => { // Iterate over the default keyboard settings - if (s === setting) {// If the current setting matches, update its value - settingsKeyboard[s] = valueIndex; - } - }); - - localStorage.setItem("settingsKeyboard", JSON.stringify(settingsKeyboard)); // Save the updated keyboard settings back to localStorage - - return true; // Return true to indicate the operation was successful - } - private loadSettings(): boolean { - Object.values(Setting).map(setting => setting as Setting).forEach(setting => setSetting(this.scene, setting, settingDefaults[setting])); + resetSettings(this.scene); if (!localStorage.hasOwnProperty("settings")) { return false; @@ -716,7 +694,7 @@ export class GameData { const settings = JSON.parse(localStorage.getItem("settings")); for (const setting of Object.keys(settings)) { - setSetting(this.scene, setting as Setting, settings[setting]); + setSetting(this.scene, setting, settings[setting]); } } diff --git a/src/system/settings.ts b/src/system/settings.ts deleted file mode 100644 index 5f526376998..00000000000 --- a/src/system/settings.ts +++ /dev/null @@ -1,279 +0,0 @@ -import { Mode } from "#app/ui/ui"; -import i18next from "i18next"; -import BattleScene from "../battle-scene"; -import { hasTouchscreen } from "../touch-controls"; -import { updateWindowType } from "../ui/ui-theme"; -import { PlayerGender } from "./game-data"; -import { CandyUpgradeNotificationChangedEvent } from "#app/battle-scene-events.js"; -import { MoneyFormat } from "../enums/money-format"; -import SettingsUiHandler from "#app/ui/settings/settings-ui-handler"; - -export enum Setting { - Game_Speed = "GAME_SPEED", - Master_Volume = "MASTER_VOLUME", - BGM_Volume = "BGM_VOLUME", - SE_Volume = "SE_VOLUME", - Language = "LANGUAGE", - Damage_Numbers = "DAMAGE_NUMBERS", - UI_Theme = "UI_THEME", - Window_Type = "WINDOW_TYPE", - Tutorials = "TUTORIALS", - Enable_Retries = "ENABLE_RETRIES", - Skip_Seen_Dialogues = "SKIP_SEEN_DIALOGUES", - Candy_Upgrade_Notification = "CANDY_UPGRADE_NOTIFICATION", - Candy_Upgrade_Display = "CANDY_UPGRADE_DISPLAY", - Money_Format = "MONEY_FORMAT", - Sprite_Set = "SPRITE_SET", - Move_Animations = "MOVE_ANIMATIONS", - Show_Moveset_Flyout = "SHOW_MOVESET_FLYOUT", - Show_Stats_on_Level_Up = "SHOW_LEVEL_UP_STATS", - EXP_Gains_Speed = "EXP_GAINS_SPEED", - EXP_Party_Display = "EXP_PARTY_DISPLAY", - HP_Bar_Speed = "HP_BAR_SPEED", - Fusion_Palette_Swaps = "FUSION_PALETTE_SWAPS", - Player_Gender = "PLAYER_GENDER", - Touch_Controls = "TOUCH_CONTROLS", - Vibration = "VIBRATION" -} - -export interface SettingOptions { - [key: string]: string[] -} - -export interface SettingDefaults { - [key: string]: integer -} - -export const settingOptions: SettingOptions = { - [Setting.Game_Speed]: ["1x", "1.25x", "1.5x", "2x", "2.5x", "3x", "4x", "5x"], - [Setting.Master_Volume]: new Array(11).fill(null).map((_, i) => i ? (i * 10).toString() : "Mute"), - [Setting.BGM_Volume]: new Array(11).fill(null).map((_, i) => i ? (i * 10).toString() : "Mute"), - [Setting.SE_Volume]: new Array(11).fill(null).map((_, i) => i ? (i * 10).toString() : "Mute"), - [Setting.Language]: ["English", "Change"], - [Setting.Damage_Numbers]: ["Off", "Simple", "Fancy"], - [Setting.UI_Theme]: ["Default", "Legacy"], - [Setting.Window_Type]: new Array(5).fill(null).map((_, i) => (i + 1).toString()), - [Setting.Tutorials]: ["Off", "On"], - [Setting.Enable_Retries]: ["Off", "On"], - [Setting.Skip_Seen_Dialogues]: ["Off", "On"], - [Setting.Candy_Upgrade_Notification]: ["Off", "Passives Only", "On"], - [Setting.Candy_Upgrade_Display]: ["Icon", "Animation"], - [Setting.Money_Format]: ["Normal", "Abbreviated"], - [Setting.Sprite_Set]: ["Consistent", "Mixed Animated"], - [Setting.Move_Animations]: ["Off", "On"], - [Setting.Show_Moveset_Flyout]: ["Off", "On"], - [Setting.Show_Stats_on_Level_Up]: ["Off", "On"], - [Setting.EXP_Gains_Speed]: ["Normal", "Fast", "Faster", "Skip"], - [Setting.EXP_Party_Display]: ["Normal", "Level Up Notification", "Skip"], - [Setting.HP_Bar_Speed]: ["Normal", "Fast", "Faster", "Instant"], - [Setting.Fusion_Palette_Swaps]: ["Off", "On"], - [Setting.Player_Gender]: ["Boy", "Girl"], - [Setting.Touch_Controls]: ["Auto", "Disabled"], - [Setting.Vibration]: ["Auto", "Disabled"] -}; - -export const settingDefaults: SettingDefaults = { - [Setting.Game_Speed]: 3, - [Setting.Master_Volume]: 5, - [Setting.BGM_Volume]: 10, - [Setting.SE_Volume]: 10, - [Setting.Language]: 0, - [Setting.Damage_Numbers]: 0, - [Setting.UI_Theme]: 0, - [Setting.Window_Type]: 0, - [Setting.Tutorials]: 1, - [Setting.Enable_Retries]: 0, - [Setting.Skip_Seen_Dialogues]: 0, - [Setting.Candy_Upgrade_Notification]: 0, - [Setting.Candy_Upgrade_Display]: 0, - [Setting.Money_Format]: 0, - [Setting.Sprite_Set]: 0, - [Setting.Move_Animations]: 1, - [Setting.Show_Moveset_Flyout]: 1, - [Setting.Show_Stats_on_Level_Up]: 1, - [Setting.EXP_Gains_Speed]: 0, - [Setting.EXP_Party_Display]: 0, - [Setting.HP_Bar_Speed]: 0, - [Setting.Fusion_Palette_Swaps]: 1, - [Setting.Player_Gender]: 0, - [Setting.Touch_Controls]: 0, - [Setting.Vibration]: 0 -}; - -export const reloadSettings: Setting[] = [Setting.UI_Theme, Setting.Language, Setting.Sprite_Set, Setting.Candy_Upgrade_Display]; - -export function setSetting(scene: BattleScene, setting: Setting, value: integer): boolean { - switch (setting) { - case Setting.Game_Speed: - scene.gameSpeed = parseFloat(settingOptions[setting][value].replace("x", "")); - break; - case Setting.Master_Volume: - scene.masterVolume = value ? parseInt(settingOptions[setting][value]) * 0.01 : 0; - scene.updateSoundVolume(); - break; - case Setting.BGM_Volume: - scene.bgmVolume = value ? parseInt(settingOptions[setting][value]) * 0.01 : 0; - scene.updateSoundVolume(); - break; - case Setting.SE_Volume: - scene.seVolume = value ? parseInt(settingOptions[setting][value]) * 0.01 : 0; - scene.updateSoundVolume(); - break; - case Setting.Damage_Numbers: - scene.damageNumbersMode = value; - break; - case Setting.UI_Theme: - scene.uiTheme = value; - break; - case Setting.Window_Type: - updateWindowType(scene, parseInt(settingOptions[setting][value])); - break; - case Setting.Tutorials: - scene.enableTutorials = settingOptions[setting][value] === "On"; - break; - case Setting.Enable_Retries: - scene.enableRetries = settingOptions[setting][value] === "On"; - break; - case Setting.Candy_Upgrade_Notification: - if (scene.candyUpgradeNotification === value) { - break; - } - - scene.candyUpgradeNotification = value; - scene.eventTarget.dispatchEvent(new CandyUpgradeNotificationChangedEvent(value)); - break; - case Setting.Candy_Upgrade_Display: - scene.candyUpgradeDisplay = value; - case Setting.Money_Format: - switch (settingOptions[setting][value]) { - case "Normal": - scene.moneyFormat = MoneyFormat.NORMAL; - break; - case "Abbreviated": - scene.moneyFormat = MoneyFormat.ABBREVIATED; - break; - } - scene.updateMoneyText(false); - break; - case Setting.Sprite_Set: - scene.experimentalSprites = !!value; - if (value) { - scene.initExpSprites(); - } - break; - case Setting.Move_Animations: - scene.moveAnimations = settingOptions[setting][value] === "On"; - break; - case Setting.Show_Moveset_Flyout: - scene.showMovesetFlyout = settingOptions[setting][value] === "On"; - break; - case Setting.Show_Stats_on_Level_Up: - scene.showLevelUpStats = settingOptions[setting][value] === "On"; - break; - case Setting.EXP_Gains_Speed: - scene.expGainsSpeed = value; - break; - case Setting.EXP_Party_Display: - scene.expParty = value; - break; - case Setting.HP_Bar_Speed: - scene.hpBarSpeed = value; - break; - case Setting.Fusion_Palette_Swaps: - scene.fusionPaletteSwaps = !!value; - break; - case Setting.Player_Gender: - if (scene.gameData) { - const female = settingOptions[setting][value] === "Girl"; - scene.gameData.gender = female ? PlayerGender.FEMALE : PlayerGender.MALE; - scene.trainer.setTexture(scene.trainer.texture.key.replace(female ? "m" : "f", female ? "f" : "m")); - } else { - return false; - } - break; - case Setting.Touch_Controls: - scene.enableTouchControls = settingOptions[setting][value] !== "Disabled" && hasTouchscreen(); - const touchControls = document.getElementById("touchControls"); - if (touchControls) { - touchControls.classList.toggle("visible", scene.enableTouchControls); - } - break; - case Setting.Vibration: - scene.enableVibration = settingOptions[setting][value] !== "Disabled" && hasTouchscreen(); - break; - case Setting.Skip_Seen_Dialogues: - scene.skipSeenDialogues = settingOptions[setting][value] === "On"; - break; - case Setting.Language: - if (value) { - if (scene.ui) { - const cancelHandler = () => { - scene.ui.revertMode(); - (scene.ui.getHandler() as SettingsUiHandler).setOptionCursor(Object.values(Setting).indexOf(Setting.Language), 0, true); - }; - const changeLocaleHandler = (locale: string): boolean => { - try { - i18next.changeLanguage(locale); - localStorage.setItem("prLang", locale); - cancelHandler(); - // Reload the whole game to apply the new locale since also some constants are translated - window.location.reload(); - return true; - } catch (error) { - console.error("Error changing locale:", error); - return false; - } - }; - scene.ui.setOverlayMode(Mode.OPTION_SELECT, { - options: [ - { - label: "English", - handler: () => changeLocaleHandler("en") - }, - { - label: "Español", - handler: () => changeLocaleHandler("es") - }, - { - label: "Italiano", - handler: () => changeLocaleHandler("it") - }, - { - label: "Français", - handler: () => changeLocaleHandler("fr") - }, - { - label: "Deutsch", - handler: () => changeLocaleHandler("de") - }, - { - label: "Português (BR)", - handler: () => changeLocaleHandler("pt_BR") - }, - { - label: "简体中文", - handler: () => changeLocaleHandler("zh_CN") - }, - { - label: "繁體中文", - handler: () => changeLocaleHandler("zh_TW") - }, - { - label: "한국어", - handler: () => changeLocaleHandler("ko") - }, - { - label: "Cancel", - handler: () => cancelHandler() - } - ], - maxOptions: 7 - }); - return false; - } - } - break; - } - - return true; -} diff --git a/src/system/settings-gamepad.ts b/src/system/settings/settings-gamepad.ts similarity index 82% rename from src/system/settings-gamepad.ts rename to src/system/settings/settings-gamepad.ts index 22cc07efce1..909b78ffe6f 100644 --- a/src/system/settings-gamepad.ts +++ b/src/system/settings/settings-gamepad.ts @@ -1,10 +1,9 @@ -import BattleScene from "../battle-scene"; -import {SettingDefaults, SettingOptions} from "./settings"; -import SettingsGamepadUiHandler from "../ui/settings/settings-gamepad-ui-handler"; -import {Mode} from "../ui/ui"; -import {truncateString} from "../utils"; -import {Button} from "../enums/buttons"; -import {SettingKeyboard} from "#app/system/settings-keyboard"; +import BattleScene from "../../battle-scene"; +import SettingsGamepadUiHandler from "../../ui/settings/settings-gamepad-ui-handler"; +import {Mode} from "../../ui/ui"; +import {truncateString} from "../../utils"; +import {Button} from "../../enums/buttons"; +import {SettingKeyboard} from "#app/system/settings/settings-keyboard"; export enum SettingGamepad { Controller = "CONTROLLER", @@ -28,29 +27,31 @@ export enum SettingGamepad { Button_Submit = "BUTTON_SUBMIT", } -export const settingGamepadOptions: SettingOptions = { +const pressAction = "Press action to assign"; + +export const settingGamepadOptions = { [SettingGamepad.Controller]: ["Default", "Change"], [SettingGamepad.Gamepad_Support]: ["Auto", "Disabled"], - [SettingGamepad.Button_Up]: [`KEY ${Button.UP.toString()}`, "Press action to assign"], - [SettingGamepad.Button_Down]: [`KEY ${Button.DOWN.toString()}`, "Press action to assign"], - [SettingGamepad.Button_Left]: [`KEY ${Button.LEFT.toString()}`, "Press action to assign"], - [SettingGamepad.Button_Right]: [`KEY ${Button.RIGHT.toString()}`, "Press action to assign"], - [SettingGamepad.Button_Action]: [`KEY ${Button.ACTION.toString()}`, "Press action to assign"], - [SettingGamepad.Button_Cancel]: [`KEY ${Button.CANCEL.toString()}`, "Press action to assign"], - [SettingGamepad.Button_Menu]: [`KEY ${Button.MENU.toString()}`, "Press action to assign"], - [SettingGamepad.Button_Stats]: [`KEY ${Button.STATS.toString()}`, "Press action to assign"], - [SettingGamepad.Button_Cycle_Form]: [`KEY ${Button.CYCLE_FORM.toString()}`, "Press action to assign"], - [SettingGamepad.Button_Cycle_Shiny]: [`KEY ${Button.CYCLE_SHINY.toString()}`, "Press action to assign"], - [SettingGamepad.Button_Cycle_Gender]: [`KEY ${Button.CYCLE_GENDER.toString()}`, "Press action to assign"], - [SettingGamepad.Button_Cycle_Ability]: [`KEY ${Button.CYCLE_ABILITY.toString()}`, "Press action to assign"], - [SettingGamepad.Button_Cycle_Nature]: [`KEY ${Button.CYCLE_NATURE.toString()}`, "Press action to assign"], - [SettingGamepad.Button_Cycle_Variant]: [`KEY ${Button.V.toString()}`, "Press action to assign"], - [SettingGamepad.Button_Speed_Up]: [`KEY ${Button.SPEED_UP.toString()}`, "Press action to assign"], - [SettingGamepad.Button_Slow_Down]: [`KEY ${Button.SLOW_DOWN.toString()}`, "Press action to assign"], - [SettingGamepad.Button_Submit]: [`KEY ${Button.SUBMIT.toString()}`, "Press action to assign"], + [SettingGamepad.Button_Up]: [`KEY ${Button.UP.toString()}`, pressAction], + [SettingGamepad.Button_Down]: [`KEY ${Button.DOWN.toString()}`, pressAction], + [SettingGamepad.Button_Left]: [`KEY ${Button.LEFT.toString()}`, pressAction], + [SettingGamepad.Button_Right]: [`KEY ${Button.RIGHT.toString()}`, pressAction], + [SettingGamepad.Button_Action]: [`KEY ${Button.ACTION.toString()}`, pressAction], + [SettingGamepad.Button_Cancel]: [`KEY ${Button.CANCEL.toString()}`, pressAction], + [SettingGamepad.Button_Menu]: [`KEY ${Button.MENU.toString()}`, pressAction], + [SettingGamepad.Button_Stats]: [`KEY ${Button.STATS.toString()}`, pressAction], + [SettingGamepad.Button_Cycle_Form]: [`KEY ${Button.CYCLE_FORM.toString()}`, pressAction], + [SettingGamepad.Button_Cycle_Shiny]: [`KEY ${Button.CYCLE_SHINY.toString()}`, pressAction], + [SettingGamepad.Button_Cycle_Gender]: [`KEY ${Button.CYCLE_GENDER.toString()}`, pressAction], + [SettingGamepad.Button_Cycle_Ability]: [`KEY ${Button.CYCLE_ABILITY.toString()}`, pressAction], + [SettingGamepad.Button_Cycle_Nature]: [`KEY ${Button.CYCLE_NATURE.toString()}`, pressAction], + [SettingGamepad.Button_Cycle_Variant]: [`KEY ${Button.V.toString()}`, pressAction], + [SettingGamepad.Button_Speed_Up]: [`KEY ${Button.SPEED_UP.toString()}`, pressAction], + [SettingGamepad.Button_Slow_Down]: [`KEY ${Button.SLOW_DOWN.toString()}`, pressAction], + [SettingGamepad.Button_Submit]: [`KEY ${Button.SUBMIT.toString()}`, pressAction], }; -export const settingGamepadDefaults: SettingDefaults = { +export const settingGamepadDefaults = { [SettingGamepad.Controller]: 0, [SettingGamepad.Gamepad_Support]: 0, [SettingGamepad.Button_Up]: 0, diff --git a/src/system/settings-keyboard.ts b/src/system/settings/settings-keyboard.ts similarity index 84% rename from src/system/settings-keyboard.ts rename to src/system/settings/settings-keyboard.ts index 4ffe6ad3e70..c394b2ef8f0 100644 --- a/src/system/settings-keyboard.ts +++ b/src/system/settings/settings-keyboard.ts @@ -1,4 +1,3 @@ -import {SettingDefaults, SettingOptions} from "#app/system/settings"; import {Button} from "#app/enums/buttons"; import BattleScene from "#app/battle-scene"; import {Mode} from "#app/ui/ui"; @@ -42,46 +41,47 @@ export enum SettingKeyboard { Alt_Button_Submit = "ALT_BUTTON_SUBMIT", } -export const settingKeyboardOptions: SettingOptions = { - // [SettingKeyboard.Default_Layout]: ['Default'], - [SettingKeyboard.Button_Up]: [`KEY ${Button.UP.toString()}`, "Press action to assign"], - [SettingKeyboard.Button_Down]: [`KEY ${Button.DOWN.toString()}`, "Press action to assign"], - [SettingKeyboard.Alt_Button_Up]: [`KEY ${Button.UP.toString()}`, "Press action to assign"], - [SettingKeyboard.Button_Left]: [`KEY ${Button.LEFT.toString()}`, "Press action to assign"], - [SettingKeyboard.Button_Right]: [`KEY ${Button.RIGHT.toString()}`, "Press action to assign"], - [SettingKeyboard.Button_Action]: [`KEY ${Button.ACTION.toString()}`, "Press action to assign"], - [SettingKeyboard.Button_Menu]: [`KEY ${Button.MENU.toString()}`, "Press action to assign"], - [SettingKeyboard.Button_Submit]: [`KEY ${Button.SUBMIT.toString()}`, "Press action to assign"], +const pressAction = "Press action to assign"; - [SettingKeyboard.Alt_Button_Down]: [`KEY ${Button.DOWN.toString()}`, "Press action to assign"], - [SettingKeyboard.Alt_Button_Left]: [`KEY ${Button.LEFT.toString()}`, "Press action to assign"], - [SettingKeyboard.Alt_Button_Right]: [`KEY ${Button.RIGHT.toString()}`, "Press action to assign"], - [SettingKeyboard.Alt_Button_Action]: [`KEY ${Button.ACTION.toString()}`, "Press action to assign"], - [SettingKeyboard.Button_Cancel]: [`KEY ${Button.CANCEL.toString()}`, "Press action to assign"], - [SettingKeyboard.Alt_Button_Cancel]: [`KEY ${Button.CANCEL.toString()}`, "Press action to assign"], - [SettingKeyboard.Alt_Button_Menu]: [`KEY ${Button.MENU.toString()}`, "Press action to assign"], - [SettingKeyboard.Button_Stats]: [`KEY ${Button.STATS.toString()}`, "Press action to assign"], - [SettingKeyboard.Alt_Button_Stats]: [`KEY ${Button.STATS.toString()}`, "Press action to assign"], - [SettingKeyboard.Button_Cycle_Form]: [`KEY ${Button.CYCLE_FORM.toString()}`, "Press action to assign"], - [SettingKeyboard.Alt_Button_Cycle_Form]: [`KEY ${Button.CYCLE_FORM.toString()}`, "Press action to assign"], - [SettingKeyboard.Button_Cycle_Shiny]: [`KEY ${Button.CYCLE_SHINY.toString()}`, "Press action to assign"], - [SettingKeyboard.Alt_Button_Cycle_Shiny]: [`KEY ${Button.CYCLE_SHINY.toString()}`, "Press action to assign"], - [SettingKeyboard.Button_Cycle_Gender]: [`KEY ${Button.CYCLE_GENDER.toString()}`, "Press action to assign"], - [SettingKeyboard.Alt_Button_Cycle_Gender]: [`KEY ${Button.CYCLE_GENDER.toString()}`, "Press action to assign"], - [SettingKeyboard.Button_Cycle_Ability]: [`KEY ${Button.CYCLE_ABILITY.toString()}`, "Press action to assign"], - [SettingKeyboard.Alt_Button_Cycle_Ability]: [`KEY ${Button.CYCLE_ABILITY.toString()}`, "Press action to assign"], - [SettingKeyboard.Button_Cycle_Nature]: [`KEY ${Button.CYCLE_NATURE.toString()}`, "Press action to assign"], - [SettingKeyboard.Alt_Button_Cycle_Nature]: [`KEY ${Button.CYCLE_NATURE.toString()}`, "Press action to assign"], - [SettingKeyboard.Button_Cycle_Variant]: [`KEY ${Button.V.toString()}`, "Press action to assign"], - [SettingKeyboard.Alt_Button_Cycle_Variant]: [`KEY ${Button.V.toString()}`, "Press action to assign"], - [SettingKeyboard.Button_Speed_Up]: [`KEY ${Button.SPEED_UP.toString()}`, "Press action to assign"], - [SettingKeyboard.Alt_Button_Speed_Up]: [`KEY ${Button.SPEED_UP.toString()}`, "Press action to assign"], - [SettingKeyboard.Button_Slow_Down]: [`KEY ${Button.SLOW_DOWN.toString()}`, "Press action to assign"], - [SettingKeyboard.Alt_Button_Slow_Down]: [`KEY ${Button.SLOW_DOWN.toString()}`, "Press action to assign"], - [SettingKeyboard.Alt_Button_Submit]: [`KEY ${Button.SUBMIT.toString()}`, "Press action to assign"], +export const settingKeyboardOptions = { + // [SettingKeyboard.Default_Layout]: ['Default'], + [SettingKeyboard.Button_Up]: [`KEY ${Button.UP.toString()}`, pressAction], + [SettingKeyboard.Button_Down]: [`KEY ${Button.DOWN.toString()}`, pressAction], + [SettingKeyboard.Alt_Button_Up]: [`KEY ${Button.UP.toString()}`, pressAction], + [SettingKeyboard.Button_Left]: [`KEY ${Button.LEFT.toString()}`, pressAction], + [SettingKeyboard.Button_Right]: [`KEY ${Button.RIGHT.toString()}`, pressAction], + [SettingKeyboard.Button_Action]: [`KEY ${Button.ACTION.toString()}`, pressAction], + [SettingKeyboard.Button_Menu]: [`KEY ${Button.MENU.toString()}`, pressAction], + [SettingKeyboard.Button_Submit]: [`KEY ${Button.SUBMIT.toString()}`, pressAction], + [SettingKeyboard.Alt_Button_Down]: [`KEY ${Button.DOWN.toString()}`, pressAction], + [SettingKeyboard.Alt_Button_Left]: [`KEY ${Button.LEFT.toString()}`, pressAction], + [SettingKeyboard.Alt_Button_Right]: [`KEY ${Button.RIGHT.toString()}`, pressAction], + [SettingKeyboard.Alt_Button_Action]: [`KEY ${Button.ACTION.toString()}`, pressAction], + [SettingKeyboard.Button_Cancel]: [`KEY ${Button.CANCEL.toString()}`, pressAction], + [SettingKeyboard.Alt_Button_Cancel]: [`KEY ${Button.CANCEL.toString()}`, pressAction], + [SettingKeyboard.Alt_Button_Menu]: [`KEY ${Button.MENU.toString()}`, pressAction], + [SettingKeyboard.Button_Stats]: [`KEY ${Button.STATS.toString()}`, pressAction], + [SettingKeyboard.Alt_Button_Stats]: [`KEY ${Button.STATS.toString()}`, pressAction], + [SettingKeyboard.Button_Cycle_Form]: [`KEY ${Button.CYCLE_FORM.toString()}`, pressAction], + [SettingKeyboard.Alt_Button_Cycle_Form]: [`KEY ${Button.CYCLE_FORM.toString()}`, pressAction], + [SettingKeyboard.Button_Cycle_Shiny]: [`KEY ${Button.CYCLE_SHINY.toString()}`, pressAction], + [SettingKeyboard.Alt_Button_Cycle_Shiny]: [`KEY ${Button.CYCLE_SHINY.toString()}`, pressAction], + [SettingKeyboard.Button_Cycle_Gender]: [`KEY ${Button.CYCLE_GENDER.toString()}`, pressAction], + [SettingKeyboard.Alt_Button_Cycle_Gender]: [`KEY ${Button.CYCLE_GENDER.toString()}`, pressAction], + [SettingKeyboard.Button_Cycle_Ability]: [`KEY ${Button.CYCLE_ABILITY.toString()}`, pressAction], + [SettingKeyboard.Alt_Button_Cycle_Ability]: [`KEY ${Button.CYCLE_ABILITY.toString()}`, pressAction], + [SettingKeyboard.Button_Cycle_Nature]: [`KEY ${Button.CYCLE_NATURE.toString()}`, pressAction], + [SettingKeyboard.Alt_Button_Cycle_Nature]: [`KEY ${Button.CYCLE_NATURE.toString()}`, pressAction], + [SettingKeyboard.Button_Cycle_Variant]: [`KEY ${Button.V.toString()}`, pressAction], + [SettingKeyboard.Alt_Button_Cycle_Variant]: [`KEY ${Button.V.toString()}`, pressAction], + [SettingKeyboard.Button_Speed_Up]: [`KEY ${Button.SPEED_UP.toString()}`, pressAction], + [SettingKeyboard.Alt_Button_Speed_Up]: [`KEY ${Button.SPEED_UP.toString()}`, pressAction], + [SettingKeyboard.Button_Slow_Down]: [`KEY ${Button.SLOW_DOWN.toString()}`, pressAction], + [SettingKeyboard.Alt_Button_Slow_Down]: [`KEY ${Button.SLOW_DOWN.toString()}`, pressAction], + [SettingKeyboard.Alt_Button_Submit]: [`KEY ${Button.SUBMIT.toString()}`, pressAction], }; -export const settingKeyboardDefaults: SettingDefaults = { +export const settingKeyboardDefaults = { // [SettingKeyboard.Default_Layout]: 0, [SettingKeyboard.Button_Up]: 0, [SettingKeyboard.Button_Down]: 0, diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts new file mode 100644 index 00000000000..e68e5ea8704 --- /dev/null +++ b/src/system/settings/settings.ts @@ -0,0 +1,453 @@ +import { Mode } from "#app/ui/ui"; +import i18next from "i18next"; +import BattleScene from "../../battle-scene"; +import { hasTouchscreen } from "../../touch-controls"; +import { updateWindowType } from "../../ui/ui-theme"; +import { PlayerGender } from "../game-data"; +import { CandyUpgradeNotificationChangedEvent } from "#app/battle-scene-events.js"; +import { MoneyFormat } from "../../enums/money-format"; +import SettingsUiHandler from "#app/ui/settings/settings-ui-handler"; + +const MUTE = "Mute"; +const VOLUME_OPTIONS = new Array(11).fill(null).map((_, i) => i ? (i * 10).toString() : MUTE); +const OFF_ON = ["Off", "On"]; +const AUTO_DISABLED = ["Auto", "Disabled"]; + +/** + * Types for helping separate settings to different menus + */ +export enum SettingType { + GENERAL, + ACCESSIBILITY +} + +export interface Setting { + key: string + label: string + options: Array + default: number + type: SettingType + requireReload?: boolean +} + +/** + * Setting Keys for existing settings + * to be used when trying to find or update Settings + */ +export const SettingKeys = { + Game_Speed: "GAME_SPEED", + Master_Volume: "MASTER_VOLUME", + BGM_Volume: "BGM_VOLUME", + SE_Volume: "SE_VOLUME", + Language: "LANGUAGE", + Damage_Numbers: "DAMAGE_NUMBERS", + UI_Theme: "UI_THEME", + Window_Type: "WINDOW_TYPE", + Tutorials: "TUTORIALS", + Enable_Retries: "ENABLE_RETRIES", + Skip_Seen_Dialogues: "SKIP_SEEN_DIALOGUES", + Candy_Upgrade_Notification: "CANDY_UPGRADE_NOTIFICATION", + Candy_Upgrade_Display: "CANDY_UPGRADE_DISPLAY", + Money_Format: "MONEY_FORMAT", + Sprite_Set: "SPRITE_SET", + Move_Animations: "MOVE_ANIMATIONS", + Show_Moveset_Flyout: "SHOW_MOVESET_FLYOUT", + Show_Stats_on_Level_Up: "SHOW_LEVEL_UP_STATS", + EXP_Gains_Speed: "EXP_GAINS_SPEED", + EXP_Party_Display: "EXP_PARTY_DISPLAY", + HP_Bar_Speed: "HP_BAR_SPEED", + Fusion_Palette_Swaps: "FUSION_PALETTE_SWAPS", + Player_Gender: "PLAYER_GENDER", + Touch_Controls: "TOUCH_CONTROLS", + Vibration: "VIBRATION" +}; + +/** + * All Settings not related to controls + */ +export const Setting: Array = [ + { + key: SettingKeys.Game_Speed, + label: "Game Speed", + options: ["1x", "1.25x", "1.5x", "2x", "2.5x", "3x", "4x", "5x"], + default: 3, + type: SettingType.GENERAL + }, + { + key: SettingKeys.Master_Volume, + label: "Master Volume", + options: VOLUME_OPTIONS, + default: 5, + type: SettingType.GENERAL + }, + { + key: SettingKeys.BGM_Volume, + label: "BGM Volume", + options: VOLUME_OPTIONS, + default: 10, + type: SettingType.GENERAL + }, + { + key: SettingKeys.SE_Volume, + label: "SE Volume", + options: VOLUME_OPTIONS, + default: 10, + type: SettingType.GENERAL + }, + { + key: SettingKeys.Language, + label: "Language", + options: ["English", "Change"], + default: 0, + type: SettingType.GENERAL, + requireReload: true + }, + { + key: SettingKeys.Damage_Numbers, + label: "Damage Numbers", + options: ["Off", "Simple", "Fancy"], + default: 0, + type: SettingType.GENERAL + }, + { + key: SettingKeys.UI_Theme, + label: "UI Theme", + options: ["Default", "Legacy"], + default: 0, + type: SettingType.GENERAL, + requireReload: true + }, + { + key: SettingKeys.Window_Type, + label: "Window Type", + options: new Array(5).fill(null).map((_, i) => (i + 1).toString()), + default: 0, + type: SettingType.GENERAL + }, + { + key: SettingKeys.Tutorials, + label: "Tutorials", + options: OFF_ON, + default: 1, + type: SettingType.GENERAL + }, + { + key: SettingKeys.Enable_Retries, + label: "Enable Retries", + options: OFF_ON, + default: 0, + type: SettingType.ACCESSIBILITY + }, + { + key: SettingKeys.Skip_Seen_Dialogues, + label: "Skip Seen Dialogues", + options: OFF_ON, + default: 0, + type: SettingType.GENERAL + }, + { + key: SettingKeys.Candy_Upgrade_Notification, + label: "Candy Upgrade Notification", + options: ["Off", "Passives Only", "On"], + default: 0, + type: SettingType.ACCESSIBILITY + }, + { + key: SettingKeys.Candy_Upgrade_Display, + label: "Candy Upgrade Display", + options: ["Icon", "Animation"], + default: 0, + type: SettingType.ACCESSIBILITY, + requireReload: true + }, + { + key: SettingKeys.Money_Format, + label: "Money Format", + options: ["Normal", "Abbreviated"], + default: 0, + type: SettingType.ACCESSIBILITY + }, + { + key: SettingKeys.Sprite_Set, + label: "Sprite Set", + options: ["Consistent", "Mixed Animated"], + default: 0, + type: SettingType.GENERAL, + requireReload: true + }, + { + key: SettingKeys.Move_Animations, + label: "Move Animations", + options: OFF_ON, + default: 1, + type: SettingType.GENERAL + }, + { + key: SettingKeys.Show_Moveset_Flyout, + label: "Show Moveset Flyout", + options: OFF_ON, + default: 1, + type: SettingType.ACCESSIBILITY + }, + { + key: SettingKeys.Show_Stats_on_Level_Up, + label: "Show Stats on Level Up", + options: OFF_ON, + default: 1, + type: SettingType.GENERAL + }, + { + key: SettingKeys.EXP_Gains_Speed, + label: "EXP Gains Speed", + options: ["Normal", "Fast", "Faster", "Skip"], + default: 0, + type: SettingType.GENERAL + }, + { + key: SettingKeys.EXP_Party_Display, + label: "EXP Party Display", + options: ["Normal", "Level Up Notification", "Skip"], + default: 0, + type: SettingType.GENERAL + }, + { + key: SettingKeys.HP_Bar_Speed, + label: "HP Bar Speed", + options: ["Normal", "Fast", "Faster", "Skip"], + default: 0, + type: SettingType.GENERAL + }, + { + key: SettingKeys.Fusion_Palette_Swaps, + label: "Fusion Palette Swaps", + options: OFF_ON, + default: 1, + type: SettingType.GENERAL + }, + { + key: SettingKeys.Player_Gender, + label: "Player Gender", + options: ["Boy", "Girl"], + default: 0, + type: SettingType.GENERAL + }, + { + key: SettingKeys.Touch_Controls, + label: "Touch Controls", + options: AUTO_DISABLED, + default: 0, + type: SettingType.GENERAL + }, + { + key: SettingKeys.Vibration, + label: "Vibration", + options: AUTO_DISABLED, + default: 0, + type: SettingType.GENERAL + } +]; + +/** + * Return the index of a Setting + * @param key SettingKey + * @returns index or -1 if doesn't exist + */ +export function settingIndex(key: string) { + return Setting.findIndex(s => s.key === key); +} + +/** + * Resets all settings to their defaults + * @param scene current BattleScene + */ +export function resetSettings(scene: BattleScene) { + Setting.forEach(s => setSetting(scene, s.key, s.default)); +} + +/** + * Updates a setting for current BattleScene + * @param scene current BattleScene + * @param setting string ideally from SettingKeys + * @param value value to update setting with + * @returns true if successful, false if not + */ +export function setSetting(scene: BattleScene, setting: string, value: integer): boolean { + const index: number = settingIndex(setting); + if ( index === -1) { + return false; + } + switch (Setting[index].key) { + case SettingKeys.Game_Speed: + scene.gameSpeed = parseFloat(Setting[index].options[value].replace("x", "")); + break; + case SettingKeys.Master_Volume: + scene.masterVolume = value ? parseInt(Setting[index].options[value]) * 0.01 : 0; + scene.updateSoundVolume(); + break; + case SettingKeys.BGM_Volume: + scene.bgmVolume = value ? parseInt(Setting[index].options[value]) * 0.01 : 0; + scene.updateSoundVolume(); + break; + case SettingKeys.SE_Volume: + scene.seVolume = value ? parseInt(Setting[index].options[value]) * 0.01 : 0; + scene.updateSoundVolume(); + break; + case SettingKeys.Damage_Numbers: + scene.damageNumbersMode = value; + break; + case SettingKeys.UI_Theme: + scene.uiTheme = value; + break; + case SettingKeys.Window_Type: + updateWindowType(scene, parseInt(Setting[index].options[value])); + break; + case SettingKeys.Tutorials: + scene.enableTutorials = Setting[index].options[value] === "On"; + break; + case SettingKeys.Enable_Retries: + scene.enableRetries = Setting[index].options[value] === "On"; + break; + case SettingKeys.Skip_Seen_Dialogues: + scene.skipSeenDialogues = Setting[index].options[value] === "On"; + break; + case SettingKeys.Candy_Upgrade_Notification: + if (scene.candyUpgradeNotification === value) { + break; + } + + scene.candyUpgradeNotification = value; + scene.eventTarget.dispatchEvent(new CandyUpgradeNotificationChangedEvent(value)); + break; + case SettingKeys.Candy_Upgrade_Display: + scene.candyUpgradeDisplay = value; + case SettingKeys.Money_Format: + switch (Setting[index].options[value]) { + case "Normal": + scene.moneyFormat = MoneyFormat.NORMAL; + break; + case "Abbreviated": + scene.moneyFormat = MoneyFormat.ABBREVIATED; + break; + } + scene.updateMoneyText(false); + break; + case SettingKeys.Sprite_Set: + scene.experimentalSprites = !!value; + if (value) { + scene.initExpSprites(); + } + break; + case SettingKeys.Move_Animations: + scene.moveAnimations = Setting[index].options[value] === "On"; + break; + case SettingKeys.Show_Moveset_Flyout: + scene.showMovesetFlyout = Setting[index].options[value] === "On"; + break; + case SettingKeys.Show_Stats_on_Level_Up: + scene.showLevelUpStats = Setting[index].options[value] === "On"; + break; + case SettingKeys.EXP_Gains_Speed: + scene.expGainsSpeed = value; + break; + case SettingKeys.EXP_Party_Display: + scene.expParty = value; + break; + case SettingKeys.HP_Bar_Speed: + scene.hpBarSpeed = value; + break; + case SettingKeys.Fusion_Palette_Swaps: + scene.fusionPaletteSwaps = !!value; + break; + case SettingKeys.Player_Gender: + if (scene.gameData) { + const female = Setting[index].options[value] === "Girl"; + scene.gameData.gender = female ? PlayerGender.FEMALE : PlayerGender.MALE; + scene.trainer.setTexture(scene.trainer.texture.key.replace(female ? "m" : "f", female ? "f" : "m")); + } else { + return false; + } + break; + case SettingKeys.Touch_Controls: + scene.enableTouchControls = Setting[index].options[value] !== "Disabled" && hasTouchscreen(); + const touchControls = document.getElementById("touchControls"); + if (touchControls) { + touchControls.classList.toggle("visible", scene.enableTouchControls); + } + break; + case SettingKeys.Vibration: + scene.enableVibration = Setting[index].options[value] !== "Disabled" && hasTouchscreen(); + break; + case SettingKeys.Language: + if (value) { + if (scene.ui) { + const cancelHandler = () => { + scene.ui.revertMode(); + const languageSetting = Setting.find(setting => setting.key === SettingKeys.Language); + (scene.ui.getHandler() as SettingsUiHandler).setOptionCursor(Setting.indexOf(languageSetting), 0, true); + }; + const changeLocaleHandler = (locale: string): boolean => { + try { + i18next.changeLanguage(locale); + localStorage.setItem("prLang", locale); + cancelHandler(); + // Reload the whole game to apply the new locale since also some constants are translated + window.location.reload(); + return true; + } catch (error) { + console.error("Error changing locale:", error); + return false; + } + }; + scene.ui.setOverlayMode(Mode.OPTION_SELECT, { + options: [ + { + label: "English", + handler: () => changeLocaleHandler("en") + }, + { + label: "Español", + handler: () => changeLocaleHandler("es") + }, + { + label: "Italiano", + handler: () => changeLocaleHandler("it") + }, + { + label: "Français", + handler: () => changeLocaleHandler("fr") + }, + { + label: "Deutsch", + handler: () => changeLocaleHandler("de") + }, + { + label: "Português (BR)", + handler: () => changeLocaleHandler("pt_BR") + }, + { + label: "简体中文", + handler: () => changeLocaleHandler("zh_CN") + }, + { + label: "繁體中文", + handler: () => changeLocaleHandler("zh_TW") + }, + { + label: "한국어", + handler: () => changeLocaleHandler("ko") + }, + { + label: "Cancel", + handler: () => cancelHandler() + } + ], + maxOptions: 7 + }); + return false; + } + } + break; + } + + return true; +} diff --git a/src/test/helpers/inGameManip.ts b/src/test/helpers/inGameManip.ts index c81602dff6d..1edab75fd27 100644 --- a/src/test/helpers/inGameManip.ts +++ b/src/test/helpers/inGameManip.ts @@ -3,7 +3,7 @@ import { getSettingNameWithKeycode } from "#app/configs/inputs/configHandler"; import {expect} from "vitest"; -import {SettingKeyboard} from "#app/system/settings-keyboard"; +import {SettingKeyboard} from "#app/system/settings/settings-keyboard"; export class InGameManip { private config; diff --git a/src/test/helpers/menuManip.ts b/src/test/helpers/menuManip.ts index 2377ab70c64..7b4080c3d75 100644 --- a/src/test/helpers/menuManip.ts +++ b/src/test/helpers/menuManip.ts @@ -8,7 +8,7 @@ import { assign, getSettingNameWithKeycode, canIAssignThisKey, canIDeleteThisKey, canIOverrideThisSetting } from "#app/configs/inputs/configHandler"; -import {SettingKeyboard} from "#app/system/settings-keyboard"; +import {SettingKeyboard} from "#app/system/settings/settings-keyboard"; export class MenuManip { private config; diff --git a/src/test/rebinding_setting.test.ts b/src/test/rebinding_setting.test.ts index 92376006263..03e8cbb51c4 100644 --- a/src/test/rebinding_setting.test.ts +++ b/src/test/rebinding_setting.test.ts @@ -10,7 +10,7 @@ import {InGameManip} from "#app/test/helpers/inGameManip"; import {Device} from "#app/enums/devices"; import {InterfaceConfig} from "#app/inputs-controller"; import cfg_keyboard_qwerty from "#app/configs/inputs/cfg_keyboard_qwerty"; -import {SettingKeyboard} from "#app/system/settings-keyboard"; +import {SettingKeyboard} from "#app/system/settings/settings-keyboard"; describe("Test Rebinding", () => { diff --git a/src/ui-inputs.ts b/src/ui-inputs.ts index a5fb631644a..c9bdc5feaf5 100644 --- a/src/ui-inputs.ts +++ b/src/ui-inputs.ts @@ -3,12 +3,13 @@ import {Mode} from "./ui/ui"; import {InputsController} from "./inputs-controller"; import MessageUiHandler from "./ui/message-ui-handler"; import StarterSelectUiHandler from "./ui/starter-select-ui-handler"; -import {Setting, settingOptions} from "./system/settings"; +import {Setting, SettingKeys, settingIndex} from "./system/settings/settings"; import SettingsUiHandler from "./ui/settings/settings-ui-handler"; import {Button} from "./enums/buttons"; import SettingsGamepadUiHandler from "./ui/settings/settings-gamepad-ui-handler"; import SettingsKeyboardUiHandler from "#app/ui/settings/settings-keyboard-ui-handler"; import BattleScene from "./battle-scene"; +import SettingsAccessibilityUiHandler from "./ui/settings/settings-accessiblity-ui-handler"; type ActionKeys = Record void>; @@ -161,7 +162,7 @@ export class UiInputs { } buttonCycleOption(button: Button): void { - const whitelist = [StarterSelectUiHandler, SettingsUiHandler, SettingsGamepadUiHandler, SettingsKeyboardUiHandler]; + const whitelist = [StarterSelectUiHandler, SettingsUiHandler, SettingsAccessibilityUiHandler, SettingsGamepadUiHandler, SettingsKeyboardUiHandler]; const uiHandler = this.scene.ui?.getHandler(); if (whitelist.some(handler => uiHandler instanceof handler)) { this.scene.ui.processInput(button); @@ -171,17 +172,14 @@ export class UiInputs { } buttonSpeedChange(up = true): void { - if (up) { - if (this.scene.gameSpeed < 5) { - this.scene.gameData.saveSetting(Setting.Game_Speed, settingOptions[Setting.Game_Speed].indexOf(`${this.scene.gameSpeed}x`) + 1); - if (this.scene.ui?.getMode() === Mode.SETTINGS) { - (this.scene.ui.getHandler() as SettingsUiHandler).show([]); - } + const settingGameSpeed = settingIndex(SettingKeys.Game_Speed); + if (up && this.scene.gameSpeed < 5) { + this.scene.gameData.saveSetting(SettingKeys.Game_Speed, Setting[settingGameSpeed].options.indexOf(`${this.scene.gameSpeed}x`) + 1); + if (this.scene.ui?.getMode() === Mode.SETTINGS) { + (this.scene.ui.getHandler() as SettingsUiHandler).show([]); } - return; - } - if (this.scene.gameSpeed > 1) { - this.scene.gameData.saveSetting(Setting.Game_Speed, Math.max(settingOptions[Setting.Game_Speed].indexOf(`${this.scene.gameSpeed}x`) - 1, 0)); + } else if (!up && this.scene.gameSpeed > 1) { + this.scene.gameData.saveSetting(SettingKeys.Game_Speed, Math.max(Setting[settingGameSpeed].options.indexOf(`${this.scene.gameSpeed}x`) - 1, 0)); if (this.scene.ui?.getMode() === Mode.SETTINGS) { (this.scene.ui.getHandler() as SettingsUiHandler).show([]); } diff --git a/src/ui/settings/abstract-control-settings-ui-handler.ts b/src/ui/settings/abstract-control-settings-ui-handler.ts new file mode 100644 index 00000000000..b8165d41980 --- /dev/null +++ b/src/ui/settings/abstract-control-settings-ui-handler.ts @@ -0,0 +1,660 @@ +import UiHandler from "../ui-handler"; +import BattleScene from "../../battle-scene"; +import {Mode} from "../ui"; +import {InterfaceConfig} from "../../inputs-controller"; +import {addWindow} from "../ui-theme"; +import {addTextObject, TextStyle} from "../text"; +import {Button} from "../../enums/buttons"; +import {getIconWithSettingName} from "#app/configs/inputs/configHandler"; +import NavigationMenu, {NavigationManager} from "#app/ui/settings/navigationMenu"; +import { Device } from "#app/enums/devices.js"; + +export interface InputsIcons { + [key: string]: Phaser.GameObjects.Sprite; +} + +export interface LayoutConfig { + optionsContainer: Phaser.GameObjects.Container; + inputsIcons: InputsIcons; + settingLabels: Phaser.GameObjects.Text[]; + optionValueLabels: Phaser.GameObjects.Text[][]; + optionCursors: integer[]; + keys: string[]; + bindingSettings: Array; +} +/** + * Abstract class for handling UI elements related to control settings. + */ +export default abstract class AbstractControlSettingsUiHandler extends UiHandler { + protected settingsContainer: Phaser.GameObjects.Container; + protected optionsContainer: Phaser.GameObjects.Container; + protected navigationContainer: NavigationMenu; + + protected scrollCursor: integer; + protected optionCursors: integer[]; + protected cursorObj: Phaser.GameObjects.NineSlice; + + protected optionsBg: Phaser.GameObjects.NineSlice; + protected actionsBg: Phaser.GameObjects.NineSlice; + + protected settingLabels: Phaser.GameObjects.Text[]; + protected optionValueLabels: Phaser.GameObjects.Text[][]; + + // layout will contain the 3 Gamepad tab for each config - dualshock, xbox, snes + protected layout: Map = new Map(); + // Will contain the input icons from the selected layout + protected inputsIcons: InputsIcons; + protected navigationIcons: InputsIcons; + // list all the setting keys used in the selected layout (because dualshock has more buttons than xbox) + protected keys: Array; + + // Store the specific settings related to key bindings for the current gamepad configuration. + protected bindingSettings: Array; + + protected setting; + protected settingBlacklisted; + protected settingDeviceDefaults; + protected settingDeviceOptions; + protected configs; + protected commonSettingsCount; + protected textureOverride; + protected titleSelected; + protected localStoragePropertyName; + protected rowsToDisplay: number; + protected device: Device; + + abstract saveSettingToLocalStorage(setting, cursor): void; + abstract setSetting(scene: BattleScene, setting, value: integer): boolean; + + /** + * Constructor for the AbstractSettingsUiHandler. + * + * @param scene - The BattleScene instance. + * @param mode - The UI mode. + */ + constructor(scene: BattleScene, mode?: Mode) { + super(scene, mode); + this.rowsToDisplay = 8; + } + + getLocalStorageSetting(): object { + // Retrieve the settings from local storage or use an empty object if none exist. + const settings: object = localStorage.hasOwnProperty(this.localStoragePropertyName) ? JSON.parse(localStorage.getItem(this.localStoragePropertyName)) : {}; + return settings; + } + + /** + * Setup UI elements. + */ + setup() { + const ui = this.getUi(); + this.navigationIcons = {}; + + this.settingsContainer = this.scene.add.container(1, -(this.scene.game.canvas.height / 6) + 1); + + this.settingsContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains); + + this.navigationContainer = new NavigationMenu(this.scene, 0, 0); + + this.optionsBg = addWindow(this.scene, 0, this.navigationContainer.height, (this.scene.game.canvas.width / 6) - 2, (this.scene.game.canvas.height / 6) - 16 - this.navigationContainer.height - 2); + this.optionsBg.setOrigin(0, 0); + + + this.actionsBg = addWindow(this.scene, 0, (this.scene.game.canvas.height / 6) - this.navigationContainer.height, (this.scene.game.canvas.width / 6) - 2, 22); + this.actionsBg.setOrigin(0, 0); + + const iconAction = this.scene.add.sprite(0, 0, "keyboard"); + iconAction.setOrigin(0, -0.1); + iconAction.setPositionRelative(this.actionsBg, this.navigationContainer.width - 32, 4); + this.navigationIcons["BUTTON_ACTION"] = iconAction; + + const actionText = addTextObject(this.scene, 0, 0, "Action", TextStyle.SETTINGS_LABEL); + actionText.setOrigin(0, 0.15); + actionText.setPositionRelative(iconAction, -actionText.width/6-2, 0); + + const iconCancel = this.scene.add.sprite(0, 0, "keyboard"); + iconCancel.setOrigin(0, -0.1); + iconCancel.setPositionRelative(this.actionsBg, this.navigationContainer.width - 100, 4); + this.navigationIcons["BUTTON_CANCEL"] = iconCancel; + + const cancelText = addTextObject(this.scene, 0, 0, "Cancel", TextStyle.SETTINGS_LABEL); + cancelText.setOrigin(0, 0.15); + cancelText.setPositionRelative(iconCancel, -cancelText.width/6-2, 0); + + const iconReset = this.scene.add.sprite(0, 0, "keyboard"); + iconReset.setOrigin(0, -0.1); + iconReset.setPositionRelative(this.actionsBg, this.navigationContainer.width - 180, 4); + this.navigationIcons["BUTTON_HOME"] = iconReset; + + const resetText = addTextObject(this.scene, 0, 0, "Reset all", TextStyle.SETTINGS_LABEL); + resetText.setOrigin(0, 0.15); + resetText.setPositionRelative(iconReset, -resetText.width/6-2, 0); + + this.settingsContainer.add(this.optionsBg); + this.settingsContainer.add(this.actionsBg); + this.settingsContainer.add(this.navigationContainer); + this.settingsContainer.add(iconAction); + this.settingsContainer.add(iconCancel); + this.settingsContainer.add(iconReset); + this.settingsContainer.add(actionText); + this.settingsContainer.add(cancelText); + this.settingsContainer.add(resetText); + + /// Initialize a new configuration "screen" for each type of gamepad. + for (const config of this.configs) { + // Create a map to store layout settings based on the pad type. + this.layout[config.padType] = new Map(); + // Create a container for gamepad options in the scene, initially hidden. + + const optionsContainer = this.scene.add.container(0, 0); + optionsContainer.setVisible(false); + + // Gather all binding settings from the configuration. + const bindingSettings = Object.keys(config.settings); + + // Array to hold labels for different settings such as 'Controller', 'Gamepad Support', etc. + const settingLabels: Phaser.GameObjects.Text[] = []; + + // Array to hold options for each setting, e.g., 'Auto', 'Disabled'. + const optionValueLabels: Phaser.GameObjects.GameObject[][] = []; + + // Object to store sprites for each button configuration. + const inputsIcons: InputsIcons = {}; + + // Fetch common setting keys such as 'Controller' and 'Gamepad Support' from gamepad settings. + const commonSettingKeys = Object.keys(this.setting).slice(0, this.commonSettingsCount).map(key => this.setting[key]); + // Combine common and specific bindings into a single array. + const specificBindingKeys = [...commonSettingKeys, ...Object.keys(config.settings)]; + // Fetch default values for these settings and prepare to highlight selected options. + const optionCursors = Object.values(Object.keys(this.settingDeviceDefaults).filter(s => specificBindingKeys.includes(s)).map(k => this.settingDeviceDefaults[k])); + // Filter out settings that are not relevant to the current gamepad configuration. + const settingFiltered = Object.keys(this.setting).filter(_key => specificBindingKeys.includes(this.setting[_key])); + // Loop through the filtered settings to manage display and options. + + settingFiltered.forEach((setting, s) => { + // Convert the setting key from format 'Key_Name' to 'Key name' for display. + const settingName = setting.replace(/\_/g, " "); + + // Create and add a text object for the setting name to the scene. + const isLock = this.settingBlacklisted.includes(this.setting[setting]); + const labelStyle = isLock ? TextStyle.SETTINGS_LOCKED : TextStyle.SETTINGS_LABEL; + settingLabels[s] = addTextObject(this.scene, 8, 28 + s * 16, settingName, labelStyle); + settingLabels[s].setOrigin(0, 0); + optionsContainer.add(settingLabels[s]); + + // Initialize an array to store the option labels for this setting. + const valueLabels: Phaser.GameObjects.GameObject[] = []; + + // Process each option for the current setting. + for (const [o, option] of this.settingDeviceOptions[this.setting[setting]].entries()) { + // Check if the current setting is for binding keys. + if (bindingSettings.includes(this.setting[setting])) { + // Create a label for non-null options, typically indicating actionable options like 'change'. + if (o) { + const valueLabel = addTextObject(this.scene, 0, 0, isLock ? "" : option, TextStyle.WINDOW); + valueLabel.setOrigin(0, 0); + optionsContainer.add(valueLabel); + valueLabels.push(valueLabel); + continue; + } + // For null options, add an icon for the key. + const icon = this.scene.add.sprite(0, 0, this.textureOverride ? this.textureOverride : config.padType); + icon.setOrigin(0, -0.15); + inputsIcons[this.setting[setting]] = icon; + optionsContainer.add(icon); + valueLabels.push(icon); + continue; + } + // For regular settings like 'Gamepad support', create a label and determine if it is selected. + const valueLabel = addTextObject(this.scene, 0, 0, option, this.settingDeviceDefaults[this.setting[setting]] === o ? TextStyle.SETTINGS_SELECTED : TextStyle.WINDOW); + valueLabel.setOrigin(0, 0); + + optionsContainer.add(valueLabel); + + //if a setting has 2 options, valueLabels will be an array of 2 elements + valueLabels.push(valueLabel); + } + // Collect all option labels for this setting into the main array. + optionValueLabels.push(valueLabels); + + // Calculate the total width of all option labels within a specific setting + // This is achieved by summing the width of each option label + const totalWidth = optionValueLabels[s].map((o) => (o as Phaser.GameObjects.Text).width).reduce((total, width) => total += width, 0); + + // Define the minimum width for a label, ensuring it's at least 78 pixels wide or the width of the setting label plus some padding + const labelWidth = Math.max(130, settingLabels[s].displayWidth + 8); + + // Calculate the total available space for placing option labels next to their setting label + // We reserve space for the setting label and then distribute the remaining space evenly + const totalSpace = (300 - labelWidth) - totalWidth / 6; + // Calculate the spacing between options based on the available space divided by the number of gaps between labels + const optionSpacing = Math.floor(totalSpace / (optionValueLabels[s].length - 1)); + + // Initialize xOffset to zero, which will be used to position each option label horizontally + let xOffset = 0; + + // Start positioning each option label one by one + for (const value of optionValueLabels[s]) { + // Set the option label's position right next to the setting label, adjusted by xOffset + (value as Phaser.GameObjects.Text).setPositionRelative(settingLabels[s], labelWidth + xOffset, 0); + // Move the xOffset to the right for the next label, ensuring each label is spaced evenly + xOffset += (value as Phaser.GameObjects.Text).width / 6 + optionSpacing; + } + }); + + // Assigning the newly created components to the layout map under the specific gamepad type. + this.layout[config.padType].optionsContainer = optionsContainer; // Container for this pad's options. + this.layout[config.padType].inputsIcons = inputsIcons; // Icons for each input specific to this pad. + this.layout[config.padType].settingLabels = settingLabels; // Text labels for each setting available on this pad. + this.layout[config.padType].optionValueLabels = optionValueLabels; // Labels for values corresponding to each setting. + this.layout[config.padType].optionCursors = optionCursors; // Cursors to navigate through the options. + this.layout[config.padType].keys = specificBindingKeys; // Keys that identify each setting specifically bound to this pad. + this.layout[config.padType].bindingSettings = bindingSettings; // Settings that define how the keys are bound. + + // Add the options container to the overall settings container to be displayed in the UI. + this.settingsContainer.add(optionsContainer); + } + // Add the settings container to the UI. + ui.add(this.settingsContainer); + + // Initially hide the settings container until needed (e.g., when a gamepad is connected or a button is pressed). + this.settingsContainer.setVisible(false); + } + + /** + * Get the active configuration. + * + * @returns The active configuration for current device + */ + getActiveConfig(): InterfaceConfig { + return this.scene.inputController.getActiveConfig(this.device); + } + + /** + * Update the bindings for the current active device configuration. + */ + updateBindings(): void { + // Hide the options container for all layouts to reset the UI visibility. + Object.keys(this.layout).forEach((key) => this.layout[key].optionsContainer.setVisible(false)); + // Fetch the active gamepad configuration from the input controller. + const activeConfig = this.getActiveConfig(); + + // Set the UI layout for the active configuration. If unsuccessful, exit the function early. + if (!this.setLayout(activeConfig)) { + return; + } + + // Retrieve the gamepad settings from local storage or use an empty object if none exist. + const settings: object = this.getLocalStorageSetting(); + + // Update the cursor for each key based on the stored settings or default cursors. + this.keys.forEach((key, index) => { + this.setOptionCursor(index, settings.hasOwnProperty(key as string) ? settings[key as string] : this.optionCursors[index]); + }); + + // If the active configuration has no custom bindings set, exit the function early. + // by default, if custom does not exists, a default is assigned to it + // it only means the gamepad is not yet initalized + if (!activeConfig.custom) { + return; + } + + // For each element in the binding settings, update the icon according to the current assignment. + for (const elm of this.bindingSettings) { + const icon = getIconWithSettingName(activeConfig, elm); + if (icon) { + this.inputsIcons[elm as string].setFrame(icon); + this.inputsIcons[elm as string].alpha = 1; + } else { + this.inputsIcons[elm as string].alpha = 0; + } + } + + // Set the cursor and scroll cursor to their initial positions. + this.setCursor(this.cursor); + this.setScrollCursor(this.scrollCursor); + } + + updateNavigationDisplay() { + const specialIcons = { + "BUTTON_HOME": "HOME.png", + "BUTTON_DELETE": "DEL.png", + }; + for (const settingName of Object.keys(this.navigationIcons)) { + if (Object.keys(specialIcons).includes(settingName)) { + this.navigationIcons[settingName].setTexture("keyboard"); + this.navigationIcons[settingName].setFrame(specialIcons[settingName]); + this.navigationIcons[settingName].alpha = 1; + continue; + } + const icon = this.scene.inputController?.getIconForLatestInputRecorded(settingName); + if (icon) { + const type = this.scene.inputController?.getLastSourceType(); + this.navigationIcons[settingName].setTexture(type); + this.navigationIcons[settingName].setFrame(icon); + this.navigationIcons[settingName].alpha = 1; + } else { + this.navigationIcons[settingName].alpha = 0; + } + } + } + + /** + * Show the UI with the provided arguments. + * + * @param args - Arguments to be passed to the show method. + * @returns `true` if successful. + */ + show(args: any[]): boolean { + super.show(args); + + this.updateNavigationDisplay(); + NavigationManager.getInstance().updateIcons(); + // Update the bindings for the current active gamepad configuration. + this.updateBindings(); + + // Make the settings container visible to the user. + this.settingsContainer.setVisible(true); + // Reset the scroll cursor to the top of the settings container. + this.resetScroll(); + + // Move the settings container to the end of the UI stack to ensure it is displayed on top. + this.getUi().moveTo(this.settingsContainer, this.getUi().length - 1); + + // Hide any tooltips that might be visible before showing the settings container. + this.getUi().hideTooltip(); + + // Return true to indicate the UI was successfully shown. + return true; + } + + /** + * Set the UI layout for the active device configuration. + * + * @param activeConfig - The active device configuration. + * @returns `true` if the layout was successfully applied, otherwise `false`. + */ + setLayout(activeConfig: InterfaceConfig): boolean { + // Check if there is no active configuration (e.g., no gamepad connected). + if (!activeConfig) { + // Retrieve the layout for when no gamepads are connected. + const layout = this.layout["noGamepads"]; + // Make the options container visible to show message. + layout.optionsContainer.setVisible(true); + // Return false indicating the layout application was not successful due to lack of gamepad. + return false; + } + // Extract the type of the gamepad from the active configuration. + const configType = activeConfig.padType; + + // Retrieve the layout settings based on the type of the gamepad. + const layout = this.layout[configType]; + // Update the main controller with configuration details from the selected layout. + this.keys = layout.keys; + this.optionsContainer = layout.optionsContainer; + this.optionsContainer.setVisible(true); + this.settingLabels = layout.settingLabels; + this.optionValueLabels = layout.optionValueLabels; + this.optionCursors = layout.optionCursors; + this.inputsIcons = layout.inputsIcons; + this.bindingSettings = layout.bindingSettings; + + // Return true indicating the layout was successfully applied. + return true; + } + + /** + * Process the input for the given button. + * + * @param button - The button to process. + * @returns `true` if the input was processed successfully. + */ + processInput(button: Button): boolean { + const ui = this.getUi(); + // Defines the maximum number of rows that can be displayed on the screen. + let success = false; + this.updateNavigationDisplay(); + + // Handle the input based on the button pressed. + if (button === Button.CANCEL) { + // Handle cancel button press, reverting UI mode to previous state. + success = true; + NavigationManager.getInstance().reset(); + this.scene.ui.revertMode(); + } else { + const cursor = this.cursor + this.scrollCursor; // Calculate the absolute cursor position. + const setting = this.setting[Object.keys(this.setting)[cursor]]; + switch (button) { + case Button.ACTION: + if (!this.optionCursors || !this.optionValueLabels) { + return; + } + if (this.settingBlacklisted.includes(setting) || !setting.includes("BUTTON_")) { + success = false; + } else { + success = this.setSetting(this.scene, setting, 1); + } + break; + case Button.UP: // Move up in the menu. + if (!this.optionValueLabels) { + return false; + } + if (cursor) { // If not at the top, move the cursor up. + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } else {// If at the top of the visible items, scroll up. + success = this.setScrollCursor(this.scrollCursor - 1); + } + } else { + // When at the top of the menu and pressing UP, move to the bottommost item. + // First, set the cursor to the last visible element, preparing for the scroll to the end. + const successA = this.setCursor(this.rowsToDisplay - 1); + // Then, adjust the scroll to display the bottommost elements of the menu. + const successB = this.setScrollCursor(this.optionValueLabels.length - this.rowsToDisplay); + success = successA && successB; // success is just there to play the little validation sound effect + } + break; + case Button.DOWN: // Move down in the menu. + if (!this.optionValueLabels) { + return false; + } + if (cursor < this.optionValueLabels.length - 1) { + if (this.cursor < this.rowsToDisplay - 1) { + success = this.setCursor(this.cursor + 1); + } else if (this.scrollCursor < this.optionValueLabels.length - this.rowsToDisplay) { + success = this.setScrollCursor(this.scrollCursor + 1); + } + } else { + // When at the bottom of the menu and pressing DOWN, move to the topmost item. + // First, set the cursor to the first visible element, resetting the scroll to the top. + const successA = this.setCursor(0); + // Then, reset the scroll to start from the first element of the menu. + const successB = this.setScrollCursor(0); + success = successA && successB; // Indicates a successful cursor and scroll adjustment. + } + break; + case Button.LEFT: // Move selection left within the current option set. + if (!this.optionCursors || !this.optionValueLabels) { + return; + } + if (this.settingBlacklisted.includes(setting) || setting.includes("BUTTON_")) { + success = false; + } else if (this.optionCursors[cursor]) { + success = this.setOptionCursor(cursor, this.optionCursors[cursor] - 1, true); + } + break; + case Button.RIGHT: // Move selection right within the current option set. + if (!this.optionCursors || !this.optionValueLabels) { + return; + } + if (this.settingBlacklisted.includes(setting) || setting.includes("BUTTON_")) { + success = false; + } else if (this.optionCursors[cursor] < this.optionValueLabels[cursor].length - 1) { + success = this.setOptionCursor(cursor, this.optionCursors[cursor] + 1, true); + } + break; + case Button.CYCLE_FORM: + case Button.CYCLE_SHINY: + success = this.navigationContainer.navigate(button); + break; + } + } + + // If a change occurred, play the selection sound. + if (success) { + ui.playSelect(); + } + + return success; // Return whether the input resulted in a successful action. + } + + resetScroll() { + this.cursorObj?.destroy(); + this.cursorObj = null; + this.cursor = null; + this.setCursor(0); + this.setScrollCursor(0); + this.updateSettingsScroll(); + } + + /** + * Set the cursor to the specified position. + * + * @param cursor - The cursor position to set. + * @returns `true` if the cursor was set successfully. + */ + setCursor(cursor: integer): boolean { + const ret = super.setCursor(cursor); + // If the optionsContainer is not initialized, return the result from the parent class directly. + if (!this.optionsContainer) { + return ret; + } + + // Check if the cursor object exists, if not, create it. + if (!this.cursorObj) { + this.cursorObj = this.scene.add.nineslice(0, 0, "summary_moves_cursor", null, (this.scene.game.canvas.width / 6) - 10, 16, 1, 1, 1, 1); + this.cursorObj.setOrigin(0, 0); // Set the origin to the top-left corner. + this.optionsContainer.add(this.cursorObj); // Add the cursor to the options container. + } + + // Update the position of the cursor object relative to the options background based on the current cursor and scroll positions. + this.cursorObj.setPositionRelative(this.optionsBg, 4, 4 + (this.cursor + this.scrollCursor) * 16); + + return ret; // Return the result from the parent class's setCursor method. + } + + /** + * Set the scroll cursor to the specified position. + * + * @param scrollCursor - The scroll cursor position to set. + * @returns `true` if the scroll cursor was set successfully. + */ + setScrollCursor(scrollCursor: integer): boolean { + // Check if the new scroll position is the same as the current one; if so, do not update. + if (scrollCursor === this.scrollCursor) { + return false; + } + + // Update the internal scroll cursor state + this.scrollCursor = scrollCursor; + + // Apply the new scroll position to the settings UI. + this.updateSettingsScroll(); + + // Reset the cursor to its current position to adjust its visibility after scrolling. + this.setCursor(this.cursor); + + return true; // Return true to indicate the scroll cursor was successfully updated. + } + + /** + * Set the option cursor to the specified position. + * + * @param settingIndex - The index of the setting. + * @param cursor - The cursor position to set. + * @param save - Whether to save the setting to local storage. + * @returns `true` if the option cursor was set successfully. + */ + setOptionCursor(settingIndex: integer, cursor: integer, save?: boolean): boolean { + // Retrieve the specific setting using the settingIndex from the settingDevice enumeration. + const setting = this.setting[Object.keys(this.setting)[settingIndex]]; + + // Get the current cursor position for this setting. + const lastCursor = this.optionCursors[settingIndex]; + + // Check if the setting is not part of the bindings (i.e., it's a regular setting). + if (!this.bindingSettings.includes(setting) && !setting.includes("BUTTON_")) { + // Get the label of the last selected option and revert its color to the default. + const lastValueLabel = this.optionValueLabels[settingIndex][lastCursor]; + lastValueLabel.setColor(this.getTextColor(TextStyle.WINDOW)); + lastValueLabel.setShadowColor(this.getTextColor(TextStyle.WINDOW, true)); + + // Update the cursor for the setting to the new position. + this.optionCursors[settingIndex] = cursor; + + // Change the color of the new selected option to indicate it's selected. + const newValueLabel = this.optionValueLabels[settingIndex][cursor]; + newValueLabel.setColor(this.getTextColor(TextStyle.SETTINGS_SELECTED)); + newValueLabel.setShadowColor(this.getTextColor(TextStyle.SETTINGS_SELECTED, true)); + } + + // If the save flag is set, save the setting to local storage + if (save) { + this.saveSettingToLocalStorage(setting, cursor); + } + + return true; // Return true to indicate the cursor was successfully updated. + } + + /** + * Update the scroll position of the settings UI. + */ + updateSettingsScroll(): void { + // Return immediately if the options container is not initialized. + if (!this.optionsContainer) { + return; + } + + // Set the vertical position of the options container based on the current scroll cursor, multiplying by the item height. + this.optionsContainer.setY(-16 * this.scrollCursor); + + // Iterate over all setting labels to update their visibility. + for (let s = 0; s < this.settingLabels.length; s++) { + // Determine if the current setting should be visible based on the scroll position. + const visible = s >= this.scrollCursor && s < this.scrollCursor + this.rowsToDisplay; + + // Set the visibility of the setting label and its corresponding options. + this.settingLabels[s].setVisible(visible); + for (const option of this.optionValueLabels[s]) { + option.setVisible(visible); + } + } + } + + /** + * Clear the UI elements and state. + */ + clear(): void { + super.clear(); + + // Hide the settings container to remove it from the view. + this.settingsContainer.setVisible(false); + + // Remove the cursor from the UI. + this.eraseCursor(); + } + + /** + * Erase the cursor from the UI. + */ + eraseCursor(): void { + // Check if a cursor object exists. + if (this.cursorObj) { + this.cursorObj.destroy(); + } // Destroy the cursor object to clean up resources. + + // Set the cursor object reference to null to fully dereference it. + this.cursorObj = null; + } + +} diff --git a/src/ui/settings/abstract-settings-ui-handler.ts b/src/ui/settings/abstract-settings-ui-handler.ts index f37ce05b69f..87d6a611662 100644 --- a/src/ui/settings/abstract-settings-ui-handler.ts +++ b/src/ui/settings/abstract-settings-ui-handler.ts @@ -1,107 +1,74 @@ -import UiHandler from "../ui-handler"; import BattleScene from "../../battle-scene"; -import {Mode} from "../ui"; -import {InterfaceConfig} from "../../inputs-controller"; -import {addWindow} from "../ui-theme"; -import {addTextObject, TextStyle} from "../text"; +import { hasTouchscreen, isMobile } from "../../touch-controls"; +import { TextStyle, addTextObject } from "../text"; +import { Mode } from "../ui"; +import UiHandler from "../ui-handler"; +import { addWindow } from "../ui-theme"; import {Button} from "../../enums/buttons"; -import {getIconWithSettingName} from "#app/configs/inputs/configHandler"; +import {InputsIcons} from "#app/ui/settings/abstract-control-settings-ui-handler.js"; import NavigationMenu, {NavigationManager} from "#app/ui/settings/navigationMenu"; +import { Setting, SettingKeys } from "#app/system/settings/settings"; -export interface InputsIcons { - [key: string]: Phaser.GameObjects.Sprite; -} -export interface LayoutConfig { - optionsContainer: Phaser.GameObjects.Container; - inputsIcons: InputsIcons; - settingLabels: Phaser.GameObjects.Text[]; - optionValueLabels: Phaser.GameObjects.Text[][]; - optionCursors: integer[]; - keys: string[]; - bindingSettings: Array; -} /** * Abstract class for handling UI elements related to settings. */ -export default abstract class AbstractSettingsUiUiHandler extends UiHandler { - protected settingsContainer: Phaser.GameObjects.Container; - protected optionsContainer: Phaser.GameObjects.Container; - protected navigationContainer: NavigationMenu; +export default class AbstractSettingsUiHandler extends UiHandler { + private settingsContainer: Phaser.GameObjects.Container; + private optionsContainer: Phaser.GameObjects.Container; + private navigationContainer: NavigationMenu; - protected scrollCursor: integer; - protected optionCursors: integer[]; - protected cursorObj: Phaser.GameObjects.NineSlice; + private scrollCursor: integer; - protected optionsBg: Phaser.GameObjects.NineSlice; - protected actionsBg: Phaser.GameObjects.NineSlice; + private optionsBg: Phaser.GameObjects.NineSlice; - protected settingLabels: Phaser.GameObjects.Text[]; - protected optionValueLabels: Phaser.GameObjects.Text[][]; + private optionCursors: integer[]; + + private settingLabels: Phaser.GameObjects.Text[]; + private optionValueLabels: Phaser.GameObjects.Text[][]; - // layout will contain the 3 Gamepad tab for each config - dualshock, xbox, snes - protected layout: Map = new Map(); - // Will contain the input icons from the selected layout - protected inputsIcons: InputsIcons; protected navigationIcons: InputsIcons; - // list all the setting keys used in the selected layout (because dualshock has more buttons than xbox) - protected keys: Array; - // Store the specific settings related to key bindings for the current gamepad configuration. - protected bindingSettings: Array; + private cursorObj: Phaser.GameObjects.NineSlice; - protected settingDevice; - protected settingBlacklisted; - protected settingDeviceDefaults; - protected settingDeviceOptions; - protected configs; - protected commonSettingsCount; - protected textureOverride; - protected titleSelected; - protected localStoragePropertyName; - protected rowsToDisplay: number; + private reloadSettings: Array; + private reloadRequired: boolean; + private rowsToDisplay: number; - abstract getLocalStorageSetting(): object; - abstract navigateMenuLeft(): boolean; - abstract navigateMenuRight(): boolean; - abstract saveSettingToLocalStorage(setting, cursor): void; - abstract getActiveConfig(): InterfaceConfig; - abstract setSetting(scene: BattleScene, setting, value: integer): boolean; + protected title: string; + protected settings: Array; + protected localStorageKey: string; - /** - * Constructor for the AbstractSettingsUiUiHandler. - * - * @param scene - The BattleScene instance. - * @param mode - The UI mode. - */ constructor(scene: BattleScene, mode?: Mode) { super(scene, mode); + + this.reloadRequired = false; this.rowsToDisplay = 8; } /** - * Setup UI elements. - */ + * Setup UI elements + */ setup() { const ui = this.getUi(); - this.navigationIcons = {}; this.settingsContainer = this.scene.add.container(1, -(this.scene.game.canvas.height / 6) + 1); - this.settingsContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains); + this.settingsContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6 - 20), Phaser.Geom.Rectangle.Contains); + + this.navigationIcons = {}; this.navigationContainer = new NavigationMenu(this.scene, 0, 0); this.optionsBg = addWindow(this.scene, 0, this.navigationContainer.height, (this.scene.game.canvas.width / 6) - 2, (this.scene.game.canvas.height / 6) - 16 - this.navigationContainer.height - 2); this.optionsBg.setOrigin(0, 0); - - this.actionsBg = addWindow(this.scene, 0, (this.scene.game.canvas.height / 6) - this.navigationContainer.height, (this.scene.game.canvas.width / 6) - 2, 22); - this.actionsBg.setOrigin(0, 0); + const actionsBg = addWindow(this.scene, 0, (this.scene.game.canvas.height / 6) - this.navigationContainer.height, (this.scene.game.canvas.width / 6) - 2, 22); + actionsBg.setOrigin(0, 0); const iconAction = this.scene.add.sprite(0, 0, "keyboard"); iconAction.setOrigin(0, -0.1); - iconAction.setPositionRelative(this.actionsBg, this.navigationContainer.width - 32, 4); + iconAction.setPositionRelative(actionsBg, this.navigationContainer.width - 32, 4); this.navigationIcons["BUTTON_ACTION"] = iconAction; const actionText = addTextObject(this.scene, 0, 0, "Action", TextStyle.SETTINGS_LABEL); @@ -110,207 +77,81 @@ export default abstract class AbstractSettingsUiUiHandler extends UiHandler { const iconCancel = this.scene.add.sprite(0, 0, "keyboard"); iconCancel.setOrigin(0, -0.1); - iconCancel.setPositionRelative(this.actionsBg, this.navigationContainer.width - 100, 4); + iconCancel.setPositionRelative(actionsBg, this.navigationContainer.width - 100, 4); this.navigationIcons["BUTTON_CANCEL"] = iconCancel; const cancelText = addTextObject(this.scene, 0, 0, "Cancel", TextStyle.SETTINGS_LABEL); cancelText.setOrigin(0, 0.15); cancelText.setPositionRelative(iconCancel, -cancelText.width/6-2, 0); - const iconReset = this.scene.add.sprite(0, 0, "keyboard"); - iconReset.setOrigin(0, -0.1); - iconReset.setPositionRelative(this.actionsBg, this.navigationContainer.width - 180, 4); - this.navigationIcons["BUTTON_HOME"] = iconReset; + this.optionsContainer = this.scene.add.container(0, 0); - const resetText = addTextObject(this.scene, 0, 0, "Reset all", TextStyle.SETTINGS_LABEL); - resetText.setOrigin(0, 0.15); - resetText.setPositionRelative(iconReset, -resetText.width/6-2, 0); + this.settingLabels = []; + this.optionValueLabels = []; - this.settingsContainer.add(this.optionsBg); - this.settingsContainer.add(this.actionsBg); - this.settingsContainer.add(this.navigationContainer); - this.settingsContainer.add(iconAction); - this.settingsContainer.add(iconCancel); - this.settingsContainer.add(iconReset); - this.settingsContainer.add(actionText); - this.settingsContainer.add(cancelText); - this.settingsContainer.add(resetText); + this.reloadSettings = this.settings.filter(s => s?.requireReload); - /// Initialize a new configuration "screen" for each type of gamepad. - for (const config of this.configs) { - // Create a map to store layout settings based on the pad type. - this.layout[config.padType] = new Map(); - // Create a container for gamepad options in the scene, initially hidden. + this.settings + .forEach((setting, s) => { + let settingName = setting.label; + if (setting?.requireReload) { + settingName += " (Requires Reload)"; + } - const optionsContainer = this.scene.add.container(0, 0); - optionsContainer.setVisible(false); + this.settingLabels[s] = addTextObject(this.scene, 8, 28 + s * 16, settingName, TextStyle.SETTINGS_LABEL); + this.settingLabels[s].setOrigin(0, 0); - // Gather all binding settings from the configuration. - const bindingSettings = Object.keys(config.settings); - - // Array to hold labels for different settings such as 'Controller', 'Gamepad Support', etc. - const settingLabels: Phaser.GameObjects.Text[] = []; - - // Array to hold options for each setting, e.g., 'Auto', 'Disabled'. - const optionValueLabels: Phaser.GameObjects.GameObject[][] = []; - - // Object to store sprites for each button configuration. - const inputsIcons: InputsIcons = {}; - - // Fetch common setting keys such as 'Controller' and 'Gamepad Support' from gamepad settings. - const commonSettingKeys = Object.keys(this.settingDevice).slice(0, this.commonSettingsCount).map(key => this.settingDevice[key]); - // Combine common and specific bindings into a single array. - const specificBindingKeys = [...commonSettingKeys, ...Object.keys(config.settings)]; - // Fetch default values for these settings and prepare to highlight selected options. - const optionCursors = Object.values(Object.keys(this.settingDeviceDefaults).filter(s => specificBindingKeys.includes(s)).map(k => this.settingDeviceDefaults[k])); - // Filter out settings that are not relevant to the current gamepad configuration. - const settingFiltered = Object.keys(this.settingDevice).filter(_key => specificBindingKeys.includes(this.settingDevice[_key])); - // Loop through the filtered settings to manage display and options. - - settingFiltered.forEach((setting, s) => { - // Convert the setting key from format 'Key_Name' to 'Key name' for display. - const settingName = setting.replace(/\_/g, " "); - - // Create and add a text object for the setting name to the scene. - const isLock = this.settingBlacklisted.includes(this.settingDevice[setting]); - const labelStyle = isLock ? TextStyle.SETTINGS_LOCKED : TextStyle.SETTINGS_LABEL; - settingLabels[s] = addTextObject(this.scene, 8, 28 + s * 16, settingName, labelStyle); - settingLabels[s].setOrigin(0, 0); - optionsContainer.add(settingLabels[s]); - - // Initialize an array to store the option labels for this setting. - const valueLabels: Phaser.GameObjects.GameObject[] = []; - - // Process each option for the current setting. - for (const [o, option] of this.settingDeviceOptions[this.settingDevice[setting]].entries()) { - // Check if the current setting is for binding keys. - if (bindingSettings.includes(this.settingDevice[setting])) { - // Create a label for non-null options, typically indicating actionable options like 'change'. - if (o) { - const valueLabel = addTextObject(this.scene, 0, 0, isLock ? "" : option, TextStyle.WINDOW); - valueLabel.setOrigin(0, 0); - optionsContainer.add(valueLabel); - valueLabels.push(valueLabel); - continue; - } - // For null options, add an icon for the key. - const icon = this.scene.add.sprite(0, 0, this.textureOverride ? this.textureOverride : config.padType); - icon.setOrigin(0, -0.15); - inputsIcons[this.settingDevice[setting]] = icon; - optionsContainer.add(icon); - valueLabels.push(icon); - continue; - } - // For regular settings like 'Gamepad support', create a label and determine if it is selected. - const valueLabel = addTextObject(this.scene, 0, 0, option, this.settingDeviceDefaults[this.settingDevice[setting]] === o ? TextStyle.SETTINGS_SELECTED : TextStyle.WINDOW); + this.optionsContainer.add(this.settingLabels[s]); + this.optionValueLabels.push(setting.options.map((option, o) => { + const valueLabel = addTextObject(this.scene, 0, 0, option, setting.default === o ? TextStyle.SETTINGS_SELECTED : TextStyle.WINDOW); valueLabel.setOrigin(0, 0); - optionsContainer.add(valueLabel); + this.optionsContainer.add(valueLabel); - //if a setting has 2 options, valueLabels will be an array of 2 elements - valueLabels.push(valueLabel); - } - // Collect all option labels for this setting into the main array. - optionValueLabels.push(valueLabels); + return valueLabel; + })); - // Calculate the total width of all option labels within a specific setting - // This is achieved by summing the width of each option label - const totalWidth = optionValueLabels[s].map((o) => (o as Phaser.GameObjects.Text).width).reduce((total, width) => total += width, 0); + const totalWidth = this.optionValueLabels[s].map(o => o.width).reduce((total, width) => total += width, 0); - // Define the minimum width for a label, ensuring it's at least 78 pixels wide or the width of the setting label plus some padding - const labelWidth = Math.max(130, settingLabels[s].displayWidth + 8); + const labelWidth = Math.max(78, this.settingLabels[s].displayWidth + 8); - // Calculate the total available space for placing option labels next to their setting label - // We reserve space for the setting label and then distribute the remaining space evenly const totalSpace = (300 - labelWidth) - totalWidth / 6; - // Calculate the spacing between options based on the available space divided by the number of gaps between labels - const optionSpacing = Math.floor(totalSpace / (optionValueLabels[s].length - 1)); + const optionSpacing = Math.floor(totalSpace / (this.optionValueLabels[s].length - 1)); - // Initialize xOffset to zero, which will be used to position each option label horizontally let xOffset = 0; - // Start positioning each option label one by one - for (const value of optionValueLabels[s]) { - // Set the option label's position right next to the setting label, adjusted by xOffset - (value as Phaser.GameObjects.Text).setPositionRelative(settingLabels[s], labelWidth + xOffset, 0); - // Move the xOffset to the right for the next label, ensuring each label is spaced evenly - xOffset += (value as Phaser.GameObjects.Text).width / 6 + optionSpacing; + for (const value of this.optionValueLabels[s]) { + value.setPositionRelative(this.settingLabels[s], labelWidth + xOffset, 0); + xOffset += value.width / 6 + optionSpacing; } }); - // Assigning the newly created components to the layout map under the specific gamepad type. - this.layout[config.padType].optionsContainer = optionsContainer; // Container for this pad's options. - this.layout[config.padType].inputsIcons = inputsIcons; // Icons for each input specific to this pad. - this.layout[config.padType].settingLabels = settingLabels; // Text labels for each setting available on this pad. - this.layout[config.padType].optionValueLabels = optionValueLabels; // Labels for values corresponding to each setting. - this.layout[config.padType].optionCursors = optionCursors; // Cursors to navigate through the options. - this.layout[config.padType].keys = specificBindingKeys; // Keys that identify each setting specifically bound to this pad. - this.layout[config.padType].bindingSettings = bindingSettings; // Settings that define how the keys are bound. + this.optionCursors = this.settings.map(setting => setting.default); + + this.settingsContainer.add(this.optionsBg); + this.settingsContainer.add(this.navigationContainer); + this.settingsContainer.add(actionsBg); + this.settingsContainer.add(this.optionsContainer); + this.settingsContainer.add(iconAction); + this.settingsContainer.add(iconCancel); + this.settingsContainer.add(actionText); + this.settingsContainer.add(cancelText); - // Add the options container to the overall settings container to be displayed in the UI. - this.settingsContainer.add(optionsContainer); - } - // Add the settings container to the UI. ui.add(this.settingsContainer); - // Initially hide the settings container until needed (e.g., when a gamepad is connected or a button is pressed). + this.setCursor(0); + this.setScrollCursor(0); + this.settingsContainer.setVisible(false); } - /** - * Update the bindings for the current active device configuration. - */ + * Update the bindings for the current active device configuration. + */ updateBindings(): void { - // Hide the options container for all layouts to reset the UI visibility. - Object.keys(this.layout).forEach((key) => this.layout[key].optionsContainer.setVisible(false)); - // Fetch the active gamepad configuration from the input controller. - const activeConfig = this.getActiveConfig(); - - // Set the UI layout for the active configuration. If unsuccessful, exit the function early. - if (!this.setLayout(activeConfig)) { - return; - } - - // Retrieve the gamepad settings from local storage or use an empty object if none exist. - const settings: object = this.getLocalStorageSetting(); - - // Update the cursor for each key based on the stored settings or default cursors. - this.keys.forEach((key, index) => { - this.setOptionCursor(index, settings.hasOwnProperty(key as string) ? settings[key as string] : this.optionCursors[index]); - }); - - // If the active configuration has no custom bindings set, exit the function early. - // by default, if custom does not exists, a default is assigned to it - // it only means the gamepad is not yet initalized - if (!activeConfig.custom) { - return; - } - - // For each element in the binding settings, update the icon according to the current assignment. - for (const elm of this.bindingSettings) { - const icon = getIconWithSettingName(activeConfig, elm); - if (icon) { - this.inputsIcons[elm as string].setFrame(icon); - this.inputsIcons[elm as string].alpha = 1; - } else { - this.inputsIcons[elm as string].alpha = 0; - } - } - - // Set the cursor and scroll cursor to their initial positions. - this.setCursor(this.cursor); - this.setScrollCursor(this.scrollCursor); - } - - updateNavigationDisplay() { - const specialIcons = { - "BUTTON_HOME": "HOME.png", - "BUTTON_DELETE": "DEL.png", - }; for (const settingName of Object.keys(this.navigationIcons)) { - if (Object.keys(specialIcons).includes(settingName)) { + if (settingName === "BUTTON_HOME") { this.navigationIcons[settingName].setTexture("keyboard"); - this.navigationIcons[settingName].setFrame(specialIcons[settingName]); + this.navigationIcons[settingName].setFrame("HOME.png"); this.navigationIcons[settingName].alpha = 1; continue; } @@ -324,112 +165,61 @@ export default abstract class AbstractSettingsUiUiHandler extends UiHandler { this.navigationIcons[settingName].alpha = 0; } } + NavigationManager.getInstance().updateIcons(); } /** - * Show the UI with the provided arguments. - * - * @param args - Arguments to be passed to the show method. - * @returns `true` if successful. + * Show the UI with the provided arguments. + * + * @param args - Arguments to be passed to the show method. + * @returns `true` if successful. */ show(args: any[]): boolean { super.show(args); - - this.updateNavigationDisplay(); - NavigationManager.getInstance().updateIcons(); - // Update the bindings for the current active gamepad configuration. this.updateBindings(); - // Make the settings container visible to the user. - this.settingsContainer.setVisible(true); - // Reset the scroll cursor to the top of the settings container. - this.resetScroll(); + const settings: object = localStorage.hasOwnProperty(this.localStorageKey) ? JSON.parse(localStorage.getItem(this.localStorageKey)) : {}; + + this.settings.forEach((setting, s) => this.setOptionCursor(s, settings.hasOwnProperty(setting.key) ? settings[setting.key] : this.settings[s].default)); + + this.settingsContainer.setVisible(true); + this.setCursor(0); - // Move the settings container to the end of the UI stack to ensure it is displayed on top. this.getUi().moveTo(this.settingsContainer, this.getUi().length - 1); - // Hide any tooltips that might be visible before showing the settings container. this.getUi().hideTooltip(); - // Return true to indicate the UI was successfully shown. return true; } /** - * Set the UI layout for the active device configuration. - * - * @param activeConfig - The active device configuration. - * @returns `true` if the layout was successfully applied, otherwise `false`. - */ - setLayout(activeConfig: InterfaceConfig): boolean { - // Check if there is no active configuration (e.g., no gamepad connected). - if (!activeConfig) { - // Retrieve the layout for when no gamepads are connected. - const layout = this.layout["noGamepads"]; - // Make the options container visible to show message. - layout.optionsContainer.setVisible(true); - // Return false indicating the layout application was not successful due to lack of gamepad. - return false; - } - // Extract the type of the gamepad from the active configuration. - const configType = activeConfig.padType; - - // Retrieve the layout settings based on the type of the gamepad. - const layout = this.layout[configType]; - // Update the main controller with configuration details from the selected layout. - this.keys = layout.keys; - this.optionsContainer = layout.optionsContainer; - this.optionsContainer.setVisible(true); - this.settingLabels = layout.settingLabels; - this.optionValueLabels = layout.optionValueLabels; - this.optionCursors = layout.optionCursors; - this.inputsIcons = layout.inputsIcons; - this.bindingSettings = layout.bindingSettings; - - // Return true indicating the layout was successfully applied. - return true; - } - - /** - * Process the input for the given button. - * - * @param button - The button to process. - * @returns `true` if the input was processed successfully. - */ + * Processes input from a specified button. + * This method handles navigation through a UI menu, including movement through menu items + * and handling special actions like cancellation. Each button press may adjust the cursor + * position or the menu scroll, and plays a sound effect if the action was successful. + * + * @param button - The button pressed by the user. + * @returns `true` if the action associated with the button was successfully processed, `false` otherwise. + */ processInput(button: Button): boolean { const ui = this.getUi(); // Defines the maximum number of rows that can be displayed on the screen. - let success = false; - this.updateNavigationDisplay(); - // Handle the input based on the button pressed. + let success = false; + if (button === Button.CANCEL) { - // Handle cancel button press, reverting UI mode to previous state. success = true; NavigationManager.getInstance().reset(); + // Reverts UI to its previous state on cancel. this.scene.ui.revertMode(); } else { - const cursor = this.cursor + this.scrollCursor; // Calculate the absolute cursor position. - const setting = this.settingDevice[Object.keys(this.settingDevice)[cursor]]; + const cursor = this.cursor + this.scrollCursor; switch (button) { - case Button.ACTION: - if (!this.optionCursors || !this.optionValueLabels) { - return; - } - if (this.settingBlacklisted.includes(setting) || !setting.includes("BUTTON_")) { - success = false; - } else { - success = this.setSetting(this.scene, setting, 1); - } - break; - case Button.UP: // Move up in the menu. - if (!this.optionValueLabels) { - return false; - } - if (cursor) { // If not at the top, move the cursor up. + case Button.UP: + if (cursor) { if (this.cursor) { success = this.setCursor(this.cursor - 1); - } else {// If at the top of the visible items, scroll up. + } else { success = this.setScrollCursor(this.scrollCursor - 1); } } else { @@ -441,12 +231,9 @@ export default abstract class AbstractSettingsUiUiHandler extends UiHandler { success = successA && successB; // success is just there to play the little validation sound effect } break; - case Button.DOWN: // Move down in the menu. - if (!this.optionValueLabels) { - return false; - } + case Button.DOWN: if (cursor < this.optionValueLabels.length - 1) { - if (this.cursor < this.rowsToDisplay - 1) { + if (this.cursor < this.rowsToDisplay - 1) {// if the visual cursor is in the frame of 0 to 8 success = this.setCursor(this.cursor + 1); } else if (this.scrollCursor < this.optionValueLabels.length - this.rowsToDisplay) { success = this.setScrollCursor(this.scrollCursor + 1); @@ -460,23 +247,14 @@ export default abstract class AbstractSettingsUiUiHandler extends UiHandler { success = successA && successB; // Indicates a successful cursor and scroll adjustment. } break; - case Button.LEFT: // Move selection left within the current option set. - if (!this.optionCursors || !this.optionValueLabels) { - return; - } - if (this.settingBlacklisted.includes(setting) || setting.includes("BUTTON_")) { - success = false; - } else if (this.optionCursors[cursor]) { + case Button.LEFT: + if (this.optionCursors[cursor]) {// Moves the option cursor left, if possible. success = this.setOptionCursor(cursor, this.optionCursors[cursor] - 1, true); } break; - case Button.RIGHT: // Move selection right within the current option set. - if (!this.optionCursors || !this.optionValueLabels) { - return; - } - if (this.settingBlacklisted.includes(setting) || setting.includes("BUTTON_")) { - success = false; - } else if (this.optionCursors[cursor] < this.optionValueLabels[cursor].length - 1) { + case Button.RIGHT: + // Moves the option cursor right, if possible. + if (this.optionCursors[cursor] < this.optionValueLabels[cursor].length - 1) { success = this.setOptionCursor(cursor, this.optionCursors[cursor] + 1, true); } break; @@ -487,130 +265,100 @@ export default abstract class AbstractSettingsUiUiHandler extends UiHandler { } } - // If a change occurred, play the selection sound. + // Plays a select sound effect if an action was successfully processed. if (success) { ui.playSelect(); } - return success; // Return whether the input resulted in a successful action. - } - - resetScroll() { - this.cursorObj?.destroy(); - this.cursorObj = null; - this.cursor = null; - this.setCursor(0); - this.setScrollCursor(0); - this.updateSettingsScroll(); + return success; } /** - * Set the cursor to the specified position. - * - * @param cursor - The cursor position to set. - * @returns `true` if the cursor was set successfully. - */ + * Set the cursor to the specified position. + * + * @param cursor - The cursor position to set. + * @returns `true` if the cursor was set successfully. + */ setCursor(cursor: integer): boolean { const ret = super.setCursor(cursor); - // If the optionsContainer is not initialized, return the result from the parent class directly. - if (!this.optionsContainer) { - return ret; - } - // Check if the cursor object exists, if not, create it. if (!this.cursorObj) { this.cursorObj = this.scene.add.nineslice(0, 0, "summary_moves_cursor", null, (this.scene.game.canvas.width / 6) - 10, 16, 1, 1, 1, 1); - this.cursorObj.setOrigin(0, 0); // Set the origin to the top-left corner. - this.optionsContainer.add(this.cursorObj); // Add the cursor to the options container. + this.cursorObj.setOrigin(0, 0); + this.optionsContainer.add(this.cursorObj); } - // Update the position of the cursor object relative to the options background based on the current cursor and scroll positions. this.cursorObj.setPositionRelative(this.optionsBg, 4, 4 + (this.cursor + this.scrollCursor) * 16); - return ret; // Return the result from the parent class's setCursor method. + return ret; } /** - * Set the scroll cursor to the specified position. - * - * @param scrollCursor - The scroll cursor position to set. - * @returns `true` if the scroll cursor was set successfully. - */ + * Set the option cursor to the specified position. + * + * @param settingIndex - The index of the setting. + * @param cursor - The cursor position to set. + * @param save - Whether to save the setting to local storage. + * @returns `true` if the option cursor was set successfully. + */ + setOptionCursor(settingIndex: integer, cursor: integer, save?: boolean): boolean { + const setting = this.settings[settingIndex]; + + if (setting.key === SettingKeys.Touch_Controls && cursor && hasTouchscreen() && isMobile()) { + this.getUi().playError(); + return false; + } + + const lastCursor = this.optionCursors[settingIndex]; + + const lastValueLabel = this.optionValueLabels[settingIndex][lastCursor]; + lastValueLabel.setColor(this.getTextColor(TextStyle.WINDOW)); + lastValueLabel.setShadowColor(this.getTextColor(TextStyle.WINDOW, true)); + + this.optionCursors[settingIndex] = cursor; + + const newValueLabel = this.optionValueLabels[settingIndex][cursor]; + newValueLabel.setColor(this.getTextColor(TextStyle.SETTINGS_SELECTED)); + newValueLabel.setShadowColor(this.getTextColor(TextStyle.SETTINGS_SELECTED, true)); + + if (save) { + this.scene.gameData.saveSetting(setting.key, cursor); + if (this.reloadSettings.includes(setting)) { + this.reloadRequired = true; + } + } + + return true; + } + + /** + * Set the scroll cursor to the specified position. + * + * @param scrollCursor - The scroll cursor position to set. + * @returns `true` if the scroll cursor was set successfully. + */ setScrollCursor(scrollCursor: integer): boolean { - // Check if the new scroll position is the same as the current one; if so, do not update. if (scrollCursor === this.scrollCursor) { return false; } - // Update the internal scroll cursor state this.scrollCursor = scrollCursor; - // Apply the new scroll position to the settings UI. this.updateSettingsScroll(); - // Reset the cursor to its current position to adjust its visibility after scrolling. this.setCursor(this.cursor); - return true; // Return true to indicate the scroll cursor was successfully updated. + return true; } /** - * Set the option cursor to the specified position. - * - * @param settingIndex - The index of the setting. - * @param cursor - The cursor position to set. - * @param save - Whether to save the setting to local storage. - * @returns `true` if the option cursor was set successfully. - */ - setOptionCursor(settingIndex: integer, cursor: integer, save?: boolean): boolean { - // Retrieve the specific setting using the settingIndex from the settingDevice enumeration. - const setting = this.settingDevice[Object.keys(this.settingDevice)[settingIndex]]; - - // Get the current cursor position for this setting. - const lastCursor = this.optionCursors[settingIndex]; - - // Check if the setting is not part of the bindings (i.e., it's a regular setting). - if (!this.bindingSettings.includes(setting) && !setting.includes("BUTTON_")) { - // Get the label of the last selected option and revert its color to the default. - const lastValueLabel = this.optionValueLabels[settingIndex][lastCursor]; - lastValueLabel.setColor(this.getTextColor(TextStyle.WINDOW)); - lastValueLabel.setShadowColor(this.getTextColor(TextStyle.WINDOW, true)); - - // Update the cursor for the setting to the new position. - this.optionCursors[settingIndex] = cursor; - - // Change the color of the new selected option to indicate it's selected. - const newValueLabel = this.optionValueLabels[settingIndex][cursor]; - newValueLabel.setColor(this.getTextColor(TextStyle.SETTINGS_SELECTED)); - newValueLabel.setShadowColor(this.getTextColor(TextStyle.SETTINGS_SELECTED, true)); - } - - // If the save flag is set, save the setting to local storage - if (save) { - this.saveSettingToLocalStorage(setting, cursor); - } - - return true; // Return true to indicate the cursor was successfully updated. - } - - /** - * Update the scroll position of the settings UI. - */ + * Update the scroll position of the settings UI. + */ updateSettingsScroll(): void { - // Return immediately if the options container is not initialized. - if (!this.optionsContainer) { - return; - } - - // Set the vertical position of the options container based on the current scroll cursor, multiplying by the item height. this.optionsContainer.setY(-16 * this.scrollCursor); - // Iterate over all setting labels to update their visibility. for (let s = 0; s < this.settingLabels.length; s++) { - // Determine if the current setting should be visible based on the scroll position. const visible = s >= this.scrollCursor && s < this.scrollCursor + this.rowsToDisplay; - - // Set the visibility of the setting label and its corresponding options. this.settingLabels[s].setVisible(visible); for (const option of this.optionValueLabels[s]) { option.setVisible(visible); @@ -619,29 +367,25 @@ export default abstract class AbstractSettingsUiUiHandler extends UiHandler { } /** - * Clear the UI elements and state. - */ - clear(): void { + * Clear the UI elements and state. + */ + clear() { super.clear(); - - // Hide the settings container to remove it from the view. this.settingsContainer.setVisible(false); - - // Remove the cursor from the UI. this.eraseCursor(); + if (this.reloadRequired) { + this.reloadRequired = false; + this.scene.reset(true, false, true); + } } /** - * Erase the cursor from the UI. - */ - eraseCursor(): void { - // Check if a cursor object exists. + * Erase the cursor from the UI. + */ + eraseCursor() { if (this.cursorObj) { this.cursorObj.destroy(); - } // Destroy the cursor object to clean up resources. - - // Set the cursor object reference to null to fully dereference it. + } this.cursorObj = null; } - } diff --git a/src/ui/settings/navigationMenu.ts b/src/ui/settings/navigationMenu.ts index 843e9fd1f86..d9664276872 100644 --- a/src/ui/settings/navigationMenu.ts +++ b/src/ui/settings/navigationMenu.ts @@ -1,10 +1,13 @@ import BattleScene from "#app/battle-scene"; import {Mode} from "#app/ui/ui"; -import {InputsIcons} from "#app/ui/settings/abstract-settings-ui-handler"; +import {InputsIcons} from "#app/ui/settings/abstract-control-settings-ui-handler.js"; import {addTextObject, setTextStyle, TextStyle} from "#app/ui/text"; import {addWindow} from "#app/ui/ui-theme"; import {Button} from "#app/enums/buttons"; +const LEFT = "LEFT"; +const RIGHT = "RIGHT"; + /** * Manages navigation and menus tabs within the setting menu. */ @@ -24,14 +27,16 @@ export class NavigationManager { constructor() { this.modes = [ Mode.SETTINGS, + Mode.SETTINGS_ACCESSIBILITY, Mode.SETTINGS_GAMEPAD, Mode.SETTINGS_KEYBOARD, ]; - this.labels = ["General", "Gamepad", "Keyboard"]; + this.labels = ["General", "Accessibility", "Gamepad", "Keyboard"]; } public reset() { this.selectedMode = Mode.SETTINGS; + this.updateNavigationMenus(); } /** @@ -46,32 +51,20 @@ export class NavigationManager { } /** - * Navigates to the previous mode in the modes array. - * @param scene The current BattleScene instance. + * Navigates modes based on given direction + * @param scene The current BattleScene instance + * @param direction LEFT or RIGHT */ - public navigateLeft(scene) { + public navigate(scene, direction) { const pos = this.modes.indexOf(this.selectedMode); const maxPos = this.modes.length - 1; - if (pos === 0) { + const increment = direction === LEFT ? -1 : 1; + if (pos === 0 && direction === LEFT) { this.selectedMode = this.modes[maxPos]; - } else { - this.selectedMode = this.modes[pos - 1]; - } - scene.ui.setMode(this.selectedMode); - this.updateNavigationMenus(); - } - - /** - * Navigates to the next mode in the modes array. - * @param scene The current BattleScene instance. - */ - public navigateRight(scene) { - const pos = this.modes.indexOf(this.selectedMode); - const maxPos = this.modes.length - 1; - if (pos === maxPos) { + } else if (pos === maxPos && direction === RIGHT) { this.selectedMode = this.modes[0]; } else { - this.selectedMode = this.modes[pos + 1]; + this.selectedMode = this.modes[pos + increment]; } scene.ui.setMode(this.selectedMode); this.updateNavigationMenus(); @@ -204,13 +197,11 @@ export default class NavigationMenu extends Phaser.GameObjects.Container { const navigationManager = NavigationManager.getInstance(); switch (button) { case Button.CYCLE_FORM: - navigationManager.navigateLeft(this.scene); + navigationManager.navigate(this.scene, LEFT); return true; - break; case Button.CYCLE_SHINY: - navigationManager.navigateRight(this.scene); + navigationManager.navigate(this.scene, RIGHT); return true; - break; } return false; } diff --git a/src/ui/settings/settings-accessiblity-ui-handler.ts b/src/ui/settings/settings-accessiblity-ui-handler.ts new file mode 100644 index 00000000000..ef700a7a9ab --- /dev/null +++ b/src/ui/settings/settings-accessiblity-ui-handler.ts @@ -0,0 +1,20 @@ +import BattleScene from "../../battle-scene"; +import { Mode } from "../ui"; +"#app/inputs-controller.js"; +import AbstractSettingsUiHandler from "./abstract-settings-ui-handler"; +import { Setting, SettingType } from "#app/system/settings/settings"; + +export default class SettingsAccessibilityUiHandler extends AbstractSettingsUiHandler { + /** + * Creates an instance of SettingsGamepadUiHandler. + * + * @param scene - The BattleScene instance. + * @param mode - The UI mode, optional. + */ + constructor(scene: BattleScene, mode?: Mode) { + super(scene, mode); + this.title = "Accessibility"; + this.settings = Setting.filter(s => s.type === SettingType.ACCESSIBILITY); + this.localStorageKey = "settings"; + } +} diff --git a/src/ui/settings/settings-gamepad-ui-handler.ts b/src/ui/settings/settings-gamepad-ui-handler.ts index 5389d4a1940..20a713012ae 100644 --- a/src/ui/settings/settings-gamepad-ui-handler.ts +++ b/src/ui/settings/settings-gamepad-ui-handler.ts @@ -7,22 +7,22 @@ import { settingGamepadBlackList, settingGamepadDefaults, settingGamepadOptions -} from "../../system/settings-gamepad"; +} from "../../system/settings/settings-gamepad"; import pad_xbox360 from "#app/configs/inputs/pad_xbox360"; import pad_dualshock from "#app/configs/inputs/pad_dualshock"; import pad_unlicensedSNES from "#app/configs/inputs/pad_unlicensedSNES"; import {InterfaceConfig} from "#app/inputs-controller"; -import AbstractSettingsUiUiHandler from "#app/ui/settings/abstract-settings-ui-handler"; +import AbstractControlSettingsUiHandler from "#app/ui/settings/abstract-control-settings-ui-handler.js"; import {Device} from "#app/enums/devices"; import {truncateString} from "#app/utils"; /** * Class representing the settings UI handler for gamepads. * - * @extends AbstractSettingsUiUiHandler + * @extends AbstractControlSettingsUiHandler */ -export default class SettingsGamepadUiHandler extends AbstractSettingsUiUiHandler { +export default class SettingsGamepadUiHandler extends AbstractControlSettingsUiHandler { /** * Creates an instance of SettingsGamepadUiHandler. @@ -33,18 +33,17 @@ export default class SettingsGamepadUiHandler extends AbstractSettingsUiUiHandle constructor(scene: BattleScene, mode?: Mode) { super(scene, mode); this.titleSelected = "Gamepad"; - this.settingDevice = SettingGamepad; + this.setting = SettingGamepad; this.settingDeviceDefaults = settingGamepadDefaults; this.settingDeviceOptions = settingGamepadOptions; this.configs = [pad_xbox360, pad_dualshock, pad_unlicensedSNES]; this.commonSettingsCount = 2; this.localStoragePropertyName = "settingsGamepad"; this.settingBlacklisted = settingGamepadBlackList; + this.device = Device.GAMEPAD; } - setSetting(scene: BattleScene, setting, value: integer): boolean { - return setSettingGamepad(scene, setting, value); - } + setSetting = setSettingGamepad; /** * Setup UI elements. @@ -65,26 +64,6 @@ export default class SettingsGamepadUiHandler extends AbstractSettingsUiUiHandle this.layout["noGamepads"].label = label; } - /** - * Get the active configuration. - * - * @returns The active gamepad configuration. - */ - getActiveConfig(): InterfaceConfig { - return this.scene.inputController.getActiveConfig(Device.GAMEPAD); - } - - /** - * Get the gamepad settings from local storage. - * - * @returns The gamepad settings from local storage. - */ - getLocalStorageSetting(): object { - // Retrieve the gamepad settings from local storage or use an empty object if none exist. - const settings: object = localStorage.hasOwnProperty("settingsGamepad") ? JSON.parse(localStorage.getItem("settingsGamepad")) : {}; - return settings; - } - /** * Set the layout for the active configuration. * @@ -105,27 +84,6 @@ export default class SettingsGamepadUiHandler extends AbstractSettingsUiUiHandle return super.setLayout(activeConfig); } - - /** - * Navigate to the left menu tab. - * - * @returns `true` indicating the navigation was successful. - */ - navigateMenuLeft(): boolean { - this.scene.ui.setMode(Mode.SETTINGS); - return true; - } - - /** - * Navigate to the right menu tab. - * - * @returns `true` indicating the navigation was successful. - */ - navigateMenuRight(): boolean { - this.scene.ui.setMode(Mode.SETTINGS_KEYBOARD); - return true; - } - /** * Update the display of the chosen gamepad. */ @@ -135,11 +93,11 @@ export default class SettingsGamepadUiHandler extends AbstractSettingsUiUiHandle this.resetScroll(); // Iterate over the keys in the settingDevice enumeration. - for (const [index, key] of Object.keys(this.settingDevice).entries()) { - const setting = this.settingDevice[key]; // Get the actual setting value using the key. + for (const [index, key] of Object.keys(this.setting).entries()) { + const setting = this.setting[key]; // Get the actual setting value using the key. // Check if the current setting corresponds to the controller setting. - if (setting === this.settingDevice.Controller) { + if (setting === this.setting.Controller) { // Iterate over all layouts excluding the 'noGamepads' special case. for (const _key of Object.keys(this.layout)) { if (_key === "noGamepads") { @@ -157,12 +115,12 @@ export default class SettingsGamepadUiHandler extends AbstractSettingsUiUiHandle /** * Save the setting to local storage. * - * @param setting - The setting to save. + * @param settingName - The setting to save. * @param cursor - The cursor position to save. */ - saveSettingToLocalStorage(setting, cursor): void { - if (this.settingDevice[setting] !== this.settingDevice.Controller) { - this.scene.gameData.saveGamepadSetting(setting, cursor); + saveSettingToLocalStorage(settingName, cursor): void { + if (this.setting[settingName] !== this.setting.Controller) { + this.scene.gameData.saveControlSetting(this.device, this.localStoragePropertyName, settingName, this.settingDeviceDefaults, cursor); } } } diff --git a/src/ui/settings/settings-keyboard-ui-handler.ts b/src/ui/settings/settings-keyboard-ui-handler.ts index 3a7751c4522..59b409fe990 100644 --- a/src/ui/settings/settings-keyboard-ui-handler.ts +++ b/src/ui/settings/settings-keyboard-ui-handler.ts @@ -7,9 +7,9 @@ import { settingKeyboardBlackList, settingKeyboardDefaults, settingKeyboardOptions -} from "#app/system/settings-keyboard"; +} from "#app/system/settings/settings-keyboard"; import {reverseValueToKeySetting, truncateString} from "#app/utils"; -import AbstractSettingsUiUiHandler from "#app/ui/settings/abstract-settings-ui-handler"; +import AbstractControlSettingsUiHandler from "#app/ui/settings/abstract-control-settings-ui-handler.js"; import {InterfaceConfig} from "#app/inputs-controller"; import {addTextObject, TextStyle} from "#app/ui/text"; import {deleteBind} from "#app/configs/inputs/configHandler"; @@ -19,9 +19,9 @@ import {NavigationManager} from "#app/ui/settings/navigationMenu"; /** * Class representing the settings UI handler for keyboards. * - * @extends AbstractSettingsUiUiHandler + * @extends AbstractControlSettingsUiHandler */ -export default class SettingsKeyboardUiHandler extends AbstractSettingsUiUiHandler { +export default class SettingsKeyboardUiHandler extends AbstractControlSettingsUiHandler { /** * Creates an instance of SettingsKeyboardUiHandler. * @@ -31,7 +31,7 @@ export default class SettingsKeyboardUiHandler extends AbstractSettingsUiUiHandl constructor(scene: BattleScene, mode?: Mode) { super(scene, mode); this.titleSelected = "Keyboard"; - this.settingDevice = SettingKeyboard; + this.setting = SettingKeyboard; this.settingDeviceDefaults = settingKeyboardDefaults; this.settingDeviceOptions = settingKeyboardOptions; this.configs = [cfg_keyboard_qwerty]; @@ -39,6 +39,7 @@ export default class SettingsKeyboardUiHandler extends AbstractSettingsUiUiHandl this.textureOverride = "keyboard"; this.localStoragePropertyName = "settingsKeyboard"; this.settingBlacklisted = settingKeyboardBlackList; + this.device = Device.KEYBOARD; const deleteEvent = scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.DELETE); const restoreDefaultEvent = scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.HOME); @@ -46,9 +47,7 @@ export default class SettingsKeyboardUiHandler extends AbstractSettingsUiUiHandl restoreDefaultEvent.on("up", this.onHomeDown, this); } - setSetting(scene: BattleScene, setting, value: integer): boolean { - return setSettingKeyboard(scene, setting, value); - } + setSetting = setSettingKeyboard; /** * Setup UI elements. @@ -114,26 +113,6 @@ export default class SettingsKeyboardUiHandler extends AbstractSettingsUiUiHandl } } - /** - * Get the active configuration. - * - * @returns The active keyboard configuration. - */ - getActiveConfig(): InterfaceConfig { - return this.scene.inputController.getActiveConfig(Device.KEYBOARD); - } - - /** - * Get the keyboard settings from local storage. - * - * @returns The keyboard settings from local storage. - */ - getLocalStorageSetting(): object { - // Retrieve the gamepad settings from local storage or use an empty object if none exist. - const settings: object = localStorage.hasOwnProperty("settingsKeyboard") ? JSON.parse(localStorage.getItem("settingsKeyboard")) : {}; - return settings; - } - /** * Set the layout for the active configuration. * @@ -154,26 +133,6 @@ export default class SettingsKeyboardUiHandler extends AbstractSettingsUiUiHandl return super.setLayout(activeConfig); } - /** - * Navigate to the left menu tab. - * - * @returns `true` indicating the navigation was successful. - */ - navigateMenuLeft(): boolean { - this.scene.ui.setMode(Mode.SETTINGS_GAMEPAD); - return true; - } - - /** - * Navigate to the right menu tab. - * - * @returns `true` indicating the navigation was successful. - */ - navigateMenuRight(): boolean { - this.scene.ui.setMode(Mode.SETTINGS); - return true; - } - /** * Update the display of the chosen keyboard layout. */ @@ -182,11 +141,11 @@ export default class SettingsKeyboardUiHandler extends AbstractSettingsUiUiHandl this.updateBindings(); // Iterate over the keys in the settingDevice enumeration. - for (const [index, key] of Object.keys(this.settingDevice).entries()) { - const setting = this.settingDevice[key]; // Get the actual setting value using the key. + for (const [index, key] of Object.keys(this.setting).entries()) { + const setting = this.setting[key]; // Get the actual setting value using the key. // Check if the current setting corresponds to the layout setting. - if (setting === this.settingDevice.Default_Layout) { + if (setting === this.setting.Default_Layout) { // Iterate over all layouts excluding the 'noGamepads' special case. for (const _key of Object.keys(this.layout)) { if (_key === "noKeyboard") { @@ -217,8 +176,8 @@ export default class SettingsKeyboardUiHandler extends AbstractSettingsUiUiHandl * @param cursor - The cursor position to save. */ saveSettingToLocalStorage(settingName, cursor): void { - if (this.settingDevice[settingName] !== this.settingDevice.Default_Layout) { - this.scene.gameData.saveKeyboardSetting(settingName, cursor); + if (this.setting[settingName] !== this.setting.Default_Layout) { + this.scene.gameData.saveControlSetting(this.device, this.localStoragePropertyName, settingName, this.settingDeviceDefaults, cursor); } } } diff --git a/src/ui/settings/settings-ui-handler.ts b/src/ui/settings/settings-ui-handler.ts index 44dc90dff88..a6332be1343 100644 --- a/src/ui/settings/settings-ui-handler.ts +++ b/src/ui/settings/settings-ui-handler.ts @@ -1,345 +1,19 @@ import BattleScene from "../../battle-scene"; -import {Setting, reloadSettings, settingDefaults, settingOptions} from "../../system/settings"; -import { hasTouchscreen, isMobile } from "../../touch-controls"; -import { TextStyle, addTextObject } from "../text"; +import {Setting, SettingType} from "../../system/settings/settings"; import { Mode } from "../ui"; -import UiHandler from "../ui-handler"; -import { addWindow } from "../ui-theme"; -import {Button} from "../../enums/buttons"; -import {InputsIcons} from "#app/ui/settings/abstract-settings-ui-handler"; -import NavigationMenu, {NavigationManager} from "#app/ui/settings/navigationMenu"; - -export default class SettingsUiHandler extends UiHandler { - private settingsContainer: Phaser.GameObjects.Container; - private optionsContainer: Phaser.GameObjects.Container; - private navigationContainer: NavigationMenu; - - private scrollCursor: integer; - - private optionsBg: Phaser.GameObjects.NineSlice; - - private optionCursors: integer[]; - - private settingLabels: Phaser.GameObjects.Text[]; - private optionValueLabels: Phaser.GameObjects.Text[][]; - - protected navigationIcons: InputsIcons; - - private cursorObj: Phaser.GameObjects.NineSlice; - - private reloadRequired: boolean; - private reloadI18n: boolean; - private rowsToDisplay: number; +import AbstractSettingsUiHandler from "./abstract-settings-ui-handler"; +export default class SettingsUiHandler extends AbstractSettingsUiHandler { + /** + * Creates an instance of SettingsGamepadUiHandler. + * + * @param scene - The BattleScene instance. + * @param mode - The UI mode, optional. + */ constructor(scene: BattleScene, mode?: Mode) { super(scene, mode); - - this.reloadRequired = false; - this.reloadI18n = false; - this.rowsToDisplay = 8; - } - - setup() { - const ui = this.getUi(); - - this.settingsContainer = this.scene.add.container(1, -(this.scene.game.canvas.height / 6) + 1); - - this.settingsContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6 - 20), Phaser.Geom.Rectangle.Contains); - - this.navigationIcons = {}; - - this.navigationContainer = new NavigationMenu(this.scene, 0, 0); - - this.optionsBg = addWindow(this.scene, 0, this.navigationContainer.height, (this.scene.game.canvas.width / 6) - 2, (this.scene.game.canvas.height / 6) - 16 - this.navigationContainer.height - 2); - this.optionsBg.setOrigin(0, 0); - - const actionsBg = addWindow(this.scene, 0, (this.scene.game.canvas.height / 6) - this.navigationContainer.height, (this.scene.game.canvas.width / 6) - 2, 22); - actionsBg.setOrigin(0, 0); - - const iconAction = this.scene.add.sprite(0, 0, "keyboard"); - iconAction.setOrigin(0, -0.1); - iconAction.setPositionRelative(actionsBg, this.navigationContainer.width - 32, 4); - this.navigationIcons["BUTTON_ACTION"] = iconAction; - - const actionText = addTextObject(this.scene, 0, 0, "Action", TextStyle.SETTINGS_LABEL); - actionText.setOrigin(0, 0.15); - actionText.setPositionRelative(iconAction, -actionText.width/6-2, 0); - - const iconCancel = this.scene.add.sprite(0, 0, "keyboard"); - iconCancel.setOrigin(0, -0.1); - iconCancel.setPositionRelative(actionsBg, this.navigationContainer.width - 100, 4); - this.navigationIcons["BUTTON_CANCEL"] = iconCancel; - - const cancelText = addTextObject(this.scene, 0, 0, "Cancel", TextStyle.SETTINGS_LABEL); - cancelText.setOrigin(0, 0.15); - cancelText.setPositionRelative(iconCancel, -cancelText.width/6-2, 0); - - this.optionsContainer = this.scene.add.container(0, 0); - - this.settingLabels = []; - this.optionValueLabels = []; - - Object.keys(Setting).forEach((setting, s) => { - let settingName = setting.replace(/\_/g, " "); - if (reloadSettings.includes(Setting[setting])) { - settingName += " (Requires Reload)"; - } - - this.settingLabels[s] = addTextObject(this.scene, 8, 28 + s * 16, settingName, TextStyle.SETTINGS_LABEL); - this.settingLabels[s].setOrigin(0, 0); - - this.optionsContainer.add(this.settingLabels[s]); - - this.optionValueLabels.push(settingOptions[Setting[setting]].map((option, o) => { - const valueLabel = addTextObject(this.scene, 0, 0, option, settingDefaults[Setting[setting]] === o ? TextStyle.SETTINGS_SELECTED : TextStyle.WINDOW); - valueLabel.setOrigin(0, 0); - - this.optionsContainer.add(valueLabel); - - return valueLabel; - })); - - const totalWidth = this.optionValueLabels[s].map(o => o.width).reduce((total, width) => total += width, 0); - - const labelWidth = Math.max(78, this.settingLabels[s].displayWidth + 8); - - const totalSpace = (300 - labelWidth) - totalWidth / 6; - const optionSpacing = Math.floor(totalSpace / (this.optionValueLabels[s].length - 1)); - - let xOffset = 0; - - for (const value of this.optionValueLabels[s]) { - value.setPositionRelative(this.settingLabels[s], labelWidth + xOffset, 0); - xOffset += value.width / 6 + optionSpacing; - } - }); - - this.optionCursors = Object.values(settingDefaults); - - this.settingsContainer.add(this.optionsBg); - this.settingsContainer.add(this.navigationContainer); - this.settingsContainer.add(actionsBg); - this.settingsContainer.add(this.optionsContainer); - this.settingsContainer.add(iconAction); - this.settingsContainer.add(iconCancel); - this.settingsContainer.add(actionText); - this.settingsContainer.add(cancelText); - - ui.add(this.settingsContainer); - - this.setCursor(0); - this.setScrollCursor(0); - - this.settingsContainer.setVisible(false); - } - - updateBindings(): void { - for (const settingName of Object.keys(this.navigationIcons)) { - if (settingName === "BUTTON_HOME") { - this.navigationIcons[settingName].setTexture("keyboard"); - this.navigationIcons[settingName].setFrame("HOME.png"); - this.navigationIcons[settingName].alpha = 1; - continue; - } - const icon = this.scene.inputController?.getIconForLatestInputRecorded(settingName); - if (icon) { - const type = this.scene.inputController?.getLastSourceType(); - this.navigationIcons[settingName].setTexture(type); - this.navigationIcons[settingName].setFrame(icon); - this.navigationIcons[settingName].alpha = 1; - } else { - this.navigationIcons[settingName].alpha = 0; - } - } - NavigationManager.getInstance().updateIcons(); - } - - show(args: any[]): boolean { - super.show(args); - this.updateBindings(); - - const settings: object = localStorage.hasOwnProperty("settings") ? JSON.parse(localStorage.getItem("settings")) : {}; - - Object.keys(settingDefaults).forEach((setting, s) => this.setOptionCursor(s, settings.hasOwnProperty(setting) ? settings[setting] : settingDefaults[setting])); - - this.settingsContainer.setVisible(true); - this.setCursor(0); - - this.getUi().moveTo(this.settingsContainer, this.getUi().length - 1); - - this.getUi().hideTooltip(); - - return true; - } - - /** - * Processes input from a specified button. - * This method handles navigation through a UI menu, including movement through menu items - * and handling special actions like cancellation. Each button press may adjust the cursor - * position or the menu scroll, and plays a sound effect if the action was successful. - * - * @param button - The button pressed by the user. - * @returns `true` if the action associated with the button was successfully processed, `false` otherwise. - */ - processInput(button: Button): boolean { - const ui = this.getUi(); - // Defines the maximum number of rows that can be displayed on the screen. - - let success = false; - - if (button === Button.CANCEL) { - success = true; - NavigationManager.getInstance().reset(); - // Reverts UI to its previous state on cancel. - this.scene.ui.revertMode(); - } else { - const cursor = this.cursor + this.scrollCursor; - switch (button) { - case Button.UP: - if (cursor) { - if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } else { - success = this.setScrollCursor(this.scrollCursor - 1); - } - } else { - // When at the top of the menu and pressing UP, move to the bottommost item. - // First, set the cursor to the last visible element, preparing for the scroll to the end. - const successA = this.setCursor(this.rowsToDisplay - 1); - // Then, adjust the scroll to display the bottommost elements of the menu. - const successB = this.setScrollCursor(this.optionValueLabels.length - this.rowsToDisplay); - success = successA && successB; // success is just there to play the little validation sound effect - } - break; - case Button.DOWN: - if (cursor < this.optionValueLabels.length - 1) { - if (this.cursor < this.rowsToDisplay - 1) {// if the visual cursor is in the frame of 0 to 8 - success = this.setCursor(this.cursor + 1); - } else if (this.scrollCursor < this.optionValueLabels.length - this.rowsToDisplay) { - success = this.setScrollCursor(this.scrollCursor + 1); - } - } else { - // When at the bottom of the menu and pressing DOWN, move to the topmost item. - // First, set the cursor to the first visible element, resetting the scroll to the top. - const successA = this.setCursor(0); - // Then, reset the scroll to start from the first element of the menu. - const successB = this.setScrollCursor(0); - success = successA && successB; // Indicates a successful cursor and scroll adjustment. - } - break; - case Button.LEFT: - if (this.optionCursors[cursor]) {// Moves the option cursor left, if possible. - success = this.setOptionCursor(cursor, this.optionCursors[cursor] - 1, true); - } - break; - case Button.RIGHT: - // Moves the option cursor right, if possible. - if (this.optionCursors[cursor] < this.optionValueLabels[cursor].length - 1) { - success = this.setOptionCursor(cursor, this.optionCursors[cursor] + 1, true); - } - break; - case Button.CYCLE_FORM: - case Button.CYCLE_SHINY: - success = this.navigationContainer.navigate(button); - break; - } - } - - // Plays a select sound effect if an action was successfully processed. - if (success) { - ui.playSelect(); - } - - return success; - } - - setCursor(cursor: integer): boolean { - const ret = super.setCursor(cursor); - - if (!this.cursorObj) { - this.cursorObj = this.scene.add.nineslice(0, 0, "summary_moves_cursor", null, (this.scene.game.canvas.width / 6) - 10, 16, 1, 1, 1, 1); - this.cursorObj.setOrigin(0, 0); - this.optionsContainer.add(this.cursorObj); - } - - this.cursorObj.setPositionRelative(this.optionsBg, 4, 4 + (this.cursor + this.scrollCursor) * 16); - - return ret; - } - - setOptionCursor(settingIndex: integer, cursor: integer, save?: boolean): boolean { - const setting = Setting[Object.keys(Setting)[settingIndex]]; - - if (setting === Setting.Touch_Controls && cursor && hasTouchscreen() && isMobile()) { - this.getUi().playError(); - return false; - } - - const lastCursor = this.optionCursors[settingIndex]; - - const lastValueLabel = this.optionValueLabels[settingIndex][lastCursor]; - lastValueLabel.setColor(this.getTextColor(TextStyle.WINDOW)); - lastValueLabel.setShadowColor(this.getTextColor(TextStyle.WINDOW, true)); - - this.optionCursors[settingIndex] = cursor; - - const newValueLabel = this.optionValueLabels[settingIndex][cursor]; - newValueLabel.setColor(this.getTextColor(TextStyle.SETTINGS_SELECTED)); - newValueLabel.setShadowColor(this.getTextColor(TextStyle.SETTINGS_SELECTED, true)); - - if (save) { - this.scene.gameData.saveSetting(setting, cursor); - if (reloadSettings.includes(setting)) { - this.reloadRequired = true; - if (setting === Setting.Language) { - this.reloadI18n = true; - } - } - } - - return true; - } - - setScrollCursor(scrollCursor: integer): boolean { - if (scrollCursor === this.scrollCursor) { - return false; - } - - this.scrollCursor = scrollCursor; - - this.updateSettingsScroll(); - - this.setCursor(this.cursor); - - return true; - } - - updateSettingsScroll(): void { - this.optionsContainer.setY(-16 * this.scrollCursor); - - for (let s = 0; s < this.settingLabels.length; s++) { - const visible = s >= this.scrollCursor && s < this.scrollCursor + this.rowsToDisplay; - this.settingLabels[s].setVisible(visible); - for (const option of this.optionValueLabels[s]) { - option.setVisible(visible); - } - } - } - - clear() { - super.clear(); - this.settingsContainer.setVisible(false); - this.eraseCursor(); - if (this.reloadRequired) { - this.reloadRequired = false; - this.scene.reset(true, false, true); - } - } - - eraseCursor() { - if (this.cursorObj) { - this.cursorObj.destroy(); - } - this.cursorObj = null; + this.title = "General"; + this.settings = Setting.filter(s => s.type === SettingType.GENERAL); + this.localStorageKey = "settings"; } } diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 90cba657fbf..b2df4d22259 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -42,6 +42,7 @@ import {PlayerGender} from "#app/system/game-data"; import GamepadBindingUiHandler from "./settings/gamepad-binding-ui-handler"; import SettingsKeyboardUiHandler from "#app/ui/settings/settings-keyboard-ui-handler"; import KeyboardBindingUiHandler from "#app/ui/settings/keyboard-binding-ui-handler"; +import SettingsAccessibilityUiHandler from "./settings/settings-accessiblity-ui-handler"; export enum Mode { MESSAGE, @@ -62,6 +63,7 @@ export enum Mode { MENU, MENU_OPTION_SELECT, SETTINGS, + SETTINGS_ACCESSIBILITY, SETTINGS_GAMEPAD, GAMEPAD_BINDING, SETTINGS_KEYBOARD, @@ -99,6 +101,7 @@ const noTransitionModes = [ Mode.GAMEPAD_BINDING, Mode.KEYBOARD_BINDING, Mode.SETTINGS, + Mode.SETTINGS_ACCESSIBILITY, Mode.SETTINGS_GAMEPAD, Mode.SETTINGS_KEYBOARD, Mode.ACHIEVEMENTS, @@ -151,6 +154,7 @@ export default class UI extends Phaser.GameObjects.Container { new MenuUiHandler(scene), new OptionSelectUiHandler(scene, Mode.MENU_OPTION_SELECT), new SettingsUiHandler(scene), + new SettingsAccessibilityUiHandler(scene), new SettingsGamepadUiHandler(scene), new GamepadBindingUiHandler(scene), new SettingsKeyboardUiHandler(scene), From e0fd11746f739d4cbf556f76638370b844cd458a Mon Sep 17 00:00:00 2001 From: Yurical Date: Tue, 4 Jun 2024 10:38:52 +0900 Subject: [PATCH 024/129] [Localization] Use proper postpositional particle for Korean (#1759) In Korean, postpositional particles vary depending on whether the preceding syllable ends in a consonant or a vowel. Currently there is no ability differentiate between the two types of particles, so both forms are being used at the same time. To remedy this problem, I added the relevant i18next processor to properly select and print the correct form of the particle. --- package-lock.json | 9 ++++++ package.json | 1 + src/locales/ko/ability-trigger.ts | 4 +-- src/locales/ko/battle.ts | 44 ++++++++++++++-------------- src/locales/ko/command-ui-handler.ts | 2 +- src/locales/ko/menu.ts | 4 +-- src/locales/ko/modifier-type.ts | 10 +++---- src/locales/ko/weather.ts | 4 +-- src/plugins/i18n.ts | 4 ++- 9 files changed, 47 insertions(+), 35 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0084d2e6022..8c625694f02 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "crypto-js": "^4.2.0", "i18next": "^23.11.1", "i18next-browser-languagedetector": "^7.2.1", + "i18next-korean-postposition-processor": "^1.0.0", "json-stable-stringify": "^1.1.0", "phaser": "^3.70.0", "phaser3-rex-plugins": "^1.1.84" @@ -3615,6 +3616,14 @@ "cross-fetch": "4.0.0" } }, + "node_modules/i18next-korean-postposition-processor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/i18next-korean-postposition-processor/-/i18next-korean-postposition-processor-1.0.0.tgz", + "integrity": "sha512-ruNXjI9awsFK6Ie+F9gYaMW8ciLMuCkeRjH9QkSv2Wb8xI0mnm773v3M9eua8dtvAXudIUk4p6Ho7hNkEASXDg==", + "peerDependencies": { + "i18next": ">=8.4.0" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", diff --git a/package.json b/package.json index 5d1621d3ba8..2e87525aeaf 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "crypto-js": "^4.2.0", "i18next": "^23.11.1", "i18next-browser-languagedetector": "^7.2.1", + "i18next-korean-postposition-processor": "^1.0.0", "json-stable-stringify": "^1.1.0", "phaser": "^3.70.0", "phaser3-rex-plugins": "^1.1.84" diff --git a/src/locales/ko/ability-trigger.ts b/src/locales/ko/ability-trigger.ts index da263358089..7fc98edce76 100644 --- a/src/locales/ko/ability-trigger.ts +++ b/src/locales/ko/ability-trigger.ts @@ -1,6 +1,6 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const abilityTriggers: SimpleTranslationEntries = { - "blockRecoilDamage" : "{{pokemonName}}(는)은 {{abilityName}} 때문에\n반동 데미지를 받지 않는다!", - "badDreams": "{{pokemonName}}(는)은\n나이트메어 때문에 시달리고 있다!", + "blockRecoilDamage" : "{{pokemonName}}[[는]] {{abilityName}} 때문에\n반동 데미지를 받지 않는다!", + "badDreams": "{{pokemonName}}[[는]]\n나이트메어 때문에 시달리고 있다!", } as const; diff --git a/src/locales/ko/battle.ts b/src/locales/ko/battle.ts index dd98a1df5fa..c6288d3d9f2 100644 --- a/src/locales/ko/battle.ts +++ b/src/locales/ko/battle.ts @@ -1,21 +1,21 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const battle: SimpleTranslationEntries = { - "bossAppeared": "{{bossName}}(이)가 나타났다.", - "trainerAppeared": "{{trainerName}}(이)가\n승부를 걸어왔다!", - "trainerAppearedDouble": "{{trainerName}}(이)가\n승부를 걸어왔다!", - "trainerSendOut": "{{trainerName}}(는)은\n{{pokemonName}}(를)을 내보냈다!", - "singleWildAppeared": "앗! 야생 {{pokemonName}}(이)가\n튀어나왔다!", - "multiWildAppeared": "야생 {{pokemonName1}}(과)와\n{{pokemonName2}}(이)가 튀어나왔다!", + "bossAppeared": "{{bossName}}[[가]] 나타났다.", + "trainerAppeared": "{{trainerName}}[[가]]\n승부를 걸어왔다!", + "trainerAppearedDouble": "{{trainerName}}[[가]]\n승부를 걸어왔다!", + "trainerSendOut": "{{trainerName}}[[는]]\n{{pokemonName}}[[를]] 내보냈다!", + "singleWildAppeared": "앗! 야생 {{pokemonName}}[[가]]\n튀어나왔다!", + "multiWildAppeared": "야생 {{pokemonName1}}[[와]]\n{{pokemonName2}}[[가]] 튀어나왔다!", "playerComeBack": "돌아와, {{pokemonName}}!", - "trainerComeBack": "{{trainerName}}(는)은 {{pokemonName}}를(을) 넣어버렸다!", + "trainerComeBack": "{{trainerName}}[[는]] {{pokemonName}}[[를]] 넣어버렸다!", "playerGo": "가랏! {{pokemonName}}!", - "trainerGo": "{{trainerName}}(는)은 {{pokemonName}}를(을) 내보냈다!", - "switchQuestion": "{{pokemonName}}를(을)\n교체하시겠습니까?", - "trainerDefeated": "{{trainerName}}과(와)의\n승부에서 이겼다!", + "trainerGo": "{{trainerName}}[[는]] {{pokemonName}}[[를]] 내보냈다!", + "switchQuestion": "{{pokemonName}}[[를]]\n교체하시겠습니까?", + "trainerDefeated": "{{trainerName}}[[와]]의\n승부에서 이겼다!", "moneyWon": "상금으로\n₽{{moneyAmount}}을 손에 넣었다!", - "pokemonCaught": "신난다-!\n{{pokemonName}}(를)을 잡았다!", - "partyFull": "지닌 포켓몬이 가득 찼습니다. {{pokemonName}}를(을)\n대신해 포켓몬을 놓아주시겠습니까?", + "pokemonCaught": "신난다-!\n{{pokemonName}}[[를]] 잡았다!", + "partyFull": "지닌 포켓몬이 가득 찼습니다. {{pokemonName}}[[를]]\n대신해 포켓몬을 놓아주시겠습니까?", "pokemon": "포켓몬", "sendOutPokemon": "가랏! {{pokemonName}}!", "hitResultCriticalHit": "급소에 맞았다!", @@ -25,22 +25,22 @@ export const battle: SimpleTranslationEntries = { "hitResultOneHitKO": "일격필살!", "attackFailed": "하지만 실패했다!", "attackHitsCount": "{{count}}번 맞았다!", - "expGain": "{{pokemonName}}(는)은\n{{exp}} 경험치를 얻었다!", - "levelUp": "{{pokemonName}}(는)은\n레벨 {{level}}(으)로 올랐다!", - "learnMove": "{{pokemonName}}(는)은 새로\n{{moveName}}를(을) 배웠다!", - "learnMovePrompt": "{{pokemonName}}(는)은 새로\n{{moveName}}를(을) 배우고 싶다!…", - "learnMoveLimitReached": "그러나 {{pokemonName}}(는)은 기술을 4개\n알고 있으므로 더 이상 배울 수 없다!", + "expGain": "{{pokemonName}}[[는]]\n{{exp}} 경험치를 얻었다!", + "levelUp": "{{pokemonName}}[[는]]\n레벨 {{level}}[[로]] 올랐다!", + "learnMove": "{{pokemonName}}[[는]] 새로\n{{moveName}}[[를]] 배웠다!", + "learnMovePrompt": "{{pokemonName}}[[는]] 새로\n{{moveName}}[[를]] 배우고 싶다!…", + "learnMoveLimitReached": "그러나 {{pokemonName}}[[는]] 기술을 4개\n알고 있으므로 더 이상 배울 수 없다!", "learnMoveReplaceQuestion": "{{moveName}} 대신 다른 기술을 잊게 하겠습니까?", - "learnMoveStopTeaching": "그럼… {{moveName}}를(을)\n배우는 것을 포기하겠습니까?", - "learnMoveNotLearned": "{{pokemonName}}(는)은 {{moveName}}를(을)\n결국 배우지 않았다!", + "learnMoveStopTeaching": "그럼… {{moveName}}[[를]]\n배우는 것을 포기하겠습니까?", + "learnMoveNotLearned": "{{pokemonName}}[[는]] {{moveName}}[[를]]\n결국 배우지 않았다!", "learnMoveForgetQuestion": "어느 기술을 잊게 하고싶은가?", - "learnMoveForgetSuccess": "{{pokemonName}}(는)은 {{moveName}}를(을) 깨끗이 잊었다!", + "learnMoveForgetSuccess": "{{pokemonName}}[[는]] {{moveName}}[[를]] 깨끗이 잊었다!", "countdownPoof": "@d{32}1, @d{15}2, @d{15}… @d{15}… @d{30}@s{pb_bounce_1}짠!", "learnMoveAnd": "그리고…", "levelCapUp": "레벨의 최대치가\n{{levelCap}}까지 상승했다!", - "moveNotImplemented": "{{moveName}}(는)은 아직 구현되지 않아 사용할 수 없다…", + "moveNotImplemented": "{{moveName}}[[는]] 아직 구현되지 않아 사용할 수 없다…", "moveNoPP": "기술의 남은 포인트가 없다!", - "moveDisabled": "{{moveName}}를(을) 쓸 수 없다!", + "moveDisabled": "{{moveName}}[[를]] 쓸 수 없다!", "noPokeballForce": "본 적 없는 힘이\n볼을 사용하지 못하게 한다.", "noPokeballTrainer": "다른 트레이너의 포켓몬은 잡을 수 없다!", "noPokeballMulti": "안돼! 2마리 있어서\n목표를 정할 수가 없어…!", diff --git a/src/locales/ko/command-ui-handler.ts b/src/locales/ko/command-ui-handler.ts index c1d3d4b680f..b10534cfb92 100644 --- a/src/locales/ko/command-ui-handler.ts +++ b/src/locales/ko/command-ui-handler.ts @@ -5,5 +5,5 @@ export const commandUiHandler: SimpleTranslationEntries = { "ball": "볼", "pokemon": "포켓몬", "run": "도망간다", - "actionMessage": "{{pokemonName}}(는)은 무엇을 할까?", + "actionMessage": "{{pokemonName}}[[는]] 무엇을 할까?", } as const; diff --git a/src/locales/ko/menu.ts b/src/locales/ko/menu.ts index 8e9f132aa93..8d46dafc721 100644 --- a/src/locales/ko/menu.ts +++ b/src/locales/ko/menu.ts @@ -38,9 +38,9 @@ export const menu: SimpleTranslationEntries = { "girl": "여자", "evolving": "…오잉!?\n{{pokemonName}}의 모습이…!", "stoppedEvolving": "얼라리…?\n{{pokemonName}}의 변화가 멈췄다!", - "pauseEvolutionsQuestion": "{{pokemonName}}를(을) 진화하지 않게 만드시겠습니까?\n포켓몬 화면에서 다시 활성화시킬 수 있습니다.", + "pauseEvolutionsQuestion": "{{pokemonName}}[[를]] 진화하지 않게 만드시겠습니까?\n포켓몬 화면에서 다시 활성화시킬 수 있습니다.", "evolutionsPaused": "{{pokemonName}}의 진화가 비활성화되었다.", - "evolutionDone": "축하합니다! {{pokemonName}}(는)은\n{{evolvedPokemonName}}(으)로 진화했습니다!", + "evolutionDone": "축하합니다! {{pokemonName}}[[는]]\n{{evolvedPokemonName}}[[로]] 진화했습니다!", "dailyRankings": "일간 랭킹", "weeklyRankings": "주간 랭킹", "noRankings": "랭킹 정보 없음", diff --git a/src/locales/ko/modifier-type.ts b/src/locales/ko/modifier-type.ts index 3694a2ef523..f3b9ece6e81 100644 --- a/src/locales/ko/modifier-type.ts +++ b/src/locales/ko/modifier-type.ts @@ -12,8 +12,8 @@ export const modifierType: ModifierTypeTranslationEntries = { }, "PokemonHeldItemModifierType": { extra: { - "inoperable": "{{pokemonName}}(는)은\n이 아이템을 얻을 수 없다!", - "tooMany": "{{pokemonName}}(는)은\n이 아이템을 너무 많이 갖고 있다!", + "inoperable": "{{pokemonName}}[[는]]\n이 아이템을 얻을 수 없다!", + "tooMany": "{{pokemonName}}[[는]]\n이 아이템을 너무 많이 갖고 있다!", } }, "PokemonHpRestoreModifierType": { @@ -46,13 +46,13 @@ export const modifierType: ModifierTypeTranslationEntries = { }, "PokemonNatureChangeModifierType": { name: "{{natureName}}민트", - description: "포켓몬의 성격을 {{natureName}}(으)로 바꾸고 스타팅에도 등록한다.", + description: "포켓몬의 성격을 {{natureName}}[[로]] 바꾸고 스타팅에도 등록한다.", }, "DoubleBattleChanceBoosterModifierType": { description: "{{battleCount}}번의 배틀 동안 더블 배틀이 등장할 확률 두 배", }, "TempBattleStatBoosterModifierType": { - description: "자신의 모든 포켓몬이 5번의 배틀 동안 {{tempBattleStatName}}(이)가 한 단계 증가" + description: "자신의 모든 포켓몬이 5번의 배틀 동안 {{tempBattleStatName}}[[가]] 한 단계 증가" }, "AttackTypeBoosterModifierType": { description: "지니게 하면 {{moveType}}타입 기술의 위력이 20% 상승", @@ -97,7 +97,7 @@ export const modifierType: ModifierTypeTranslationEntries = { }, "TmModifierType": { name: "No.{{moveId}} {{moveName}}", - description: "포켓몬에게 {{moveName}}를(을) 가르침", + description: "포켓몬에게 {{moveName}}[[를]] 가르침", }, "EvolutionItemModifierType": { description: "어느 특정 포켓몬을 진화", diff --git a/src/locales/ko/weather.ts b/src/locales/ko/weather.ts index 6bfc6552b50..70654d247b6 100644 --- a/src/locales/ko/weather.ts +++ b/src/locales/ko/weather.ts @@ -15,12 +15,12 @@ export const weather: SimpleTranslationEntries = { "sandstormStartMessage": "모래바람이 불기 시작했다!", "sandstormLapseMessage": "모래바람이 세차게 분다", "sandstormClearMessage": "모래바람이 가라앉았다!", - "sandstormDamageMessage": "모래바람이\n{{pokemonPrefix}}{{pokemonName}}를(을) 덮쳤다!", + "sandstormDamageMessage": "모래바람이\n{{pokemonPrefix}}{{pokemonName}}[[를]] 덮쳤다!", "hailStartMessage": "싸라기눈이 내리기 시작했다!", "hailLapseMessage": "싸라기눈이 계속 내리고 있다", "hailClearMessage": "싸라기눈이 그쳤다!", - "hailDamageMessage": "싸라기눈이\n{{pokemonPrefix}}{{pokemonName}}를(을) 덮쳤다!", + "hailDamageMessage": "싸라기눈이\n{{pokemonPrefix}}{{pokemonName}}[[를]] 덮쳤다!", "snowStartMessage": "눈이 내리기 시작했다!", "snowLapseMessage": "눈이 계속 내리고 있다", diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts index 9bd03b501f4..3f6469904d4 100644 --- a/src/plugins/i18n.ts +++ b/src/plugins/i18n.ts @@ -1,5 +1,6 @@ import i18next from "i18next"; import LanguageDetector from "i18next-browser-languagedetector"; +import processor, { KoreanPostpositionProcessor } from "i18next-korean-postposition-processor"; import { deConfig } from "#app/locales/de/config.js"; import { enConfig } from "#app/locales/en/config.js"; @@ -148,7 +149,7 @@ export function initI18n(): void { * A: In src/system/settings.ts, add a new case to the Setting.Language switch statement. */ - i18next.use(LanguageDetector).init({ + i18next.use(LanguageDetector).use(processor).use(new KoreanPostpositionProcessor()).init({ lng: lang, nonExplicitSupportedLngs: true, fallbackLng: "en", @@ -186,6 +187,7 @@ export function initI18n(): void { ...koConfig }, }, + postProcess: ["korean-postposition"], }); } From b9575d3ffc84ec34da4a288cef13a4bd3ae62fca Mon Sep 17 00:00:00 2001 From: MrWaterT <87186129+MrWaterT@users.noreply.github.com> Date: Tue, 4 Jun 2024 10:59:23 +0900 Subject: [PATCH 025/129] [Localization] Korean Font update (#1678) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update PokePT_Wansung.ttf https://github.com/MrWaterT/PokePT_Wansung v2.1 * Update PokePT_Wansung.ttf with é https://github.com/MrWaterT/PokePT_Wansung v2.2 --- public/fonts/PokePT_Wansung.ttf | Bin 565172 -> 564596 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/public/fonts/PokePT_Wansung.ttf b/public/fonts/PokePT_Wansung.ttf index 6c6e0eb5128322fd51f4f82e0147dfff297f2317..a4eca3d3242631b85db6dd013949d9fb23f67e2c 100644 GIT binary patch literal 564596 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 z@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF z@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF z@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF z@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF z@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF z@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF z@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF@;~xF z@jvlD@jvlD@jvlD@jvlD@jvlD@jvlD@jvlD@jvlD@jvlD@qhmxM*qM6Z*xujPyA2( zPyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2( zPyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2( zPyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2( zPyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2( zPyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2( zPyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2( zPyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2( zPyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2( zPyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyA2(PyEmP z&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B) z&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B) z&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B) z&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B) z&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B) z&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B) z&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B) z&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B) z&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B) z&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&;0NC-}Arcf6xD( z|2_YE{`dUv`QP)u=YP-tp8q}nd;a(Q@A=>Jzvq9?|DOLn|9k%T{O|eS^S|eR&;OqP zJ^y?D_x$hq-}Arcf6xD(|2_YE{`dUv`QP)u=YP-tp8q}nd;a(Q@A=>Jzvq9?|DOLn z|9k%T{O|eS^S|eR&;OqPJ^y?D_x$hq-}Arcf6xD(|2_YE{`dUv`QP)u=YP-tp8q}n zd;a(Q@A=>Jzvq9?|DOLn|9k%T{O|eS^S|eR&;OqPJ^y?D_x$hq-}Arcf6xD(|2_YE z{`dUv`QP)u=YP-tp8q}nd;a(Q@A=>Jzvq9?|DOLn|9k%T{O|eS^S|eR&;OqPJ^y?D z_x$hq-}Arcf6xD(|2_YE{`dUv`QP)u=YP-tp8q}nd;a(Q@A=>Jzvq9?|DOLn|9k%T z{O|eS^S|eR&;OqPJ^y?D_x$hq-}Arcf6xD(|2_YE{`dUv`QP)u=YP-tp8q}nd;a(Q z@A=>Jzvq9?|DOLn|9k%T{O|eS^S|eR&;OqPJ^y?D_x$hq-}Arcf6xD(|2_YE{`dUv z`QP)u=YP-tp8q}nd;a(Q@A=>Jzvq9?|DOLn|9k%T{O|eS^S|eR&;OqPJ^y?D_x$hq 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 z|AGI3|AGI3|AGI3|AGI3|AGI3|AGI3|AGI3|AGI3|AGI3|AGI3|AGI3|AGI3|AGI3 z|AGI3|AGI3|AGI3|AGI3|AGI3|AGI3|AGI3|AGI3|AGI3|AGI3|AGI3|AGI3|AGI3 z|AGI3|AGI3|AGI3|AGI3|AGI3|AGI3|AGI3|AGI3|AGI3|AGI3|AGI3|AGI3|AGI3 z|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ z|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ z|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ z|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ z|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ z|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ z|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ z|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ z|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ z|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B?TZ|B3&J|B3&J z|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J z|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J z|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J z|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J z|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J z|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J z|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J z|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J z|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J z|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|B3&J|C#@p|C#@p|C#@p|C#@p z|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p z|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p z|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p z|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p z|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p z|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p z|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p z|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p z|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p z|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|C#@p|2_YE{`dUv`QP)u=YRkIYPw^b33}s9 zz`wK$IC1RISJ-VfStP?|lYNH0f)m?`LkG@%Cttufup^&fZ{Ved&k7JmZ%II5YW|N8 zs6qAczwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2C zzwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2C zzwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2C zzwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2C zzwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2C zzwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2C zzwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2C zzwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2C zzwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2C zzwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwp2Czwkf!AN&vg2mgcr!T;cY@IUw; z{15&I|AYU*|KNY{KlmT~5B>-Lga5(*;D7Kx_#gZa{s;eq|H1#@fABx}AN&vg2mgcr z!T;cY@IUw;{15&I|AYU*|KNY{KlmT~5B>-Lga5(*;D7Kx_#gZa{s;eq|H1#@fABx} zAN&vg2mgcr!T;cY@IUw;{15&I|AYU*|KNY{KlmT~5B>-Lga5(*;D7Kx_#gZa{s;eq z|H1#@fABx}AN&vg2mgcr!T;cY@IUw;{15&I|AYU*|KNY{KlmT~5B>-Lga5(*;D7Kx z_#gZa{s;eq|H1#@fABx}AN&vg2mgcr!T;cY@IUw;{15&I|AYU*|KNY{KlmT~5B>-L zga5(*;D7Kx_#gZa{s;eq|H1#@fABx}AN&vg2mgcr!T;cY@IUw;{15&I|AYU*|KNY{ zKlmT~5B>-Lga5(*;D7Kx_#gZa{s;eq|H1#@fABx}AN&vg2mgcr!T;cY@IUw;{15&I z|AYU*|KNY{KlmT~5B>-Lga5(*;D7Kx_#gZa{s;eq|H1#@fABx}AN&vg2mgcr!T;cY z@IUw;{15&I|AYU*|KNY{KlmT~5B>-Lga5(*;D7Kx_#gZa{s;eq|H1#@fAGKZzw*EG zzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EG zzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EG zzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EG zzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EG zzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EG zzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EG zzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EG zzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EG zzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EG zzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EGzw*EFzwy8Ezwy8Ezwy8E zzwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8E zzwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8E zzwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8E zzwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8E zzwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8E zzwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8E zzwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8E zzwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8E zzwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8E zzwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Ezwy8Gzw^KIzw^KIzw^KIzw^KIzw^KI zzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KI zzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KI zzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KI zzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KI zzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KI zzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KI zzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KI zzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KI zzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KIzw^KI zzw^KIzw^KIzw^KIzw^KIzw^KIzw`g$|HJ=>{}2Bk{y+SG`2X<#;s3+`hyM@%AO1i5 zfB66K|Kb0`|A+q%{~!K8{D1iW@c-fe!~ci>5C0$jKm33A|M36e|HJ=>{}2Bk{y+SG z`2X<#;s3+`hyM@%AO1i5fB66K|Kb0`|A+q%{~!K8{D1iW@c-fe!~ci>5C0$jKm33A z|M36e|HJ=>{}2Bk{y+SG`2X<#;s3+`hyM@%AO1i5fB66K|Kb0`|A+q%{~!K8{D1iW z@c-fe!~ci>5C0$jKm33A|M36e|HJ=>{}2Bk{y+SG`2X<#;s3+`hyM@%AO1i5fB66K z|Kb0`|A+q%{~!K8{D1iW@c-fe!~ci>5C0$jKm33A|M36e|HJ=>{}2Bk{y+SG`2X<# z;s3+`hyM@%AO1i5fB66K|Kb0`|A+q%{~!K8{D1iW@c-fe!~ci>5C0$jKm33A|M36e z|HJ=>{}2Bk{y+SG`2X<#;s3+`hyM@%AO1i5fB66K|Kb0`|A+q%{~!K8{D1iW@c-fe z!~ci>5C0$jKm33A|M36e|HJ=>{}2Bk{y+SG`2X<#;s3+`hyM@%AO1i5fB66K|Kb0` z|A+q%{~!K8{D1iW@c-fe!~ci>5C0$jKm33A|M36e|HJ=>{}2Bk{y+SG`2X<#;s3+` zhyM@%AO1i5fB66K|Kb0`|A+q%{~!K8{D1iW@c-fe!~ci>5C0$jKm33A|L}kCfAD|s zfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|s zfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|s zfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|s zfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|s zfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|s zfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|s zfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|s zfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|s zfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|s zfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD|sfAD`li2twt>F@t;{|xkb z{{MbJy?mbkzaQ8xpXdMY2bjy}`TzTY-tu|=|9-%=e4hWmANVYv=l|~qAj{|Z|1bYv z{=fWx`Tz3&<^Rk7m;W#SU;e-RfBFCN|K5r~Cip|8)PK{GaaslmFBGfAW92|4;r;{!ji-{!ji-{!ji-{!ji-{!ji-{!ji- z{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji- z{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji- z{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji- z{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji- z{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji- z{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji- z{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji- z{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji- z{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{!ji-{xAM7{xAM7{xAM7{xAM7 z{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7 z{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7 z{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7 z{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7 z{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7 z{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7 z{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7 z{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7 z{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7 z{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{xAM7{(t=c`2X?$s$ z|KtD1|BwG4|3ChJ{Qvm>@&Duh$N!K2AOAo8fBgUW|MCCh|HuE2{~!N9{(t=c`2X?$ zs$|KtD1|BwG4|3ChJ{Qvm>@&Duh$N!K2AOAo8fBgUW|MCCh z|HuE2{~!N9{(t=c`2X?$s$|KtD1|BwG4|3ChJ{Qvm>@&Duh z$N!K2AOAo8fBgUW|MCCh|HuE2{~!N9{(t=c`2X?$s$|KtD1 z|BwG4|3ChJ{Qvm>@&Duh$N!K2AOAo8fBgUW|MCCh|HuE2{~!N9{(t=c`2X?$s$|KtD1|BwG4|3ChJ{Qvm>@&Duh$N!K2AOAo8fBgUW|MCCh|HuE2 z{~!N9{(t=c`2X?$s$|KtD1|BwG4|3ChJ{Qvm>@&Duh$N!K2 zAOAo8fBgUW|MCCh|HuE2{~!N9{(t=c`2X?$s$|KtD1|BwG4 z|3ChJ{Qvm>@&Duh$N!K2AOAo8fBgUW|MCCh|HuE2{~!N9{(t=c`2X?$s$|KtD1|BwG4|3ChJ{Qvm>@&Duh$N!K2AOAo8fBgUW|MCCh|HuE2{~!Oq zwo;;S^wkz&iWt!_tzczAOCmO|MT5F^*{datpD+UXZ?@=JL`Y^-&z0T|Nfdq z|KtD8`XB#y*8ljwv;N2bo%KKd@2vmve`o!V|2yk{{NG>i=zsj*S^wkz&iWt!ch>*- zzq9_w|DE+e{_m{+@qcIikN^8?CH;^8JL`Y^-&z0T|IYd!|995^_`kFM$N!!6KmPBm z|M7o+ou&Wre`o!V|2yk{{NGvsT5F^*{dauhH~B{_m{+@qcIi zkN-RCfBfHB|KtD8`XB#y*8ljwv;N2b{q>#x$N!!6KmPBm|M7ok{g3}U>wo;;S^wkz z&iWt!ch>*-zrQxr|MT5F^*{datpD+UXZ?@=JL`Y^-&z0T|IYd!|M%CG`XB#y z*8ljwv;N2bo%KKd@2vmve`o!V|2yk{{NGvs*-zq9_w|DE+e z{_m{+@qcIikN-RCfBfHH&+32t-&z0T|IYd!|995^_`kFM$N!!6KmPBm|M7ok{g40q zYhnG5|2yk{{NGvsT5F^*{datpD+Ue;uv=@qcIikN-RCfBfHB z|KtD8`XB#y*8ljwv;N2bo%KKd@2|o2KmPBm|M7ok{g3}U>wo;;S^wkz&iWt!ch>*- zzq9`QzxaRg|Kk6}|BL?@|1bVu{J;2r@&Drg#s7={7ymE*U;MxLfARm~|Hc1{{}=x+ z{$KpR_zxjXj|K|VA|C|3e|8M@^ z{J;5s^Z(}m&HtPKH~(+`-~7M%fAjz5|IPoK|2O|{{@?t+`G52O=KszAoBuceZ~ou> zzxjXj|K|VA|C|3e|8M@^{J;5s^Z(}m&HtPKH~(+`-~7M%fAjz5|IPoK|2O|{{@?t+ z`G52O=KszAoBuceZ~ou>zxjXj|K|VA|C|3e|8M@^{J;5s^Z(}m&HtPKH~(+`-~7M% zfAjz5|IPoK|2O|{{@?t+`G52O=KszAoBuceZ~ou>zxjXj|K|VA|C|3e|8M@^{J;5s z^Z(}m&HtPKH~(+`-~7M%fAjz5|IPoK|2O|{{@?t+`G52O=KszAoBuceZ~ou>zxjXj z|K|VA|C|3e|8M@^{J;5s^Z(}m&HtPKH~(+`-~7M%fAjz5|IPoK|2O|{{@?t+`G52O z=KszAoBuceZ~ou>zxjXj|K|VA|C|3e|8M@^{J;5s^Z(}m&HtPKH~(+`-~7M%fAjz5 z|IPoK|2O|{{@?t+`G52O=KszAoBuceZ~ou>zxjXj|K|VA|C|3e|8M@^{J;5s^Z(}m z&HtPKH~(+`-~7M%fAjz5|IPoK|2O|{{@?t+`G52O=KszAoBuceZ~ou>&-~B)&-~B) z&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B) z&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B) z&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B) z&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B) z&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B) z&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B) z&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B) z&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B) z&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B) z&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)&-~B)FZ?h3FZ?h3FZ?h3FZ?h3 zFZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3 zFZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3 zFZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3 zFZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3 zFZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3 zFZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3 zFZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3 zFZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3 zFZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3 zFZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h35B>-Lga5(*;D7Kx_#gZa{s;eq|H1#@ zfABx}AN&vg2mgcr!T;cY@IUw;{15&I|AYU*|KNY{KlmT~5B>-Lga5(*;D7Kx_#gZa z{s;eq|H1#@fABx}AN&vg2mgcr!T;cY@IUw;{15&I|AYU*|KNY{KlmT~5B>-Lga5(* z;D7Kx_#gZa{s;eq|H1#@fABx}AN&vg2mgcr!T;cY@IUw;{15&I|AYU*|KNY{KlmT~ z5B>-Lga5(*;D7Kx_#gZa{s;eq|H1#@fABx}AN&vg2mgcr!T;cY@IUw;{15&I|AYU* z|KNY{KlmT~5B>-Lga5(*;D7Kx_#gZa{s;eq|H1#@fABx}AN&vg2mgcr!T;cY@IUw; z{15&I|AYU*|KNY{KlmT~5B>-Lga5(*;D7Kx_#gZa{s;eq|H1#@fABx}AN&vg2mgcr z!T;cY@IUw;{15&I|AYU*|KNY{KlmT~5B>-Lga5(*;D7Kx_#gZa{s;eq|H1#@fABx} zAN&vg2mgcr!T;cY@IUw;{15&I|AYU*|KNY{KlmT~5B>-Lga5(*;D7Kx_#gZa{s;eq z|H1#@fABx}AN&vg2mgcr!T;cY@IUw;{15&I|AYU*|KNY{KlmT~ul%q4ul%q4ul%q4 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඲T4a$M~X|Mt(acLUKiL+_XOBs=I)uX}a8mBF5|*whJDy)@(cY!22{ ztA9-O9mqeb92dIqj!r8cZ&moIlsY=lsYddG_S)a$jXK0(9lLT%%{FR{(=tnjFy zw(Oq>w$=CC!S&fcqy6ps?Vd>gYM<4qtf+752W_g0em9&^bA5$=x~s<5XF&BdxC5)R zN@w0Vt)vTsy99mK)Gd%7kYx3pLi+DjYL0{FdgI9Ip4IUkRIZUbgc^nGp0Aqv7H6RB zU@Y$g{>*sn6DN}W;3qJA>9PE~-0(c=(aaId{ZYU7hG3MMH%i+3UhzI;2)?T7XFq(^ z*gZz_?@Rt@{~vxPuc|u#G6?&|_bPwn&wLp_lh^N~;p6tik;-E~Zi73k{n+utB5yFh z4UQfkq}d;HOn;9aC&rl8qt_pg3&ef|(@pirJbDP(X+9Vo(|@k<{>z=WJ~%7E>tp!m z{~6KRY|h^!?E?(>ahCKyI!SiJs4B%i`R9N0XarBZVX&(*?5g_ zYS!WU>OUzD?n$Z>)uFfR6?Ju0(#@tm5b9pkpOTt8&qW3AUZb8tXu=s}Fb-RVIs3Wy zbitYE-&L|4O8)hJ?$`=!*Y9W0m+DO&DYag>|Mkc7o}iw=XoDV~{Q#<#?v=};na#Ee z>gq82rbc^bgq}GTYE>3mgFdVM`-}JXqy7si{ocWzbAiX>P44xD=15=trzLfE)t2<9 zdi2g}pwIqWgxBXtzq)5{*ZO_)JlEt2(R-HAM}a9e)4Xdlw%+%acdT`v-Vu7DcMWj0 z#QCaoVroq$t7bE7qwWENpB8wtRqJffuBzH`r7(4Vj9F=Uciz#q>b|Rwi$9hgsns8c z^W2}ier6e7mHJ9#5A;5$?S$DB)IRE`6xN1XWLXaNU?0_dz4yMs7XDeQW#bif+}OOp zf23{E8_&XA6N5JD=V0oZU=IhaybD=978cZw8jV}3dH=ZT_WiT!wf4@c8c&brZS2qA z*is!$t~j-;Op7DpU31>?NUPbY*|2zm$5b5w61CLooVi-9+49aAW3WGQ_Nd=hU4MLb zzxO%kKYnNCUJmzzZv3&D{>bP2PqO28-t`A6z3Z(J**`>N|7go@e8%hZ$d>iTds{@t zUrV{^JpVZ&JC4ZS9+ABxB70{@*^KZ$$P!Th>2E@3&?Bb95Gw zeIO#^uY%n4{CzMY>qKN9ipV}3k^Sp1W6iP_Ug52UJ!36+pWhhw*21oL-F{@W%}Dld z5!pv=*^SS7eO}nIyYzS;8)n{m9U1SwVHTd}$0M@Ki0t1ZvQI>0|1r$M^Xv|@@H{^m zk$q~Ih5P*UFbns26_NetFbns|ovD9q8h!T95oao#_0Lf+BI7=K({`Va$i5JfT}Nd9 z6_I^0B2zUXTo?YtSPS0q%ILcAhu(E-uM0Y}*9D!~>jFoST4S#Zx}Ci)=*(UhbY`y$ zIw?bgbwOwEjTp~f7xX!@*9G0D zy)Nj?UKey`uM0Y}*9D!~>w?bgbwOwLx}Y_11>Lch-H>@&m6IPW*~IkMLU-OgSYbVh4SUC^1mF6hi&7j$N?3p%sc1)bUJg3kQ9 z;O%?=ec{h;vKIb0P#28jb(hZkx)9F%x?p5CI2t2$!N~f32I@lp-0AWBx)5&X*M)HA z*M)HA*M)HA*M)Evs0$4>R+5MrZFCX5l{lx)9C+bzx+leqAuy z_0LhDE{wGE>w?j)fByWs5YGI%5YGI%5YGI%5YGI%5YGI%5YFs%p?#CJuoqDmbY`y$ zIw?bgbwOwLx}YWR4l}dX=*(Uh^f|KE1>Np!*r$2F)tS96=*(UhbY`y$ zIGJ9RnecJ1S&X}#Gp6JY87j)LI3tao&dd|F7U70ujGPD*R&*yjE^*WV4^#Osg z%?nPbatFgyC>zj2Pt-|RCMkv%9PyCovyzgORMyaz{Q4~fWbjmX9$vWG@w4~xhi z9+AZ(vPVQ@kF;g|bMz=%*1ska5!r1K*`p(}+at1MME017?6DEq7Wd3*pSK3*pSK3*n3_(Y!xqe$QJAe`Tx%{rvas-dgzc zz*-1r{#poU{#r0IhO#^(nd1f5f|>bi!R%Ad(_ahW%wG%P%wG%P%wG%PEVvfTdHQQ1 z+|FMM;Vifo%s&0K5N_wMg>V*J3ud4GS}@z`bL6iDGt`0LPG(Chs5*0o?}!L?v!!L^_>p13S)!E6^?3uYEv3uYEv z3uYEv3uYEv3uYEv3uYEv3uYEv3uYEv3uYEv3uYEv3uYEv3uYEv3uYEv3p&&1D7Y5P zcD>PiYQHY%>m|4r%yz-GU}nL!U}nL!U}nL!U}nL!U}pWbz_stKlm7d{--On}-$3g9NFI&bUXX|g3j#k3p%sEFX+tv zzM!*Y#4)qKFX(oUjcDh;FX*-KW_4xWI0&tUr+aH*!Mkps8R*Zi0l;+*()QmN<{Xmi0su7*=t6#%)Yl4{x-B0e%M};4_k$eQG#Q z{jO(b!Dl2h3qB*6S@0Rj%!1EII@9MU_>5$>>ka1_z9xdtNM^g>Gm@DFpOMTg_>5#` z!Dl2h`(ngA`_D-Ff1A$#PW7GMTKK!rTKG9{E$|*;Sqo+sTnlCvTnjqm3COY*%yz-G zU}nL!U}nL!U}nL!U}nL!U}nL!U}nL!U}nL!U}m2ToF6j_t_3p-t_3p-t_3p-t_3p- zt_7Xxa}-<)X1m~8FtgxVFtgxVFtgxVFtgxVFtgxVFtgxVFtgxV&~@SOLu)~=t-tZE z+qVYJotgbZMD~wf79Xs;{yqFbUY|!1?cO%b%zLAr%Rdb>^M0hWe~!qGBeJ(gWbcT` zxR3OICKsOP$uJAg^IZ|yyN6l0&-Vno6H)e_zm<{e3}a_V8J+(PbDsA11>LR?(a!$9pxfEs7j*W5h(7J_3%Z^CeL-h0isPC*MiRMYe8rBwV*TmTF{w& zE$Hl35!bGLE$DXrwJ>;Y$?&{SUEoP4(BCGX-+9;Hsnq!I&bl6&8STygc6YVg6|7v7JOeYv*7!JnFZe$%&Z$R&re2VpBiT2 zeGk4bnBxWC7tAd9zF=m-_XVBla}<1EFxv&+7tAd9zF=m-_XRTxzAu&)GFFkf7Dn39t0jAg*Jt2;VWeH)ePJXE zyf2Jof%k=x?ASUNy?<|y$lei=1>P4%#tXbJjAViLg^?`qzA%yn-WNu)!27~T7IRig^|pEU(ox5#`pN^Pk@EOT$7koxCv*0t5&h$A7J|mg!g3m~1 z#y#779=Ksw?bgbwOwLx}dXnMeLuw zF6eglx}Yw?ZY5y#A47j!#&UC^1m zE}R={!K@2^6j2v+X0Ho6v)6@(Mbrh|&R!REX0Ho6v)2Wk+3SMN{(X3y!~1Tp3%Z@X zF6hi&7j$N?3p%sc1)bUJg3j!9L1+It+&}L=*S}Be>@&m6+;@GB>~%r6v)2Wk+3SMN z>~%qB_PU@mdtK0(y)Nj?UKe!c*9C9iGrBJD6l6Une;lX_=JC>*Ul+oeUl+{m;eonf zX1dQnUFe@Xo%wYk+|I8H;mofK;mofK;mofK;Ve)WM&{|)g>bufMI7sQ4>R++)%*UQ zVHWPwuM6QUP!~q#>DL9bojyl_x-intuM1{7Jx{+bgfqV`gfqV`gfqV`gfqV`gfqV` zgfn|x;O=fc&U+DcL1*^5pfh`2h)2`~-OgSYbY`y$I~%qBpRt~IeU3hB&2+n7L}srGx}Ci)=*(UhbY`y$ zIM?};G-OgSYbY`y$I_PU@mdtK0( zy)Nj?UKe!M8SaC5f7hA4F6hi&7j$N?3;a~aI@X^?)CHZ{>w?bgb>Wc_bwRhY*9D!~ z>w?bgbwOwLx}YWNBoA$|5%}q1z@;A-6>MfZ*M|`(r`aC2evfCoEM@MA0M`X!}>@gA9VeqAs#j^fDwX4}m4c)_}0j_22f za67*)gfqV`gfqV`gfqV`gtK5>Fz4ylg>XB+E`+mST`>Ff>q5AlUl+nzur8Q=`gOr< zr_YgJ7tBnbBfl<$v(H6bOP`O({JIeC)2|ER%&!aK%&!aKEcm|g``%jMDjLpFKaVYV zYvIoWYayKZYayKZYr)L8YmBS~Gt=V**Md10e=UUD`D-DZ`D-DZ`D-DZ`D-DZ1=oT( zPk$|h+xcrDoCVi{*{8o2!tMOE5YB>Y!R*st3uZfgj{LP?X8IiYYayKZYayKZYayKZ zYayKZYayKZYayKZYayHk*MhDKTq%LIpzj^8x2^><3$6t-3$6v7Q6(*F!E6^?3uYEv z3uYEv3ueY1j?{ZI3$6t-3$6t-3$6t-3$6t-3$6t-3$6t-3$6t-3$6t-3$6t-3$6v7 z>2nlZ3ue3ES}?QEMI7tkS}@xM*MgY^*MgY^*MgY^*MgbZ-xs(V0`pIE#op}i{+pTo zec{{H_5M`*`vOmeH+^5w?d{Sujt0S`4jAog?^40>?e)RJjZ}QfHmm2TR`kBkjg3m~17JNq18LQaxjAXV8 zJ|me~@EOU>J|3|Tml0X;8OiK3_>5#`!Dl2h3qB*6S@0Rj%!1EIX7=fbeF#1yneBql zNM;s%M$(x+N5N+#vt95R$;>_%G0)&LlG!f!jAUlPXCyNVJ|me~@EOU>?jN0J=EPeI zoVn4@Z~VNs7XI41ZuwtmjMOeO3$6t-3$6v7QMD{V_{X<0dkJgN* zG}gkAHPhq0ZJ6onL9b<<{nIcD&-0%nvg3&C?Gf2KBC>Z5v+z8*$Bf)R%zb!QME34s z7Vh&s!z{e-rxDqEhgrDK_YE_%PkoNwKg`1I&W4%UPM@O>L}dRGk$o^C>qKN9ipV}3 zk^Spv)_-64RA?>S%@upIzx!`y&l>Ho|DI7(*ZWiNwPqKTh&yoFoLASHN zFX+tvzMwPv`-0A18F4%-5!tIEvR6lBuhALTzPFw;->a_78=t<(T6lwZ-9FRWpU&)S zL1*^0@FfvzLASH71)bT~g3jz~L1*^0pfmeg(3yQL=*+$rbY@=*IU7JOeYv*7!J&c0NQ82bM%G}{H=7tAd9zF=m-_XRTxzAuE{x&PwrSF&s}B~d|xoL;QNA^1>YBRrq5CE zeZg!Od|xoL;QNA^1>YCU>GG5?) zVI&K@FN|b?_l1!x@V+pT1>P4%vcUVoNcO(rKIrEJeQxzR3cN3jv^%r5)7Q%fBC>yp z$UYd6bt1CB`@+aP1MdqXng70^&mYet)CGDQj-@|mjj|U0Ag~s~nZFjonZFjyjH*2H z?rdgyyx>|e=i;x0a65l3gfo9Fgfo9Fgfo9FgtOpUFz4y7g>XB6Erhe+S}^1s>+Dgd`2?c1)q_0##5-}8OdxHd`2>}`y%ENd`2?c z1)q`3EclFMW}k={FZhgPwhKNZnOX1|$;^V!NM`owh`9uxk<50%XCyNVJ|pQ&pQGS2 zlG!f!jAUlPXCyPD?u(+lpGS`;Q>~%qB_PW4Z*G=n!ZfCCxI{T-HdD`oOZfCCxImt%w89C zJ9}NwnY}KA|L(tDKYtWa7j$N?3p%scg_lRv1>MeG7j$N?3;z$_?jC<>b)EOc^8FUy zCE5N&pxcPcIWyd`W(1yLm_aiFW(JIegrug8QVMP8JxN=)7EPf|(xy>ksjXzE=`FTe zw@J&L5Bfu*HRXPt8^gd45FoH@38NW?;o|PIpOd|w_gT+b{VV-_z4p7`v-fkpYYjSJ z-V0#f3t-+0V1G8wfPD$(y#VID0Oq{_=Dh&sy#VID0Cr@&L;o(D^VuF9jNcDuG{a1=-K!Ht_;~USQ#c%)$%IE)_2@!bw`gycfW{7r?w1z`Pg0 zycfW{7r?w1z`Pg0ycfW{7r+h_=XY>0b`9#h7jQ=23#jv60P|h|^Iib+UI1Gg@4(;h zVBQO0-V0#f3*T9hg*yu`fO#)~c`xiKyns6I1u*XgFz*F0?*%aL1u*XgFz*F0?*%aL z1u*XgFz*F0?*%aL1u*XgoRRke>WXIE3$uP?5s=2MYGVf_Is zf_%N@* zUe7}Nww?Zc{+XVIyB3swzW~QF3om3AUSM{4U$dELdEc4gdFlmzUU(tbg%>glFJu;8 z$Sk~&nRR?5A=gC~GSe*ZnvsQE7g@+mv%qUc7Pt;)6j@+~Gm0!^7FozF zvXEJ1A+yLrW|4)=A`6*m7Jjd1q1_b80=_%GZnMBlv%pNVz%0CgJu=fQFw-nB(=0I4 zEHKk7Fw-nB(=0I4EHKk7Fw-nB(=0I4EHKk7Fw-nB(=0I4EHKk7Fgsq{GtC0mX%?7i z7MN)knE833-3Hksdh{&DN3t)a;06V#O_fraXYQbg&n-|Q_3pk?h zTMBkT!L}Amf4_jw6MNZK)NL==MV8^+zhD`jx1(S?EyEdIZ5h_Q*fQ+pnu1+x8P?oY zu-ygQV;S~xon?4ln~M0~XlCD9+{}5i{q*niFSft0TmQ`krP&$%3^VPKm}!p$)>2^p zw|QLm^4JUJ9cJ1iG1DH2nf6G`v`1p5JrXnRk(gnGwqSMPJ1L~+9NU39*LRG3wwJO+R4ct|549EPfE%H zGtB}s%>vk`Z5Fsrv%pNVz)Z8iOtZjDv%pNVz)Z8iOtZjDv%pNV!0ff+j5G^er&(a8 zSpdTsX%@Inv%pNVz)Z8iOtZjDv%pNVz)Z8iOtSzlw9}E#`HMXZclUdHZ^7;>*!`9* zrkDKNe}3MBqmI3Ry*xA+e~*DZT(Cz9_GrN#E7;?M<$XReSl(y*{$V#S9gKI_H&|YC z|6qB|0|h%cSYGqV!FWxa(V@X|-BW{c9p2$^!JaPIGX+~K*s}$Du3*nk*>YZJ-$v8F z&(Yt0b#ZSuTw&P_ooy`GCd;nsb6*GCa@E z3wYk;sB2fh=Xv%w7dO-V_8)iiYeg1rEV2N$$?oy9MHW!!vjFC^fPJ&n=#qe*4dBzC7ySW@fJp z1~)U)d4ZYE3(Rz0V5ai|Go2Th>Ab+Kb)=KbGShj1*=t2zJHeG}>b$^f>bwAkGtzm1 z>vUdVrt<=`u!i34Z zFkvz;OxTllpE#pKgZ1yENuA6K6Lxsiq4(gs^mM^wUYM-8R@BM7FsYMyVZvfw!26xw zv(Q!>&%)nZ4qFy(k}PBvS;#E1!0bfH0y8{Mv%q_aEabY#LS~VL%pwb!MHVvCEbu-f z3%M?`keOzI*NiOWy2wIingw1nvcPpXBh3QWJyqP7$O5m4vyCid7Foz_tv~PQTR+p; z{Nr>#BMZ4MvXEJ10hw*RR`Wuu81IJO8=V_0` zOnW3|+9NU39*LRuNX)cHV)ni9?l>Q2+9NU39*LRuNX%Xv&&y||JrdVxkHk!SBru$j z_DEc(JrXnRk(gAiq8y%)e9F5ba=0d?LBVBQO0-V0#f3t)STeR?mT&U*pO zdjZUQ0nB>=XXL$rI`0KA?*%aL1u*Xgu(ke~sTaV!7r?w1z`Pf}O%t3}g%>glFJu;8$V|L2*=Kkm*X=Dn>wSZDUryHCKUiKf zypWlAVX|g;f$MNa;)O|Fc!BG1M&X6b!V8&&7czqv{?h1?@V$fQg%@&Ncp)?Ih4$@k zKj+&EFMxS3fO#)`xbOn%ycfW{7r?w1z`Pg0ycfW{7r?w1z`Pg0ycfW{7r?w1z`Pg0 zycfWZ+H=Ply>1!mjup&%0d?LBVBQO0-V0#f3t-+0VBQPu*4lTwqwoTl_X3#r!n(o> zsPkR`^Iib+UI6o60P|h|^Iib+UI6o60P|h|^Iib+UI05#oZrF0*fps0Ucec7FQCqQ z0nB>=%zFXMdjV{1yaRu~gLyB2c`tx@FSLKvvCq1*@B*0k0+{#0M+z^X&U*pOdjZUQ z0nB>=%zFXMdjZUQ0nB>=%zFXMdjZUQ0nB>=%zFXMdjV(Uy@0x7#eI3BV8;vQy@2O= zFMxS3fO#)~ExpjLe$Use{kQw}Z>sI~$sQN~lCbKx|IFT3)SY12NsA|LIMK2%b=Lkx zZ}M+z{Ji%6Udh_Tx01CEcqMBc!AjP?Ay=|?V^*^EmMhuG1v{l+rxt8huzA7OTZS{* zU>VNnw1S;purmsFX2H%9_VIo{f4kq8vxV_}Ij3Of4u*fr{Qbph^Y(2!{rmi?{;aze zl>Rr{a4fU%LT2FwJnQbhX1fWK{|!d2Q!nuI!V9@BypUOVf%k&<3oqol@Iq$l1>R?P zA=iZ$GE*<`n&E|97hcFry})aR7q|{*6kcG4&n&!L7ykL;X3pC!8qY#Mc3r=>-{|+Y{VUVtJC0doA=gC~n6+=dNfwyld71@&USuKH zMHVuPEMyj0$SksunP!3a8Cl46k%i1O3%q7zA=gC~GSe*Znvn&r!x=>unBj~f3zog0@ zGz(x~?`vunm}wSxP0a!`%>pyc0yE75GtB}s%>pyc0yE75GtB}s%>o$CNVC9ongwQ> z1!kHBW^WdE&}RYnQ?tNpY8IIJd7<3~*#~;AFE8#``t3h6KQExp&kM7@=847g{JelQ z{k#C?=LIl7FM#=Z0nE<}V18ZzYaRE*rTm$-4rC>3-^eRjyP+#t`&3u5dBN6OhBNZ> z0^aX5TL@0a6ya0B#F#fFlynwox7rHZMe{XR!=k421)@$px+4(E| z-dgPZ~q3e^4)h7Y^P;7qpK~$=XtSZ*vmBqyVf$SxvOBig&_;5+arwcZtDkEelC|! z*=#Y-y5IgUp0et<|ID;UVx~P3Sovlh2Fm z+PB0?racnZX^+HAdn9JsBQet+iJA6D%#MtA$e-uY!T9sU8EKEiYif_gOnW3|+9NT0 z)9&S){l54f2~2w=UQ>G{W;!qI>se?gC;M1*V(Zp_tM5Z+XJ(oOW|{@CW?qv8uG1_q z(=0I4EHKk7Fw-nB(=0I4EHKk7Fw-nB(=0I4EHL{)aekTwuG1`l;fyp3T&G!JrdeR7 zSzxAFVCJ)c`>9#rI?Vzz%>um8PDegVz*2n>u?T-3-)xuo+;RxF!V@x-m}8^?mkzr=cjBrFSKu?>EGw*xxTu%x9PY4U^jHO z(bj=&vh4I;=jR2~`FR1%&kNY+RrYzP+gz|M1@rR)*4$du`FR0#+lo3rFQCrP3wU=w zFQ9Hm@w}au;f(yefHnQRfW2H}*95!PGOW3)V7rAO3#i*8jPLGsmZ5GrFSM)Q^E~@o zi<@bF`;Ya$R%GGEA`4(X3uhErK%LJ5n9l9c@3p9L_V1u&llFrNi5p9QdO_I$v$ z7tCh?@9wjJHGLMqd=_vvKW|4)=A`8q;lq@jA^E3;*m&ih{i!5XoS;#E1kXd9Q zGtC0;GqRBDA`6*m7I@9bLavJ}WTsi*H6sgLhcnVFa9w19>u`RDi@OwA$aRs0%+`uE zBMZ4MvXEJ10lB=aXQAD`@hrIO=X(}z?)UbEf@zP$buZbv_xClwV;P>OJrb{}JrXnR zk(g=%zFWBt@zBm7f|QD0Oq~WZqaxad*Rl?3t-+0VBQNKEWCg^?*%aL1u*XgFz*F0 z?*%aL1u*XgubW)^zWTHz8k@g4#ww>GxAbw`gycfW{ z7r?w1z`Pg0ycgQHpMAGG3NL_pFMxS3tSh{LI`0KA?*%aL1u*XgFz*F0?*%aL1u*Xg zFz*F0?*%aL1+W9f`5hdLU4uIB1)P!h0_waMz`Pg0ycfW{7r@rWJMi~AnD+vh_X3#r zLOWIatUC)YfO#)~c`tmV@B-?*7r?w1z`Pg0ycfW{7r?w1z`Pg0ycfW{7r?w1z`Pg0 zycfW{7r?w1a7Nw>s5@5Nmp2M_ykOo7c%JtHnD+vh_X60`3+?Lne9iv);%3g<{_j?@)&Z|%ts_{;+Bf7% z)^5y7*4}a@JGo$|6ztT3%?dUz*m}!wMjI@{8J$+J(+hS+!Okq$Sp_@0VCNL<+`+mp zXaBfop;>$S_xWdg7VcWm^xJ=C;RX1YS$Kh2y9ty34F)qjPrblvh8J>OcpQDz3tWdYiYze0 z8ATQ{i!5XoS;#E1kXd9Qv&cebk%i1O3-H4Jlm&cu{I1OcGtB}s%>r06y~zUCX%?7i z7MN)km}wT6X%?7i7MN)km}wT6X%?7i7MN)km}wT6X%?7i7Qk>ungy=YEHKk7Fw-nB z(=0H1OTLepX%?7i7MS^Y;Xuj)dh{?(^=LI~^&kJCFUI6p+ z0+^o{!2G-b=H~@4KQDm!c>%0-AZC^TYv0H#S-YVtS^HF1GCwcid468N8Eq)m^z#DN zJiVy%^8)Jpya0AqvF6zYJEvghPTB15FK*`iV9G*UqxDE%>G$@EY0Vt$`pz~MY?Ec} zWLg%kvgP|AJ+B_ZX&KJw zYRjny|bE}yd5|FgK6^Cwdl{?qpNRlogbraclf z?UBIRH<9U)xK4W{X4)e$(;kW0ccX^+HAdn9JsBQet+iJA6D%(O>h zc4WLe=0|2n2SYy%hBMM0iR-jSVx~P3GwqR>X^+HAdn9JsBQet+iJ8s|ZBl4iIFz!m zFw?5v{xj1oFw-o6HS?M*aGhp>nP!2RW`UVzfthB3nP!2RW`UVzfthB3nP!2RW`UVz zf!UF9-;o7onguYNk!FGGGz-i$3(PbN%rpzkGz-i$3(PbN%rpz|!c!>=$j{yV-rhSt zOaATvyRTsPTh=} (=!dkeO2 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 zFZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3FZ?h3 zFZ?h3FZ?h3FZ?h3ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4 zul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4 zul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4 zul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4 zul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4 zul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4 zul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4 zul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4 zul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4 zul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4ul%q4 zul%q4Z~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~Slk zZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~Slk zZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~Slk zZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~Slk zZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~Slk zZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~Slk zZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~SlkZ~Slk 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 z{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC z{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC z{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC z{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC z{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;aC{s;a?{zv{t{zv{t z{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t z{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t z{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t z{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t z{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t z{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t z{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t z{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t z{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t z{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{t{zv{N{wMw?{wMw?{wMw?{wMw? z{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw? z{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw? z{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw? z{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw? z{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw? z{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw? z{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw? z{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw? z{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMw? z{wMw?{wMw?{wMw?{wMw?{wMw?{wMw?{wMxt{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY z{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY z{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY z{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY z{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY z{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY z{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY z{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY z{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY z{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY{%8JY z{%8JY{%8JY{%8JY{%8JY{%8L8{O|eS^S|eR&;OqPJ^y?D_x$hq-}Arcf6xD(|2_YE z{`dUv`QP)u=YP-tp8q}nd;a(Q@A=>Jzvq9?|DOLn|9k%T{O|eS^S|eR&;OqPJ^y?D z_x$hq-}Arcf6xD(|2_YE{`dUv`QP)u=YP-tp8q}nd;a(Q@A=>Jzvq9?|DOLn|9k%T z{O|eS^S|eR&;OqPJ^y?D_x$hq-}Arcf6xD(|2_YE{`dUv`QP)u=YP-tp8q}nd;a(Q z@A=>Jzvq9?|DOLn|9k%T{O|eS^S|eR&;OqPJ^y?D_x$hq-}Arcf6xD(|2_YE{`dUv z`QP)u=YP-tp8q}nd;a(Q@A=>Jzvq9?|DOLn|9k%T{O|eS^S|eR&;OqPJ^y?D_x$hq z-}Arcf6xD(|NZ}~>5g$H*pd4H?+ON-C|0N|*v%%3WJor<&!8(Pks?Kc5~t2|AzeT> zkfOcFns4}dfrr^&ZBUq+_vZ)hpnCXU_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6 z_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6 z_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6 z_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6 z_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_+R*6_#gZa{s;eq|H1#@ zfABx}AN&vg2mgcr!T;cY@IUw;{15&I|AYU*|KNY{KlmT~5B>-Lga5(*;D7Kx_#gZa z{s;eq|H1#@fABx}AN&vg2mgcr!T;cY@IUw;{15&I|AYU*|KNY{KlmT~5B>-Lga5(* z;D7Kx_#gZa{s;eq|H1#@fABx}AN&vg2mgcr!T;cY@IUw;{15&I|AYU*|KNY{KlmT~ z5B>-Lga5(*;D7Kx_#gZa{s;eq|H1#@fABx}AN&vg2mgcr!T;cY@IUw;{15&I|AYU* z|KNY{KlmT~5B>-Lga5(*;D7Kx_#gZa{s;eq|H1#@fABx}AN&vg2mgcr!T;cY@IUw; z{15&I|AYU*|KNY{KlmT~5B>-Lga5(*;D7Kx_#gZa{s;eq|H1#@fABx}AN&vg2mgcr z!T;cY@IUw;{15&I|AYU*|KNY{KlmT~5B>-Lga5(*;D7Kx_#gZa{s;eq|H1#@fABx} zAN&vg2mgcr!T;cY@IUw;{15&I|AYU*|KNY{KlmT~5B>-Lga5(*;D7Kx_#gZa{s;eq z|H1#@fABx}AN&vg2mgcr!T;cY@IUw;{15&I|AYU*|KNY{KlmT~5B>-Lga5(*;D7Kx z_+R;7`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8 z`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8 z`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8 z`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8 z`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8 z`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8 z`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8 z`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8 z`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8 z`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`Cs{8`QP~8_}}>7 z_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7 z_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7 z_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7 z_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7 z_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7 z_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7 z_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7 z_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7 z_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7 z_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}>7_}}^8`QQ29`QQ29`QQ29 z`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29 z`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29 z`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29 z`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29 z`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29 z`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29 z`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29 z`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29 z`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29 z`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`QQ29`9JtS_&@kR_&@kR_&@kR_&@kR_&@kR z_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR z_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR z_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR z_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR z_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR_&@kR 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 zH~(+`-~7M%fAjz5|IPoK|2O|{{@?t+`G52O=KszAoBuceZ~ou>zxjXj|K|VA|C|3e z|8M@^{J;5s^Z(}m&HtPKH~(+`-~7M%fAjz5|IPoK|2O|{{@?t+`G52O=KszAoBuce zZ~ou>zxjXj|K|VA|C|3e|8M@^{J;5s^Z(}m&HtPKH~(+`-~7M%fAjz5|IPoK|2O|{ z{@?t+`G52O=KszAoBuceZ~ou>zxjXj|K|VA|C|3e|8M@^{J;5s^Z(}m&HtPKH~(+` z-~7M%fAjz5|IPoK|2O|{{@?t+`G52O=KszAoBuceZ~ou>zxjXj|K|VA|C|3e|8M@^ z{J;5s^Z(}m&HtPKH~(+`-~7M%fAjz5|IPoK|2O|{{@?t+`G52O=KszAoBuceZ~ou> zzxjXj|K|VA|C|3e|8M@^{J;5s^Z(}m&HtPKH~(+`-~7M%fAjz5|IPoK|2O|{{@?u1 z{LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ) z{LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ) z{LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ) z{LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ) z{LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ) z{LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ) z{LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ) z{LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ) z{LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ) z{LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlQ){LlO^{4e}3{4e}3 z{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3 z{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3 z{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3 z{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3 z{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3 z{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3 z{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3 z{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3 z{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3 z{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e}3{4e|u{s;eq|H1#@fABx}AN&vg z2mgcr!T;cY@IUw;{15&I|AYU*|KNY{KlmT~5B>-Lga5(*;D7Kx_#gZa{s;eq|H1#@ zfABx}AN&vg2mgcr!T;cY@IUw;{15&I|AYU*|KNY{KlmT~5B>-Lga5(*;D7Kx_#gZa z{s;eq|H1#@fABx}AN&vg2mgcr!T;cY@IUw;{15&I|AYU*|KNY{KlmT~5B>-Lga5(* z;D7Kx_#gZa{s;eq|H1#@fABx}AN&vg2mgcr!T;cY@IUw;{15&I|AYU*|KNY{KlmT~ z5B>-Lga5(*;D7Kx_#gZa{s;eq|H1#@fABx}AN&vg2mgcr!T;cY@IUw;{15&I|AYU* z|KNY{KlmT~5B>-Lga5(*;D7Kx_#gZa{s;eq|H1#@fABx}AN&vg2mgcr!T;cY@IUw; z{15&I|AYU*|KNY{KlmT~5B>-Lga5(*;D7Kx_#gZa{s;eq|H1#@fABx}AN&vg2mgcr z!T;cY@IUw;{15&I|AYU*|KNY{KlmT~5B>-Lga5(*;D7Kx_#gZa{s;eq|H1#@fABx} zAN&vg2mgcr!T;cY@IUw;{15&I|AYU*|KNY{KlmT~5B>-Lga5(*;D7Kx_#gbQ{IC44 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 From 98cff12fd1f625d826c6d4e6eb4da549978b661f Mon Sep 17 00:00:00 2001 From: Benjamin Odom Date: Mon, 3 Jun 2024 21:18:09 -0500 Subject: [PATCH 026/129] [QoL] Add Arena Info Flyout for Weather, Terrain, etc. (#1734) * Initial Commit * Add Time of Day Icons and Remove Fight UI Dependancy * Rename to Match * Update battle-scene.ts * Add Settings * Add Comments --- public/images/ui/dawn_icon.png | Bin 0 -> 581 bytes public/images/ui/day_icon.png | Bin 0 -> 593 bytes public/images/ui/dusk_icon.png | Bin 0 -> 534 bytes public/images/ui/legacy/dawn_icon.png | Bin 0 -> 327 bytes public/images/ui/legacy/day_icon.png | Bin 0 -> 285 bytes public/images/ui/legacy/dusk_icon.png | Bin 0 -> 300 bytes public/images/ui/legacy/night_icon.png | Bin 0 -> 288 bytes public/images/ui/night_icon.png | Bin 0 -> 686 bytes src/battle-scene.ts | 15 + src/field/arena-events.ts | 31 +- src/field/arena.ts | 12 +- src/loading-scene.ts | 5 + src/system/settings/settings.ts | 11 + src/ui-inputs.ts | 12 +- src/ui/arena-flyout.ts | 384 +++++++++++++++++++++++++ src/ui/ui.ts | 15 + 16 files changed, 472 insertions(+), 13 deletions(-) create mode 100644 public/images/ui/dawn_icon.png create mode 100644 public/images/ui/day_icon.png create mode 100644 public/images/ui/dusk_icon.png create mode 100644 public/images/ui/legacy/dawn_icon.png create mode 100644 public/images/ui/legacy/day_icon.png create mode 100644 public/images/ui/legacy/dusk_icon.png create mode 100644 public/images/ui/legacy/night_icon.png create mode 100644 public/images/ui/night_icon.png create mode 100644 src/ui/arena-flyout.ts diff --git a/public/images/ui/dawn_icon.png b/public/images/ui/dawn_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e04e6024aa0b097dff236c4b4a1ca56e24bd4531 GIT binary patch literal 581 zcmV-L0=oT)P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0oqAKK~y+Tjgw7F z+)xyT-z3KIYf#5lXmn6ysHiY4rh?E`L2PFyYFF*bh4TYusNk>AAK=EFZrluX;ihf` z1(niL3$4;BiYZmckCExL(ZrmSA2^(Ia_4>TxpNZL^A#r@6w^~U;zcVG<9rvtKhI!m^CQgsn#9(Ms+}HZe|Bkp5;l z?D7Z;eB|sVj34tXc1T;M;l9cQAI1Bag46iy?X37hBJJ$XACoZf2^!sk5V`=D1CXre ThUT}f00000NkvXXu0mjfz();` literal 0 HcmV?d00001 diff --git a/public/images/ui/day_icon.png b/public/images/ui/day_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..fe41acffd9cfb2096a87a86cadbbc7f57ea136a8 GIT binary patch literal 593 zcmV-X0Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!T|WK~y+TV`M-B ztSZKZK&lVI!vICU@0{(2vcUju0J28!qzndI{}2!p4i0Zz!?1PU3@8s_5ZC~iX6L9B z2G{TikPuF=d*up-z02l+m|#PgK`dq=spIIHRrx+L_(&aP&=;4!@HjxU~y#~ z9fto*Yz+4=pL75B?s+3K)OPpC(q;yB4i>N?kY?*wa~Zg=En#4Lc#J`k;UmLWAuYHe zqOz(Chu1FyS^S9!$Ok(e8CVLRVc>eTn}PBDTQCN(L1M@Ns;3WR0E4+lAc!If!1RC( zKnKU)nlOA5b7lB1C=A9RHb@K`z-qwlPy7r=#gZAm*q4AYhz%0M22c$6^zkQ@1uTAV>bk`KybV0iKT6T{M@FByKWEnr~y$4~@GiL%E3nKe8a zSQMK{C;?r5t0)5r9S0kT0@ f09+#kFfafBum-cC{w~Yc00000NkvXXu0mjf3PJjV literal 0 HcmV?d00001 diff --git a/public/images/ui/dusk_icon.png b/public/images/ui/dusk_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e848fa313459ce44809049ffe80f8a0ce298da7c GIT binary patch literal 534 zcmV+x0_pvUP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0jo(wK~y+Tjg!kt z!%z^0|J-OmisGeGQ9-*EM2ZVVUA73~6Da5-vnB%CR}p2kH{Vt{ur@=C(bPW6(Vf9i9@dVitK?fa!ot~o5cNzb&O5LAnTt{_I8`Z zN{_#$v(qOw5eAaX)x}(sSbhN3mLW~YSr`-(^9y~#(dk_Bs{hw|P%QU4W8>NHLMEq9 z#M(HZPe>e;dtJ!xd|RlpHY~O)oZz6`>!u@vc64MwYc|U^P&?wFoSqRf9Sk}oP1H0V z>aWhJA>3>#BVC4ZdjE|?tswrcuH*3Cgs6yJl-lL77`>jjlBOe1qm9p5)?WB YKRnE+uEonOR{#J207*qoM6N<$g63c9P5=M^ literal 0 HcmV?d00001 diff --git a/public/images/ui/legacy/dawn_icon.png b/public/images/ui/legacy/dawn_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..eb24a799ab96cdd032ef8a57595bd602482bc62b GIT binary patch literal 327 zcmV-N0l5B&P)Px#|4BqaR5(v#WWWyo;}B;=7e^NWYdloaj-u}J-REF__WDUMakxHY1O7joJQ=R> z&ciQoHp~E+0MsC`UYG$WnqdlwF$7`2;>ipQA5eXWZa`p;D#Q9Iw~@WW2r}URGmvH& zKr#fM3t(XaHsHth5U}=lZ|ot)Kn)?ufPjM6V7<@Yy#v!z;-j#}1hOC~Fu^YHP`8I` zM2}~XLFlmn3k$dbdZM~G(hWp7-Nq5CJlp_FWm~v{ub&_Rz|ALsqA5nt5X@h4={#Kg z_}#;ZbbxFKUgtn1L7GA72%ZioH2`GVe_{=R*$#FBnsdRK370`ojVSssvM1C)qO&sq ZXBK-4;%UQk00000NkvXXu0mjf008%?gY^Ia literal 0 HcmV?d00001 diff --git a/public/images/ui/legacy/day_icon.png b/public/images/ui/legacy/day_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..310ba50dcf306bc4fb559b4a96ae9da8d53bc40b GIT binary patch literal 285 zcmV+&0pk9NP)Px#*GWV{R5(v#WWWNXm0bQ~6MuSd4Lki*gF1_po zv-QNwQM@3Ur2*!@o^}H+etu0q13Vg$4I$`x1_lO@W>8|HjsdXLNUSShwuAIx%}lrq j0%^pVnNUKXgzO9eAn$t--D6GR00000NkvXXu0mjfSW0br literal 0 HcmV?d00001 diff --git a/public/images/ui/legacy/dusk_icon.png b/public/images/ui/legacy/dusk_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f383ebf52463d1dbe123698d570ef84645f209ba GIT binary patch literal 300 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|o_M-AhFJI~ zryO8fV%hpHS-Ae!Us>f{7F`TJ$y57S%>MG^hQ~D=oyglH&2Xv5d-1YO8eNSWrS%W< zGDzD(vPBlle_=Ij|i#u**btO<>n`h6yTW&MwS94z}&R_d(F-aBRZD zWY+CZA1$s<*tSn5tKlnKmr(;xNB4f_@ajjW4B0A7l%$(3pY;$@IJ-c^QbO8@`> literal 0 HcmV?d00001 diff --git a/public/images/ui/legacy/night_icon.png b/public/images/ui/legacy/night_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1796081c05bf64e52bb85513f3f59c65f1d23efd GIT binary patch literal 288 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|Zg{#lhFJI~ zrySt>_gEm^MciGSA)Dt@#=%wfcl)GfSj5P0V*Fp28MEV1r=new{GEUmjtP4WmwjJPUkYp-i46Ji6XKZ6^@Aq^6xNdE! zn%?`xENVAc7wx%ZA)0K=A?CNBKp}(SL`uxMMrj504BqmC-|hJiPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!2kdb!2!6DYwZ940z*keK~y+Tjgw7G z6HySy|Ld})w8R#%SgRFjfY?|yp%)U2(Sz(wC4rj>Q9P?`ylB+mO%thJ^lH3#FkHPE zB|S)uDORQ0sA(!32Q^=kB% zz9&q9=0tnD6KxJJ-a|*^` z>BVPrF;*py@$r+mGkqFD%NB}#SuCNWvlYI6H(o{(;9ZF9F~_S*QY&V{*YWWFEG1i5 zZ{oN|i^COp*N`Y_`p0NnrZMZH%57 zqHp9#76)o7aCBJ4V97bNM6umd$gO1LPIos7`Jc3U1Ag=m?4`9@Eh3jmLumY_#G#R6aCvuP_R#~brD%h+ zHK^|B*$1!B&t2G^HnJRVw@=>z>BS`3#C#NAQ*4k{*dXzMX!8_`xvZ?RpW^6nu?efw zvREavE56FP3X*SMQ!+zoRP<5iurAEY)ZC3hI{D5>(this.arenaFlyout, this.fieldOverlay); + this.updateUIPositions(); this.damageNumberHandler = new DamageNumberHandler(); @@ -1272,6 +1280,13 @@ export default class BattleScene extends SceneBase { return sprite; } + moveBelowOverlay(gameObject: T) { + this.fieldUI.moveBelow(gameObject, this.fieldOverlay); + } + processInfoButton(pressed: boolean): void { + this.arenaFlyout.toggleFlyout(pressed); + } + showFieldOverlay(duration: integer): Promise { return new Promise(resolve => { this.tweens.add({ diff --git a/src/field/arena-events.ts b/src/field/arena-events.ts index 1cc632030a5..b3d03fdfcae 100644 --- a/src/field/arena-events.ts +++ b/src/field/arena-events.ts @@ -10,8 +10,10 @@ export enum ArenaEventType { /** Triggers when a {@linkcode TerrainType} is added, overlapped, or removed */ TERRAIN_CHANGED = "onTerrainChanged", - /** Triggers when a {@linkcode ArenaTagType} is added or removed */ - TAG_CHANGED = "onTagChanged", + /** Triggers when a {@linkcode ArenaTagType} is added */ + TAG_ADDED = "onTagAdded", + /** Triggers when a {@linkcode ArenaTagType} is removed */ + TAG_REMOVED = "onTagRemoved", } /** @@ -59,17 +61,34 @@ export class TerrainChangedEvent extends ArenaEvent { this.newTerrainType = newTerrainType; } } + /** - * Container class for {@linkcode ArenaEventType.TAG_CHANGED} events + * Container class for {@linkcode ArenaEventType.TAG_ADDED} events * @extends ArenaEvent */ -export class TagChangedEvent extends ArenaEvent { - /** The {@linkcode ArenaTagType} being set */ +export class TagAddedEvent extends ArenaEvent { + /** The {@linkcode ArenaTagType} being added */ public arenaTagType: ArenaTagType; /** The {@linkcode ArenaTagSide} the tag is being placed on */ public arenaTagSide: ArenaTagSide; constructor(arenaTagType: ArenaTagType, arenaTagSide: ArenaTagSide, duration: number) { - super(ArenaEventType.TAG_CHANGED, duration); + super(ArenaEventType.TAG_ADDED, duration); + + this.arenaTagType = arenaTagType; + this.arenaTagSide = arenaTagSide; + } +} +/** + * Container class for {@linkcode ArenaEventType.TAG_REMOVED} events + * @extends ArenaEvent +*/ +export class TagRemovedEvent extends ArenaEvent { + /** The {@linkcode ArenaTagType} being removed */ + public arenaTagType: ArenaTagType; + /** The {@linkcode ArenaTagSide} the tag was being placed on */ + public arenaTagSide: ArenaTagSide; + constructor(arenaTagType: ArenaTagType, arenaTagSide: ArenaTagSide, duration: number) { + super(ArenaEventType.TAG_REMOVED, duration); this.arenaTagType = arenaTagType; this.arenaTagSide = arenaTagSide; diff --git a/src/field/arena.ts b/src/field/arena.ts index eac2eafb265..f6390e40db5 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -19,7 +19,7 @@ import { Terrain, TerrainType } from "../data/terrain"; import { PostTerrainChangeAbAttr, PostWeatherChangeAbAttr, applyPostTerrainChangeAbAttrs, applyPostWeatherChangeAbAttrs } from "../data/ability"; import Pokemon from "./pokemon"; import * as Overrides from "../overrides"; -import { WeatherChangedEvent, TerrainChangedEvent, TagChangedEvent } from "./arena-events"; +import { WeatherChangedEvent, TerrainChangedEvent, TagAddedEvent, TagRemovedEvent } from "./arena-events"; export class Arena { public scene: BattleScene; @@ -550,7 +550,7 @@ export class Arena { this.tags.push(newTag); newTag.onAdd(this); - this.eventTarget.dispatchEvent(new TagChangedEvent(newTag.tagType, newTag.side, newTag.turnCount)); + this.eventTarget.dispatchEvent(new TagAddedEvent(newTag.tagType, newTag.side, newTag.turnCount)); return true; } @@ -577,6 +577,8 @@ export class Arena { this.tags.filter(t => !(t.lapse(this))).forEach(t => { t.onRemove(this); this.tags.splice(this.tags.indexOf(t), 1); + + this.eventTarget.dispatchEvent(new TagRemovedEvent(t.tagType, t.side, t.turnCount)); }); } @@ -586,6 +588,8 @@ export class Arena { if (tag) { tag.onRemove(this); tags.splice(tags.indexOf(tag), 1); + + this.eventTarget.dispatchEvent(new TagRemovedEvent(tag.tagType, tag.side, tag.turnCount)); } return !!tag; } @@ -595,6 +599,8 @@ export class Arena { if (tag) { tag.onRemove(this); this.tags.splice(this.tags.indexOf(tag), 1); + + this.eventTarget.dispatchEvent(new TagRemovedEvent(tag.tagType, tag.side, tag.turnCount)); } return !!tag; } @@ -603,6 +609,8 @@ export class Arena { removeAllTags(): void { while (this.tags.length) { this.tags[0].onRemove(this); + this.eventTarget.dispatchEvent(new TagRemovedEvent(this.tags[0].tagType, this.tags[0].side, this.tags[0].turnCount)); + this.tags.splice(0, 1); } } diff --git a/src/loading-scene.ts b/src/loading-scene.ts index fe63b39f805..0b15358c4bc 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -95,6 +95,11 @@ export class LoadingScene extends SceneBase { this.loadImage("type_tera", "ui"); this.loadAtlas("type_bgs", "ui"); + this.loadImage("dawn_icon", "ui"); + this.loadImage("day_icon", "ui"); + this.loadImage("dusk_icon", "ui"); + this.loadImage("night_icon", "ui"); + this.loadImage("pb_tray_overlay_player", "ui"); this.loadImage("pb_tray_overlay_enemy", "ui"); this.loadAtlas("pb_tray_ball", "ui"); diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index e68e5ea8704..cf657d0e828 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -52,6 +52,7 @@ export const SettingKeys = { Sprite_Set: "SPRITE_SET", Move_Animations: "MOVE_ANIMATIONS", Show_Moveset_Flyout: "SHOW_MOVESET_FLYOUT", + Show_Arena_Flyout: "SHOW_ARENA_FLYOUT", Show_Stats_on_Level_Up: "SHOW_LEVEL_UP_STATS", EXP_Gains_Speed: "EXP_GAINS_SPEED", EXP_Party_Display: "EXP_PARTY_DISPLAY", @@ -189,6 +190,13 @@ export const Setting: Array = [ default: 1, type: SettingType.ACCESSIBILITY }, + { + key: SettingKeys.Show_Arena_Flyout, + label: "Show Battle Effects Flyout", + options: OFF_ON, + default: 1, + type: SettingType.ACCESSIBILITY + }, { key: SettingKeys.Show_Stats_on_Level_Up, label: "Show Stats on Level Up", @@ -343,6 +351,9 @@ export function setSetting(scene: BattleScene, setting: string, value: integer): case SettingKeys.Show_Moveset_Flyout: scene.showMovesetFlyout = Setting[index].options[value] === "On"; break; + case SettingKeys.Show_Arena_Flyout: + scene.showArenaFlyout = Setting[index].options[value] === "On"; + break; case SettingKeys.Show_Stats_on_Level_Up: scene.showLevelUpStats = Setting[index].options[value] === "On"; break; diff --git a/src/ui-inputs.ts b/src/ui-inputs.ts index c9bdc5feaf5..d4815ad5a7c 100644 --- a/src/ui-inputs.ts +++ b/src/ui-inputs.ts @@ -69,7 +69,7 @@ export class UiInputs { [Button.CYCLE_GENDER]: () => this.buttonCycleOption(Button.CYCLE_GENDER), [Button.CYCLE_ABILITY]: () => this.buttonCycleOption(Button.CYCLE_ABILITY), [Button.CYCLE_NATURE]: () => this.buttonCycleOption(Button.CYCLE_NATURE), - [Button.V]: () => this.buttonCycleOption(Button.V), + [Button.V]: () => this.buttonCycleOption(Button.V), [Button.SPEED_UP]: () => this.buttonSpeedChange(), [Button.SLOW_DOWN]: () => this.buttonSpeedChange(false), }; @@ -119,12 +119,14 @@ export class UiInputs { } } buttonInfo(pressed: boolean = true): void { - if (!this.scene.showMovesetFlyout) { - return; + if (this.scene.showMovesetFlyout ) { + for (const p of this.scene.getField().filter(p => p?.isActive(true))) { + p.toggleFlyout(pressed); + } } - for (const p of this.scene.getField().filter(p => p?.isActive(true))) { - p.toggleFlyout(pressed); + if (this.scene.showArenaFlyout) { + this.scene.ui.processInfoButton(pressed); } } diff --git a/src/ui/arena-flyout.ts b/src/ui/arena-flyout.ts new file mode 100644 index 00000000000..73660ca4457 --- /dev/null +++ b/src/ui/arena-flyout.ts @@ -0,0 +1,384 @@ +import * as Utils from "../utils"; +import { addTextObject, TextStyle } from "./text"; +import BattleScene from "#app/battle-scene.js"; +import { ArenaTagSide } from "#app/data/arena-tag.js"; +import { WeatherType } from "#app/data/weather.js"; +import { TerrainType } from "#app/data/terrain.js"; +import { addWindow, WindowVariant } from "./ui-theme"; +import { ArenaEvent, ArenaEventType, TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/field/arena-events.js"; +import { BattleSceneEventType, TurnEndEvent } from "#app/battle-scene-events.js"; +import { ArenaTagType } from "#app/data/enums/arena-tag-type.js"; +import { TimeOfDay } from "#app/data/enums/time-of-day.js"; + +/** Enum used to differentiate {@linkcode Arena} effects */ +enum ArenaEffectType { + PLAYER, + WEATHER, + TERRAIN, + FIELD, + ENEMY, +} +/** Container for info about an {@linkcode Arena}'s effects */ +interface ArenaEffectInfo { + /** The enum string representation of the effect */ + name: string; + /** {@linkcode ArenaEffectType} type of effect */ + type: ArenaEffectType, + + /** The maximum duration set by the effect */ + maxDuration: number; + /** The current duration left on the effect */ + duration: number; +} + +export default class ArenaFlyout extends Phaser.GameObjects.Container { + /** An alias for the scene typecast to a {@linkcode BattleScene} */ + private battleScene: BattleScene; + + /** The restricted width of the flyout which should be drawn to */ + private flyoutWidth = 170; + /** The restricted height of the flyout which should be drawn to */ + private flyoutHeight = 51; + + /** The amount of translation animation on the x-axis */ + private translationX: number; + /** The x-axis point where the flyout should sit when activated */ + private anchorX: number; + /** The y-axis point where the flyout should sit when activated */ + private anchorY: number; + + /** The initial container which defines where the flyout should be attached */ + private flyoutParent: Phaser.GameObjects.Container; + /** The container which defines the drawable dimensions of the flyout */ + private flyoutContainer: Phaser.GameObjects.Container; + + /** The background {@linkcode Phaser.GameObjects.NineSlice} window for the flyout */ + private flyoutWindow: Phaser.GameObjects.NineSlice; + + /** The header {@linkcode Phaser.GameObjects.NineSlice} window for the flyout */ + private flyoutWindowHeader: Phaser.GameObjects.NineSlice; + /** The {@linkcode Phaser.GameObjects.Text} that goes inside of the header */ + private flyoutTextHeader: Phaser.GameObjects.Text; + + /** The {@linkcode Phaser.GameObjects.Sprite} that represents the current time of day */ + private timeOfDayIcon: Phaser.GameObjects.Sprite; + + /** The {@linkcode Phaser.GameObjects.Text} header used to indicate the player's effects */ + private flyoutTextHeaderPlayer: Phaser.GameObjects.Text; + /** The {@linkcode Phaser.GameObjects.Text} header used to indicate the enemy's effects */ + private flyoutTextHeaderEnemy: Phaser.GameObjects.Text; + /** The {@linkcode Phaser.GameObjects.Text} header used to indicate neutral effects */ + private flyoutTextHeaderField: Phaser.GameObjects.Text; + + /** The {@linkcode Phaser.GameObjects.Text} used to indicate the player's effects */ + private flyoutTextPlayer: Phaser.GameObjects.Text; + /** The {@linkcode Phaser.GameObjects.Text} used to indicate the enemy's effects */ + private flyoutTextEnemy: Phaser.GameObjects.Text; + /** The {@linkcode Phaser.GameObjects.Text} used to indicate neutral effects */ + private flyoutTextField: Phaser.GameObjects.Text; + + /** Container for all field effects observed by this object */ + private readonly fieldEffectInfo: ArenaEffectInfo[] = []; + + // Stores callbacks in a variable so they can be unsubscribed from when destroyed + private onNewArenaEvent = (event: Event) => this.onNewArena(event); + private onTurnInitEvent = (event: Event) => this.onTurnInit(event); + private onTurnEndEvent = (event: Event) => this.onTurnEnd(event); + + private onFieldEffectChangedEvent = (event: Event) => this.onFieldEffectChanged(event); + + constructor(scene: Phaser.Scene) { + super(scene, 0, 0); + this.battleScene = this.scene as BattleScene; + + this.translationX = this.flyoutWidth; + this.anchorX = 0; + this.anchorY = -98; + + this.flyoutParent = this.scene.add.container(this.anchorX - this.translationX, this.anchorY); + this.flyoutParent.setAlpha(0); + this.add(this.flyoutParent); + + this.flyoutContainer = this.scene.add.container(0, 0); + this.flyoutParent.add(this.flyoutContainer); + + this.flyoutWindow = addWindow(this.scene as BattleScene, 0, 0, this.flyoutWidth, this.flyoutHeight, false, false, 0, 0, WindowVariant.THIN); + this.flyoutContainer.add(this.flyoutWindow); + + this.flyoutWindowHeader = addWindow(this.scene as BattleScene, this.flyoutWidth / 2, 0, this.flyoutWidth / 2, 14, false, false, 0, 0, WindowVariant.XTHIN); + this.flyoutWindowHeader.setOrigin(); + + this.flyoutContainer.add(this.flyoutWindowHeader); + + this.flyoutTextHeader = addTextObject(this.scene, this.flyoutWidth / 2, 0, "Active Battle Effects", TextStyle.BATTLE_INFO); + this.flyoutTextHeader.setFontSize(54); + this.flyoutTextHeader.setAlign("center"); + this.flyoutTextHeader.setOrigin(); + + this.flyoutContainer.add(this.flyoutTextHeader); + + this.timeOfDayIcon = this.scene.add.sprite((this.flyoutWidth / 2) + (this.flyoutWindowHeader.displayWidth / 2), 0, "dawn_icon").setOrigin(); + this.timeOfDayIcon.setVisible(false); + + this.flyoutContainer.add(this.timeOfDayIcon); + + this.flyoutTextHeaderPlayer = addTextObject(this.scene, 6, 5, "Player", TextStyle.SUMMARY_BLUE); + this.flyoutTextHeaderPlayer.setFontSize(54); + this.flyoutTextHeaderPlayer.setAlign("left"); + this.flyoutTextHeaderPlayer.setOrigin(0, 0); + + this.flyoutContainer.add(this.flyoutTextHeaderPlayer); + + this.flyoutTextHeaderField = addTextObject(this.scene, this.flyoutWidth / 2, 5, "Neutral", TextStyle.SUMMARY_GREEN); + this.flyoutTextHeaderField.setFontSize(54); + this.flyoutTextHeaderField.setAlign("center"); + this.flyoutTextHeaderField.setOrigin(0.5, 0); + + this.flyoutContainer.add(this.flyoutTextHeaderField); + + this.flyoutTextHeaderEnemy = addTextObject(this.scene, this.flyoutWidth - 6, 5, "Enemy", TextStyle.SUMMARY_RED); + this.flyoutTextHeaderEnemy.setFontSize(54); + this.flyoutTextHeaderEnemy.setAlign("right"); + this.flyoutTextHeaderEnemy.setOrigin(1, 0); + + this.flyoutContainer.add(this.flyoutTextHeaderEnemy); + + this.flyoutTextPlayer = addTextObject(this.scene, 6, 13, "", TextStyle.BATTLE_INFO); + this.flyoutTextPlayer.setLineSpacing(-1); + this.flyoutTextPlayer.setFontSize(48); + this.flyoutTextPlayer.setAlign("left"); + this.flyoutTextPlayer.setOrigin(0, 0); + + this.flyoutContainer.add(this.flyoutTextPlayer); + + this.flyoutTextField = addTextObject(this.scene, this.flyoutWidth / 2, 13, "", TextStyle.BATTLE_INFO); + this.flyoutTextField.setLineSpacing(-1); + this.flyoutTextField.setFontSize(48); + this.flyoutTextField.setAlign("center"); + this.flyoutTextField.setOrigin(0.5, 0); + + this.flyoutContainer.add(this.flyoutTextField); + + this.flyoutTextEnemy = addTextObject(this.scene, this.flyoutWidth - 6, 13, "", TextStyle.BATTLE_INFO); + this.flyoutTextEnemy.setLineSpacing(-1); + this.flyoutTextEnemy.setFontSize(48); + this.flyoutTextEnemy.setAlign("right"); + this.flyoutTextEnemy.setOrigin(1, 0); + + this.flyoutContainer.add(this.flyoutTextEnemy); + + this.name = "Fight Flyout"; + this.flyoutParent.name = "Fight Flyout Parent"; + + // Subscribes to required events available on game start + this.battleScene.eventTarget.addEventListener(BattleSceneEventType.NEW_ARENA, this.onNewArenaEvent); + this.battleScene.eventTarget.addEventListener(BattleSceneEventType.TURN_INIT, this.onTurnInitEvent); + this.battleScene.eventTarget.addEventListener(BattleSceneEventType.TURN_END, this.onTurnEndEvent); + } + + private setTimeOfDayIcon() { + this.timeOfDayIcon.setTexture(TimeOfDay[this.battleScene.arena.getTimeOfDay()].toLowerCase() + "_icon"); + } + + private onTurnInit(event: Event) { + this.setTimeOfDayIcon(); + } + + private onNewArena(event: Event) { + this.fieldEffectInfo.length = 0; + + // Subscribes to required events available on battle start + this.battleScene.arena.eventTarget.addEventListener(ArenaEventType.WEATHER_CHANGED, this.onFieldEffectChangedEvent); + this.battleScene.arena.eventTarget.addEventListener(ArenaEventType.TERRAIN_CHANGED, this.onFieldEffectChangedEvent); + this.battleScene.arena.eventTarget.addEventListener(ArenaEventType.TAG_ADDED, this.onFieldEffectChangedEvent); + this.battleScene.arena.eventTarget.addEventListener(ArenaEventType.TAG_REMOVED, this.onFieldEffectChangedEvent); + + this.setTimeOfDayIcon(); + } + + /** + * Formats a string to title case + * @param unformattedText Text to be formatted + * @returns the formatted string + */ + private formatText(unformattedText: string): string { + const text = unformattedText.split("_"); + for (let i = 0; i < text.length; i++) { + text[i] = text[i].charAt(0).toUpperCase() + text[i].substring(1).toLowerCase(); + } + + return text.join(" "); + } + + /** Clears out the current string stored in all arena effect texts */ + private clearText() { + this.flyoutTextPlayer.text = ""; + this.flyoutTextField.text = ""; + this.flyoutTextEnemy.text = ""; + } + + /** Parses through all set Arena Effects and puts them into the proper {@linkcode Phaser.GameObjects.Text} object */ + private updateFieldText() { + this.clearText(); + + this.fieldEffectInfo.sort((infoA, infoB) => infoA.duration - infoB.duration); + + for (let i = 0; i < this.fieldEffectInfo.length; i++) { + const fieldEffectInfo = this.fieldEffectInfo[i]; + + // Creates a proxy object to decide which text object needs to be updated + let textObject: Phaser.GameObjects.Text; + switch (fieldEffectInfo.type) { + case ArenaEffectType.PLAYER: + textObject = this.flyoutTextPlayer; + break; + + case ArenaEffectType.WEATHER: + case ArenaEffectType.TERRAIN: + case ArenaEffectType.FIELD: + textObject = this.flyoutTextField; + + break; + + case ArenaEffectType.ENEMY: + textObject = this.flyoutTextEnemy; + break; + } + + textObject.text += this.formatText(fieldEffectInfo.name); + if (fieldEffectInfo.type === ArenaEffectType.TERRAIN) { + textObject.text += " Terrain"; // Adds 'Terrain' since the enum does not contain it + } + + if (fieldEffectInfo.maxDuration !== 0) { + textObject.text += " " + fieldEffectInfo.duration + "/" + fieldEffectInfo.maxDuration; + } + + textObject.text += "\n"; + } + } + + /** + * Parses the {@linkcode Event} being passed and updates the state of the fieldEffectInfo array + * @param event {@linkcode Event} being sent + */ + private onFieldEffectChanged(event: Event) { + const arenaEffectChangedEvent = event as ArenaEvent; + if (!arenaEffectChangedEvent) { + return; + } + + let foundIndex: number; + switch (arenaEffectChangedEvent.constructor) { + case TagAddedEvent: + const tagAddedEvent = arenaEffectChangedEvent as TagAddedEvent; + this.fieldEffectInfo.push({ + name: ArenaTagType[tagAddedEvent.arenaTagType], + type: tagAddedEvent.arenaTagSide === ArenaTagSide.BOTH + ? ArenaEffectType.FIELD + : tagAddedEvent.arenaTagSide === ArenaTagSide.PLAYER + ? ArenaEffectType.PLAYER + : ArenaEffectType.ENEMY, + maxDuration: tagAddedEvent.duration, + duration: tagAddedEvent.duration}); + break; + case TagRemovedEvent: + const tagRemovedEvent = arenaEffectChangedEvent as TagRemovedEvent; + foundIndex = this.fieldEffectInfo.findIndex(info => info.name === ArenaTagType[tagRemovedEvent.arenaTagType]); + if (foundIndex !== -1) { // If the tag was being tracked, remove it + this.fieldEffectInfo.splice(foundIndex, 1); + } + break; + + case WeatherChangedEvent: + case TerrainChangedEvent: + const fieldEffectChangedEvent = arenaEffectChangedEvent as WeatherChangedEvent | TerrainChangedEvent; + + // Stores the old Weather/Terrain name in case it's in the array already + const oldName = + fieldEffectChangedEvent instanceof WeatherChangedEvent + ? WeatherType[fieldEffectChangedEvent.oldWeatherType] + : TerrainType[fieldEffectChangedEvent.oldTerrainType]; + // Stores the new Weather/Terrain info + const newInfo = { + name: + fieldEffectChangedEvent instanceof WeatherChangedEvent + ? WeatherType[fieldEffectChangedEvent.newWeatherType] + : TerrainType[fieldEffectChangedEvent.newTerrainType], + type: fieldEffectChangedEvent instanceof WeatherChangedEvent + ? ArenaEffectType.WEATHER + : ArenaEffectType.TERRAIN, + maxDuration: fieldEffectChangedEvent.duration, + duration: fieldEffectChangedEvent.duration}; + + foundIndex = this.fieldEffectInfo.findIndex(info => [newInfo.name, oldName].includes(info.name)); + if (foundIndex === -1) { + if (newInfo.name !== undefined) { + this.fieldEffectInfo.push(newInfo); // Adds the info to the array if it doesn't already exist and is defined + } + } else if (!newInfo.name) { + this.fieldEffectInfo.splice(foundIndex, 1); // Removes the old info if the new one is undefined + } else { + this.fieldEffectInfo[foundIndex] = newInfo; // Otherwise, replace the old info + } + break; + } + + this.updateFieldText(); + } + + /** + * Iterates through the fieldEffectInfo array and decrements the duration of each item + * @param event {@linkcode Event} being sent + */ + private onTurnEnd(event: Event) { + const turnEndEvent = event as TurnEndEvent; + if (!turnEndEvent) { + return; + } + + const fieldEffectInfo: ArenaEffectInfo[] = []; + this.fieldEffectInfo.forEach(i => fieldEffectInfo.push(i)); + + for (let i = 0; i < fieldEffectInfo.length; i++) { + const info = fieldEffectInfo[i]; + + if (info.maxDuration === 0) { + continue; + } + + --info.duration; + if (info.duration <= 0) { // Removes the item if the duration has expired + this.fieldEffectInfo.splice(this.fieldEffectInfo.indexOf(info), 1); + } + } + + this.updateFieldText(); + } + + /** + * Animates the flyout to either show or hide it by applying a fade and translation + * @param visible Should the flyout be shown? + */ + toggleFlyout(visible: boolean): void { + this.scene.tweens.add({ + targets: this.flyoutParent, + x: visible ? this.anchorX : this.anchorX - this.translationX, + duration: Utils.fixedInt(125), + ease: "Sine.easeInOut", + alpha: visible ? 1 : 0, + }); + } + + destroy(fromScene?: boolean): void { + this.battleScene.eventTarget.removeEventListener(BattleSceneEventType.NEW_ARENA, this.onNewArenaEvent); + this.battleScene.eventTarget.removeEventListener(BattleSceneEventType.TURN_END, this.onTurnEndEvent); + + this.battleScene.arena.eventTarget.removeEventListener(ArenaEventType.WEATHER_CHANGED, this.onFieldEffectChangedEvent); + this.battleScene.arena.eventTarget.removeEventListener(ArenaEventType.TERRAIN_CHANGED, this.onFieldEffectChangedEvent); + this.battleScene.arena.eventTarget.removeEventListener(ArenaEventType.TAG_ADDED, this.onFieldEffectChangedEvent); + this.battleScene.arena.eventTarget.removeEventListener(ArenaEventType.TAG_REMOVED, this.onFieldEffectChangedEvent); + + super.destroy(); + } +} diff --git a/src/ui/ui.ts b/src/ui/ui.ts index b2df4d22259..0f33a9cb6c7 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -222,6 +222,21 @@ export default class UI extends Phaser.GameObjects.Container { return this.handlers[Mode.MESSAGE] as BattleMessageUiHandler; } + processInfoButton(pressed: boolean) { + if (this.overlayActive) { + return false; + } + + const battleScene = this.scene as BattleScene; + if ([Mode.CONFIRM, Mode.COMMAND, Mode.FIGHT, Mode.MESSAGE].includes(this.mode)) { + battleScene?.processInfoButton(pressed); + return true; + } + + battleScene?.processInfoButton(false); + return true; + } + processInput(button: Button): boolean { if (this.overlayActive) { return false; From f503080167b5041a04756a72466d81b6c6942d91 Mon Sep 17 00:00:00 2001 From: Tempoanon <163687446+Tempo-anon@users.noreply.github.com> Date: Mon, 3 Jun 2024 22:33:26 -0400 Subject: [PATCH 027/129] [Bug] Curse should do at least 1 damage (#1740) * [Bug] Curse should do at least 1 damage * Used the wrong math function lol --- src/data/battler-tags.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index c4fd5dfb45b..63ac9fd895d 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1341,7 +1341,7 @@ export class CursedTag extends BattlerTag { applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); if (!cancelled.value) { - pokemon.damageAndUpdate(Math.floor(pokemon.getMaxHp() / 4)); + pokemon.damageAndUpdate(Math.max(Math.floor(pokemon.getMaxHp() / 4), 1)); pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is hurt by the ${this.getMoveName()}!`)); } } From 041519a78ff0b981afb8a558e845fe85f9615c90 Mon Sep 17 00:00:00 2001 From: returntoice Date: Tue, 4 Jun 2024 12:46:42 +0900 Subject: [PATCH 028/129] [Localization] #1761 Korean double trainer dialogue (#1765) * Your commit message * localization --- src/locales/ko/dialogue.ts | 258 ++++++++++++++++++------------------- 1 file changed, 129 insertions(+), 129 deletions(-) diff --git a/src/locales/ko/dialogue.ts b/src/locales/ko/dialogue.ts index 37c4d67936e..a2b0b2f28bc 100644 --- a/src/locales/ko/dialogue.ts +++ b/src/locales/ko/dialogue.ts @@ -1,4 +1,4 @@ -import {DialogueTranslationEntries, SimpleTranslationEntries} from "#app/plugins/i18n"; +import { DialogueTranslationEntries, SimpleTranslationEntries } from "#app/plugins/i18n"; // Dialogue of the NPCs in the game when the player character is male (or unset) export const PGMdialogue: DialogueTranslationEntries = { @@ -285,7 +285,7 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "worker_female": { "encounter": { - 1: `It bothers me that people always misunderstand me. + 1: `It bothers me that people always misunderstand me. $I'm a lot more pure than everyone thinks.` }, "victory": { @@ -788,7 +788,7 @@ export const PGMdialogue: DialogueTranslationEntries = { "cheren": { "encounter": { 1: "You remind me of an old friend. That makes me excited about this Pokémon battle!", - 2: `Pokémon battles have no meaning if you don't think why you battle. + 2: `Pokémon battles have no meaning if you don't think why you battle. $Or better said, it makes battling together with Pokémon meaningless.`, 3: "My name's Cheren! I'm a Gym Leader and a teacher! Pleasure to meet you." }, @@ -829,13 +829,13 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "victory": { 1: "Er… Is it over now?", - 2: `…What a surprise. You are very strong, aren't you? + 2: `…What a surprise. You are very strong, aren't you? $I guess my brothers wouldn't have been able to defeat you either…`, 3: "…Huh. Looks like my timing was, um, off?" }, "defeat": { 1: "Huh? Did I win?", - 2: `I guess… + 2: `I guess… $I suppose I won, because I've been competing with my brothers Chili and Cress, and we all were able to get tougher.`, 3: "It…it was quite a thrilling experience…" } @@ -864,9 +864,9 @@ export const PGMdialogue: DialogueTranslationEntries = { "encounter": { 1: `With a little more, I could see a future in which I meet the legendary Pokémon. $You're going to help me reach that level!`, - 2: `It's said that a rainbow-hued Pokémon will come down to appear before a truly powerful Trainer. - $I believed that tale, so I have secretly trained here all my life. As a result, I can now see what others cannot. - $I see a shadow of the person who will make the Pokémon appear. + 2: `It's said that a rainbow-hued Pokémon will come down to appear before a truly powerful Trainer. + $I believed that tale, so I have secretly trained here all my life. As a result, I can now see what others cannot. + $I see a shadow of the person who will make the Pokémon appear. $I believe that person is me! You're going to help me reach that level!`, 3: "Whether you choose to believe or not, mystic power does exist.", 4: "You can bear witness to the fruits of my training.", @@ -931,7 +931,7 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "drayton": { "encounter": { - 1: `Man, I love chairs. Don't you love chairs? What lifesavers. + 1: `Man, I love chairs. Don't you love chairs? What lifesavers. $I don't get why everyone doesn't just sit all the time. Standing up's tiring work!`, }, "victory": { @@ -956,7 +956,7 @@ export const PGMdialogue: DialogueTranslationEntries = { "viola": { "encounter": { 1: `Whether it's the tears of frustration that follow a loss or the blossoming of joy that comes with victory… - $They're both great subjects for my camera! Fantastic! This'll be just fantastic! + $They're both great subjects for my camera! Fantastic! This'll be just fantastic! $Now come at me!`, 2: "My lens is always focused on victory--I won't let anything ruin this shot!" }, @@ -972,14 +972,14 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "candice": { "encounter": { - 1: `You want to challenge Candice? Sure thing! I was waiting for someone tough! + 1: `You want to challenge Candice? Sure thing! I was waiting for someone tough! $But I should tell you, I'm tough because I know how to focus.`, - 2: `Pokémon, fashion, romance… It's all about focus! + 2: `Pokémon, fashion, romance… It's all about focus! $I'll show you just what I mean. Get ready to lose!` }, "victory": { 1: "I must say, I'm warmed up to you! I might even admire you a little.", - 2: `Wow! You're great! You've earned my respect! + 2: `Wow! You're great! You've earned my respect! $I think your focus and will bowled us over totally. ` }, "defeat": { @@ -1040,7 +1040,7 @@ export const PGMdialogue: DialogueTranslationEntries = { 1: "Man oh man… It feels good to go all out and still be defeated!" }, "defeat": { - 1: `What's important is how ya react to losin'. + 1: `What's important is how ya react to losin'. $That's why folks who use losin' as fuel to get better are tough.`, } }, @@ -1081,8 +1081,8 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "phoebe": { "encounter": { - 1: `While I trained, I gained the ability to commune with Ghost-type Pokémon. - $Yes, the bond I developed with Pokémon is extremely tight. + 1: `While I trained, I gained the ability to commune with Ghost-type Pokémon. + $Yes, the bond I developed with Pokémon is extremely tight. $So, come on, just try and see if you can even inflict damage on my Pokémon!`, }, "victory": { @@ -1094,12 +1094,12 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "glacia": { "encounter": { - 1: `All I have seen are challenges by weak Trainers and their Pokémon. + 1: `All I have seen are challenges by weak Trainers and their Pokémon. $What about you? It would please me to no end if I could go all out against you!`, }, "victory": { 1: `You and your Pokémon… How hot your spirits burn! - $The all-consuming heat overwhelms. + $The all-consuming heat overwhelms. $It's no surprise that my icy skills failed to harm you.`, }, "defeat": { @@ -1108,7 +1108,7 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "drake": { "encounter": { - 1: `For us to battle with Pokémon as partners, do you know what it takes? Do you know what is needed? + 1: `For us to battle with Pokémon as partners, do you know what it takes? Do you know what is needed? $If you don't, then you will never prevail over me!`, }, "victory": { @@ -1120,12 +1120,12 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "wallace": { "encounter": { - 1: `There's something about you… A difference in your demeanor. - $I think I sense that in you. Now, show me. Show me the power you wield with your Pokémon. + 1: `There's something about you… A difference in your demeanor. + $I think I sense that in you. Now, show me. Show me the power you wield with your Pokémon. $And I, in turn, shall present you with a performance of illusions in water by me and my Pokémon!`, }, "victory": { - 1: `Bravo. I realize now your authenticity and magnificence as a Pokémon Trainer. + 1: `Bravo. I realize now your authenticity and magnificence as a Pokémon Trainer. $I find much joy in having met you and your Pokémon. You have proven yourself worthy.`, }, "defeat": { @@ -1158,7 +1158,7 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "malva": { "encounter": { - 1: `I feel like my heart might just burst into flames. + 1: `I feel like my heart might just burst into flames. $I'm burning up with my hatred for you, runt!`, }, "victory": { @@ -1181,7 +1181,7 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "molayne": { "encounter": { - 1: `I gave the captain position to my cousin Sophocles, but I'm confident in my ability. + 1: `I gave the captain position to my cousin Sophocles, but I'm confident in my ability. $My strength is like that of a supernova!`, }, "victory": { @@ -1240,8 +1240,8 @@ export const PGMdialogue: DialogueTranslationEntries = { 1: "Well, would you show this old lady how much you've learned?" }, "victory": { - 1: `Well! Dear child, I must say, that was most impressive. - $Your Pokémon believed in you and did their best to earn you the win. + 1: `Well! Dear child, I must say, that was most impressive. + $Your Pokémon believed in you and did their best to earn you the win. $Even though I've lost, I find myself with this silly grin!`, }, "defeat": { @@ -1267,7 +1267,7 @@ export const PGMdialogue: DialogueTranslationEntries = { 1: "I shall store my memory of you and your Pokémon forever away within my heart." }, "defeat": { - 1: `Our Pokémon battle was like food for my soul. It shall keep me going. + 1: `Our Pokémon battle was like food for my soul. It shall keep me going. $That is how I will pay my respects to you for giving your all in battle!`, } }, @@ -1301,7 +1301,7 @@ export const PGMdialogue: DialogueTranslationEntries = { 1: "Uagh?! Mmmuuuggghhh…" }, "defeat": { - 1: `Yaaay! I did it! I de-feet-ed you! You can come for… For… An avenge match? + 1: `Yaaay! I did it! I de-feet-ed you! You can come for… For… An avenge match? $Come for an avenge match anytime you want!`, } }, @@ -1341,8 +1341,8 @@ export const PGMdialogue: DialogueTranslationEntries = { "caitlin": { "encounter": { 1: `It's me who appeared when the flower opened up. You who have been waiting… - $You look like a Pokémon Trainer with refined strength and deepened kindness. - $What I look for in my opponent is superb strength… + $You look like a Pokémon Trainer with refined strength and deepened kindness. + $What I look for in my opponent is superb strength… $Please unleash your power to the fullest!`, }, "victory": { @@ -1354,7 +1354,7 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "diantha": { "encounter": { - 1: `Battling against you and your Pokémon, all of you brimming with hope for the future… + 1: `Battling against you and your Pokémon, all of you brimming with hope for the future… $Honestly, it just fills me up with energy I need to keep facing each new day! It does!`, }, "victory": { @@ -1366,14 +1366,14 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "wikstrom": { "encounter": { - 1: `Well met, young challenger! Verily am I the famed blade of hardened steel, Duke Wikstrom! + 1: `Well met, young challenger! Verily am I the famed blade of hardened steel, Duke Wikstrom! $Let the battle begin! En garde!`, }, "victory": { 1: "Glorious! The trust that you share with your honorable Pokémon surpasses even mine!" }, "defeat": { - 1: `What manner of magic is this? My heart, it doth hammer ceaselessly in my breast! + 1: `What manner of magic is this? My heart, it doth hammer ceaselessly in my breast! $Winning against such a worthy opponent doth give my soul wings--thus do I soar!`, } }, @@ -1433,8 +1433,8 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "milo": { "encounter": { - 1: `Sure seems like you understand Pokémon real well. - $This is gonna be a doozy of a battle! + 1: `Sure seems like you understand Pokémon real well. + $This is gonna be a doozy of a battle! $I'll have to Dynamax my Pokémon if I want to win!`, }, "victory": { @@ -1446,9 +1446,9 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "lucian": { "encounter": { - 1: `Just a moment, please. The book I'm reading has nearly reached its thrilling climax… - $The hero has obtained a mystic sword and is about to face their final trial… Ah, never mind. - $Since you've made it this far, I'll put that aside and battle you. + 1: `Just a moment, please. The book I'm reading has nearly reached its thrilling climax… + $The hero has obtained a mystic sword and is about to face their final trial… Ah, never mind. + $Since you've made it this far, I'll put that aside and battle you. $Let me see if you'll achieve as much glory as the hero of my book!,` }, "victory": { @@ -1486,7 +1486,7 @@ export const PGMdialogue: DialogueTranslationEntries = { 1: "Prepare to learn firsthand how the fiery breath of ferocious battle feels!" }, "victory": { - 1: `Fortune smiled on me this time, but… + 1: `Fortune smiled on me this time, but… $Judging from how the match went, who knows if I will be so lucky next time.`, }, "defeat": { @@ -1550,10 +1550,10 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "steven": { "encounter": { - 1: `Tell me… What have you seen on your journey with your Pokémon? - $What have you felt, meeting so many other Trainers out there? - $Traveling this rich land… Has it awoken something inside you? - $I want you to come at me with all that you've learned. + 1: `Tell me… What have you seen on your journey with your Pokémon? + $What have you felt, meeting so many other Trainers out there? + $Traveling this rich land… Has it awoken something inside you? + $I want you to come at me with all that you've learned. $My Pokémon and I will respond in turn with all that we know!`, }, "victory": { @@ -1576,11 +1576,11 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "iris": { "encounter": { - 1: `Know what? I really look forward to having serious battles with strong Trainers! - $I mean, come on! The Trainers who make it here are Trainers who desire victory with every fiber of their being! - #And they are battling alongside Pokémon that have been through countless difficult battles! - $If I battle with people like that, not only will I get stronger, my Pokémon will, too! - $And we'll get to know each other even better! OK! Brace yourself! + 1: `Know what? I really look forward to having serious battles with strong Trainers! + $I mean, come on! The Trainers who make it here are Trainers who desire victory with every fiber of their being! + #And they are battling alongside Pokémon that have been through countless difficult battles! + $If I battle with people like that, not only will I get stronger, my Pokémon will, too! + $And we'll get to know each other even better! OK! Brace yourself! $I'm Iris, the Pokémon League Champion, and I'm going to defeat you!`, }, "victory": { @@ -1604,7 +1604,7 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "geeta": { "encounter": { - 1: `I decided to throw my hat in the ring once more. + 1: `I decided to throw my hat in the ring once more. $Come now… Show me the fruits of your training.`, }, "victory": { @@ -1630,8 +1630,8 @@ export const PGMdialogue: DialogueTranslationEntries = { 1: "We're gonna have an absolutely champion time!" }, "victory": { - 1: `My time as Champion is over… - $But what a champion time it's been! + 1: `My time as Champion is over… + $But what a champion time it's been! $Thank you for the greatest battle I've ever had!`, }, "defeat": { @@ -1695,7 +1695,7 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "maylene": { "encounter": { - 1: `I've come to challenge you now, and I won't hold anything back. + 1: `I've come to challenge you now, and I won't hold anything back. $Please prepare yourself for battle!`, }, "victory": { @@ -1707,7 +1707,7 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "fantina": { "encounter": { - 1: `You shall challenge me, yes? But I shall win. + 1: `You shall challenge me, yes? But I shall win. $That is what the Gym Leader of Hearthome does, non?`, }, "victory": { @@ -1719,8 +1719,8 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "byron": { "encounter": { - 1: `Trainer! You're young, just like my son, Roark. - $With more young Trainers taking charge, the future of Pokémon is bright! + 1: `Trainer! You're young, just like my son, Roark. + $With more young Trainers taking charge, the future of Pokémon is bright! $So, as a wall for young people, I'll take your challenge!`, }, "victory": { @@ -1748,19 +1748,19 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "victory": { 1: `You've got me beat… - $Your desire and the noble way your Pokémon battled for you… + $Your desire and the noble way your Pokémon battled for you… $I even felt thrilled during our match. That was a very good battle.`, }, "defeat": { - 1: `It was not shocking at all… + 1: `It was not shocking at all… $That is not what I wanted!`, } }, "burgh": { "encounter": { - 1: `M'hm… If I win this battle, I feel like I can draw a picture unlike any before it. + 1: `M'hm… If I win this battle, I feel like I can draw a picture unlike any before it. $OK! I can hear my battle muse loud and clear. Let's get straight to it!`, - 2: `Of course, I'm really proud of all of my Pokémon! + 2: `Of course, I'm really proud of all of my Pokémon! $Well now… Let's get right to it!` }, "victory": { @@ -1769,13 +1769,13 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "defeat": { 1: "Wow… It's beautiful somehow, isn't it…", - 2: `Sometimes I hear people say something was an ugly win. + 2: `Sometimes I hear people say something was an ugly win. $I think if you're trying your best, any win is beautiful.` } }, "elesa": { "encounter": { - 1: `C'est fini! When I'm certain of that, I feel an electric jolt run through my body! + 1: `C'est fini! When I'm certain of that, I feel an electric jolt run through my body! $I want to feel the sensation, so now my beloved Pokémon are going to make your head spin!`, }, "victory": { @@ -1787,8 +1787,8 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "skyla": { "encounter": { - 1: `It's finally time for a showdown! That means the Pokémon battle that decides who's at the top, right? - $I love being on the summit! 'Cause you can see forever and ever from high places! + 1: `It's finally time for a showdown! That means the Pokémon battle that decides who's at the top, right? + $I love being on the summit! 'Cause you can see forever and ever from high places! $So, how about you and I have some fun?`, }, "victory": { @@ -1800,7 +1800,7 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "brycen": { "encounter": { - 1: `There is also strength in being with other people and Pokémon. + 1: `There is also strength in being with other people and Pokémon. $Receiving their support makes you stronger. I'll show you this power!`, }, "victory": { @@ -1812,7 +1812,7 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "drayden": { "encounter": { - 1: `What I want to find is a young Trainer who can show me a bright future. + 1: `What I want to find is a young Trainer who can show me a bright future. $Let's battle with everything we have: your skill, my experience, and the love we've raised our Pokémon with!`, }, "victory": { @@ -1824,15 +1824,15 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "grant": { "encounter": { - 1: `There is only one thing I wish for. + 1: `There is only one thing I wish for. $That by surpassing one another, we find a way to even greater heights.`, }, "victory": { 1: "You are a wall that I am unable to surmount!" }, "defeat": { - 1: `Do not give up. - $That is all there really is to it. + 1: `Do not give up. + $That is all there really is to it. $The most important lessons in life are simple.`, } }, @@ -1860,8 +1860,8 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "valerie": { "encounter": { - 1: `Oh, if it isn't a young Trainer… It is lovely to get to meet you like this. - $Then I suppose you have earned yourself the right to a battle, as a reward for your efforts. + 1: `Oh, if it isn't a young Trainer… It is lovely to get to meet you like this. + $Then I suppose you have earned yourself the right to a battle, as a reward for your efforts. $The elusive Fairy may appear frail as the breeze and delicate as a bloom, but it is strong.`, }, "victory": { @@ -1874,7 +1874,7 @@ export const PGMdialogue: DialogueTranslationEntries = { "wulfric": { "encounter": { 1: `You know what? We all talk big about what you learn from battling and bonds and all that… - $But really, I just do it 'cause it's fun. + $But really, I just do it 'cause it's fun. $Who cares about the grandstanding? Let's get to battling!`, }, "victory": { @@ -1886,8 +1886,8 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "kabu": { "encounter": { - 1: `Every Trainer and Pokémon trains hard in pursuit of victory. - $But that means your opponent is also working hard to win. + 1: `Every Trainer and Pokémon trains hard in pursuit of victory. + $But that means your opponent is also working hard to win. $In the end, the match is decided by which side is able to unleash their true potential.`, }, "victory": { @@ -1899,7 +1899,7 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "bea": { "encounter": { - 1: `Do you have an unshakable spirit that won't be moved, no matter how you are attacked? + 1: `Do you have an unshakable spirit that won't be moved, no matter how you are attacked? $I think I'll just test that out, shall I?`, }, "victory": { @@ -1944,7 +1944,7 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "marnie": { "encounter": { - 1: `The truth is, when all's said and done… I really just wanna become Champion for myself! + 1: `The truth is, when all's said and done… I really just wanna become Champion for myself! $So don't take it personal when I kick your butt!`, }, "victory": { @@ -1959,8 +1959,8 @@ export const PGMdialogue: DialogueTranslationEntries = { 1: "I'm going to defeat the Champion, win the whole tournament, and prove to the world just how strong the great Raihan really is!" }, "victory": { - 1: `I look this good even when I lose. - $It's a real curse. + 1: `I look this good even when I lose. + $It's a real curse. $Guess it's time for another selfie!`, }, "defeat": { @@ -1982,7 +1982,7 @@ export const PGMdialogue: DialogueTranslationEntries = { "encounter": { 1: `How're ya feelin' about this battle? $... - $Let's get this show on the road! How strong is our challenger? + $Let's get this show on the road! How strong is our challenger? $I 'unno! Let's find out together!`, }, "victory": { @@ -2335,113 +2335,113 @@ export const PGFmiscDialogue: SimpleTranslationEntries = PGMmiscDialogue; export const PGMdoubleBattleDialogue: DialogueTranslationEntries = { "blue_red_double": { "encounter": { - 1: `Blue: Hey Red, let's show them what we're made of! - $Red: ... - $Blue: This is Pallet Town Power!`, + 1: `그린: 어이 레드! 우리가 누군지 보여주자고! + $레드: ... + $그린: 태초마을의 힘을 보여주지!`, }, "victory": { - 1: `Blue: That was a great battle! - $Red: ...`, + 1: `그린: 훌륭한 승부였어! + $레드: ...`, }, }, "red_blue_double": { "encounter": { - 1: `Red: ...! - $Blue: He never talks much. - $Blue: But dont let that fool you! He is a champ after all!`, + 1: `레드: ...! + $그린: 이 녀석은 여전히 말이 없구나. + $그린: 그렇지만 방심해선 안 돼! 그래도 챔피언이라고!`, }, "victory": { - 1: `Red: ...! - $Blue: Next time we will beat you!`, + 1: `레드: ...! + $그린: 다음에는 우리가 이길 테다!`, }, }, "tate_liza_double": { "encounter": { - 1: `Tate: Are you suprised? - $Liza: We are two gym leaders at once! - $Tate: We are twins! - $Liza: We dont need to talk to understand each other! - $Tate: Twice the power... - $Liza: Can you handle it?`, + 1: `풍: 에헤헤... 체육관 관장이 + $란: 두 명이나 있어서 놀랐지? + $풍: 우리는 쌍둥이! + $란: 굳이 말을 하지 않아도 서로가 무슨 생각을 하고 있는지 + $풍: 자동으로 머릿속에 떠오르니까 + $란: 호흡을 척척 맞출 수가 있지!`, }, "victory": { - 1: `Tate: What? Our combination was perfect! - $Liza: Looks like we need to train more...`, + 1: `풍: 우, 우리들의 + $란: 팀워크가...!`, }, }, "liza_tate_double": { "encounter": { - 1: `Liza: Hihihi... Are you suprised? - $Tate: Yes, we are really two gym leaders at once! - $Liza: This is my twin brother Tate! - $Tate: And this is my twin sister Liza! - $Liza: Don't you think we are a perfect combination?` + 1: `란: 우후후... 체육관 관장이 + $풍: 두 명이나 있어서 놀랐어? + $란: 우리는 쌍둥이! + $풍: 완벽한 우리의 콤비네이션을 + $란: 과연 네가 깨뜨릴 수 있을까?` }, "victory": { - 1: `Liza: Are we... - $Tate: ...not as strong as we thought?`, + 1: `란: 우리들이 생각한 만큼 + $풍: 우리가 강하지 않았던 걸까?`, }, }, "wallace_steven_double": { "encounter": { - 1: `Steven: Wallace, let's show them the power of the champions! - $Wallace: We will show you the power of Hoenn! - $Steven: Let's go!`, + 1: `성호: 윤진! 우리 챔피언의 힘을 보여주자! + $윤진: 호연의 힘을 보여주마! + $성호: 간다!`, }, "victory": { - 1: `Steven: That was a great battle! - $Wallace: We will win next time!`, + 1: `성호: 훌륭한 승부였어! + $윤진: 다음엔 우리가 이길 거다!`, }, }, "steven_wallace_double": { "encounter": { - 1: `Steven: Do you have any rare pokémon? - $Wallace: Steven... We are here for a battle, not to show off our pokémon. - $Steven: Oh... I see... Let's go then!`, + 1: `성호: 너 혹시 희귀한 포켓몬 가지고 있니? + $윤진: 성호야... 우리는 포켓몬을 자랑하러 온 게 아니라 승부하러 온 거야. + $성호: 오... 그렇지... 그럼 간다!`, }, "victory": { - 1: `Steven: Now that we are done with the battle, let's show off our pokémon! - $Wallace: Steven...`, + 1: `성호: 이제 승부는 끝났으니 포켓몬을 자랑해 볼까! + $윤진: 성호야...`, }, }, "alder_iris_double": { "encounter": { - 1: `Alder: We are the strongest trainers in Unova! - $Iris: Fights against strong trainers are the best!`, + 1: `노간주: 우리는 하나 지방 최강의 트레이너들이란다! + $아이리스: 이렇게 강한 트레이너와 싸울 수 있어서 정말 기뻐~!!`, }, "victory": { - 1: `Alder: Wow! You are super strong! - $Iris: We will win next time!`, + 1: `노간주: 장하구나! 실로 견줄 자가 천하에 없도다! + $아이리스: 다음 번엔 우리가 꼭 이길 거야~!`, }, }, "iris_alder_double": { "encounter": { - 1: `Iris: Welcome Challenger! I am THE Unova Champion! - $Alder: Iris, aren't you a bit too excited?`, + 1: `아이리스: 어서 와, 도전자! 내가 바로 하나 지방 챔피언이야~! + $노간주: 아이리스야, 너무 흥분한 것 아니냐?`, }, "victory": { - 1: `Iris: A loss like this is not easy to take... - $Alder: But we will only get stronger with every loss!`, + 1: `아이리스: 후와아아아아... 최선을 다했는데도... 우리가 져버렸네! + $노간주: 하지만 우리의 패배를 발판 삼아 나아가리라!`, }, }, "piers_marnie_double": { "encounter": { - 1: `Marnie: Brother, let's show them the power of Spikemuth! - $Piers: We bring darkness!`, + 1: `마리: 오빠, 스파이크마을의 힘을 보여주자! + $두송: 우리가 어둠을 불러올 것이다!`, }, "victory": { - 1: `Marnie: You brought light to our darkness! - $Piers: Its too bright...`, + 1: `마리: 네가 우리의 어둠에 빛을 불러왔구나! + $두송: 여긴 너무 밝네...`, }, }, "marnie_piers_double": { "encounter": { - 1: `Piers: Ready for a concert? - $Marnie: Brother... They are here to fight, not to sing...`, + 1: `두송: 큰서트 즐길 준비 됐어? + $마리: 오빠... 얘들은 노래가 아니라 승부를 하러 왔어...`, }, "victory": { - 1: `Piers: Now that was a great concert! - $Marnie: Brother...`, + 1: `두송: 훌륭한 콘서트였다! + $마리: 오빠...`, }, }, }; From c499f351d1972397695b00398a81e5c4897406e1 Mon Sep 17 00:00:00 2001 From: Tempoanon <163687446+Tempo-anon@users.noreply.github.com> Date: Tue, 4 Jun 2024 00:51:36 -0400 Subject: [PATCH 029/129] [Bug] Add missing snowscape TM mons (#1742) --- src/data/tms.ts | 49 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/src/data/tms.ts b/src/data/tms.ts index 6e55ea496ba..dfde4ef948e 100644 --- a/src/data/tms.ts +++ b/src/data/tms.ts @@ -63123,9 +63123,12 @@ export const tmSpecies: TmSpecies = { [Moves.SNOWSCAPE]: [ Species.SLOWPOKE, Species.SLOWBRO, + Species.SEEL, + Species.DEWGONG, Species.SHELLDER, Species.CLOYSTER, Species.CHANSEY, + Species.LAPRAS, Species.ARTICUNO, Species.DRAGONITE, Species.MEW, @@ -63140,6 +63143,7 @@ export const tmSpecies: TmSpecies = { Species.PILOSWINE, Species.DELIBIRD, Species.BLISSEY, + Species.SUICUNE, Species.WINGULL, Species.PELIPPER, Species.SPOINK, @@ -63148,11 +63152,20 @@ export const tmSpecies: TmSpecies = { Species.SNORUNT, Species.GLALIE, Species.LUVDISC, + Species.REGICE, Species.PIPLUP, Species.PRINPLUP, Species.EMPOLEON, - Species.SHELLOS, - Species.GASTRODON, + [ + Species.SHELLOS, + "east", + "west", + ], + [ + Species.GASTRODON, + "east", + "west", + ], Species.MISMAGIUS, Species.HAPPINY, Species.SNOVER, @@ -63170,7 +63183,17 @@ export const tmSpecies: TmSpecies = { Species.CUBCHOO, Species.BEARTIC, Species.CRYOGONAL, - Species.TORNADUS, + [ + Species.TORNADUS, + "incarnate", + "therian", + ], + [ + Species.KYUREM, + "", + "black", + "white", + ], Species.FROAKIE, Species.FROGADIER, [ @@ -63184,8 +63207,13 @@ export const tmSpecies: TmSpecies = { Species.BERGMITE, Species.AVALUGG, Species.DIANCIE, + Species.PRIMARINA, Species.CRABOMINABLE, - Species.MAGEARNA, + [ + Species.MAGEARNA, + "", + "original", + ], Species.INTELEON, Species.FROSMOTH, Species.EISCUE, @@ -63633,12 +63661,21 @@ export const tmSpecies: TmSpecies = { Species.WEAVILE, Species.GLACEON, Species.FROSLASS, - Species.PALKIA, + [ + Species.PALKIA, + "", + "origin", + ], Species.ARCEUS, Species.OSHAWOTT, Species.DEWOTT, Species.SAMUROTT, - Species.BASCULIN, + [ + Species.BASCULIN, + "red-striped", + "blue-striped", + "white-striped", + ], Species.DUCKLETT, Species.SWANNA, Species.ALOMOMOLA, From a01cb96de65b8431f74e8bdd146d66de5a3ec30c Mon Sep 17 00:00:00 2001 From: Madmadness65 Date: Tue, 4 Jun 2024 00:33:57 -0500 Subject: [PATCH 030/129] Allow Pumpkaboo & Gourgeist forms to be caught They do not have sprite differences currently, so they won't be able to be identified at a glance, but they do have the canon stat differences between "sizes". --- src/battle-scene.ts | 2 ++ src/data/pokemon-species.ts | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 12e0bcb699b..957052d9881 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1111,6 +1111,8 @@ export default class BattleScene extends SceneBase { case Species.FLOETTE: case Species.FLORGES: case Species.FURFROU: + case Species.PUMPKABOO: + case Species.GOURGEIST: case Species.ORICORIO: case Species.MAGEARNA: case Species.ZARUDE: diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index cde6b741423..793f4ff9927 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -2010,15 +2010,15 @@ export function initSpecies() { new PokemonSpecies(Species.TREVENANT, 6, false, false, false, "Elder Tree Pokémon", Type.GHOST, Type.GRASS, 1.5, 71, Abilities.NATURAL_CURE, Abilities.FRISK, Abilities.HARVEST, 474, 85, 110, 76, 65, 82, 56, 60, 50, 166, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.PUMPKABOO, 6, false, false, false, "Pumpkin Pokémon", Type.GHOST, Type.GRASS, 0.4, 5, Abilities.PICKUP, Abilities.FRISK, Abilities.INSOMNIA, 335, 49, 66, 70, 44, 55, 51, 120, 50, 67, GrowthRate.MEDIUM_FAST, 50, false, false, new PokemonForm("Average Size", "", Type.GHOST, Type.GRASS, 0.4, 5, Abilities.PICKUP, Abilities.FRISK, Abilities.INSOMNIA, 335, 49, 66, 70, 44, 55, 51, 120, 50, 67, false, null, true), - new PokemonForm("Small Size", "small", Type.GHOST, Type.GRASS, 0.3, 3.5, Abilities.PICKUP, Abilities.FRISK, Abilities.INSOMNIA, 335, 44, 66, 70, 44, 55, 56, 120, 50, 67, false, null, true), - new PokemonForm("Large Size", "large", Type.GHOST, Type.GRASS, 0.5, 7.5, Abilities.PICKUP, Abilities.FRISK, Abilities.INSOMNIA, 335, 54, 66, 70, 44, 55, 46, 120, 50, 67, false, null, true), - new PokemonForm("Super Size", "super", Type.GHOST, Type.GRASS, 0.8, 15, Abilities.PICKUP, Abilities.FRISK, Abilities.INSOMNIA, 335, 59, 66, 70, 44, 55, 41, 120, 50, 67, false, null, true), + new PokemonForm("Small Size", "small", Type.GHOST, Type.GRASS, 0.3, 3.5, Abilities.PICKUP, Abilities.FRISK, Abilities.INSOMNIA, 335, 44, 66, 70, 44, 55, 56, 120, 50, 67, false, "", true), + new PokemonForm("Large Size", "large", Type.GHOST, Type.GRASS, 0.5, 7.5, Abilities.PICKUP, Abilities.FRISK, Abilities.INSOMNIA, 335, 54, 66, 70, 44, 55, 46, 120, 50, 67, false, "", true), + new PokemonForm("Super Size", "super", Type.GHOST, Type.GRASS, 0.8, 15, Abilities.PICKUP, Abilities.FRISK, Abilities.INSOMNIA, 335, 59, 66, 70, 44, 55, 41, 120, 50, 67, false, "", true), ), new PokemonSpecies(Species.GOURGEIST, 6, false, false, false, "Pumpkin Pokémon", Type.GHOST, Type.GRASS, 0.9, 12.5, Abilities.PICKUP, Abilities.FRISK, Abilities.INSOMNIA, 494, 65, 90, 122, 58, 75, 84, 60, 50, 173, GrowthRate.MEDIUM_FAST, 50, false, false, new PokemonForm("Average Size", "", Type.GHOST, Type.GRASS, 0.9, 12.5, Abilities.PICKUP, Abilities.FRISK, Abilities.INSOMNIA, 494, 65, 90, 122, 58, 75, 84, 60, 50, 173, false, null, true), - new PokemonForm("Small Size", "small", Type.GHOST, Type.GRASS, 0.7, 9.5, Abilities.PICKUP, Abilities.FRISK, Abilities.INSOMNIA, 494, 55, 85, 122, 58, 75, 99, 60, 50, 173, false, null, true), - new PokemonForm("Large Size", "large", Type.GHOST, Type.GRASS, 1.1, 14, Abilities.PICKUP, Abilities.FRISK, Abilities.INSOMNIA, 494, 75, 95, 122, 58, 75, 69, 60, 50, 173, false, null, true), - new PokemonForm("Super Size", "super", Type.GHOST, Type.GRASS, 1.7, 39, Abilities.PICKUP, Abilities.FRISK, Abilities.INSOMNIA, 494, 85, 100, 122, 58, 75, 54, 60, 50, 173, false, null, true), + new PokemonForm("Small Size", "small", Type.GHOST, Type.GRASS, 0.7, 9.5, Abilities.PICKUP, Abilities.FRISK, Abilities.INSOMNIA, 494, 55, 85, 122, 58, 75, 99, 60, 50, 173, false, "", true), + new PokemonForm("Large Size", "large", Type.GHOST, Type.GRASS, 1.1, 14, Abilities.PICKUP, Abilities.FRISK, Abilities.INSOMNIA, 494, 75, 95, 122, 58, 75, 69, 60, 50, 173, false, "", true), + new PokemonForm("Super Size", "super", Type.GHOST, Type.GRASS, 1.7, 39, Abilities.PICKUP, Abilities.FRISK, Abilities.INSOMNIA, 494, 85, 100, 122, 58, 75, 54, 60, 50, 173, false, "", true), ), new PokemonSpecies(Species.BERGMITE, 6, false, false, false, "Ice Chunk Pokémon", Type.ICE, null, 1, 99.5, Abilities.OWN_TEMPO, Abilities.ICE_BODY, Abilities.STURDY, 304, 55, 69, 85, 32, 35, 28, 190, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.AVALUGG, 6, false, false, false, "Iceberg Pokémon", Type.ICE, null, 2, 505, Abilities.OWN_TEMPO, Abilities.ICE_BODY, Abilities.STURDY, 514, 95, 117, 184, 44, 46, 28, 55, 50, 180, GrowthRate.MEDIUM_FAST, 50, false), From 38f533276fb706bffacf191c714905c87656a26b Mon Sep 17 00:00:00 2001 From: chaosgrimmon <31082757+chaosgrimmon@users.noreply.github.com> Date: Tue, 4 Jun 2024 01:53:58 -0400 Subject: [PATCH 031/129] [Bug] Lustrous Globe named Lustrous Orb (#1781) * [Bug] Lustrous Globe, not Orb for Palkia Origin * [Bug] Lustrous Globe named Lustrous Orb Translation sourced from https://www.pokewiki.de/Wei%C3%9Fkristall * [Bug] Lustrous Globe named Lustrous Orb * [Bug] Lustrous Globe named Lustrous Orb Prior translation appears correct, cross-referenced with https://www.wikidex.net/wiki/Gran_lustresfera * [Bug] Lustrous Globe named Lustrous Orb Translation sourced from https://www.pokepedia.fr/Globe_Perl%C3%A9 * [Bug] Lustrous Globe named Lustrous Orb Translation sourced from https://wiki.pokemoncentral.it/Splendisferoide * [Bug] Lustrous Globe named Lustrous Orb Prior translation seems correct, cross-referenced with https://pokemon.fandom.com/ko/wiki/%ED%81%B0%EB%B0%B1%EC%98%A5 * [Bug] Lustrous Globe named Lustrous Orb No source found. Brilhante take from canonical translation of https://bulbapedia.bulbagarden.net/wiki/Lustrous_Orb * [Bug] Lustrous Globe named Lustrous Orb Translation sourced from https://bulbapedia.bulbagarden.net/wiki/Lustrous_Globe * [Bug] Lustrous Globe named Lustrous Orb Translation sourced from https://bulbapedia.bulbagarden.net/wiki/Lustrous_Globe --- src/data/pokemon-forms.ts | 4 ++-- src/locales/de/modifier-type.ts | 2 +- src/locales/en/modifier-type.ts | 2 +- src/locales/es/modifier-type.ts | 2 +- src/locales/fr/modifier-type.ts | 2 +- src/locales/it/modifier-type.ts | 2 +- src/locales/ko/modifier-type.ts | 2 +- src/locales/pt_BR/modifier-type.ts | 2 +- src/locales/zh_CN/modifier-type.ts | 2 +- src/locales/zh_TW/modifier-type.ts | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index ebcf1c34f33..d87e4d90299 100644 --- a/src/data/pokemon-forms.ts +++ b/src/data/pokemon-forms.ts @@ -66,7 +66,7 @@ export enum FormChangeItem { HARD_METEORITE, SMOOTH_METEORITE, ADAMANT_CRYSTAL, - LUSTROUS_ORB, + LUSTROUS_GLOBE, GRISEOUS_CORE, REVEAL_GLASS, GRACIDEA, @@ -520,7 +520,7 @@ export const pokemonFormChanges: PokemonFormChanges = { new SpeciesFormChange(Species.DIALGA, "", SpeciesFormKey.ORIGIN, new SpeciesFormChangeItemTrigger(FormChangeItem.ADAMANT_CRYSTAL)) ], [Species.PALKIA]: [ - new SpeciesFormChange(Species.PALKIA, "", SpeciesFormKey.ORIGIN, new SpeciesFormChangeItemTrigger(FormChangeItem.LUSTROUS_ORB)) + new SpeciesFormChange(Species.PALKIA, "", SpeciesFormKey.ORIGIN, new SpeciesFormChangeItemTrigger(FormChangeItem.LUSTROUS_GLOBE)) ], [Species.GIRATINA]: [ new SpeciesFormChange(Species.GIRATINA, "altered", SpeciesFormKey.ORIGIN, new SpeciesFormChangeItemTrigger(FormChangeItem.GRISEOUS_CORE)) diff --git a/src/locales/de/modifier-type.ts b/src/locales/de/modifier-type.ts index a03eb64d07e..055751970ab 100644 --- a/src/locales/de/modifier-type.ts +++ b/src/locales/de/modifier-type.ts @@ -366,7 +366,7 @@ export const modifierType: ModifierTypeTranslationEntries = { "HARD_METEORITE": "Harter Meteorit", "SMOOTH_METEORITE": "Glatter Meteorit", "ADAMANT_CRYSTAL": "Adamantkristall", - "LUSTROUS_ORB": "Weiß-Orb", + "LUSTROUS_GLOBE": "Weißkristall", "GRISEOUS_CORE": "Platinumkristall", "REVEAL_GLASS": "Wahrspiegel", "GRACIDEA": "Gracidea", diff --git a/src/locales/en/modifier-type.ts b/src/locales/en/modifier-type.ts index e5c37843a99..e988f0a5250 100644 --- a/src/locales/en/modifier-type.ts +++ b/src/locales/en/modifier-type.ts @@ -365,7 +365,7 @@ export const modifierType: ModifierTypeTranslationEntries = { "HARD_METEORITE": "Hard Meteorite", "SMOOTH_METEORITE": "Smooth Meteorite", "ADAMANT_CRYSTAL": "Adamant Crystal", - "LUSTROUS_ORB": "Lustrous Orb", + "LUSTROUS_GLOBE": "Lustrous Globe", "GRISEOUS_CORE": "Griseous Core", "REVEAL_GLASS": "Reveal Glass", "GRACIDEA": "Gracidea", diff --git a/src/locales/es/modifier-type.ts b/src/locales/es/modifier-type.ts index 8e48b8e6627..e5f77549737 100644 --- a/src/locales/es/modifier-type.ts +++ b/src/locales/es/modifier-type.ts @@ -365,7 +365,7 @@ export const modifierType: ModifierTypeTranslationEntries = { "HARD_METEORITE": "Meteorito Duro", "SMOOTH_METEORITE": "Meteorito Suave", "ADAMANT_CRYSTAL": "Gran Diamansfera", - "LUSTROUS_ORB": "Gran Lustresfera", + "LUSTROUS_GLOBE": "Gran Lustresfera", "GRISEOUS_CORE": "Gran Griseosfera", "REVEAL_GLASS": "Espejo Veraz", "GRACIDEA": "Gracídea", diff --git a/src/locales/fr/modifier-type.ts b/src/locales/fr/modifier-type.ts index 7b82b1d4ad5..a6960c4f0c5 100644 --- a/src/locales/fr/modifier-type.ts +++ b/src/locales/fr/modifier-type.ts @@ -365,7 +365,7 @@ export const modifierType: ModifierTypeTranslationEntries = { "HARD_METEORITE": "Méteorite Solide", "SMOOTH_METEORITE": "Méteorite Lisse", "ADAMANT_CRYSTAL": "Globe Adamant", - "LUSTROUS_ORB": "Orbe Perlé", + "LUSTROUS_GLOBE": "Globe Perlé", "GRISEOUS_CORE": "Globe Platiné", "REVEAL_GLASS": "Miroir Sacré", "GRACIDEA": "Gracidée", diff --git a/src/locales/it/modifier-type.ts b/src/locales/it/modifier-type.ts index ca703073d63..668df9601fc 100644 --- a/src/locales/it/modifier-type.ts +++ b/src/locales/it/modifier-type.ts @@ -365,7 +365,7 @@ export const modifierType: ModifierTypeTranslationEntries = { "HARD_METEORITE": "Meteorite Dura", "SMOOTH_METEORITE": "Meteorite Liscia", "ADAMANT_CRYSTAL": "Adamasferoide", - "LUSTROUS_ORB": "Splendisfera", + "LUSTROUS_GLOBE": "Splendisferoide", "GRISEOUS_CORE": "Grigiosferoide", "REVEAL_GLASS": "Verispecchio", "GRACIDEA": "Gracidea", diff --git a/src/locales/ko/modifier-type.ts b/src/locales/ko/modifier-type.ts index f3b9ece6e81..e2d90ed1ff9 100644 --- a/src/locales/ko/modifier-type.ts +++ b/src/locales/ko/modifier-type.ts @@ -365,7 +365,7 @@ export const modifierType: ModifierTypeTranslationEntries = { "HARD_METEORITE": "단단한운석", "SMOOTH_METEORITE": "부드러운운석", "ADAMANT_CRYSTAL": "큰금강옥", - "LUSTROUS_ORB": "큰백옥", + "LUSTROUS_GLOBE": "큰백옥", "GRISEOUS_CORE": "큰백금옥", "REVEAL_GLASS": "비추는거울", "GRACIDEA": "그라시데아꽃", diff --git a/src/locales/pt_BR/modifier-type.ts b/src/locales/pt_BR/modifier-type.ts index e023fc7a6ba..ea384282f15 100644 --- a/src/locales/pt_BR/modifier-type.ts +++ b/src/locales/pt_BR/modifier-type.ts @@ -365,7 +365,7 @@ export const modifierType: ModifierTypeTranslationEntries = { "HARD_METEORITE": "Meteorito Duro", "SMOOTH_METEORITE": " Meteorito Liso", "ADAMANT_CRYSTAL": "Cristal Adamante", - "LUSTROUS_ORB": "Orbe Pérola", + "LUSTROUS_GLOBE": "Globo Brilhante", "GRISEOUS_CORE": "Núcleo Platinado", "REVEAL_GLASS": "Espelho da Verdade", "GRACIDEA": "Gracídea", diff --git a/src/locales/zh_CN/modifier-type.ts b/src/locales/zh_CN/modifier-type.ts index 39e8ed9bfca..c51ce2cd788 100644 --- a/src/locales/zh_CN/modifier-type.ts +++ b/src/locales/zh_CN/modifier-type.ts @@ -365,7 +365,7 @@ export const modifierType: ModifierTypeTranslationEntries = { "HARD_METEORITE": "坚硬陨石", "SMOOTH_METEORITE": "光滑陨石", "ADAMANT_CRYSTAL": "大金刚宝玉", - "LUSTROUS_ORB": "白玉宝珠", + "LUSTROUS_GLOBE": "大白宝玉", "GRISEOUS_CORE": "大白金宝玉", "REVEAL_GLASS": "现形镜", "GRACIDEA": "葛拉西蒂亚花", diff --git a/src/locales/zh_TW/modifier-type.ts b/src/locales/zh_TW/modifier-type.ts index 55a4e268ae5..854e65212f4 100644 --- a/src/locales/zh_TW/modifier-type.ts +++ b/src/locales/zh_TW/modifier-type.ts @@ -419,7 +419,7 @@ export const modifierType: ModifierTypeTranslationEntries = { HARD_METEORITE: "堅硬隕石", SMOOTH_METEORITE: "光滑隕石", ADAMANT_CRYSTAL: "大金剛寶玉", - LUSTROUS_ORB: "白玉寶珠", + LUSTROUS_GLOBE: "大白寶玉", GRISEOUS_CORE: "大白金寶玉", REVEAL_GLASS: "現形鏡", GRACIDEA: "葛拉西蒂亞花", From 7da8bdfdb62038319d18b6e95a76c47723bdc98d Mon Sep 17 00:00:00 2001 From: chaosgrimmon <31082757+chaosgrimmon@users.noreply.github.com> Date: Tue, 4 Jun 2024 09:26:03 -0400 Subject: [PATCH 032/129] [Bug] Epic Drifblim using Drifloon sprite (#1788) --- public/images/pokemon/variant/_masterlist.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/images/pokemon/variant/_masterlist.json b/public/images/pokemon/variant/_masterlist.json index 333ed303443..adca124fc89 100644 --- a/public/images/pokemon/variant/_masterlist.json +++ b/public/images/pokemon/variant/_masterlist.json @@ -4065,7 +4065,7 @@ "426": [ 0, 1, - 2 + 1 ], "427": [ 0, From 7ac7c2b63b5eaebcb4b663bc3a96553e2ef3be5e Mon Sep 17 00:00:00 2001 From: Matthew Olker Date: Tue, 4 Jun 2024 09:54:10 -0400 Subject: [PATCH 033/129] fix mobile settings touch controls --- index.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.css b/index.css index 1a695fa0c4f..9ca2cd60dff 100644 --- a/index.css +++ b/index.css @@ -146,8 +146,8 @@ body { margin-left: 10%; } -#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadRectBtnContainer > .apadSqBtn, -#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadSqBtnContainer +#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_ACCESSIBILITY']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadRectBtnContainer > .apadSqBtn, +#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_ACCESSIBILITY']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadSqBtnContainer { display: none; } From ef8b6aa4f9fee580d178b8e5333b41286825d19c Mon Sep 17 00:00:00 2001 From: hayuna Date: Tue, 4 Jun 2024 17:29:38 +0200 Subject: [PATCH 034/129] [Feature] Add possibility to override whole user party (#1643) * Add possibility to override whole user party * Update species overriding * Replace SPARTER_SPECIES_OVERRIDE with array * Replace SPARTER_SPECIES_OVERRIDE with array * Add possibility to override species forms * Add possibility to override species forms * Fix eslint styling * Add possibility to override Abilities for party * Override status, gender, moveset * Add possibility to override shinies * Fix CI --- src/battle-scene.ts | 4 +- src/data/daily-run.ts | 2 +- src/field/pokemon.ts | 35 ++++++++------ src/overrides.ts | 103 +++++++++++++++++++++++++++++++++++------- src/phases.ts | 19 +++++--- 5 files changed, 123 insertions(+), 40 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 957052d9881..d4b74ea0979 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -686,8 +686,8 @@ export default class BattleScene extends SceneBase { return findInParty(this.getParty()) || findInParty(this.getEnemyParty()); } - addPlayerPokemon(species: PokemonSpecies, level: integer, abilityIndex: integer, formIndex: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData, postProcess?: (playerPokemon: PlayerPokemon) => void): PlayerPokemon { - const pokemon = new PlayerPokemon(this, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource); + addPlayerPokemon(species: PokemonSpecies, level: integer, abilityIndex: integer, formIndex: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData, postProcess?: (playerPokemon: PlayerPokemon) => void, indexInParty?: number): PlayerPokemon { + const pokemon = new PlayerPokemon(this, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource, indexInParty); if (postProcess) { postProcess(pokemon); } diff --git a/src/data/daily-run.ts b/src/data/daily-run.ts index cb4bfddc685..4683792aeb8 100644 --- a/src/data/daily-run.ts +++ b/src/data/daily-run.ts @@ -61,7 +61,7 @@ export function getDailyRunStarters(scene: BattleScene, seed: string): Starter[] function getDailyRunStarter(scene: BattleScene, starterSpeciesForm: PokemonSpeciesForm, startingLevel: integer): Starter { const starterSpecies = starterSpeciesForm instanceof PokemonSpecies ? starterSpeciesForm : getPokemonSpecies(starterSpeciesForm.speciesId); const formIndex = starterSpeciesForm instanceof PokemonSpecies ? undefined : starterSpeciesForm.formIndex; - const pokemon = new PlayerPokemon(scene, starterSpecies, startingLevel, undefined, formIndex, undefined, undefined, undefined, undefined, undefined, undefined); + const pokemon = new PlayerPokemon(scene, starterSpecies, startingLevel, undefined, formIndex, undefined, undefined, undefined, undefined, undefined, undefined, undefined); const starter: Starter = { species: starterSpecies, dexAttr: pokemon.getDexAttr(), diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 8065b4633b7..bc118bcfdbe 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -98,6 +98,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public battleData: PokemonBattleData; public battleSummonData: PokemonBattleSummonData; public turnData: PokemonTurnData; + public indexInParty: number; public fieldPosition: FieldPosition; @@ -106,7 +107,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { private shinySparkle: Phaser.GameObjects.Sprite; - constructor(scene: BattleScene, x: number, y: number, species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData) { + constructor(scene: BattleScene, x: number, y: number, species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData, indexInParty?: number) { super(scene, x, y); if (!species.isObtainable() && this.isPlayer()) { @@ -139,6 +140,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (variant !== undefined) { this.variant = variant; } + if (indexInParty !== undefined) { + this.indexInParty = indexInParty; + } this.exp = dataSource?.exp || getLevelTotalExp(this.level, species.growthRate); this.levelExp = dataSource?.levelExp || 0; if (dataSource) { @@ -816,7 +820,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { : this.moveset; // Overrides moveset based on arrays specified in overrides.ts - const overrideArray: Array = this.isPlayer() ? Overrides.MOVESET_OVERRIDE : Overrides.OPP_MOVESET_OVERRIDE; + const overrideArray: Array = this.isPlayer() ? Overrides.STARTER_OVERRIDE[this.indexInParty]?.moveset : Overrides.OPP_MOVESET_OVERRIDE; if (overrideArray.length > 0) { overrideArray.forEach((move: Moves, index: number) => { const ppUsed = this.moveset[index]?.ppUsed || 0; @@ -901,9 +905,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!ignoreOverride && this.summonData?.ability) { return allAbilities[this.summonData.ability]; } - if (Overrides.ABILITY_OVERRIDE && this.isPlayer()) { - return allAbilities[Overrides.ABILITY_OVERRIDE]; - } if (Overrides.OPP_ABILITY_OVERRIDE && !this.isPlayer()) { return allAbilities[Overrides.OPP_ABILITY_OVERRIDE]; } @@ -911,6 +912,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)]; } let abilityId = this.getSpeciesForm(ignoreOverride).getAbility(this.abilityIndex); + if (Overrides.STARTER_OVERRIDE[this.indexInParty]?.ability) { + abilityId = Overrides.STARTER_OVERRIDE[this.indexInParty].ability; + } + if (abilityId === Abilities.NONE) { abilityId = this.species.ability1; } @@ -925,8 +930,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns {Ability} The passive ability of the pokemon */ getPassiveAbility(): Ability { - if (Overrides.PASSIVE_ABILITY_OVERRIDE && this.isPlayer()) { - return allAbilities[Overrides.PASSIVE_ABILITY_OVERRIDE]; + if (Overrides.STARTER_OVERRIDE[this.indexInParty]?.passiveAbility && this.isPlayer()) { + return allAbilities[Overrides.STARTER_OVERRIDE[this.indexInParty].passiveAbility]; } if (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE && !this.isPlayer()) { return allAbilities[Overrides.OPP_PASSIVE_ABILITY_OVERRIDE]; @@ -948,7 +953,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ hasPassive(): boolean { // returns override if valid for current case - if ((Overrides.PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE && this.isPlayer()) || + if ((Overrides.STARTER_OVERRIDE[this.indexInParty]?.passiveAbility !== Abilities.NONE && this.isPlayer()) || (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE && !this.isPlayer())) { return true; } @@ -2822,19 +2827,21 @@ export default interface Pokemon { export class PlayerPokemon extends Pokemon { public compatibleTms: Moves[]; + public indexInParty: number; - constructor(scene: BattleScene, species: PokemonSpecies, level: integer, abilityIndex: integer, formIndex: integer, gender: Gender, shiny: boolean, variant: Variant, ivs: integer[], nature: Nature, dataSource: Pokemon | PokemonData) { + constructor(scene: BattleScene, species: PokemonSpecies, level: integer, abilityIndex: integer, formIndex: integer, gender: Gender, shiny: boolean, variant: Variant, ivs: integer[], nature: Nature, dataSource: Pokemon | PokemonData, indexInParty: number) { super(scene, 106, 148, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource); - if (Overrides.STATUS_OVERRIDE) { - this.status = new Status(Overrides.STATUS_OVERRIDE); + if (Overrides.STARTER_OVERRIDE[indexInParty]?.status) { + this.status = new Status(Overrides.STARTER_OVERRIDE[indexInParty].status); } + this.indexInParty = indexInParty; - if (Overrides.SHINY_OVERRIDE) { + if (Overrides.STARTER_OVERRIDE[this.indexInParty]?.shiny) { this.shiny = true; this.initShinySparkle(); - if (Overrides.VARIANT_OVERRIDE) { - this.variant = Overrides.VARIANT_OVERRIDE; + if (Overrides.STARTER_OVERRIDE[this.indexInParty]?.shinyVariant) { + this.variant = Overrides.STARTER_OVERRIDE[this.indexInParty].shinyVariant; } } diff --git a/src/overrides.ts b/src/overrides.ts index 8f5d4978be3..d866973e669 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -52,24 +52,95 @@ export const POKEBALL_OVERRIDE: { active: boolean, pokeballs: PokeballCounts } = * PLAYER OVERRIDES */ -// forms can be found in pokemon-species.ts -export const STARTER_FORM_OVERRIDE: integer = 0; // default 5 or 20 for Daily export const STARTING_LEVEL_OVERRIDE: integer = 0; -/** - * SPECIES OVERRIDE - * will only apply to the first starter in your party or each enemy pokemon - * default is 0 to not override - * @example SPECIES_OVERRIDE = Species.Bulbasaur; - */ -export const STARTER_SPECIES_OVERRIDE: Species | integer = 0; -export const ABILITY_OVERRIDE: Abilities = Abilities.NONE; -export const PASSIVE_ABILITY_OVERRIDE: Abilities = Abilities.NONE; -export const STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE; -export const GENDER_OVERRIDE: Gender = null; -export const MOVESET_OVERRIDE: Array = []; -export const SHINY_OVERRIDE: boolean = false; -export const VARIANT_OVERRIDE: Variant = 0; +interface StarterOverride { + /** + * SPECIES OVERRIDE + * will apply to each starter in your party + * default is 0 to not override + * @example STARTER_OVERRIDE.species = Species.Bulbasaur; + */ + + species: Species | integer; + // forms can be found in pokemon-species.ts + form: integer; + ability: Abilities; + passiveAbility: Abilities; + status: StatusEffect; + gender: Gender; + moveset: Moves[]; + shiny: boolean; + shinyVariant: Variant; +} +export const STARTER_OVERRIDE: StarterOverride[] = [ + { + species: 0, + form: 0, + ability: Abilities.NONE, + passiveAbility: Abilities.NONE, + status: StatusEffect.NONE, + gender: null, + moveset: [], + shiny: false, + shinyVariant: 0, + }, + { + species: 0, + form: 0, + ability: Abilities.NONE, + passiveAbility: Abilities.NONE, + status: StatusEffect.NONE, + gender: null, + moveset: [], + shiny: false, + shinyVariant: 0, + }, + { + species: 0, + form: 0, + ability: Abilities.NONE, + passiveAbility: Abilities.NONE, + status: StatusEffect.NONE, + gender: null, + moveset: [], + shiny: false, + shinyVariant: 0, + }, + { + species: 0, + form: 0, + ability: Abilities.NONE, + passiveAbility: Abilities.NONE, + status: StatusEffect.NONE, + gender: null, + moveset: [], + shiny: false, + shinyVariant: 0, + }, + { + species: 0, + form: 0, + ability: Abilities.NONE, + passiveAbility: Abilities.NONE, + status: StatusEffect.NONE, + gender: null, + moveset: [], + shiny: false, + shinyVariant: 0, + }, + { + species: 0, + form: 0, + ability: Abilities.NONE, + passiveAbility: Abilities.NONE, + status: StatusEffect.NONE, + gender: null, + moveset: [], + shiny: false, + shinyVariant: 0, + } +]; /** * OPPONENT / ENEMY OVERRIDES diff --git a/src/phases.ts b/src/phases.ts index 110c4155849..c1e4a0e17cf 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -529,22 +529,27 @@ export class SelectStarterPhase extends Phase { const party = this.scene.getParty(); const loadPokemonAssets: Promise[] = []; starters.forEach((starter: Starter, i: integer) => { - if (!i && Overrides.STARTER_SPECIES_OVERRIDE) { - starter.species = getPokemonSpecies(Overrides.STARTER_SPECIES_OVERRIDE as Species); + if (Overrides.STARTER_OVERRIDE[i]?.species) { + starter.species = getPokemonSpecies(Overrides.STARTER_OVERRIDE[i].species as Species); } const starterProps = this.scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); let starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0)); - if (!i && Overrides.STARTER_SPECIES_OVERRIDE) { - starterFormIndex = Overrides.STARTER_FORM_OVERRIDE; + if (Overrides.STARTER_OVERRIDE[i]?.form) { + starterFormIndex = Overrides.STARTER_OVERRIDE[i].form; + const availableForms = starter.species.forms.length; + // prevent use forms which does not exist for species + if (Overrides.STARTER_OVERRIDE[i].form >= availableForms) { + starterFormIndex = 0; + } } let starterGender = starter.species.malePercent !== null ? !starterProps.female ? Gender.MALE : Gender.FEMALE : Gender.GENDERLESS; - if (Overrides.GENDER_OVERRIDE !== null) { - starterGender = Overrides.GENDER_OVERRIDE; + if (Overrides.STARTER_OVERRIDE[i]?.gender !== null) { + starterGender = Overrides.STARTER_OVERRIDE[i].gender; } const starterIvs = this.scene.gameData.dexData[starter.species.speciesId].ivs.slice(0); - const starterPokemon = this.scene.addPlayerPokemon(starter.species, this.scene.gameMode.getStartingLevel(), starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, starterIvs, starter.nature); + const starterPokemon = this.scene.addPlayerPokemon(starter.species, this.scene.gameMode.getStartingLevel(), starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, starterIvs, starter.nature, null, null, i); starterPokemon.tryPopulateMoveset(starter.moveset); if (starter.passive) { starterPokemon.passive = true; From 1c73b3b08448d059e8d65ea1509822f7687e3de0 Mon Sep 17 00:00:00 2001 From: Tempoanon <163687446+Tempo-anon@users.noreply.github.com> Date: Tue, 4 Jun 2024 11:39:02 -0400 Subject: [PATCH 035/129] Revert "[Feature] Add possibility to override whole user party (#1643)" (#1796) This reverts commit ef8b6aa4f9fee580d178b8e5333b41286825d19c. --- src/battle-scene.ts | 4 +- src/data/daily-run.ts | 2 +- src/field/pokemon.ts | 35 ++++++-------- src/overrides.ts | 103 +++++++----------------------------------- src/phases.ts | 19 +++----- 5 files changed, 40 insertions(+), 123 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index d4b74ea0979..957052d9881 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -686,8 +686,8 @@ export default class BattleScene extends SceneBase { return findInParty(this.getParty()) || findInParty(this.getEnemyParty()); } - addPlayerPokemon(species: PokemonSpecies, level: integer, abilityIndex: integer, formIndex: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData, postProcess?: (playerPokemon: PlayerPokemon) => void, indexInParty?: number): PlayerPokemon { - const pokemon = new PlayerPokemon(this, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource, indexInParty); + addPlayerPokemon(species: PokemonSpecies, level: integer, abilityIndex: integer, formIndex: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData, postProcess?: (playerPokemon: PlayerPokemon) => void): PlayerPokemon { + const pokemon = new PlayerPokemon(this, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource); if (postProcess) { postProcess(pokemon); } diff --git a/src/data/daily-run.ts b/src/data/daily-run.ts index 4683792aeb8..cb4bfddc685 100644 --- a/src/data/daily-run.ts +++ b/src/data/daily-run.ts @@ -61,7 +61,7 @@ export function getDailyRunStarters(scene: BattleScene, seed: string): Starter[] function getDailyRunStarter(scene: BattleScene, starterSpeciesForm: PokemonSpeciesForm, startingLevel: integer): Starter { const starterSpecies = starterSpeciesForm instanceof PokemonSpecies ? starterSpeciesForm : getPokemonSpecies(starterSpeciesForm.speciesId); const formIndex = starterSpeciesForm instanceof PokemonSpecies ? undefined : starterSpeciesForm.formIndex; - const pokemon = new PlayerPokemon(scene, starterSpecies, startingLevel, undefined, formIndex, undefined, undefined, undefined, undefined, undefined, undefined, undefined); + const pokemon = new PlayerPokemon(scene, starterSpecies, startingLevel, undefined, formIndex, undefined, undefined, undefined, undefined, undefined, undefined); const starter: Starter = { species: starterSpecies, dexAttr: pokemon.getDexAttr(), diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index bc118bcfdbe..8065b4633b7 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -98,7 +98,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public battleData: PokemonBattleData; public battleSummonData: PokemonBattleSummonData; public turnData: PokemonTurnData; - public indexInParty: number; public fieldPosition: FieldPosition; @@ -107,7 +106,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { private shinySparkle: Phaser.GameObjects.Sprite; - constructor(scene: BattleScene, x: number, y: number, species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData, indexInParty?: number) { + constructor(scene: BattleScene, x: number, y: number, species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData) { super(scene, x, y); if (!species.isObtainable() && this.isPlayer()) { @@ -140,9 +139,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (variant !== undefined) { this.variant = variant; } - if (indexInParty !== undefined) { - this.indexInParty = indexInParty; - } this.exp = dataSource?.exp || getLevelTotalExp(this.level, species.growthRate); this.levelExp = dataSource?.levelExp || 0; if (dataSource) { @@ -820,7 +816,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { : this.moveset; // Overrides moveset based on arrays specified in overrides.ts - const overrideArray: Array = this.isPlayer() ? Overrides.STARTER_OVERRIDE[this.indexInParty]?.moveset : Overrides.OPP_MOVESET_OVERRIDE; + const overrideArray: Array = this.isPlayer() ? Overrides.MOVESET_OVERRIDE : Overrides.OPP_MOVESET_OVERRIDE; if (overrideArray.length > 0) { overrideArray.forEach((move: Moves, index: number) => { const ppUsed = this.moveset[index]?.ppUsed || 0; @@ -905,6 +901,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!ignoreOverride && this.summonData?.ability) { return allAbilities[this.summonData.ability]; } + if (Overrides.ABILITY_OVERRIDE && this.isPlayer()) { + return allAbilities[Overrides.ABILITY_OVERRIDE]; + } if (Overrides.OPP_ABILITY_OVERRIDE && !this.isPlayer()) { return allAbilities[Overrides.OPP_ABILITY_OVERRIDE]; } @@ -912,10 +911,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)]; } let abilityId = this.getSpeciesForm(ignoreOverride).getAbility(this.abilityIndex); - if (Overrides.STARTER_OVERRIDE[this.indexInParty]?.ability) { - abilityId = Overrides.STARTER_OVERRIDE[this.indexInParty].ability; - } - if (abilityId === Abilities.NONE) { abilityId = this.species.ability1; } @@ -930,8 +925,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns {Ability} The passive ability of the pokemon */ getPassiveAbility(): Ability { - if (Overrides.STARTER_OVERRIDE[this.indexInParty]?.passiveAbility && this.isPlayer()) { - return allAbilities[Overrides.STARTER_OVERRIDE[this.indexInParty].passiveAbility]; + if (Overrides.PASSIVE_ABILITY_OVERRIDE && this.isPlayer()) { + return allAbilities[Overrides.PASSIVE_ABILITY_OVERRIDE]; } if (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE && !this.isPlayer()) { return allAbilities[Overrides.OPP_PASSIVE_ABILITY_OVERRIDE]; @@ -953,7 +948,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ hasPassive(): boolean { // returns override if valid for current case - if ((Overrides.STARTER_OVERRIDE[this.indexInParty]?.passiveAbility !== Abilities.NONE && this.isPlayer()) || + if ((Overrides.PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE && this.isPlayer()) || (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE && !this.isPlayer())) { return true; } @@ -2827,21 +2822,19 @@ export default interface Pokemon { export class PlayerPokemon extends Pokemon { public compatibleTms: Moves[]; - public indexInParty: number; - constructor(scene: BattleScene, species: PokemonSpecies, level: integer, abilityIndex: integer, formIndex: integer, gender: Gender, shiny: boolean, variant: Variant, ivs: integer[], nature: Nature, dataSource: Pokemon | PokemonData, indexInParty: number) { + constructor(scene: BattleScene, species: PokemonSpecies, level: integer, abilityIndex: integer, formIndex: integer, gender: Gender, shiny: boolean, variant: Variant, ivs: integer[], nature: Nature, dataSource: Pokemon | PokemonData) { super(scene, 106, 148, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource); - if (Overrides.STARTER_OVERRIDE[indexInParty]?.status) { - this.status = new Status(Overrides.STARTER_OVERRIDE[indexInParty].status); + if (Overrides.STATUS_OVERRIDE) { + this.status = new Status(Overrides.STATUS_OVERRIDE); } - this.indexInParty = indexInParty; - if (Overrides.STARTER_OVERRIDE[this.indexInParty]?.shiny) { + if (Overrides.SHINY_OVERRIDE) { this.shiny = true; this.initShinySparkle(); - if (Overrides.STARTER_OVERRIDE[this.indexInParty]?.shinyVariant) { - this.variant = Overrides.STARTER_OVERRIDE[this.indexInParty].shinyVariant; + if (Overrides.VARIANT_OVERRIDE) { + this.variant = Overrides.VARIANT_OVERRIDE; } } diff --git a/src/overrides.ts b/src/overrides.ts index d866973e669..8f5d4978be3 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -52,95 +52,24 @@ export const POKEBALL_OVERRIDE: { active: boolean, pokeballs: PokeballCounts } = * PLAYER OVERRIDES */ +// forms can be found in pokemon-species.ts +export const STARTER_FORM_OVERRIDE: integer = 0; // default 5 or 20 for Daily export const STARTING_LEVEL_OVERRIDE: integer = 0; -interface StarterOverride { - /** - * SPECIES OVERRIDE - * will apply to each starter in your party - * default is 0 to not override - * @example STARTER_OVERRIDE.species = Species.Bulbasaur; - */ - - species: Species | integer; - // forms can be found in pokemon-species.ts - form: integer; - ability: Abilities; - passiveAbility: Abilities; - status: StatusEffect; - gender: Gender; - moveset: Moves[]; - shiny: boolean; - shinyVariant: Variant; -} -export const STARTER_OVERRIDE: StarterOverride[] = [ - { - species: 0, - form: 0, - ability: Abilities.NONE, - passiveAbility: Abilities.NONE, - status: StatusEffect.NONE, - gender: null, - moveset: [], - shiny: false, - shinyVariant: 0, - }, - { - species: 0, - form: 0, - ability: Abilities.NONE, - passiveAbility: Abilities.NONE, - status: StatusEffect.NONE, - gender: null, - moveset: [], - shiny: false, - shinyVariant: 0, - }, - { - species: 0, - form: 0, - ability: Abilities.NONE, - passiveAbility: Abilities.NONE, - status: StatusEffect.NONE, - gender: null, - moveset: [], - shiny: false, - shinyVariant: 0, - }, - { - species: 0, - form: 0, - ability: Abilities.NONE, - passiveAbility: Abilities.NONE, - status: StatusEffect.NONE, - gender: null, - moveset: [], - shiny: false, - shinyVariant: 0, - }, - { - species: 0, - form: 0, - ability: Abilities.NONE, - passiveAbility: Abilities.NONE, - status: StatusEffect.NONE, - gender: null, - moveset: [], - shiny: false, - shinyVariant: 0, - }, - { - species: 0, - form: 0, - ability: Abilities.NONE, - passiveAbility: Abilities.NONE, - status: StatusEffect.NONE, - gender: null, - moveset: [], - shiny: false, - shinyVariant: 0, - } -]; +/** + * SPECIES OVERRIDE + * will only apply to the first starter in your party or each enemy pokemon + * default is 0 to not override + * @example SPECIES_OVERRIDE = Species.Bulbasaur; + */ +export const STARTER_SPECIES_OVERRIDE: Species | integer = 0; +export const ABILITY_OVERRIDE: Abilities = Abilities.NONE; +export const PASSIVE_ABILITY_OVERRIDE: Abilities = Abilities.NONE; +export const STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE; +export const GENDER_OVERRIDE: Gender = null; +export const MOVESET_OVERRIDE: Array = []; +export const SHINY_OVERRIDE: boolean = false; +export const VARIANT_OVERRIDE: Variant = 0; /** * OPPONENT / ENEMY OVERRIDES diff --git a/src/phases.ts b/src/phases.ts index c1e4a0e17cf..110c4155849 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -529,27 +529,22 @@ export class SelectStarterPhase extends Phase { const party = this.scene.getParty(); const loadPokemonAssets: Promise[] = []; starters.forEach((starter: Starter, i: integer) => { - if (Overrides.STARTER_OVERRIDE[i]?.species) { - starter.species = getPokemonSpecies(Overrides.STARTER_OVERRIDE[i].species as Species); + if (!i && Overrides.STARTER_SPECIES_OVERRIDE) { + starter.species = getPokemonSpecies(Overrides.STARTER_SPECIES_OVERRIDE as Species); } const starterProps = this.scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); let starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0)); - if (Overrides.STARTER_OVERRIDE[i]?.form) { - starterFormIndex = Overrides.STARTER_OVERRIDE[i].form; - const availableForms = starter.species.forms.length; - // prevent use forms which does not exist for species - if (Overrides.STARTER_OVERRIDE[i].form >= availableForms) { - starterFormIndex = 0; - } + if (!i && Overrides.STARTER_SPECIES_OVERRIDE) { + starterFormIndex = Overrides.STARTER_FORM_OVERRIDE; } let starterGender = starter.species.malePercent !== null ? !starterProps.female ? Gender.MALE : Gender.FEMALE : Gender.GENDERLESS; - if (Overrides.STARTER_OVERRIDE[i]?.gender !== null) { - starterGender = Overrides.STARTER_OVERRIDE[i].gender; + if (Overrides.GENDER_OVERRIDE !== null) { + starterGender = Overrides.GENDER_OVERRIDE; } const starterIvs = this.scene.gameData.dexData[starter.species.speciesId].ivs.slice(0); - const starterPokemon = this.scene.addPlayerPokemon(starter.species, this.scene.gameMode.getStartingLevel(), starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, starterIvs, starter.nature, null, null, i); + const starterPokemon = this.scene.addPlayerPokemon(starter.species, this.scene.gameMode.getStartingLevel(), starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, starterIvs, starter.nature); starterPokemon.tryPopulateMoveset(starter.moveset); if (starter.passive) { starterPokemon.passive = true; From 6dbf99cc72d4bb03d3368899173326b478df9153 Mon Sep 17 00:00:00 2001 From: Benjamin Odom Date: Tue, 4 Jun 2024 13:03:02 -0500 Subject: [PATCH 036/129] [Bug] Fix Circular Dependency with BerryType (#1802) * Fix Potential Circular Dependency with BerryType * remove .js --- src/data/berry.ts | 15 +-------------- src/data/enums/berry-type.ts | 14 ++++++++++++++ src/field/pokemon.ts | 2 +- src/modifier/modifier-type.ts | 3 ++- src/modifier/modifier.ts | 3 ++- src/overrides.ts | 2 +- 6 files changed, 21 insertions(+), 18 deletions(-) create mode 100644 src/data/enums/berry-type.ts diff --git a/src/data/berry.ts b/src/data/berry.ts index e832ab0a43e..f5bcd5e4be8 100644 --- a/src/data/berry.ts +++ b/src/data/berry.ts @@ -7,20 +7,7 @@ import { getStatusEffectHealText } from "./status-effect"; import * as Utils from "../utils"; import { DoubleBerryEffectAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs } from "./ability"; import i18next from "../plugins/i18n"; - -export enum BerryType { - SITRUS, - LUM, - ENIGMA, - LIECHI, - GANLON, - PETAYA, - APICOT, - SALAC, - LANSAT, - STARF, - LEPPA -} +import { BerryType } from "./enums/berry-type"; export function getBerryName(berryType: BerryType): string { return i18next.t(`berry:${BerryType[berryType]}.name`); diff --git a/src/data/enums/berry-type.ts b/src/data/enums/berry-type.ts new file mode 100644 index 00000000000..8b7aab16010 --- /dev/null +++ b/src/data/enums/berry-type.ts @@ -0,0 +1,14 @@ + +export enum BerryType { + SITRUS, + LUM, + ENIGMA, + LIECHI, + GANLON, + PETAYA, + APICOT, + SALAC, + LANSAT, + STARF, + LEPPA +} diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 8065b4633b7..2b8f28c4826 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -44,7 +44,7 @@ import { SpeciesFormChange, SpeciesFormChangeActiveTrigger, SpeciesFormChangeMov import { TerrainType } from "../data/terrain"; import { TrainerSlot } from "../data/trainer-config"; import * as Overrides from "../overrides"; -import { BerryType } from "../data/berry"; +import { BerryType } from "../data/enums/berry-type"; import i18next from "../plugins/i18n"; import { speciesEggMoves } from "../data/egg-moves"; import { ModifierTier } from "../modifier/modifier-tier"; diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 5705ee43f16..59185811590 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -11,7 +11,8 @@ import { Type } from "../data/type"; import PartyUiHandler, { PokemonMoveSelectFilter, PokemonSelectFilter } from "../ui/party-ui-handler"; import * as Utils from "../utils"; import { TempBattleStat, getTempBattleStatBoosterItemName, getTempBattleStatName } from "../data/temp-battle-stat"; -import { BerryType, getBerryEffectDescription, getBerryName } from "../data/berry"; +import { getBerryEffectDescription, getBerryName } from "../data/berry"; +import { BerryType } from "../data/enums/berry-type"; import { Unlockables } from "../system/unlockables"; import { StatusEffect, getStatusEffectDescriptor } from "../data/status-effect"; import { SpeciesFormKey } from "../data/pokemon-species"; diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 8b965cb4ddc..975420ab528 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -12,7 +12,8 @@ import { FusionSpeciesFormEvolution, pokemonEvolutions, pokemonPrevolutions } fr import { getPokemonMessage } from "../messages"; import * as Utils from "../utils"; import { TempBattleStat } from "../data/temp-battle-stat"; -import { BerryType, getBerryEffectFunc, getBerryPredicate } from "../data/berry"; +import { getBerryEffectFunc, getBerryPredicate } from "../data/berry"; +import { BerryType } from "../data/enums/berry-type"; import { StatusEffect, getStatusEffectHealText } from "../data/status-effect"; import { achvs } from "../system/achv"; import { VoucherType } from "../system/voucher"; diff --git a/src/overrides.ts b/src/overrides.ts index 8f5d4978be3..661f2d14253 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -4,7 +4,7 @@ import { Biome } from "./data/enums/biome"; import { Moves } from "./data/enums/moves"; import { WeatherType } from "./data/weather"; import { Variant } from "./data/variant"; -import { BerryType } from "./data/berry"; +import { BerryType } from "./data/enums/berry-type"; import { TempBattleStat } from "./data/temp-battle-stat"; import { Nature } from "./data/nature"; import { Type } from "./data/type"; From 809c86599d978046d31b095a40c19b7acb546c67 Mon Sep 17 00:00:00 2001 From: Tempoanon <163687446+Tempo-anon@users.noreply.github.com> Date: Tue, 4 Jun 2024 14:37:04 -0400 Subject: [PATCH 037/129] [Feature] Updated Champion teams (#1676) * Updated champion teams * Forgot Alder's legendary * Give Alder Genesect * Merge and update * Update teams a bit more * Red now leads with Pikachu, fixed Iris dialogue * Add champ leads --- src/data/trainer-config.ts | 138 +++++++++++++++++++++++++++++-------- src/locales/en/dialogue.ts | 2 +- 2 files changed, 110 insertions(+), 30 deletions(-) diff --git a/src/data/trainer-config.ts b/src/data/trainer-config.ts index 83c4f45b244..af47991ad14 100644 --- a/src/data/trainer-config.ts +++ b/src/data/trainer-config.ts @@ -618,7 +618,7 @@ export class TrainerConfig { * @param isMale - Whether the Champion is Male or Female (for localization of the title). * @returns {TrainerConfig} - The updated TrainerConfig instance. **/ - initForChampion(signatureSpecies: (Species | Species[])[],isMale: boolean): TrainerConfig { + initForChampion(signatureSpecies: (Species | Species[])[], isMale: boolean): TrainerConfig { // Check if the internationalization (i18n) system is initialized. if (!getIsInitialized()) { initI18n(); @@ -917,20 +917,20 @@ export const signatureSpecies: SignatureSpecies = { AMARYS: [Species.SKARMORY, Species.EMPOLEON, Species.SCIZOR, Species.METAGROSS], LACEY: [Species.EXCADRILL, Species.PRIMARINA, Species.ALCREMIE, Species.GALAR_SLOWBRO], DRAYTON: [Species.DRAGONITE, Species.ARCHALUDON, Species.FLYGON, Species.SCEPTILE], - BLUE: [Species.GYARADOS, Species.MEWTWO, Species.ARCANINE, Species.ALAKAZAM, Species.PIDGEOT], - RED: [Species.CHARIZARD, [Species.LUGIA, Species.HO_OH], Species.SNORLAX, Species.RAICHU, Species.ESPEON], - LANCE_CHAMPION: [Species.DRAGONITE, Species.ZYGARDE, Species.AERODACTYL, Species.KINGDRA, Species.ALOLA_EXEGGUTOR], - STEVEN: [Species.METAGROSS, [Species.DIALGA, Species.PALKIA], Species.SKARMORY, Species.AGGRON, Species.CARBINK], - WALLACE: [Species.MILOTIC, Species.KYOGRE, Species.WHISCASH, Species.WALREIN, Species.LUDICOLO], - CYNTHIA: [Species.SPIRITOMB, Species.GIRATINA, Species.GARCHOMP, Species.MILOTIC, Species.LUCARIO, Species.TOGEKISS], - ALDER: [Species.VOLCARONA, Species.GROUDON, Species.BOUFFALANT, Species.ACCELGOR, Species.CONKELDURR], - IRIS: [Species.HAXORUS, Species.YVELTAL, Species.DRUDDIGON, Species.AGGRON, Species.LAPRAS], - DIANTHA: [Species.HAWLUCHA, Species.XERNEAS, Species.GOURGEIST, Species.GOODRA, Species.GARDEVOIR], - HAU: [Species.ALOLA_RAICHU, [Species.SOLGALEO, Species.LUNALA], Species.NOIVERN, [Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA], Species.CRABOMINABLE], - LEON: [Species.DRAGAPULT, [Species.ZACIAN, Species.ZAMAZENTA], Species.SEISMITOAD, Species.AEGISLASH, Species.CHARIZARD], - GEETA: [Species.GLIMMORA, Species.MIRAIDON, Species.ESPATHRA, Species.VELUZA, Species.KINGAMBIT], - NEMONA: [Species.LYCANROC, Species.KORAIDON, Species.KOMMO_O, Species.PAWMOT, Species.DUSKNOIR], - KIERAN: [Species.POLITOED, [Species.OGERPON, Species.TERAPAGOS], Species.HYDRAPPLE, Species.PORYGON_Z, Species.GRIMMSNARL], + BLUE: [[Species.GYARADOS, Species.EXEGGUTOR, Species.ARCANINE], Species.HO_OH, [Species.RHYPERIOR, Species.MAGNEZONE]], // Alakazam lead, Mega Pidgeot + RED: [Species.LUGIA, Species.SNORLAX, [Species.ESPEON, Species.UMBREON, Species.SYLVEON]], // GMax Pikachu lead, Mega gen 1 starter + LANCE_CHAMPION: [Species.DRAGONITE, Species.KINGDRA, Species.ALOLA_EXEGGUTOR], // Aerodactyl lead, Mega Lati@s + STEVEN: [Species.AGGRON, [Species.ARMALDO, Species.CRADILY], Species.DIALGA], // Skarmorly lead, Mega Metagross + WALLACE: [Species.MILOTIC, Species.PALKIA, Species.LUDICOLO], // Pelipper lead, Mega Swampert + CYNTHIA: [Species.GIRATINA, Species.LUCARIO, Species.TOGEKISS], // Spiritomb lead, Mega Garchomp + ALDER: [Species.VOLCARONA, Species.ZEKROM, [Species.ACCELGOR, Species.ESCAVALIER], Species.KELDEO], // Bouffalant/Braviary lead + IRIS: [Species.HAXORUS, Species.RESHIRAM, Species.ARCHEOPS], // Druddigon lead, Gmax Lapras + DIANTHA: [Species.HAWLUCHA, Species.XERNEAS, Species.GOODRA], // Gourgeist lead, Mega Gardevoir + HAU: [[Species.SOLGALEO, Species.LUNALA], Species.NOIVERN, [Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA], [Species.TAPU_BULU, Species.TAPU_FINI, Species.TAPU_KOKO, Species.TAPU_LELE]], // Alola Raichu lead + LEON: [Species.DRAGAPULT, [Species.ZACIAN, Species.ZAMAZENTA], Species.AEGISLASH], // Rillaboom/Cinderace/Inteleon lead + GEETA: [Species.MIRAIDON, [Species.ESPATHRA, Species.VELUZA], [Species.AVALUGG, Species.HISUI_AVALUGG], Species.KINGAMBIT], // Glimmora lead + NEMONA: [Species.KORAIDON, Species.PAWMOT, [Species.DUDUNSPARCE, Species.ORTHWORM], [Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL]], // Lycanroc lead + KIERAN: [[Species.GRIMMSNARL, Species.INCINEROAR, Species.PORYGON_Z], Species.OGERPON, Species.TERAPAGOS, Species.HYDRAPPLE], // Poliwrath/Politoed lead }; export const trainerConfigs: TrainerConfigs = { @@ -1212,20 +1212,100 @@ export const trainerConfigs: TrainerConfigs = { [TrainerType.LACEY]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["LACEY"],false, Type.FAIRY), [TrainerType.DRAYTON]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["DRAYTON"],true, Type.DRAGON), - [TrainerType.BLUE]: new TrainerConfig((t = TrainerType.BLUE)).initForChampion(signatureSpecies["BLUE"],true).setBattleBgm("battle_kanto_champion").setHasDouble("blue_red_double").setDoubleTrainerType(TrainerType.RED).setDoubleTitle("champion_double"), - [TrainerType.RED]: new TrainerConfig(++t).initForChampion(signatureSpecies["RED"],true).setBattleBgm("battle_johto_champion").setHasDouble("red_blue_double").setDoubleTrainerType(TrainerType.BLUE).setDoubleTitle("champion_double"), - [TrainerType.LANCE_CHAMPION]: new TrainerConfig(++t).setName("Lance").initForChampion(signatureSpecies["LANCE_CHAMPION"],true).setBattleBgm("battle_johto_champion"), - [TrainerType.STEVEN]: new TrainerConfig(++t).initForChampion(signatureSpecies["STEVEN"],true).setBattleBgm("battle_hoenn_champion").setHasDouble("steven_wallace_double").setDoubleTrainerType(TrainerType.WALLACE).setDoubleTitle("champion_double"), - [TrainerType.WALLACE]: new TrainerConfig(++t).initForChampion(signatureSpecies["WALLACE"],true).setBattleBgm("battle_hoenn_champion").setHasDouble("wallace_steven_double").setDoubleTrainerType(TrainerType.STEVEN).setDoubleTitle("champion_double"), - [TrainerType.CYNTHIA]: new TrainerConfig(++t).initForChampion(signatureSpecies["CYNTHIA"],false).setBattleBgm("battle_sinnoh_champion"), - [TrainerType.ALDER]: new TrainerConfig(++t).initForChampion(signatureSpecies["ALDER"],true).setHasDouble("alder_iris_double").setDoubleTrainerType(TrainerType.IRIS).setDoubleTitle("champion_double").setBattleBgm("battle_champion_alder"), - [TrainerType.IRIS]: new TrainerConfig(++t).initForChampion(signatureSpecies["IRIS"],false).setBattleBgm("battle_champion_iris").setHasDouble("iris_alder_double").setDoubleTrainerType(TrainerType.ALDER).setDoubleTitle("champion_double"), - [TrainerType.DIANTHA]: new TrainerConfig(++t).initForChampion(signatureSpecies["DIANTHA"],false), - [TrainerType.HAU]: new TrainerConfig(++t).initForChampion(signatureSpecies["HAU"],true), - [TrainerType.LEON]: new TrainerConfig(++t).initForChampion(signatureSpecies["LEON"],true), - [TrainerType.GEETA]: new TrainerConfig(++t).initForChampion(signatureSpecies["GEETA"],false), - [TrainerType.NEMONA]: new TrainerConfig(++t).initForChampion(signatureSpecies["NEMONA"],false), - [TrainerType.KIERAN]: new TrainerConfig(++t).initForChampion(signatureSpecies["KIERAN"],true), + [TrainerType.BLUE]: new TrainerConfig((t = TrainerType.BLUE)).initForChampion(signatureSpecies["BLUE"],true).setBattleBgm("battle_kanto_champion").setHasDouble("blue_red_double").setDoubleTrainerType(TrainerType.RED).setDoubleTitle("champion_double") + .setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.ALAKAZAM], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + })) + .setPartyMemberFunc(1, getRandomPartyMemberFunc([Species.PIDGEOT], TrainerSlot.TRAINER, true, p => { + p.formIndex = 1; + p.generateAndPopulateMoveset(); + })), + [TrainerType.RED]: new TrainerConfig(++t).initForChampion(signatureSpecies["RED"],true).setBattleBgm("battle_johto_champion").setHasDouble("red_blue_double").setDoubleTrainerType(TrainerType.BLUE).setDoubleTitle("champion_double") + .setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.PIKACHU], TrainerSlot.TRAINER, true, p => { + p.formIndex = 8; + p.generateAndPopulateMoveset(); + })) + .setPartyMemberFunc(1, getRandomPartyMemberFunc([Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE], TrainerSlot.TRAINER, true, p => { + p.formIndex = 1; + p.generateAndPopulateMoveset(); + })), + [TrainerType.LANCE_CHAMPION]: new TrainerConfig(++t).setName("Lance").initForChampion(signatureSpecies["LANCE_CHAMPION"],true).setBattleBgm("battle_johto_champion") + .setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.AERODACTYL], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + })) + .setPartyMemberFunc(1, getRandomPartyMemberFunc([Species.LATIAS, Species.LATIOS], TrainerSlot.TRAINER, true, p => { + p.formIndex = 1; + p.generateAndPopulateMoveset(); + })), + [TrainerType.STEVEN]: new TrainerConfig(++t).initForChampion(signatureSpecies["STEVEN"],true).setBattleBgm("battle_hoenn_champion").setHasDouble("steven_wallace_double").setDoubleTrainerType(TrainerType.WALLACE).setDoubleTitle("champion_double") + .setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.SKARMORY], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + })) + .setPartyMemberFunc(1, getRandomPartyMemberFunc([Species.METAGROSS], TrainerSlot.TRAINER, true, p => { + p.formIndex = 1; + p.generateAndPopulateMoveset(); + })), + [TrainerType.WALLACE]: new TrainerConfig(++t).initForChampion(signatureSpecies["WALLACE"],true).setBattleBgm("battle_hoenn_champion").setHasDouble("wallace_steven_double").setDoubleTrainerType(TrainerType.STEVEN).setDoubleTitle("champion_double") + .setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.PELIPPER], TrainerSlot.TRAINER, true, p => { + p.abilityIndex = 1; // Drizzle + p.generateAndPopulateMoveset(); + })) + .setPartyMemberFunc(1, getRandomPartyMemberFunc([Species.SWAMPERT], TrainerSlot.TRAINER, true, p => { + p.formIndex = 1; + p.generateAndPopulateMoveset(); + })), + [TrainerType.CYNTHIA]: new TrainerConfig(++t).initForChampion(signatureSpecies["CYNTHIA"],false).setBattleBgm("battle_sinnoh_champion") + .setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.SPIRITOMB], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + })) + .setPartyMemberFunc(1, getRandomPartyMemberFunc([Species.GARCHOMP], TrainerSlot.TRAINER, true, p => { + p.formIndex = 1; + p.generateAndPopulateMoveset(); + })), + [TrainerType.ALDER]: new TrainerConfig(++t).initForChampion(signatureSpecies["ALDER"],true).setHasDouble("alder_iris_double").setDoubleTrainerType(TrainerType.IRIS).setDoubleTitle("champion_double").setBattleBgm("battle_champion_alder") + .setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.BOUFFALANT, Species.BRAVIARY], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + })), + [TrainerType.IRIS]: new TrainerConfig(++t).initForChampion(signatureSpecies["IRIS"],false).setBattleBgm("battle_champion_iris").setHasDouble("iris_alder_double").setDoubleTrainerType(TrainerType.ALDER).setDoubleTitle("champion_double") + .setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.DRUDDIGON], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + })) + .setPartyMemberFunc(1, getRandomPartyMemberFunc([Species.LAPRAS], TrainerSlot.TRAINER, true, p => { + p.formIndex = 1; + p.generateAndPopulateMoveset(); + })), + [TrainerType.DIANTHA]: new TrainerConfig(++t).initForChampion(signatureSpecies["DIANTHA"],false) + .setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.GOURGEIST], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + })) + .setPartyMemberFunc(1, getRandomPartyMemberFunc([Species.GARDEVOIR], TrainerSlot.TRAINER, true, p => { + p.formIndex = 1; + p.generateAndPopulateMoveset(); + })), + [TrainerType.HAU]: new TrainerConfig(++t).initForChampion(signatureSpecies["HAU"],true) + .setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.ALOLA_RAICHU], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + })), + [TrainerType.LEON]: new TrainerConfig(++t).initForChampion(signatureSpecies["LEON"],true) + .setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.RILLABOOM, Species.CINDERACE, Species.INTELEON], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + })) + .setPartyMemberFunc(1, getRandomPartyMemberFunc([Species.CHARIZARD], TrainerSlot.TRAINER, true, p => { + p.formIndex = 3; + p.generateAndPopulateMoveset(); + })), + [TrainerType.GEETA]: new TrainerConfig(++t).initForChampion(signatureSpecies["GEETA"],false) + .setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.GLIMMORA], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + })), + [TrainerType.NEMONA]: new TrainerConfig(++t).initForChampion(signatureSpecies["NEMONA"],false) + .setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.LYCANROC], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + })), + [TrainerType.KIERAN]: new TrainerConfig(++t).initForChampion(signatureSpecies["KIERAN"],true) + .setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.POLIWRATH, Species.POLITOED], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + })), [TrainerType.RIVAL]: new TrainerConfig((t = TrainerType.RIVAL)).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setStaticParty().setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival").setPartyTemplates(trainerPartyTemplates.RIVAL) diff --git a/src/locales/en/dialogue.ts b/src/locales/en/dialogue.ts index 7899ec21d36..c5b0d72d3d7 100644 --- a/src/locales/en/dialogue.ts +++ b/src/locales/en/dialogue.ts @@ -1578,7 +1578,7 @@ export const PGMdialogue: DialogueTranslationEntries = { "encounter": { 1: `Know what? I really look forward to having serious battles with strong Trainers! $I mean, come on! The Trainers who make it here are Trainers who desire victory with every fiber of their being! - #And they are battling alongside Pokémon that have been through countless difficult battles! + $And they are battling alongside Pokémon that have been through countless difficult battles! $If I battle with people like that, not only will I get stronger, my Pokémon will, too! $And we'll get to know each other even better! OK! Brace yourself! $I'm Iris, the Pokémon League Champion, and I'm going to defeat you!`, From a8205ae8199237a669e87624bca04d4f91bd2e3b Mon Sep 17 00:00:00 2001 From: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> Date: Tue, 4 Jun 2024 22:11:02 +0200 Subject: [PATCH 038/129] =?UTF-8?q?[Bug]=20Handle=20if=20the=20browser=20g?= =?UTF-8?q?ives=20a=20long=20form=20of=20a=20language=20(like=20"de-DE")?= =?UTF-8?q?=20=E2=80=A6=20(#1795)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Handle if the browser gives a long form of a language (like "de-DE") for cases where we only have the short form "de". * Changed it so that now resolved Language is now used anywhere. This is basically what i orignally did manually but provided from i18next directly --- src/loading-scene.ts | 2 +- src/ui/fight-ui-handler.ts | 4 ++-- src/ui/pokemon-info-container.ts | 2 +- src/ui/starter-select-ui-handler.ts | 6 +++--- src/ui/summary-ui-handler.ts | 6 +++--- src/ui/text.ts | 2 +- src/utils.ts | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/loading-scene.ts b/src/loading-scene.ts index 0b15358c4bc..7c108a3c30e 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -213,7 +213,7 @@ export class LoadingScene extends SceneBase { this.loadAtlas("types", ""); // Get current lang and load the types atlas for it. English will only load types while all other languages will load types and types_ - const lang = i18next.language; + const lang = i18next.resolvedLanguage; if (lang !== "en") { if (Utils.verifyLang(lang)) { this.loadAtlas(`types_${lang}`, ""); diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 22a0acb14ae..acbf66b7075 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -35,7 +35,7 @@ export default class FightUiHandler extends UiHandler { this.movesContainer = this.scene.add.container(18, -38.7); ui.add(this.movesContainer); - this.typeIcon = this.scene.add.sprite((this.scene.game.canvas.width / 6) - 57, -36,`types${Utils.verifyLang(i18next.language) ? `_${i18next.language}` : ""}` , "unknown"); + this.typeIcon = this.scene.add.sprite((this.scene.game.canvas.width / 6) - 57, -36,`types${Utils.verifyLang(i18next.resolvedLanguage) ? `_${i18next.resolvedLanguage}` : ""}` , "unknown"); this.typeIcon.setVisible(false); ui.add(this.typeIcon); @@ -168,7 +168,7 @@ export default class FightUiHandler extends UiHandler { if (hasMove) { const pokemonMove = moveset[cursor]; - this.typeIcon.setTexture(`types${Utils.verifyLang(i18next.language) ? `_${i18next.language}` : ""}`, Type[pokemonMove.getMove().type].toLowerCase()).setScale(0.8); + this.typeIcon.setTexture(`types${Utils.verifyLang(i18next.resolvedLanguage) ? `_${i18next.resolvedLanguage}` : ""}`, Type[pokemonMove.getMove().type].toLowerCase()).setScale(0.8); this.moveCategoryIcon.setTexture("categories", MoveCategory[pokemonMove.getMove().category].toLowerCase()).setScale(1.0); const power = pokemonMove.getMove().power; diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index cf4c94e1164..b731b0d22b4 100644 --- a/src/ui/pokemon-info-container.ts +++ b/src/ui/pokemon-info-container.ts @@ -73,7 +73,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { } setup(): void { - const currentLanguage = i18next.language; + const currentLanguage = i18next.resolvedLanguage; const langSettingKey = Object.keys(languageSettings).find(lang => currentLanguage.includes(lang)); const textSettings = languageSettings[langSettingKey]; const infoBg = addWindow(this.scene, 0, 0, this.infoWindowWidth, 132); diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 3faec4bfcc6..abba4a081df 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -251,7 +251,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { setup() { const ui = this.getUi(); - const currentLanguage = i18next.language; + const currentLanguage = i18next.resolvedLanguage; const langSettingKey = Object.keys(languageSettings).find(lang => currentLanguage.includes(lang)); const textSettings = languageSettings[langSettingKey]; @@ -518,11 +518,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.pokemonSprite.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true }); this.starterSelectContainer.add(this.pokemonSprite); - this.type1Icon = this.scene.add.sprite(8, 98, `types${Utils.verifyLang(i18next.language) ? `_${i18next.language}` : ""}`); this.type1Icon.setScale(0.5); + this.type1Icon = this.scene.add.sprite(8, 98, `types${Utils.verifyLang(i18next.resolvedLanguage) ? `_${i18next.resolvedLanguage}` : ""}`); this.type1Icon.setScale(0.5); this.type1Icon.setOrigin(0, 0); this.starterSelectContainer.add(this.type1Icon); - this.type2Icon = this.scene.add.sprite(26, 98, `types${Utils.verifyLang(i18next.language) ? `_${i18next.language}` : ""}`); this.type2Icon.setScale(0.5); + this.type2Icon = this.scene.add.sprite(26, 98, `types${Utils.verifyLang(i18next.resolvedLanguage) ? `_${i18next.resolvedLanguage}` : ""}`); this.type2Icon.setScale(0.5); this.type2Icon.setOrigin(0, 0); this.starterSelectContainer.add(this.type2Icon); diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index 32f5bffeb1b..1133e7a755c 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -695,7 +695,7 @@ export default class SummaryUiHandler extends UiHandler { const getTypeIcon = (index: integer, type: Type, tera: boolean = false) => { const xCoord = 39 + 34 * index; const typeIcon = !tera - ? this.scene.add.sprite(xCoord, 42, `types${Utils.verifyLang(i18next.language) ? `_${i18next.language}` : ""}`, Type[type].toLowerCase()) : this.scene.add.sprite(xCoord, 42, "type_tera"); + ? this.scene.add.sprite(xCoord, 42, `types${Utils.verifyLang(i18next.resolvedLanguage) ? `_${i18next.resolvedLanguage}` : ""}`, Type[type].toLowerCase()) : this.scene.add.sprite(xCoord, 42, "type_tera"); if (tera) { typeIcon.setScale(0.5); const typeRgb = getTypeRgb(type); @@ -897,7 +897,7 @@ export default class SummaryUiHandler extends UiHandler { if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) { this.extraMoveRowContainer.setVisible(true); - const newMoveTypeIcon = this.scene.add.sprite(0, 0, `types${Utils.verifyLang(i18next.language) ? `_${i18next.language}` : ""}`, Type[this.newMove.type].toLowerCase()); + const newMoveTypeIcon = this.scene.add.sprite(0, 0, `types${Utils.verifyLang(i18next.resolvedLanguage) ? `_${i18next.resolvedLanguage}` : ""}`, Type[this.newMove.type].toLowerCase()); newMoveTypeIcon.setOrigin(0, 1); this.extraMoveRowContainer.add(newMoveTypeIcon); @@ -920,7 +920,7 @@ export default class SummaryUiHandler extends UiHandler { this.moveRowsContainer.add(moveRowContainer); if (move) { - const typeIcon = this.scene.add.sprite(0, 0, `types${Utils.verifyLang(i18next.language) ? `_${i18next.language}` : ""}`, Type[move.getMove().type].toLowerCase()); typeIcon.setOrigin(0, 1); + const typeIcon = this.scene.add.sprite(0, 0, `types${Utils.verifyLang(i18next.resolvedLanguage) ? `_${i18next.resolvedLanguage}` : ""}`, Type[move.getMove().type].toLowerCase()); typeIcon.setOrigin(0, 1); moveRowContainer.add(typeIcon); } diff --git a/src/ui/text.ts b/src/ui/text.ts index 4e76386aae7..56e1b492dfa 100644 --- a/src/ui/text.ts +++ b/src/ui/text.ts @@ -98,7 +98,7 @@ export function addTextInputObject(scene: Phaser.Scene, x: number, y: number, wi } function getTextStyleOptions(style: TextStyle, uiTheme: UiTheme, extraStyleOptions?: Phaser.Types.GameObjects.Text.TextStyle): [ Phaser.Types.GameObjects.Text.TextStyle | InputText.IConfig, string, number, number ] { - const lang = i18next.language; + const lang = i18next.resolvedLanguage; let shadowXpos = 4; let shadowYpos = 5; diff --git a/src/utils.ts b/src/utils.ts index adfe0b0df20..6d965369ca3 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -396,7 +396,7 @@ English itself counts as not available export function verifyLang(lang?: string): boolean { //IMPORTANT - ONLY ADD YOUR LANG HERE IF YOU'VE ALREADY ADDED ALL THE NECESSARY IMAGES if (!lang) { - lang = i18next.language; + lang = i18next.resolvedLanguage; } switch (lang) { From eecad0fdc46115ef0d1521398c6781fa06fddd6d Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Tue, 4 Jun 2024 17:39:22 -0400 Subject: [PATCH 039/129] Balance endless tokens (#1733) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Balanced tokens * Remove existing tokens, all 10 stack limit * Linter complained * Sorry Mr. Lint I’ll do better next time * Removed redundant min * Used a version system to update tokens * The linter has peculiar tastes * See if this works * I'm at my limit omg wtf Sam * Call me Swoobat the way I keep it Simple * Clean up some log statements * Adjust token weights to make up for removal of sleep and freeze * Be so fr GitHub that’s not a real merge conflict --- src/locales/de/modifier-type.ts | 4 +--- src/locales/en/modifier-type.ts | 4 +--- src/locales/es/modifier-type.ts | 4 +--- src/locales/fr/modifier-type.ts | 4 +--- src/locales/it/modifier-type.ts | 4 +--- src/locales/ko/modifier-type.ts | 4 +--- src/locales/pt_BR/modifier-type.ts | 4 +--- src/locales/zh_CN/modifier-type.ts | 4 +--- src/locales/zh_TW/modifier-type.ts | 4 +--- src/modifier/modifier-type.ts | 34 +++++++++++++----------------- src/modifier/modifier.ts | 34 ++++++++++++++++++------------ src/system/game-data.ts | 5 +++++ 12 files changed, 50 insertions(+), 59 deletions(-) diff --git a/src/locales/de/modifier-type.ts b/src/locales/de/modifier-type.ts index 055751970ab..9f70c31ca63 100644 --- a/src/locales/de/modifier-type.ts +++ b/src/locales/de/modifier-type.ts @@ -230,10 +230,8 @@ export const modifierType: ModifierTypeTranslationEntries = { "ENEMY_HEAL": { name: "Wiederherstellungsmarke", description: "Heilt 2% der maximalen KP pro Runde" }, "ENEMY_ATTACK_POISON_CHANCE": { name: "Giftmarke" }, "ENEMY_ATTACK_PARALYZE_CHANCE": { "name": "Lähmungsmarke" }, - "ENEMY_ATTACK_SLEEP_CHANCE": { "name": "Schlafmarke" }, - "ENEMY_ATTACK_FREEZE_CHANCE": { "name": "Gefriermarke" }, "ENEMY_ATTACK_BURN_CHANCE": { "name": "Brandmarke" }, - "ENEMY_STATUS_EFFECT_HEAL_CHANCE": { "name": "Vollheilungsmarke", "description": "Fügt eine 10%ige Chance hinzu, jede Runde einen Statuszustand zu heilen" }, + "ENEMY_STATUS_EFFECT_HEAL_CHANCE": { "name": "Vollheilungsmarke", "description": "Fügt eine 2,5%ige Chance hinzu, jede Runde einen Statuszustand zu heilen" }, "ENEMY_ENDURE_CHANCE": { "name": "Ausdauer-Marke" }, "ENEMY_FUSED_CHANCE": { "name": "Fusionsmarke", "description": "Fügt eine 1%ige Chance hinzu, dass ein wildes Pokémon eine Fusion ist" }, diff --git a/src/locales/en/modifier-type.ts b/src/locales/en/modifier-type.ts index e988f0a5250..dac87e1d939 100644 --- a/src/locales/en/modifier-type.ts +++ b/src/locales/en/modifier-type.ts @@ -230,10 +230,8 @@ export const modifierType: ModifierTypeTranslationEntries = { "ENEMY_HEAL": { name: "Recovery Token", description: "Heals 2% of max HP every turn" }, "ENEMY_ATTACK_POISON_CHANCE": { name: "Poison Token" }, "ENEMY_ATTACK_PARALYZE_CHANCE": { name: "Paralyze Token" }, - "ENEMY_ATTACK_SLEEP_CHANCE": { name: "Sleep Token" }, - "ENEMY_ATTACK_FREEZE_CHANCE": { name: "Freeze Token" }, "ENEMY_ATTACK_BURN_CHANCE": { name: "Burn Token" }, - "ENEMY_STATUS_EFFECT_HEAL_CHANCE": { name: "Full Heal Token", description: "Adds a 10% chance every turn to heal a status condition" }, + "ENEMY_STATUS_EFFECT_HEAL_CHANCE": { name: "Full Heal Token", description: "Adds a 2.5% chance every turn to heal a status condition" }, "ENEMY_ENDURE_CHANCE": { name: "Endure Token" }, "ENEMY_FUSED_CHANCE": { name: "Fusion Token", description: "Adds a 1% chance that a wild Pokémon will be a fusion" }, }, diff --git a/src/locales/es/modifier-type.ts b/src/locales/es/modifier-type.ts index e5f77549737..7b5b1e0c90b 100644 --- a/src/locales/es/modifier-type.ts +++ b/src/locales/es/modifier-type.ts @@ -230,10 +230,8 @@ export const modifierType: ModifierTypeTranslationEntries = { "ENEMY_HEAL": { name: "Recovery Token", description: "Cura el 2% de los PS máximo en cada turno" }, "ENEMY_ATTACK_POISON_CHANCE": { name: "Poison Token" }, "ENEMY_ATTACK_PARALYZE_CHANCE": { name: "Paralyze Token" }, - "ENEMY_ATTACK_SLEEP_CHANCE": { name: "Sleep Token" }, - "ENEMY_ATTACK_FREEZE_CHANCE": { name: "Freeze Token" }, "ENEMY_ATTACK_BURN_CHANCE": { name: "Burn Token" }, - "ENEMY_STATUS_EFFECT_HEAL_CHANCE": { name: "Full Heal Token", description: "Agrega un 10% de probabilidad cada turno de curar un problema de estado" }, + "ENEMY_STATUS_EFFECT_HEAL_CHANCE": { name: "Full Heal Token", description: "Agrega un 2.5% de probabilidad cada turno de curar un problema de estado" }, "ENEMY_ENDURE_CHANCE": { name: "Endure Token" }, "ENEMY_FUSED_CHANCE": { name: "Fusion Token", description: "Agrega un 1% de probabilidad de que un Pokémon salvaje sea una fusión" }, }, diff --git a/src/locales/fr/modifier-type.ts b/src/locales/fr/modifier-type.ts index a6960c4f0c5..8315910adb3 100644 --- a/src/locales/fr/modifier-type.ts +++ b/src/locales/fr/modifier-type.ts @@ -230,10 +230,8 @@ export const modifierType: ModifierTypeTranslationEntries = { "ENEMY_HEAL": { name: "Jeton Soin", description: "Soigne 2% des PV max à chaque tour" }, "ENEMY_ATTACK_POISON_CHANCE": { name: "Jeton Poison" }, "ENEMY_ATTACK_PARALYZE_CHANCE": { name: "Jeton Paralysie" }, - "ENEMY_ATTACK_SLEEP_CHANCE": { name: "Jeton Sommeil" }, - "ENEMY_ATTACK_FREEZE_CHANCE": { name: "Jeton Gel" }, "ENEMY_ATTACK_BURN_CHANCE": { name: "Jeton Brulure" }, - "ENEMY_STATUS_EFFECT_HEAL_CHANCE": { name: "Jeton Total Soin", description: "Ajoute 10% de chances à chaque tour de se soigner d’un problème de statut." }, + "ENEMY_STATUS_EFFECT_HEAL_CHANCE": { name: "Jeton Total Soin", description: "Ajoute 2.5% de chances à chaque tour de se soigner d’un problème de statut." }, "ENEMY_ENDURE_CHANCE": { name: "Jeton Ténacité" }, "ENEMY_FUSED_CHANCE": { name: "Jeton Fusion", description: "Ajoute 1% de chances qu’un Pokémon sauvage soit une fusion." }, }, diff --git a/src/locales/it/modifier-type.ts b/src/locales/it/modifier-type.ts index 668df9601fc..b311aa1e8fa 100644 --- a/src/locales/it/modifier-type.ts +++ b/src/locales/it/modifier-type.ts @@ -230,10 +230,8 @@ export const modifierType: ModifierTypeTranslationEntries = { "ENEMY_HEAL": { name: "Gettone del Recupero", description: "Cura il 2% dei PS massimi ogni turno" }, "ENEMY_ATTACK_POISON_CHANCE": { name: "Gettone del Veleno" }, "ENEMY_ATTACK_PARALYZE_CHANCE": { name: "Gettone della Paralisi" }, - "ENEMY_ATTACK_SLEEP_CHANCE": { name: "Gettone del Sonno" }, - "ENEMY_ATTACK_FREEZE_CHANCE": { name: "Gettone del Congelamento" }, "ENEMY_ATTACK_BURN_CHANCE": { name: "Gettone della Bruciatura" }, - "ENEMY_STATUS_EFFECT_HEAL_CHANCE": { name: "Gettone Guarigione Completa", description: "Aggiunge una probabilità del 10% a ogni turno di curare una condizione di stato" }, + "ENEMY_STATUS_EFFECT_HEAL_CHANCE": { name: "Gettone Guarigione Completa", description: "Aggiunge una probabilità del 2.5% a ogni turno di curare una condizione di stato" }, "ENEMY_ENDURE_CHANCE": { name: "Gettone di Resistenza" }, "ENEMY_FUSED_CHANCE": { name: "Gettone della fusione", description: "Aggiunge l'1% di possibilità che un Pokémon selvatico sia una fusione" }, }, diff --git a/src/locales/ko/modifier-type.ts b/src/locales/ko/modifier-type.ts index e2d90ed1ff9..5d54018cc96 100644 --- a/src/locales/ko/modifier-type.ts +++ b/src/locales/ko/modifier-type.ts @@ -230,10 +230,8 @@ export const modifierType: ModifierTypeTranslationEntries = { "ENEMY_HEAL": { name: "회복 토큰", description: "매 턴 최대 체력의 2%를 회복" }, "ENEMY_ATTACK_POISON_CHANCE": { name: "독 토큰" }, "ENEMY_ATTACK_PARALYZE_CHANCE": { name: "마비 토큰" }, - "ENEMY_ATTACK_SLEEP_CHANCE": { name: "잠듦 토큰" }, - "ENEMY_ATTACK_FREEZE_CHANCE": { name: "얼음 토큰" }, "ENEMY_ATTACK_BURN_CHANCE": { name: "화상 토큰" }, - "ENEMY_STATUS_EFFECT_HEAL_CHANCE": { name: "만병통치 토큰", description: "매 턴 상태이상에서 회복될 확률 10% 추가" }, + "ENEMY_STATUS_EFFECT_HEAL_CHANCE": { name: "만병통치 토큰", description: "매 턴 상태이상에서 회복될 확률 2.5% 추가" }, "ENEMY_ENDURE_CHANCE": { name: "버티기 토큰" }, "ENEMY_FUSED_CHANCE": { name: "합체 토큰", description: "야생 포켓몬이 합체할 확률 1% 추가" }, }, diff --git a/src/locales/pt_BR/modifier-type.ts b/src/locales/pt_BR/modifier-type.ts index ea384282f15..4865cfb64a2 100644 --- a/src/locales/pt_BR/modifier-type.ts +++ b/src/locales/pt_BR/modifier-type.ts @@ -230,10 +230,8 @@ export const modifierType: ModifierTypeTranslationEntries = { "ENEMY_HEAL": { name: "Token de Recuperação", description: "Cura 2% dos PS máximos a cada turno" }, "ENEMY_ATTACK_POISON_CHANCE": { name: "Token de Veneno" }, "ENEMY_ATTACK_PARALYZE_CHANCE": { name: "Token de Paralisia" }, - "ENEMY_ATTACK_SLEEP_CHANCE": { name: "Token de Sono" }, - "ENEMY_ATTACK_FREEZE_CHANCE": { name: "Token de Congelamento" }, "ENEMY_ATTACK_BURN_CHANCE": { name: "Token de Queimadura" }, - "ENEMY_STATUS_EFFECT_HEAL_CHANCE": { name: "Token de Cura Total", description: "Adiciona uma chance de 10% a cada turno de curar uma condição de status" }, + "ENEMY_STATUS_EFFECT_HEAL_CHANCE": { name: "Token de Cura Total", description: "Adiciona uma chance de 2.5% a cada turno de curar uma condição de status" }, "ENEMY_ENDURE_CHANCE": { name: "Token de Persistência" }, "ENEMY_FUSED_CHANCE": { name: "Token de Fusão", description: "Adiciona uma chance de 1% de que um Pokémon selvagem seja uma fusão" }, }, diff --git a/src/locales/zh_CN/modifier-type.ts b/src/locales/zh_CN/modifier-type.ts index c51ce2cd788..7230f21e330 100644 --- a/src/locales/zh_CN/modifier-type.ts +++ b/src/locales/zh_CN/modifier-type.ts @@ -230,10 +230,8 @@ export const modifierType: ModifierTypeTranslationEntries = { "ENEMY_HEAL": { name: "回复硬币", description: "每回合回复2%最大HP" }, "ENEMY_ATTACK_POISON_CHANCE": { name: "剧毒硬币" }, "ENEMY_ATTACK_PARALYZE_CHANCE": { name: "麻痹硬币" }, - "ENEMY_ATTACK_SLEEP_CHANCE": { name: "睡眠硬币" }, - "ENEMY_ATTACK_FREEZE_CHANCE": { name: "冰冻硬币" }, "ENEMY_ATTACK_BURN_CHANCE": { name: "灼烧硬币" }, - "ENEMY_STATUS_EFFECT_HEAL_CHANCE": { name: "万灵药硬币", description: "增加10%每回合治愈异常状态的概率" }, + "ENEMY_STATUS_EFFECT_HEAL_CHANCE": { name: "万灵药硬币", description: "增加2.5%每回合治愈异常状态的概率" }, "ENEMY_ENDURE_CHANCE": { name: "忍受硬币" }, "ENEMY_FUSED_CHANCE": { name: "融合硬币", description: "增加1%野生融合宝可梦出现概率" }, }, diff --git a/src/locales/zh_TW/modifier-type.ts b/src/locales/zh_TW/modifier-type.ts index 854e65212f4..1ad51965937 100644 --- a/src/locales/zh_TW/modifier-type.ts +++ b/src/locales/zh_TW/modifier-type.ts @@ -282,12 +282,10 @@ export const modifierType: ModifierTypeTranslationEntries = { ENEMY_HEAL: { name: "恢復硬幣", description: "每回合恢復2%最大HP" }, ENEMY_ATTACK_POISON_CHANCE: { name: "劇毒硬幣" }, ENEMY_ATTACK_PARALYZE_CHANCE: { name: "麻痹硬幣" }, - ENEMY_ATTACK_SLEEP_CHANCE: { name: "睡眠硬幣" }, - ENEMY_ATTACK_FREEZE_CHANCE: { name: "冰凍硬幣" }, ENEMY_ATTACK_BURN_CHANCE: { name: "灼燒硬幣" }, ENEMY_STATUS_EFFECT_HEAL_CHANCE: { name: "萬靈藥硬幣", - description: "增加10%每回合治癒異常狀態的概率", + description: "增加2.5%每回合治癒異常狀態的概率", }, ENEMY_ENDURE_CHANCE: { name: "忍受硬幣" }, ENEMY_FUSED_CHANCE: { diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 59185811590..5f1fb1d2956 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -978,8 +978,8 @@ export class EnemyAttackStatusEffectChanceModifierType extends ModifierType { private chancePercent: integer; private effect: StatusEffect; - constructor(localeKey: string, iconImage: string, chancePercent: integer, effect: StatusEffect) { - super(localeKey, iconImage, (type, args) => new Modifiers.EnemyAttackStatusEffectChanceModifier(type, effect, chancePercent), "enemy_status_chance"); + constructor(localeKey: string, iconImage: string, chancePercent: integer, effect: StatusEffect, stackCount?: integer) { + super(localeKey, iconImage, (type, args) => new Modifiers.EnemyAttackStatusEffectChanceModifier(type, effect, chancePercent, stackCount), "enemy_status_chance"); this.chancePercent = chancePercent; this.effect = effect; @@ -1216,14 +1216,12 @@ export const modifierTypes = { ENEMY_DAMAGE_BOOSTER: () => new ModifierType("modifierType:ModifierType.ENEMY_DAMAGE_BOOSTER", "wl_item_drop", (type, _args) => new Modifiers.EnemyDamageBoosterModifier(type, 5)), ENEMY_DAMAGE_REDUCTION: () => new ModifierType("modifierType:ModifierType.ENEMY_DAMAGE_REDUCTION", "wl_guard_spec", (type, _args) => new Modifiers.EnemyDamageReducerModifier(type, 2.5)), //ENEMY_SUPER_EFFECT_BOOSTER: () => new ModifierType('Type Advantage Token', 'Increases damage of super effective attacks by 30%', (type, _args) => new Modifiers.EnemySuperEffectiveDamageBoosterModifier(type, 30), 'wl_custom_super_effective'), - ENEMY_HEAL: () => new ModifierType("modifierType:ModifierType.ENEMY_HEAL", "wl_potion", (type, _args) => new Modifiers.EnemyTurnHealModifier(type, 2)), - ENEMY_ATTACK_POISON_CHANCE: () => new EnemyAttackStatusEffectChanceModifierType("modifierType:ModifierType.ENEMY_ATTACK_POISON_CHANCE", "wl_antidote", 10, StatusEffect.POISON), - ENEMY_ATTACK_PARALYZE_CHANCE: () => new EnemyAttackStatusEffectChanceModifierType("modifierType:ModifierType.ENEMY_ATTACK_PARALYZE_CHANCE", "wl_paralyze_heal", 10, StatusEffect.PARALYSIS), - ENEMY_ATTACK_SLEEP_CHANCE: () => new EnemyAttackStatusEffectChanceModifierType("modifierType:ModifierType.ENEMY_ATTACK_SLEEP_CHANCE", "wl_awakening", 10, StatusEffect.SLEEP), - ENEMY_ATTACK_FREEZE_CHANCE: () => new EnemyAttackStatusEffectChanceModifierType("modifierType:ModifierType.ENEMY_ATTACK_FREEZE_CHANCE", "wl_ice_heal", 10, StatusEffect.FREEZE), - ENEMY_ATTACK_BURN_CHANCE: () => new EnemyAttackStatusEffectChanceModifierType("modifierType:ModifierType.ENEMY_ATTACK_BURN_CHANCE", "wl_burn_heal", 10, StatusEffect.BURN), - ENEMY_STATUS_EFFECT_HEAL_CHANCE: () => new ModifierType("modifierType:ModifierType.ENEMY_STATUS_EFFECT_HEAL_CHANCE", "wl_full_heal", (type, _args) => new Modifiers.EnemyStatusEffectHealChanceModifier(type, 10)), - ENEMY_ENDURE_CHANCE: () => new EnemyEndureChanceModifierType("modifierType:ModifierType.ENEMY_ENDURE_CHANCE", "wl_reset_urge", 2.5), + ENEMY_HEAL: () => new ModifierType("modifierType:ModifierType.ENEMY_HEAL", "wl_potion", (type, _args) => new Modifiers.EnemyTurnHealModifier(type, 2, 10)), + ENEMY_ATTACK_POISON_CHANCE: () => new EnemyAttackStatusEffectChanceModifierType("modifierType:ModifierType.ENEMY_ATTACK_POISON_CHANCE", "wl_antidote", 5, StatusEffect.POISON, 10), + ENEMY_ATTACK_PARALYZE_CHANCE: () => new EnemyAttackStatusEffectChanceModifierType("modifierType:ModifierType.ENEMY_ATTACK_PARALYZE_CHANCE", "wl_paralyze_heal", 2.5, StatusEffect.PARALYSIS, 10), + ENEMY_ATTACK_BURN_CHANCE: () => new EnemyAttackStatusEffectChanceModifierType("modifierType:ModifierType.ENEMY_ATTACK_BURN_CHANCE", "wl_burn_heal", 5, StatusEffect.BURN, 10), + ENEMY_STATUS_EFFECT_HEAL_CHANCE: () => new ModifierType("modifierType:ModifierType.ENEMY_STATUS_EFFECT_HEAL_CHANCE", "wl_full_heal", (type, _args) => new Modifiers.EnemyStatusEffectHealChanceModifier(type, 2.5, 10)), + ENEMY_ENDURE_CHANCE: () => new EnemyEndureChanceModifierType("modifierType:ModifierType.ENEMY_ENDURE_CHANCE", "wl_reset_urge", 2), ENEMY_FUSED_CHANCE: () => new ModifierType("modifierType:ModifierType.ENEMY_FUSED_CHANCE", "wl_custom_spliced", (type, _args) => new Modifiers.EnemyFusionChanceModifier(type, 1)), }; @@ -1466,15 +1464,13 @@ const trainerModifierPool: ModifierPool = { const enemyBuffModifierPool: ModifierPool = { [ModifierTier.COMMON]: [ - new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 10), - new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 10), - new WeightedModifierType(modifierTypes.ENEMY_ATTACK_POISON_CHANCE, 2), - new WeightedModifierType(modifierTypes.ENEMY_ATTACK_PARALYZE_CHANCE, 2), - new WeightedModifierType(modifierTypes.ENEMY_ATTACK_SLEEP_CHANCE, 2), - new WeightedModifierType(modifierTypes.ENEMY_ATTACK_FREEZE_CHANCE, 2), - new WeightedModifierType(modifierTypes.ENEMY_ATTACK_BURN_CHANCE, 2), - new WeightedModifierType(modifierTypes.ENEMY_STATUS_EFFECT_HEAL_CHANCE, 10), - new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 5), + new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 9), + new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 9), + new WeightedModifierType(modifierTypes.ENEMY_ATTACK_POISON_CHANCE, 3), + new WeightedModifierType(modifierTypes.ENEMY_ATTACK_PARALYZE_CHANCE, 3), + new WeightedModifierType(modifierTypes.ENEMY_ATTACK_BURN_CHANCE, 3), + new WeightedModifierType(modifierTypes.ENEMY_STATUS_EFFECT_HEAL_CHANCE, 9), + new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 4), new WeightedModifierType(modifierTypes.ENEMY_FUSED_CHANCE, 1) ].map(m => { m.setTier(ModifierTier.COMMON); return m; diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 975420ab528..8b2d12d89a0 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -2141,7 +2141,7 @@ export class EnemyDamageReducerModifier extends EnemyDamageMultiplierModifier { } export class EnemyTurnHealModifier extends EnemyPersistentModifier { - private healPercent: number; + public healPercent: number; constructor(type: ModifierType, healPercent: number, stackCount?: integer) { super(type, stackCount); @@ -2176,23 +2176,23 @@ export class EnemyTurnHealModifier extends EnemyPersistentModifier { } getMaxStackCount(scene: BattleScene): integer { - return 15; + return 10; } } export class EnemyAttackStatusEffectChanceModifier extends EnemyPersistentModifier { public effect: StatusEffect; - private chance: number; + public chance: number; constructor(type: ModifierType, effect: StatusEffect, chancePercent: number, stackCount?: integer) { super(type, stackCount); this.effect = effect; - this.chance = (chancePercent || 10) / 100; + this.chance = (chancePercent || 5) / 100; } match(modifier: Modifier): boolean { - return modifier instanceof EnemyAttackStatusEffectChanceModifier && modifier.effect === this.effect && modifier.chance === this.chance; + return modifier instanceof EnemyAttackStatusEffectChanceModifier && modifier.effect === this.effect; } clone(): EnemyAttackStatusEffectChanceModifier { @@ -2211,19 +2211,23 @@ export class EnemyAttackStatusEffectChanceModifier extends EnemyPersistentModifi return false; } + + getMaxStackCount(scene: BattleScene): integer { + return 10; + } } export class EnemyStatusEffectHealChanceModifier extends EnemyPersistentModifier { - private chance: number; + public chance: number; constructor(type: ModifierType, chancePercent: number, stackCount?: integer) { super(type, stackCount); - this.chance = (chancePercent || 10) / 100; + this.chance = (chancePercent || 2.5) / 100; } match(modifier: Modifier): boolean { - return modifier instanceof EnemyStatusEffectHealChanceModifier && modifier.chance === this.chance; + return modifier instanceof EnemyStatusEffectHealChanceModifier; } clone(): EnemyStatusEffectHealChanceModifier { @@ -2245,19 +2249,23 @@ export class EnemyStatusEffectHealChanceModifier extends EnemyPersistentModifier return false; } + + getMaxStackCount(scene: BattleScene): integer { + return 10; + } } export class EnemyEndureChanceModifier extends EnemyPersistentModifier { - private chance: number; + public chance: number; - constructor(type: ModifierType, chancePercent: number, stackCount?: integer) { - super(type, stackCount); + constructor(type: ModifierType, chancePercent?: number, stackCount?: integer) { + super(type, stackCount || 10); - this.chance = (chancePercent || 2.5) / 100; + this.chance = (chancePercent || 2) / 100; } match(modifier: Modifier) { - return modifier instanceof EnemyEndureChanceModifier && modifier.chance === this.chance; + return modifier instanceof EnemyEndureChanceModifier; } clone() { diff --git a/src/system/game-data.ts b/src/system/game-data.ts index ec4a814b643..0b81c4014b5 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -34,6 +34,8 @@ import {setSettingGamepad, SettingGamepad, settingGamepadDefaults} from "./setti import {setSettingKeyboard, SettingKeyboard} from "#app/system/settings/settings-keyboard"; import { TerrainChangedEvent, WeatherChangedEvent } from "#app/field/arena-events.js"; import { Device } from "#app/enums/devices.js"; +import { EnemyAttackStatusEffectChanceModifier } from "../modifier/modifier"; +import { StatusEffect } from "#app/data/status-effect.js"; const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet necessary @@ -1078,6 +1080,9 @@ export class GameData { if (md?.className === "ExpBalanceModifier") { // Temporarily limit EXP Balance until it gets reworked md.stackCount = Math.min(md.stackCount, 4); } + if (md instanceof EnemyAttackStatusEffectChanceModifier && md.effect === StatusEffect.FREEZE || md.effect === StatusEffect.SLEEP) { + continue; + } ret.push(new PersistentModifierData(md, player)); } return ret; From d04010226d8d02f1c288abafc33da45b149c90de Mon Sep 17 00:00:00 2001 From: Benjamin Odom Date: Tue, 4 Jun 2024 16:59:39 -0500 Subject: [PATCH 040/129] [Bug] Fix Leppa Berries not Updating Flyout PP (#1806) * Fix Leppa Berries not Updating Flyout PP * Code Cleanup * Update battle-flyout.ts --- src/battle-scene-events.ts | 26 ++++++++++++++++++++-- src/phases.ts | 3 ++- src/ui/arena-flyout.ts | 10 ++++----- src/ui/battle-flyout.ts | 45 +++++++++++++++++++++++++++++--------- 4 files changed, 66 insertions(+), 18 deletions(-) diff --git a/src/battle-scene-events.ts b/src/battle-scene-events.ts index 128fd5b5ceb..74fac97d2b7 100644 --- a/src/battle-scene-events.ts +++ b/src/battle-scene-events.ts @@ -1,4 +1,5 @@ import Move from "./data/move"; +import { BerryModifier } from "./modifier/modifier"; /** Alias for all {@linkcode BattleScene} events */ export enum BattleSceneEventType { @@ -13,6 +14,12 @@ export enum BattleSceneEventType { * @see {@linkcode MoveUsedEvent} */ MOVE_USED = "onMoveUsed", + /** + * Triggers when a berry gets successfully used + * @see {@linkcode BerryUsedEvent} + */ + BERRY_USED = "onBerryUsed", + /** * Triggers on the first turn of a new battle * @see {@linkcode TurnInitEvent} @@ -23,6 +30,7 @@ export enum BattleSceneEventType { * @see {@linkcode TurnEndEvent} */ TURN_END = "onTurnEnd", + /** * Triggers when a new {@linkcode Arena} is created during initialization * @see {@linkcode NewArenaEvent} @@ -50,7 +58,7 @@ export class CandyUpgradeNotificationChangedEvent extends Event { */ export class MoveUsedEvent extends Event { /** The ID of the {@linkcode Pokemon} that used the {@linkcode Move} */ - public userId: number; + public pokemonId: number; /** The {@linkcode Move} used */ public move: Move; /** The amount of PP used on the {@linkcode Move} this turn */ @@ -58,11 +66,25 @@ export class MoveUsedEvent extends Event { constructor(userId: number, move: Move, ppUsed: number) { super(BattleSceneEventType.MOVE_USED); - this.userId = userId; + this.pokemonId = userId; this.move = move; this.ppUsed = ppUsed; } } +/** + * Container class for {@linkcode BattleSceneEventType.BERRY_USED} events + * @extends Event +*/ +export class BerryUsedEvent extends Event { + /** The {@linkcode BerryModifier} being used */ + public berryModifier: BerryModifier; + constructor(berry: BerryModifier) { + super(BattleSceneEventType.BERRY_USED); + + this.berryModifier = berry; + } +} + /** * Container class for {@linkcode BattleSceneEventType.TURN_INIT} events * @extends Event diff --git a/src/phases.ts b/src/phases.ts index 110c4155849..c9c93ab414d 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -61,7 +61,7 @@ import { Abilities } from "./data/enums/abilities"; import * as Overrides from "./overrides"; import { TextStyle, addTextObject } from "./ui/text"; import { Type } from "./data/type"; -import { MoveUsedEvent, TurnEndEvent, TurnInitEvent } from "./battle-scene-events"; +import { BerryUsedEvent, MoveUsedEvent, TurnEndEvent, TurnInitEvent } from "./battle-scene-events"; export class LoginPhase extends Phase { @@ -2244,6 +2244,7 @@ export class BerryPhase extends FieldPhase { berryModifier.consumed = false; } } + this.scene.eventTarget.dispatchEvent(new BerryUsedEvent(berryModifier)); // Announce a berry was used } this.scene.updateModifiers(pokemon.isPlayer()); diff --git a/src/ui/arena-flyout.ts b/src/ui/arena-flyout.ts index 73660ca4457..77996625fed 100644 --- a/src/ui/arena-flyout.ts +++ b/src/ui/arena-flyout.ts @@ -81,11 +81,11 @@ export default class ArenaFlyout extends Phaser.GameObjects.Container { private readonly fieldEffectInfo: ArenaEffectInfo[] = []; // Stores callbacks in a variable so they can be unsubscribed from when destroyed - private onNewArenaEvent = (event: Event) => this.onNewArena(event); - private onTurnInitEvent = (event: Event) => this.onTurnInit(event); - private onTurnEndEvent = (event: Event) => this.onTurnEnd(event); + private readonly onNewArenaEvent = (event: Event) => this.onNewArena(event); + private readonly onTurnInitEvent = (event: Event) => this.onTurnInit(event); + private readonly onTurnEndEvent = (event: Event) => this.onTurnEnd(event); - private onFieldEffectChangedEvent = (event: Event) => this.onFieldEffectChanged(event); + private readonly onFieldEffectChangedEvent = (event: Event) => this.onFieldEffectChanged(event); constructor(scene: Phaser.Scene) { super(scene, 0, 0); @@ -379,6 +379,6 @@ export default class ArenaFlyout extends Phaser.GameObjects.Container { this.battleScene.arena.eventTarget.removeEventListener(ArenaEventType.TAG_ADDED, this.onFieldEffectChangedEvent); this.battleScene.arena.eventTarget.removeEventListener(ArenaEventType.TAG_REMOVED, this.onFieldEffectChangedEvent); - super.destroy(); + super.destroy(fromScene); } } diff --git a/src/ui/battle-flyout.ts b/src/ui/battle-flyout.ts index 3add54920b0..9a9e3ef46a9 100644 --- a/src/ui/battle-flyout.ts +++ b/src/ui/battle-flyout.ts @@ -4,7 +4,9 @@ import * as Utils from "../utils"; import BattleScene from "#app/battle-scene.js"; import { UiTheme } from "#app/enums/ui-theme.js"; import Move from "#app/data/move.js"; -import { BattleSceneEventType, MoveUsedEvent } from "#app/battle-scene-events.js"; +import { BattleSceneEventType, BerryUsedEvent, MoveUsedEvent } from "#app/battle-scene-events.js"; +import { BerryType } from "#app/data/enums/berry-type.js"; +import { Moves } from "#app/data/enums/moves.js"; /** Container for info about a {@linkcode Move} */ interface MoveInfo { @@ -53,7 +55,9 @@ export default class BattleFlyout extends Phaser.GameObjects.Container { /** The array of {@linkcode MoveInfo} used to track moves for the {@linkcode Pokemon} linked to the flyout */ private moveInfo: MoveInfo[] = new Array(); - private readonly onMoveUsed = (event) => this.updateInfo(event); + // Stores callbacks in a variable so they can be unsubscribed from when destroyed + private readonly onMoveUsedEvent = (event: Event) => this.onMoveUsed(event); + private readonly onBerryUsedEvent = (event: Event) => this.onBerryUsed(event); constructor(scene: Phaser.Scene, player: boolean) { super(scene, 0, 0); @@ -109,11 +113,12 @@ export default class BattleFlyout extends Phaser.GameObjects.Container { this.name = `Flyout ${this.pokemon.name}`; this.flyoutParent.name = `Flyout Parent ${this.pokemon.name}`; - this.battleScene.eventTarget.addEventListener(BattleSceneEventType.MOVE_USED, this.onMoveUsed); + this.battleScene.eventTarget.addEventListener(BattleSceneEventType.MOVE_USED, this.onMoveUsedEvent); + this.battleScene.eventTarget.addEventListener(BattleSceneEventType.BERRY_USED, this.onBerryUsedEvent); } /** Sets and formats the text property for all {@linkcode Phaser.GameObjects.Text} in the flyoutText array */ - setText() { + private setText() { for (let i = 0; i < this.flyoutText.length; i++) { const flyoutText = this.flyoutText[i]; const moveInfo = this.moveInfo[i]; @@ -122,21 +127,23 @@ export default class BattleFlyout extends Phaser.GameObjects.Container { continue; } - const currentPp = Math.max(moveInfo.maxPp - moveInfo.ppUsed, 0); + const currentPp = moveInfo.maxPp - moveInfo.ppUsed; flyoutText.text = `${moveInfo.move.name} ${currentPp}/${moveInfo.maxPp}`; } } /** Updates all of the {@linkcode MoveInfo} objects in the moveInfo array */ - updateInfo(event: Event) { + private onMoveUsed(event: Event) { const moveUsedEvent = event as MoveUsedEvent; - if (!moveUsedEvent || moveUsedEvent.userId !== this.pokemon?.id) { + if (!moveUsedEvent + || moveUsedEvent.pokemonId !== this.pokemon?.id + || moveUsedEvent.move.id === Moves.STRUGGLE) { // Ignore Struggle return; } const foundInfo = this.moveInfo.find(x => x?.move.id === moveUsedEvent.move.id); if (foundInfo) { - foundInfo.ppUsed += moveUsedEvent.ppUsed; + foundInfo.ppUsed = Math.min(foundInfo.ppUsed + moveUsedEvent.ppUsed, foundInfo.maxPp); } else { this.moveInfo.push({move: moveUsedEvent.move, maxPp: moveUsedEvent.move.pp, ppUsed: moveUsedEvent.ppUsed}); } @@ -144,6 +151,23 @@ export default class BattleFlyout extends Phaser.GameObjects.Container { this.setText(); } + private onBerryUsed(event: Event) { + const berryUsedEvent = event as BerryUsedEvent; + if (!berryUsedEvent + || berryUsedEvent.berryModifier.pokemonId !== this.pokemon?.id + || berryUsedEvent.berryModifier.berryType !== BerryType.LEPPA) { // We only care about Leppa berries + return; + } + + const foundInfo = this.moveInfo.find(info => info.ppUsed === info.maxPp); + if (!foundInfo) { // This will only happen on a de-sync of PP tracking + return; + } + foundInfo.ppUsed = Math.max(foundInfo.ppUsed - 10, 0); + + this.setText(); + } + /** Animates the flyout to either show or hide it by applying a fade and translation */ toggleFlyout(visible: boolean): void { this.scene.tweens.add({ @@ -156,8 +180,9 @@ export default class BattleFlyout extends Phaser.GameObjects.Container { } destroy(fromScene?: boolean): void { - this.battleScene.eventTarget.removeEventListener(BattleSceneEventType.MOVE_USED, this.onMoveUsed); + this.battleScene.eventTarget.removeEventListener(BattleSceneEventType.MOVE_USED, this.onMoveUsedEvent); + this.battleScene.eventTarget.removeEventListener(BattleSceneEventType.BERRY_USED, this.onBerryUsedEvent); - super.destroy(); + super.destroy(fromScene); } } From 48f60a5b50f6222448823f6c8da4bc43e00bf5ef Mon Sep 17 00:00:00 2001 From: Adrian T <68144167+torranx@users.noreply.github.com> Date: Wed, 5 Jun 2024 06:11:30 +0800 Subject: [PATCH 041/129] [QoL] add message when quick claw is triggered (#1684) --- src/modifier/modifier.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 8b2d12d89a0..28bf2f61c75 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -776,6 +776,9 @@ export class BypassSpeedChanceModifier extends PokemonHeldItemModifier { if (!bypassSpeed.value && pokemon.randSeedInt(10) < this.getStackCount()) { bypassSpeed.value = true; + if (this.type instanceof ModifierTypes.PokemonHeldItemModifierType && this.type.id === "QUICK_CLAW") { + pokemon.scene.queueMessage(getPokemonMessage(pokemon, " used its quick claw to move faster!")); + } return true; } From cfe9b3303a5b97f386c759c7deef3fd12d5136eb Mon Sep 17 00:00:00 2001 From: Frede Date: Wed, 5 Jun 2024 03:05:25 +0200 Subject: [PATCH 042/129] [QoL] Pokemon Info Container always on top of Battle UI (#1782) * Added "Skip Dialogues" option (if at least 1 classic win) * Removed error sound and hide option instead when classic wins = 0 * Add skip dialogues option to Unlockables and show unlocked message on first classic win * Only skips seen dialogues, removed dialogue option from unlockables, seen dialogues get saved to local storage * oops * dont show charSprite when skipping a dialogue, small fixes * pokemonInfoContainer always on top of battle UI when shown --------- Co-authored-by: Frederik Hobein --- src/ui/pokemon-info-container.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index b731b0d22b4..0952158a78d 100644 --- a/src/ui/pokemon-info-container.ts +++ b/src/ui/pokemon-info-container.ts @@ -62,6 +62,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { private initialX: number; private movesContainerInitialX: number; + private initialParentDepth: number; public statsContainer: StatsContainer; @@ -246,6 +247,8 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.setVisible(true); this.shown = true; + this.initialParentDepth = this.parentContainer.depth; + this.parentContainer.setDepth(3); }); } @@ -266,6 +269,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { hide(speedMultiplier: number = 1): Promise { return new Promise(resolve => { if (!this.shown) { + this.parentContainer.setDepth(this.initialParentDepth); return resolve(); } @@ -286,6 +290,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.pokemonShinyIcon.off("pointerover"); this.pokemonShinyIcon.off("pointerout"); (this.scene as BattleScene).ui.hideTooltip(); + this.parentContainer.setDepth(this.initialParentDepth); resolve(); } }); From 5464f964c9e2f27e161c75d35d12a657d321bf5c Mon Sep 17 00:00:00 2001 From: Matthew Olker Date: Tue, 4 Jun 2024 21:06:46 -0400 Subject: [PATCH 043/129] add more gameobject names for debugging --- src/ui/pokemon-info-container.ts | 16 ++++++++++++++++ src/ui/stats-container.ts | 1 + src/ui/ui.ts | 1 + 3 files changed, 18 insertions(+) diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index 0952158a78d..017237c1fe3 100644 --- a/src/ui/pokemon-info-container.ts +++ b/src/ui/pokemon-info-container.ts @@ -74,13 +74,16 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { } setup(): void { + this.setName("container-pkmn-info"); const currentLanguage = i18next.resolvedLanguage; const langSettingKey = Object.keys(languageSettings).find(lang => currentLanguage.includes(lang)); const textSettings = languageSettings[langSettingKey]; const infoBg = addWindow(this.scene, 0, 0, this.infoWindowWidth, 132); infoBg.setOrigin(0.5, 0.5); + infoBg.setName("window-info-bg"); this.pokemonMovesContainer = this.scene.add.container(6, 14); + this.pokemonMovesContainer.setName("container-pkmn-moves"); this.movesContainerInitialX = this.pokemonMovesContainer.x; @@ -90,21 +93,26 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { const movesBg = addWindow(this.scene, 0, 0, 58, 52); movesBg.setOrigin(1, 0); + movesBg.setName("window-moves-bg"); this.pokemonMovesContainer.add(movesBg); const movesLabel = addTextObject(this.scene, -movesBg.width / 2, 6, i18next.t("pokemonInfoContainer:moveset"), TextStyle.WINDOW, { fontSize: "64px" }); movesLabel.setOrigin(0.5, 0); + movesLabel.setName("text-moves"); this.pokemonMovesContainer.add(movesLabel); for (let m = 0; m < 4; m++) { const moveContainer = this.scene.add.container(-6, 18 + 7 * m); moveContainer.setScale(0.5); + moveContainer.setName("container-move"); const moveBg = this.scene.add.nineslice(0, 0, "type_bgs", "unknown", 92, 14, 2, 2, 2, 2); moveBg.setOrigin(1, 0); + moveBg.setName("nineslice-move-bg"); const moveLabel = addTextObject(this.scene, -moveBg.width / 2, 0, "-", TextStyle.PARTY); moveLabel.setOrigin(0.5, 0); + moveLabel.setName("text-move-label"); this.pokemonMoveBgs.push(moveBg); this.pokemonMoveLabels.push(moveLabel); @@ -133,38 +141,46 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.pokemonGenderLabelText = addTextObject(this.scene, infoContainerLabelXPos, 18, i18next.t("pokemonInfoContainer:gender"), TextStyle.WINDOW, { fontSize: infoContainerTextSize }); this.pokemonGenderLabelText.setOrigin(1, 0); this.pokemonGenderLabelText.setVisible(false); + this.pokemonGenderLabelText.setName("text-pkmn-gender-label"); this.add(this.pokemonGenderLabelText); this.pokemonGenderText = addTextObject(this.scene, infoContainerTextXPos, 18, "", TextStyle.WINDOW, { fontSize: infoContainerTextSize }); this.pokemonGenderText.setOrigin(0, 0); this.pokemonGenderText.setVisible(false); + this.pokemonGenderText.setName("text-pkmn-gender"); this.add(this.pokemonGenderText); this.pokemonAbilityLabelText = addTextObject(this.scene, infoContainerLabelXPos, 28, i18next.t("pokemonInfoContainer:ability"), TextStyle.WINDOW, { fontSize: infoContainerTextSize }); this.pokemonAbilityLabelText.setOrigin(1, 0); + this.pokemonAbilityLabelText.setName("text-pkmn-ability-label"); this.add(this.pokemonAbilityLabelText); this.pokemonAbilityText = addTextObject(this.scene, infoContainerTextXPos, 28, "", TextStyle.WINDOW, { fontSize: infoContainerTextSize }); this.pokemonAbilityText.setOrigin(0, 0); + this.pokemonAbilityText.setName("text-pkmn-ability"); this.add(this.pokemonAbilityText); this.pokemonNatureLabelText = addTextObject(this.scene, infoContainerLabelXPos, 38, i18next.t("pokemonInfoContainer:nature"), TextStyle.WINDOW, { fontSize: infoContainerTextSize }); this.pokemonNatureLabelText.setOrigin(1, 0); + this.pokemonNatureLabelText.setName("text-pkmn-nature-label"); this.add(this.pokemonNatureLabelText); this.pokemonNatureText = addBBCodeTextObject(this.scene, infoContainerTextXPos, 38, "", TextStyle.WINDOW, { fontSize: infoContainerTextSize, lineSpacing: 3, maxLines: 2 }); this.pokemonNatureText.setOrigin(0, 0); + this.pokemonNatureText.setName("text-pkmn-nature"); this.add(this.pokemonNatureText); this.pokemonShinyIcon = this.scene.add.image(-43.5, 48.5, "shiny_star"); this.pokemonShinyIcon.setOrigin(0, 0); this.pokemonShinyIcon.setScale(0.75); this.pokemonShinyIcon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 12, 15), Phaser.Geom.Rectangle.Contains); + this.pokemonShinyIcon.setName("img-pkmn-shiny-icon"); this.add(this.pokemonShinyIcon); this.pokemonFusionShinyIcon = this.scene.add.image(this.pokemonShinyIcon.x, this.pokemonShinyIcon.y, "shiny_star_2"); this.pokemonFusionShinyIcon.setOrigin(0, 0); this.pokemonFusionShinyIcon.setScale(0.75); + this.pokemonFusionShinyIcon.setName("img-pkmn-fusion-shiny-icon"); this.add(this.pokemonFusionShinyIcon); this.setVisible(false); diff --git a/src/ui/stats-container.ts b/src/ui/stats-container.ts index f5b88773dd8..6dccba18fa8 100644 --- a/src/ui/stats-container.ts +++ b/src/ui/stats-container.ts @@ -23,6 +23,7 @@ export class StatsContainer extends Phaser.GameObjects.Container { } setup() { + this.setName("container-stats"); const ivChartBgData = new Array(6).fill(null).map((_, i: integer) => [ ivChartSize * ivChartStatCoordMultipliers[ivChartStatIndexes[i]][0], ivChartSize * ivChartStatCoordMultipliers[ivChartStatIndexes[i]][1] ] ).flat(); const ivChartBg = this.scene.add.polygon(48, 44, ivChartBgData, 0xd8e0f0, 0.625); diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 0f33a9cb6c7..7640112e084 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -174,6 +174,7 @@ export default class UI extends Phaser.GameObjects.Container { } setup(): void { + this.setName("container-ui"); for (const handler of this.handlers) { handler.setup(); } From 7c9e5e9f525dbab3853948c768583f05c8b72fa2 Mon Sep 17 00:00:00 2001 From: Matthew Olker Date: Tue, 4 Jun 2024 21:41:36 -0400 Subject: [PATCH 044/129] loading screen disclaimer --- src/loading-scene.ts | 43 +++++++++++++++++++++++++++++++++------ src/locales/de/menu.ts | 2 ++ src/locales/en/menu.ts | 2 ++ src/locales/es/menu.ts | 2 ++ src/locales/fr/menu.ts | 2 ++ src/locales/it/menu.ts | 2 ++ src/locales/ko/menu.ts | 2 ++ src/locales/pt_BR/menu.ts | 2 ++ src/locales/zh_CN/menu.ts | 2 ++ src/locales/zh_TW/menu.ts | 2 ++ 10 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/loading-scene.ts b/src/loading-scene.ts index 7c108a3c30e..d45aa83c740 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -352,14 +352,17 @@ export class LoadingScene extends SceneBase { const width = this.cameras.main.width; const height = this.cameras.main.height; - const logo = this.add.image(width / 2, 240, ""); + const midWidth = width / 2; + const midHeight = height / 2; + + const logo = this.add.image(midWidth, 240, ""); logo.setVisible(false); logo.setOrigin(0.5, 0.5); logo.setScale(4); const percentText = this.make.text({ - x: width / 2, - y: height / 2 - 24, + x: midWidth, + y: midHeight - 24, text: "0%", style: { font: "72px emerald", @@ -369,8 +372,8 @@ export class LoadingScene extends SceneBase { percentText.setOrigin(0.5, 0.5); const assetText = this.make.text({ - x: width / 2, - y: height / 2 + 48, + x: midWidth, + y: midHeight + 48, text: "", style: { font: "48px emerald", @@ -379,6 +382,32 @@ export class LoadingScene extends SceneBase { }); assetText.setOrigin(0.5, 0.5); + const disclaimerText = this.make.text({ + x: midWidth, + y: assetText.y + 152, + text: i18next.t("menu:disclaimer"), + style: { + font: "72px emerald", + color: "#DA3838", + }, + }); + disclaimerText.setOrigin(0.5, 0.5); + + const disclaimerDescriptionText = this.make.text({ + x: midWidth, + y: disclaimerText.y + 120, + text: i18next.t("menu:disclaimerDescription"), + style: { + font: "48px emerald", + color: "#ffffff", + align: "center" + }, + }); + disclaimerDescriptionText.setOrigin(0.5, 0.5); + + disclaimerText.setVisible(false); + disclaimerDescriptionText.setVisible(false); + const intro = this.add.video(0, 0); intro.setOrigin(0, 0); intro.setScale(3); @@ -388,7 +417,7 @@ export class LoadingScene extends SceneBase { percentText.setText(`${Math.floor(parsedValue * 100)}%`); progressBar.clear(); progressBar.fillStyle(0xffffff, 0.8); - progressBar.fillRect(width / 2 - 320, 360, 640 * parsedValue, 64); + progressBar.fillRect(midWidth - 320, 360, 640 * parsedValue, 64); }); this.load.on("fileprogress", file => { @@ -423,6 +452,8 @@ export class LoadingScene extends SceneBase { ease: "Sine.easeIn" }); loadingGraphics.map(g => g.setVisible(true)); + disclaimerText.setVisible(true); + disclaimerDescriptionText.setVisible(true); }); intro.play(); break; diff --git a/src/locales/de/menu.ts b/src/locales/de/menu.ts index 8901eb5b9c2..e1e5db72b9c 100644 --- a/src/locales/de/menu.ts +++ b/src/locales/de/menu.ts @@ -49,4 +49,6 @@ export const menu: SimpleTranslationEntries = { "empty":"Leer", "yes":"Ja", "no":"Nein", + "disclaimer": "DISCLAIMER", + "disclaimerDescription": "This game is an unfinished product; it might have playability issues (including the potential loss of save data),\n change without notice, and may or may not be updated further or completed." } as const; diff --git a/src/locales/en/menu.ts b/src/locales/en/menu.ts index 12b197bf245..d43ac0983f4 100644 --- a/src/locales/en/menu.ts +++ b/src/locales/en/menu.ts @@ -49,4 +49,6 @@ export const menu: SimpleTranslationEntries = { "empty":"Empty", "yes":"Yes", "no":"No", + "disclaimer": "DISCLAIMER", + "disclaimerDescription": "This game is an unfinished product; it might have playability issues (including the potential loss of save data),\n change without notice, and may or may not be updated further or completed." } as const; diff --git a/src/locales/es/menu.ts b/src/locales/es/menu.ts index c369ecceaab..517569ff40b 100644 --- a/src/locales/es/menu.ts +++ b/src/locales/es/menu.ts @@ -49,4 +49,6 @@ export const menu: SimpleTranslationEntries = { "empty":"Vacío", "yes":"Sí", "no":"No", + "disclaimer": "DISCLAIMER", + "disclaimerDescription": "This game is an unfinished product; it might have playability issues (including the potential loss of save data),\n change without notice, and may or may not be updated further or completed." } as const; diff --git a/src/locales/fr/menu.ts b/src/locales/fr/menu.ts index a60afdf44a8..e955d4970c0 100644 --- a/src/locales/fr/menu.ts +++ b/src/locales/fr/menu.ts @@ -44,4 +44,6 @@ export const menu: SimpleTranslationEntries = { "empty":"Vide", "yes":"Oui", "no":"Non", + "disclaimer": "DISCLAIMER", + "disclaimerDescription": "This game is an unfinished product; it might have playability issues (including the potential loss of save data),\n change without notice, and may or may not be updated further or completed." } as const; diff --git a/src/locales/it/menu.ts b/src/locales/it/menu.ts index afa6567836c..e891146f754 100644 --- a/src/locales/it/menu.ts +++ b/src/locales/it/menu.ts @@ -49,4 +49,6 @@ export const menu: SimpleTranslationEntries = { "empty":"Vuoto", "yes":"Si", "no":"No", + "disclaimer": "DISCLAIMER", + "disclaimerDescription": "This game is an unfinished product; it might have playability issues (including the potential loss of save data),\n change without notice, and may or may not be updated further or completed." } as const; diff --git a/src/locales/ko/menu.ts b/src/locales/ko/menu.ts index 8d46dafc721..b91d674521e 100644 --- a/src/locales/ko/menu.ts +++ b/src/locales/ko/menu.ts @@ -49,4 +49,6 @@ export const menu: SimpleTranslationEntries = { "empty":"빈 슬롯", "yes":"예", "no":"아니오", + "disclaimer": "DISCLAIMER", + "disclaimerDescription": "This game is an unfinished product; it might have playability issues (including the potential loss of save data),\n change without notice, and may or may not be updated further or completed." } as const; diff --git a/src/locales/pt_BR/menu.ts b/src/locales/pt_BR/menu.ts index d96d4b68051..2d1b7058301 100644 --- a/src/locales/pt_BR/menu.ts +++ b/src/locales/pt_BR/menu.ts @@ -49,4 +49,6 @@ export const menu: SimpleTranslationEntries = { "empty": "Vazio", "yes": "Sim", "no": "Não", + "disclaimer": "DISCLAIMER", + "disclaimerDescription": "This game is an unfinished product; it might have playability issues (including the potential loss of save data),\n change without notice, and may or may not be updated further or completed." } as const; diff --git a/src/locales/zh_CN/menu.ts b/src/locales/zh_CN/menu.ts index 99e4b35e9ce..d8cad6b05af 100644 --- a/src/locales/zh_CN/menu.ts +++ b/src/locales/zh_CN/menu.ts @@ -49,4 +49,6 @@ export const menu: SimpleTranslationEntries = { "empty": "空", "yes": "是", "no": "否", + "disclaimer": "DISCLAIMER", + "disclaimerDescription": "This game is an unfinished product; it might have playability issues (including the potential loss of save data),\n change without notice, and may or may not be updated further or completed." } as const; diff --git a/src/locales/zh_TW/menu.ts b/src/locales/zh_TW/menu.ts index 41813457c32..680db51e8ac 100644 --- a/src/locales/zh_TW/menu.ts +++ b/src/locales/zh_TW/menu.ts @@ -49,4 +49,6 @@ export const menu: SimpleTranslationEntries = { "empty":"空", "yes":"是", "no":"否", + "disclaimer": "DISCLAIMER", + "disclaimerDescription": "This game is an unfinished product; it might have playability issues (including the potential loss of save data),\n change without notice, and may or may not be updated further or completed." } as const; From 7eb0e8e77dd07fd7c1e268fd40411ab2836fbb10 Mon Sep 17 00:00:00 2001 From: Matthew Olker Date: Tue, 4 Jun 2024 21:45:36 -0400 Subject: [PATCH 045/129] revert pokemon info container depth change --- src/ui/pokemon-info-container.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index 017237c1fe3..41992b42212 100644 --- a/src/ui/pokemon-info-container.ts +++ b/src/ui/pokemon-info-container.ts @@ -62,7 +62,6 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { private initialX: number; private movesContainerInitialX: number; - private initialParentDepth: number; public statsContainer: StatsContainer; @@ -263,8 +262,6 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.setVisible(true); this.shown = true; - this.initialParentDepth = this.parentContainer.depth; - this.parentContainer.setDepth(3); }); } @@ -285,7 +282,6 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { hide(speedMultiplier: number = 1): Promise { return new Promise(resolve => { if (!this.shown) { - this.parentContainer.setDepth(this.initialParentDepth); return resolve(); } @@ -306,7 +302,6 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.pokemonShinyIcon.off("pointerover"); this.pokemonShinyIcon.off("pointerout"); (this.scene as BattleScene).ui.hideTooltip(); - this.parentContainer.setDepth(this.initialParentDepth); resolve(); } }); From dd3ffb43156ff0baed4f4b999dc11baca8d321b3 Mon Sep 17 00:00:00 2001 From: damocleas Date: Tue, 4 Jun 2024 20:05:29 -0600 Subject: [PATCH 046/129] Voucher Item tier changes/addition (#1516) * Voucher Item tier changes/addition - Voucher moved from Ultra -> Great Tier, given a weight of 1 and disappears after first reroll, should still appear more often with a healthy team than in ultra tier at all. - Voucher Plus moved from Master -> Rogue Tier, with weight starting at 9 -> 5 and decreasing with each reroll with 3 -> 2 Should appear just a bit more often than before. - Voucher Premium added to Master (based on suggestion from Madmadness) with same weight as new Voucher Plus, and disabled in Endless / Endless Spliced Should appear ~20% of the time with *perfect luck* in a whole average classic run. Overall would be a 40-45% increase in total eggs in perfect conditions (luck, healthy team, etc.) * fixed an extra spacebar at the end of 1303 * fixed an extra spacebar at the end of 1360 * fixed to account for Wide Lens being added --- src/modifier/modifier-type.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 5f1fb1d2956..2fa24903726 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1322,6 +1322,7 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3), new WeightedModifierType(modifierTypes.TERA_SHARD, 1), new WeightedModifierType(modifierTypes.DNA_SPLICERS, (party: Pokemon[]) => party[0].scene.gameMode.isSplicedOnly && party.filter(p => !p.fusionSpecies).length > 1 ? 4 : 0), + new WeightedModifierType(modifierTypes.VOUCHER, (party: Pokemon[], rerollCount: integer) => !party[0].scene.gameMode.isDaily ? Math.max(1 - rerollCount, 0) : 0, 1), ].map(m => { m.setTier(ModifierTier.GREAT); return m; }), @@ -1382,6 +1383,8 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.FORM_CHANGE_ITEM, 18), new WeightedModifierType(modifierTypes.MEGA_BRACELET, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 50), 4) * 8, 32), new WeightedModifierType(modifierTypes.DYNAMAX_BAND, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 50), 4) * 8, 32), + new WeightedModifierType(modifierTypes.VOUCHER_PLUS, (party: Pokemon[], rerollCount: integer) => !party[0].scene.gameMode.isDaily ? Math.max(5 - rerollCount * 2, 0) : 0, 5), + new WeightedModifierType(modifierTypes.WIDE_LENS, 4), ].map(m => { m.setTier(ModifierTier.ROGUE); return m; }), @@ -1390,7 +1393,7 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.SHINY_CHARM, 14), new WeightedModifierType(modifierTypes.HEALING_CHARM, 18), new WeightedModifierType(modifierTypes.MULTI_LENS, 18), - new WeightedModifierType(modifierTypes.VOUCHER_PLUS, (party: Pokemon[], rerollCount: integer) => !party[0].scene.gameMode.isDaily ? Math.max(9 - rerollCount * 3, 0) : 0, 9), + new WeightedModifierType(modifierTypes.VOUCHER_PREMIUM, (party: Pokemon[], rerollCount: integer) => !party[0].scene.gameMode.isDaily && !party[0].scene.gameMode.isEndless && !party[0].scene.gameMode.isSplicedOnly ? Math.max(6 - rerollCount * 2, 0) : 0, 6), new WeightedModifierType(modifierTypes.DNA_SPLICERS, (party: Pokemon[]) => !party[0].scene.gameMode.isSplicedOnly && party.filter(p => !p.fusionSpecies).length > 1 ? 24 : 0, 24), new WeightedModifierType(modifierTypes.MINI_BLACK_HOLE, (party: Pokemon[]) => party[0].scene.gameData.unlocks[Unlockables.MINI_BLACK_HOLE] ? 1 : 0, 1), ].map(m => { From 1dd7a792d4fd3ec8931664363583f29dabe92b68 Mon Sep 17 00:00:00 2001 From: Madmadness65 Date: Tue, 4 Jun 2024 21:15:10 -0500 Subject: [PATCH 047/129] Fix Wide Lens being accidentally added to Rogue pool --- src/modifier/modifier-type.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 2fa24903726..e626ec3f8f7 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1384,7 +1384,6 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.MEGA_BRACELET, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 50), 4) * 8, 32), new WeightedModifierType(modifierTypes.DYNAMAX_BAND, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 50), 4) * 8, 32), new WeightedModifierType(modifierTypes.VOUCHER_PLUS, (party: Pokemon[], rerollCount: integer) => !party[0].scene.gameMode.isDaily ? Math.max(5 - rerollCount * 2, 0) : 0, 5), - new WeightedModifierType(modifierTypes.WIDE_LENS, 4), ].map(m => { m.setTier(ModifierTier.ROGUE); return m; }), From 7cc5ca18399dfe241ae7a94d6f1850449ce58f5b Mon Sep 17 00:00:00 2001 From: sodam <66295123+sodaMelon@users.noreply.github.com> Date: Wed, 5 Jun 2024 11:17:49 +0900 Subject: [PATCH 048/129] [Localization] Korean Kanto gym leader dialogue ( (#1794) * localized to korean (Gym leader's dialouge at Kanto region) * modified Brock's dialogue clearly * Add missed word to lt_surge's dialogue --- src/locales/ko/dialogue.ts | 150 ++++++++++++++++++------------------- 1 file changed, 75 insertions(+), 75 deletions(-) diff --git a/src/locales/ko/dialogue.ts b/src/locales/ko/dialogue.ts index a2b0b2f28bc..625554b2968 100644 --- a/src/locales/ko/dialogue.ts +++ b/src/locales/ko/dialogue.ts @@ -373,141 +373,141 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "brock": { "encounter": { - 1: "My expertise on Rock-type Pokémon will take you down! Come on!", - 2: "My rock-hard willpower will overwhelm you!", - 3: "Allow me to show you the true strength of my Pokémon!" + 1: "내 전문인 바위 타입 포켓몬으로 널 쓰러뜨려줄게! 덤벼!", + 2: "바위같은 의지로 널 압도하겠어!", + 3: "내 포켓몬의 진정한 힘을 보여줄게!" }, "victory": { - 1: "Your Pokémon's strength have overcome my rock-hard defenses!", - 2: "The world is huge! I'm glad to have had a chance to battle you.", - 3: "Perhaps I should go back to pursuing my dream as a Pokémon Breeder…" + 1: "네 포켓몬의 힘이 바위같은 내 방어를 이겼어!", + 2: "세상은 넓구나! 너랑 겨뤄볼 수 있어서 즐거웠어.", + 3: "아마도 난 포켓몬 브리더의 꿈을 이루러 가야할지도…" }, "defeat": { - 1: "The best offense is a good defense!\nThat's my way of doing things!", - 2: "Come study rocks with me next time to better learn how to fight them!", - 3: "Hah, all my traveling around the regions is paying off!" + 1: "최선의 공격은 적절한 방어지!\n그게 내 방식이야!", + 2: "다음에 나한테 더 배우러와. 바위타입과 어떻게 싸워야하는지 알려주지!", + 3: "아, 여러 지역을 돌아다니며 여행한 보람이 있군!" } }, "misty": { "encounter": { - 1: "My policy is an all out offensive with Water-type Pokémon!", - 2: "Hiya, I'll show you the strength of my aquatic Pokémon!", - 3: "My dream was to go on a journey and battle powerful trainers…\nWill you be a sufficient challenge?" + 1: "내 방침은 물타입 포켓몬으로 공격하고 공격하고 또 공격하는 거!", + 2: "아하핫, 너한테 내 물타입 포켓몬들의 힘을 보여줄게!", + 3: "내 꿈은 여행을 다니며 강한 트레이너들과 배틀하는 거였어…\n네가 그 충분한 도전자가 될 수 있는지 볼까?" }, "victory": { - 1: "You really are strong… I'll admit that you are skilled…", - 2: "Grrr… You know you just got lucky, right?!", - 3: "Wow, you're too much! I can't believe you beat me!" + 1: "너 정말로 강하구나… 그 실력 인정하도록 할게…", + 2: "으으… 너 그냥 운이 좋았던거야, 그치?!", + 3: "우와, 너 대단해! 날 이기다니 믿을 수 없어!" }, "defeat": { - 1: "Was the mighty Misty too much for you?", - 2: "I hope you saw my Pokémon's elegant swimming techniques!", - 3: "Your Pokémon were no match for my pride and joys!" + 1: "최강인 최이슬! 너한테 좀 심했나?", + 2: "내 포켓몬들의 우아한 수영 테크닉을 봤길 바랄게!", + 3: "내 프라이드와 즐거움엔 네 포켓몬들은 상대가 안 돼. " } }, "lt_surge": { "encounter": { - 1: "My Electric Pokémon saved me during the war! I'll show you how!", - 2: "Ten-hut! I'll shock you into surrender!", - 3: "I'll zap you just like I do to all my enemies in battle!" + 1: "마이 전기 포켓몬은 전쟁에서 미를 구했어요! 하우를 유에게 보여줄게요!", + 2: "헤이! 쇼크로 유를 항복시키겠어요!", + 3: "배틀에서 마이 에너미에게 했던 것처럼 유에게도 펀치를 날리겠어요!" }, "victory": { - 1: "Whoa! Your team's the real deal, kid!", - 2: "Aaargh, you're strong! Even my electric tricks lost against you.", - 3: "That was an absolutely shocking loss!" + 1: "와우, 키드! 유어 팀은 진짜 대단하군요!", + 2: "으흐흑, 유는 스트롱하네요! 마이 전기 트릭도 유에겐 로스트입니다.", + 3: "앱솔루트하고 쇼킹한 패배였어요!" }, "defeat": { - 1: "Oh yeah! When it comes to Electric-type Pokémon, I'm number one in the world!", - 2: "Hahaha! That was an electrifying battle, kid!", - 3: "A Pokémon battle is war, and I have showed you first-hand combat!" + 1: "오우 예! 전기 타입 포켓몬이라면, 미가 월드에서 넘버 원이에요!", + 2: "하하하! 키드, 이것이 찌릿찌릿 일렉트릭 배틀입니다!", + 3: "포켓몬 배틀은 전쟁, 앤드 나는 유에게 직접 전투를 보여줬습니다!" } }, "erika": { "encounter": { - 1: "Ah, the weather is lovely here…\nOh, a battle? Very well then.", - 2: "My Pokémon battling skills rival that of my flower arranging skills.", - 3: "Oh, I hope the pleasant aroma of my Pokémon doesn't put me to sleep again…", - 4: "Seeing flowers in a garden is so soothing." + 1: "아, 오늘은 날씨가 좋네요…\n음, 배틀일까요? 그럼 더 좋죠.", + 2: "제 포켓몬들의 배틀 실력은 제 꽃꽂이 실력만큼 대단하답니다.", + 3: "아, 제 포켓몬의 달콤한 향기가 저를 다시 잠들게 하지 않았으면 좋겠는데……", + 4: "정원에서 꽃을 보면 마음이 편안해져요.”." }, "victory": { - 1: "Oh! I concede defeat.", - 2: "That match was most delightful.", - 3: "Ah, it appears it is my loss…", - 4: "Oh, my goodness." + 1: "앗! 제 패배를 인정합니다.", + 2: "방금 경기 정말 달콤했어요.", + 3: "아, 제가 진 것 같네요…", + 4: "앗, 맙소사." }, "defeat": { - 1: "I was afraid I would doze off…", - 2: "Oh my, it seems my Grass Pokémon overwhelmed you.", - 3: "That battle was such a soothing experience.", - 4: "Oh… Is that all?" + 1: "저 조금 걱정했어요. 너무 졸려서…", + 2: "어머, 제 풀 포켓몬이 당신을 압도한 것 같네요.", + 3: "이 배틀 정말로 편안한 경험이었네요.", + 4: "어머… 이게 끝인가요?" } }, "janine": { "encounter": { - 1: "I am mastering the art of poisonous attacks.\nI shall spar with you today!", - 2: "Father trusts that I can hold my own.\nI will prove him right!", - 3: "My ninja techniques are only second to my Father's!\nCan you keep up?" + 1: "난 독을 사용하는 인술을 갈고 닦고 있어.\n오늘 수련에서는 너랑 대련할거야!", + 2: "아버지는 내가 잘해낼 수 있다고 신뢰하셔.\n 그게 맞는다는 걸 증명할게!", + 3: "내 인술은 아버지한테 뒤처지지 않아! 따라올 수 있겠어? " }, "victory": { - 1: "Even now, I still need training… I understand.", - 2: "Your battle technique has outmatched mine.", - 3: "I'm going to really apply myself and improve my skills." + 1: "역시 아직도, 난 더 수련이 필요해… 납득했어.", + 2: "네 배틀 기술이 내 인술보다 한 수위야.", + 3: "더 스스로 갈고 닦아서, 내 인술을 향상 시키겠어." }, "defeat": { - 1: "Fufufu… the poison has sapped all your strength to battle.", - 2: "Ha! You didn't stand a chance against my superior ninja skills!", - 3: "Father's faith in me has proven to not be misplaced." + 1: "후후후… 독이 네 기력을 모두 가져가버렸네.", + 2: "하핫, 너 내 인술에 맞설 기회를 잡지 못했구나!", + 3: "나를 향한 아버지의 신뢰, 틀리지 않았다는 걸 증명해냈어." } }, "sabrina": { "encounter": { - 1: "Through my psychic ability, I had a vision of your arrival!", - 2: "I dislike fighting, but if you wish, I will show you my powers!", - 3: "I can sense great ambition in you. I shall see if it not unfounded." + 1: "내 초능력을 통해서, 너의 도착은 예상하고 있었어!", + 2: "싸우는 건 좋아하지 않지만 네가 원한다면… 나의 힘을 보여줄게!", + 3: "네게서 큰 염원이 느껴져. 그것이 근거 없는 것이 아닌지 지켜보겠어." }, "victory": { - 1: "Your power… It far exceeds what I foresaw…", - 2: "I failed to accurately predict your power.", - 3: "Even with my immense psychic powers, I cannot sense another as strong as you." + 1: "너의 힘은… 내가 예견했던 것보다 훨씬 뛰어나…", + 2: "나는 너의 힘을 정확하게 예측하지 못했어.", + 3: "나 엄청난 초능력을 가지고도, 너처럼 강한 사람을 느끼지 못했네." }, "defeat": { - 1: "This victory… It is exactly as I foresaw in my visions!", - 2: "Perhaps it was another I sensed a great desire in…", - 3: "Hone your abilities before recklessly charging into battle.\nYou never know what the future may hold if you do…" + 1: "이 승리는… 내가 환상에서 예견한 그대로네!", + 2: "아마도 그건, 내가 깊이 느꼈던 또 다른 염원이었을거야…", + 3: "무모하게 배틀에 임하기 전에 능력을 갈고닦도록.\n넌 미래가 어떻게 될지 예지할 수 없으니까…" } }, "blaine": { "encounter": { - 1: "Hah! Hope you brought a Burn Heal!", - 2: "My fiery Pokémon will incinerate all challengers!", - 3: "Get ready to play with fire!" + 1: "우오오~옷! 화상치료제는 잘 준비했는가!", + 2: "나의 포켓몬은 모든 것을 불꽃으로 태워버리는 강한 녀석들뿐이다!", + 3: "불꽃과 함께할 준비는 됐는가!" }, "victory": { - 1: "I have burned down to nothing! Not even ashes remain!", - 2: "Didn't I stoke the flames high enough?", - 3: "I'm all burned out… But this makes my motivation to improve burn even hotter!" + 1: "아무것도 남지 않고 불타버렸다! 재조차 남지 않았어!", + 2: "내가 불을 너무 세게 피우지 않았나?", + 3: "불태웠다… 하지만 이건 불꽃을 향상시키려는 내 동기를 더욱 뜨겁게 만드는군!" }, "defeat": { - 1: "My raging inferno cannot be quelled!", - 2: "My Pokémon have been powered up with the heat from this victory!", - 3: "Hah! My passion burns brighter than yours!" + 1: "나의 타오르는 불길은 진압할 수 없다!", + 2: "내 포켓몬은 이번 승리의 열기로 더욱 강해졌다!", + 3: "하! 내 열정이 네 것보다 더 밝게 타오르고 있군!" } }, "giovanni": { "encounter": { - 1: "I, the leader of Team Rocket, will make you feel a world of pain!", - 2: "My training here will be vital before I am to face my old associates again.", - 3: "I do not think you are prepared for the level of failure you are about to experience!" + 1: "나, 로켓단의 리더가, 고통의 세계를 느끼게 해주마!", + 2: "옛 동료들과 다시 만나기 전, 이곳에서의 훈련은 매우 중요하겠군.", + 3: "너는 곧 경험하게 될 실패에 대한 준비가 되어 있지 않군!" }, "victory": { - 1: "WHAT! Me, lose?! There is nothing I wish to say to you!", - 2: "Hmph… You could never understand what I hope to achieve.", - 3: "This defeat is merely delaying the inevitable.\nI will rise Team Rocket from the ashes in due time." + 1: "하! 내가 졌다고?! 더 이상 할말이 없군!", + 2: "흐음… 넌 내가 이루고자 하는 것을 결코 이해할 수 없을 거다.", + 3: "이 패배는 피할 수 없는 것을 단지 지연시킬 뿐.\n때가 되면 잿더미에서 로켓단을 일으켜 세울 것이다." }, "defeat": { - 1: "Not being able to measure your own strength shows that you are still but a child.", - 2: "Do not try to interfere with me again.", - 3: "I hope you understand how foolish challenging me was." + 1: "자신의 힘을 스스로 잴수 없다는 것은 네가 아직 꼬맹이라는 것을 보여준다고 할 수 있지.", + 2: "다시는 나를 방해하지 말도록.", + 3: "나에게 도전하는 것이 얼마나 어리석은 짓인지 이해했으면 좋겠군." } }, "roxanne": { From 219f227cab9a176e843cedaabcaedf9482d8d220 Mon Sep 17 00:00:00 2001 From: Madmadness65 Date: Tue, 4 Jun 2024 21:53:22 -0500 Subject: [PATCH 049/129] Remove basic Voucher from Ultra item pool It should only be in the Great item pool now. --- src/modifier/modifier-type.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index e626ec3f8f7..ca3712f8295 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1358,7 +1358,6 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.EXP_SHARE, 12), new WeightedModifierType(modifierTypes.EXP_BALANCE, 4), new WeightedModifierType(modifierTypes.TERA_ORB, (party: Pokemon[]) => Math.min(Math.max(Math.floor(party[0].scene.currentBattle.waveIndex / 50) * 2, 1), 4), 4), - new WeightedModifierType(modifierTypes.VOUCHER, (party: Pokemon[], rerollCount: integer) => !party[0].scene.gameMode.isDaily ? Math.max(3 - rerollCount, 0) : 0, 3), new WeightedModifierType(modifierTypes.WIDE_LENS, 4), ].map(m => { m.setTier(ModifierTier.ULTRA); return m; From bd34fc0b476effe2b57983513962830afbdd82b4 Mon Sep 17 00:00:00 2001 From: Adrian T <68144167+torranx@users.noreply.github.com> Date: Wed, 5 Jun 2024 21:38:43 +0800 Subject: [PATCH 050/129] [Bug] Fix quick claw message showing if command is not fight (#1819) --- src/modifier/modifier.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 28bf2f61c75..6f296c997ca 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -22,6 +22,7 @@ import { Nature } from "#app/data/nature"; import { BattlerTagType } from "#app/data/enums/battler-tag-type"; import * as Overrides from "../overrides"; import { ModifierType, modifierTypes } from "./modifier-type"; +import { Command } from "#app/ui/command-ui-handler.js"; export type ModifierPredicate = (modifier: Modifier) => boolean; @@ -776,7 +777,10 @@ export class BypassSpeedChanceModifier extends PokemonHeldItemModifier { if (!bypassSpeed.value && pokemon.randSeedInt(10) < this.getStackCount()) { bypassSpeed.value = true; - if (this.type instanceof ModifierTypes.PokemonHeldItemModifierType && this.type.id === "QUICK_CLAW") { + const isCommandFight = pokemon.scene.currentBattle.turnCommands[pokemon.getBattlerIndex()]?.command === Command.FIGHT; + const hasQuickClaw = this.type instanceof ModifierTypes.PokemonHeldItemModifierType && this.type.id === "QUICK_CLAW"; + + if (isCommandFight && hasQuickClaw) { pokemon.scene.queueMessage(getPokemonMessage(pokemon, " used its quick claw to move faster!")); } return true; From e599931ff32dec13377d739fc26174fde6bf03c7 Mon Sep 17 00:00:00 2001 From: Lee ByungHoon Date: Wed, 5 Jun 2024 23:00:43 +0900 Subject: [PATCH 051/129] [Localization] Add Korean trainer dialogue (youngster, lass) (#1809) * [Localization] #1761 Korean trainer dialogue (youngster, lass) * changed ellipsis character that used 3 character to used 1 character. * Update src/locales/ko/dialogue.ts Co-authored-by: returntoice * Update src/locales/ko/dialogue.ts Co-authored-by: returntoice * Update src/locales/ko/dialogue.ts Co-authored-by: returntoice --------- Co-authored-by: returntoice --- src/locales/ko/dialogue.ts | 88 +++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/src/locales/ko/dialogue.ts b/src/locales/ko/dialogue.ts index 625554b2968..7e65d9e0f09 100644 --- a/src/locales/ko/dialogue.ts +++ b/src/locales/ko/dialogue.ts @@ -4,58 +4,58 @@ import { DialogueTranslationEntries, SimpleTranslationEntries } from "#app/plugi export const PGMdialogue: DialogueTranslationEntries = { "youngster": { "encounter": { - 1: "Hey, wanna battle?", - 2: "Are you a new trainer too?", - 3: "Hey, I haven't seen you before. Let's battle!", - 4: "I just lost, so I'm trying to find more Pokémon.\nWait! You look weak! Come on, let's battle!", - 5: "Have we met or not? I don't really remember. Well, I guess it's nice to meet you anyway!", - 6: "All right! Let's go!", - 7: "All right! Here I come! I'll show you my power!", - 8: "Haw haw haw... I'll show you how hawesome my Pokémon are!", - 9: "No need to waste time saying hello. Bring it on whenever you're ready!", - 10: "Don't let your guard down, or you may be crying when a kid beats you.", - 11: "I've raised my Pokémon with great care. You're not allowed to hurt them!", - 12: "Glad you made it! It won't be an easy job from here.", - 13: "The battles continue forever! Welcome to the world with no end!" + 1: "거기 너! 나와 배틀 어때?", + 2: "넌 새내기 트레이너구나. 맞지?", + 3: "거기 너! 처음보는 얼굴인데? 나랑 배틀하자!", + 4: "방금 배틀에서 져서 새로운 포켓몬을 찾는 중이야.\n잠깐! 넌 약해보이는데? 어서 나와 배틀하자!", + 5: "우리 만난 적이 있었던가? 잘 기억은 안나지만 어쨌든 만나서 반가워!", + 6: "좋아! 시작하자!", + 7: "좋아! 내가 왔다! 내 힘을 보여주지!", + 8: "하하하… 내 포켓몬이 얼마나 멋진지 보여주겠어!", + 9: "인사할 시간도 없어. 준비가 되었다면 이리 와!", + 10: "긴장을 늦추지마. 그렇지 않으면 어린이에게 맞아 울지도 몰라.", + 11: "난 내 포켓몬들을 소중히 키웠어. 내 포켓몬에게 상처를 입히게 놔두지 않겠어!", + 12: "여기까지 잘 왔구나! 하지만 지금부턴 쉽지 않을거야.", + 13: "배틀은 끝나지 않아! 끝없는 배틀의 세계에 온 것을 환영해!" }, "victory": { - 1: "Wow! You're strong!", - 2: "I didn't stand a chance, huh?", - 3: "I'll find you again when I'm older and beat you!", - 4: "Ugh. I don't have any more Pokémon.", - 5: "No way… NO WAY! How could I lose again…", - 6: "No! I lost!", - 7: "Whoa! You are incredible! I'm amazed and surprised!", - 8: "Could it be… How… My Pokémon and I are the strongest, though…", - 9: "I won't lose next time! Let's battle again sometime!", - 10: "Sheesh! Can't you see that I'm just a kid! It wasn't fair of you to go all out like that!", - 11: "Your Pokémon are more amazing! Trade with me!", - 12: "I got a little carried away earlier, but what job was I talking about?", - 13: "Ahaha! There it is! That's right! You're already right at home in this world!" + 1: "우와! 넌 강하구나!", + 2: "하? 난 기회가 없었어.", + 3: "내가 조금 더 큰 다음엔 널 찾아서 때리겠어!", + 4: "으.. 더이상 가지고 있는 포켓몬이 없어.", + 5: "말도 안돼… 안돼! 내가 또 지다니…", + 6: "안돼! 내가 지다니!", + 7: "우와! 정말 깜짝 놀랐어! 넌 정말 강하구나!", + 8: "이럴수가… 내 포켓몬과 난 최강인데… 어떻게…", + 9: "다음엔 지지 않을거야! 다음에 다시 배틀하자!", + 10: "쳇! 내가 어린애인게 보이지 않아?! 그렇게 최선을 다하는건 불공평해!", + 11: "네 포켓몬은 정말 굉장하구나! 나와 교환하자!", + 12: "내가 잠깐 정신이 나갔었나 봐. 내가 무슨 말을 하고 있었지?", + 13: "아하! 거기구나! 좋아! 넌 이미 이 세계에 머무를 곳이 있구나!" } }, "lass": { "encounter": { - 1: "Let's have a battle, shall we?", - 2: "You look like a new trainer. Let's have a battle!", - 3: "I don't recognize you. How about a battle?", - 4: "Let's have a fun Pokémon battle!", - 5: "I'll show you the ropes of how to really use Pokémon!", - 6: "A serious battle starts from a serious beginning! Are you sure you're ready?", - 7: "You're only young once. And you only get one shot at a given battle. Soon, you'll be nothing but a memory.", - 8: "You'd better go easy on me, OK? Though I'll be seriously fighting!", - 9: "School is boring. I've got nothing to do. Yawn. I'm only battling to kill the time." + 1: "나랑 배틀하자, 어때?", + 2: "넌 신입 트레이너구나. 나랑 배틀하자!", + 3: "너 거기 있었구나? 나랑 배틀할래?", + 4: "재밌는 포켓몬 배틀하자!", + 5: "내가 포켓몬을 어떻게 다뤄야하는지 보여줄게!", + 6: "진정한 배틀은 진지한 자세부터 시작이야! 준비됐어?", + 7: "젊음이 한순간이듯 배틀에서 네 기회도 단 한번만 주어질거야. 곧 넌 추억속으로 사라질거야.", + 8: "나에겐 살살해도 돼, 알았지? 그래도 난 진지하게 싸울거야!", + 9: "학교는 지겨워. 나는 할 일이 없어. 하암~ 난 그저 시간을 때우기 위해 싸울뿐이야." }, "victory": { - 1: "That was impressive! I've got a lot to learn.", - 2: "I didn't think you'd beat me that bad…", - 3: "I hope we get to have a rematch some day.", - 4: "That was pretty amazingly fun! You've totally exhausted me…", - 5: "You actually taught me a lesson! You're pretty amazing!", - 6: "Seriously, I lost. That is, like, seriously depressing, but you were seriously cool.", - 7: "I don't need memories like this. Deleting memory…", - 8: "Hey! I told you to go easy on me! Still, you're pretty cool when you're serious.", - 9: "I'm actually getting tired of battling… There's gotta be something new to do…" + 1: "인상적이었어! 난 아직 배울게 많구나.", + 2: "내가 이렇게까지 크게 질 줄은 몰랐어…", + 3: "언젠가 우리가 다시 배틀할 수 있을 날을 기다릴게.", + 4: "놀라울 정도로 엄청 재미있었어! 넌 날 완전히 지치게 만들어버렸네…", + 5: "넌 나에게 진짜 교훈을 주었어! 넌 정말 대단해!", + 6: "세상에, 내가 지다니. 이거 정말 우울하지만… 넌 정말 멋있었어.", + 7: "난 이런 기억따윈 필요없어. 잊어버리겠어…", + 8: "거기 너! 살살하라고 했지! 그래도 넌 진지할때 정말 멋지구나!", + 9: "사실 배틀하는 것이 지루하던 참이야… 뭔가 새로운 것이 없을까?" } }, "breeder": { From fe732bbbe601bf41a75eef24607ae20b53f14376 Mon Sep 17 00:00:00 2001 From: Frede Date: Wed, 5 Jun 2024 16:09:06 +0200 Subject: [PATCH 052/129] [QoL] Pokemon Info Container hides Enemy Modifier Bar (#1820) * Added "Skip Dialogues" option (if at least 1 classic win) * Removed error sound and hide option instead when classic wins = 0 * Add skip dialogues option to Unlockables and show unlocked message on first classic win * Only skips seen dialogues, removed dialogue option from unlockables, seen dialogues get saved to local storage * oops * dont show charSprite when skipping a dialogue, small fixes * pokemonInfoContainer always on top of battle UI when shown * removed setDepth and rather hide enemyModifierBar --------- Co-authored-by: Frederik Hobein --- src/battle-scene.ts | 8 ++++++++ src/ui/pokemon-info-container.ts | 3 +++ 2 files changed, 11 insertions(+) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 957052d9881..f1ccbbc17ec 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1313,6 +1313,14 @@ export default class BattleScene extends SceneBase { }); } + showEnemyModifierBar(): void { + this.enemyModifierBar.setVisible(true); + } + + hideEnemyModifierBar(): void { + this.enemyModifierBar.setVisible(false); + } + updateBiomeWaveText(): void { const isBoss = !(this.currentBattle.waveIndex % 10); const biomeString: string = getBiomeName(this.arena.biomeType); diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index 41992b42212..1ffa32d2394 100644 --- a/src/ui/pokemon-info-container.ts +++ b/src/ui/pokemon-info-container.ts @@ -262,6 +262,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.setVisible(true); this.shown = true; + this.scene.hideEnemyModifierBar(); }); } @@ -282,6 +283,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { hide(speedMultiplier: number = 1): Promise { return new Promise(resolve => { if (!this.shown) { + this.scene.showEnemyModifierBar(); return resolve(); } @@ -302,6 +304,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.pokemonShinyIcon.off("pointerover"); this.pokemonShinyIcon.off("pointerout"); (this.scene as BattleScene).ui.hideTooltip(); + this.scene.showEnemyModifierBar(); resolve(); } }); From dce4518b930bb30fe71567ad0f6de7399ac4d43d Mon Sep 17 00:00:00 2001 From: GoldTra <162721984+GoldTra@users.noreply.github.com> Date: Wed, 5 Jun 2024 16:24:33 +0200 Subject: [PATCH 053/129] Updated Spanish translations (#1825) --- src/locales/es/pokemon-info-container.ts | 14 +++++++------- src/locales/es/weather.ts | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/locales/es/pokemon-info-container.ts b/src/locales/es/pokemon-info-container.ts index 068c9ebb431..785f5fce275 100644 --- a/src/locales/es/pokemon-info-container.ts +++ b/src/locales/es/pokemon-info-container.ts @@ -1,11 +1,11 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const pokemonInfoContainer: SimpleTranslationEntries = { - "moveset": "Moveset", - "gender": "Gender:", - "ability": "Ability:", - "nature": "Nature:", - "epic": "Epic", - "rare": "Rare", - "common": "Common" + "moveset": "Movimientos", + "gender": "Género:", + "ability": "Habilid:", + "nature": "Natur:", + "epic": "Épico", + "rare": "Raro", + "common": "Común" } as const; diff --git a/src/locales/es/weather.ts b/src/locales/es/weather.ts index 04ebf977bc2..a5ba94d1e7d 100644 --- a/src/locales/es/weather.ts +++ b/src/locales/es/weather.ts @@ -15,12 +15,12 @@ export const weather: SimpleTranslationEntries = { "sandstormStartMessage": "¡Se ha desatado una tormenta de arena!", "sandstormLapseMessage": "La tormenta de arena arrecia...", "sandstormClearMessage": "La tormenta de arena termino.", - "sandstormDamageMessage": "¡La tormenta de arena zarandea al\n{{pokemonName}}{{pokemonPrefix}}!", + "sandstormDamageMessage": "¡La tormenta de arena zarandea al\n{{pokemonName}} {{pokemonPrefix}}!", "hailStartMessage": "¡Ha empezado a granizar!", "hailLapseMessage": "Sigue granizando...", "hailClearMessage": "Had dejado de granizar.", - "hailDamageMessage": "El granizo golpea al\n{{pokemonName}}{{pokemonPrefix}}!", + "hailDamageMessage": "El granizo golpea al\n{{pokemonName}} {{pokemonPrefix}}!", "snowStartMessage": "¡Ha empezado a nevar!", "snowLapseMessage": "Sigue nevando...", From 395fa6e33d18a1806bfb0f7be38d39c366cf3375 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Wed, 5 Jun 2024 07:36:42 -0700 Subject: [PATCH 054/129] [Bug] Allow second mon of a fusion to learn/remember on-evo moves (#1778) Fixes #520 --- src/field/pokemon.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 2b8f28c4826..b659923c47d 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1168,6 +1168,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.fusionSpecies) { const evolutionLevelMoves = levelMoves.slice(0, Math.max(levelMoves.findIndex(lm => !!lm[0]), 0)); const fusionLevelMoves = this.getFusionSpeciesForm(true).getLevelMoves(); + const fusionEvolutionLevelMoves = fusionLevelMoves.slice(0, Math.max(fusionLevelMoves.findIndex(flm => !!flm[0]), 0)); const newLevelMoves: LevelMoves = []; while (levelMoves.length && levelMoves[0][0] < startingLevel) { levelMoves.shift(); @@ -1179,6 +1180,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { for (const elm of evolutionLevelMoves.reverse()) { levelMoves.unshift(elm); } + for (const felm of fusionEvolutionLevelMoves.reverse()) { + fusionLevelMoves.unshift(felm); + } } for (let l = includeEvolutionMoves ? 0 : startingLevel; l <= this.level; l++) { if (l === 1 && startingLevel > 1) { From 6d71db0f13da3ee8184001486342af1a16dafa6f Mon Sep 17 00:00:00 2001 From: Matthew Olker Date: Wed, 5 Jun 2024 11:02:41 -0400 Subject: [PATCH 055/129] luck based encounter --- src/battle-scene.ts | 2 +- src/field/arena.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index f1ccbbc17ec..4a21d8bd551 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1426,7 +1426,7 @@ export default class BattleScene extends SceneBase { randomSpecies(waveIndex: integer, level: integer, fromArenaPool?: boolean, speciesFilter?: PokemonSpeciesFilter, filterAllEvolutions?: boolean): PokemonSpecies { if (fromArenaPool) { - return this.arena.randomSpecies(waveIndex, level); + return this.arena.randomSpecies(waveIndex, level,null , getPartyLuckValue(this.party)); } const filteredSpecies = speciesFilter ? [...new Set(allSpecies.filter(s => s.isCatchable()).filter(speciesFilter).map(s => { if (!filterAllEvolutions) { diff --git a/src/field/arena.ts b/src/field/arena.ts index f6390e40db5..9bf8a3eac0a 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -68,14 +68,20 @@ export class Arena { } } - randomSpecies(waveIndex: integer, level: integer, attempt?: integer): PokemonSpecies { + randomSpecies(waveIndex: integer, level: integer, attempt?: integer, luckValue?: integer): PokemonSpecies { const overrideSpecies = this.scene.gameMode.getOverrideSpecies(waveIndex); if (overrideSpecies) { return overrideSpecies; } const isBoss = !!this.scene.getEncounterBossSegments(waveIndex, level) && !!this.pokemonPool[BiomePoolTier.BOSS].length && (this.biomeType !== Biome.END || this.scene.gameMode.isClassic || this.scene.gameMode.isWaveFinal(waveIndex)); - const tierValue = Utils.randSeedInt(!isBoss ? 512 : 64); + const randVal = isBoss ? 64 : 512; + // luck influences encounter rarity + let luckModifier = 0; + if (typeof luckValue !== "undefined") { + luckModifier = luckValue * (isBoss ? 2 : 0.5); + } + const tierValue = Utils.randSeedInt(randVal - luckModifier); let tier = !isBoss ? tierValue >= 156 ? BiomePoolTier.COMMON : tierValue >= 32 ? BiomePoolTier.UNCOMMON : tierValue >= 6 ? BiomePoolTier.RARE : tierValue >= 1 ? BiomePoolTier.SUPER_RARE : BiomePoolTier.ULTRA_RARE : tierValue >= 20 ? BiomePoolTier.BOSS : tierValue >= 6 ? BiomePoolTier.BOSS_RARE : tierValue >= 1 ? BiomePoolTier.BOSS_SUPER_RARE : BiomePoolTier.BOSS_ULTRA_RARE; From b532a6b2d013032bda987a8560cb412ffc284189 Mon Sep 17 00:00:00 2001 From: Matthew Olker Date: Wed, 5 Jun 2024 11:21:47 -0400 Subject: [PATCH 056/129] okay not that lucky --- src/field/arena.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/field/arena.ts b/src/field/arena.ts index 9bf8a3eac0a..6999eb39785 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -79,7 +79,7 @@ export class Arena { // luck influences encounter rarity let luckModifier = 0; if (typeof luckValue !== "undefined") { - luckModifier = luckValue * (isBoss ? 2 : 0.5); + luckModifier = luckValue * (isBoss ? 0.5 : 2); } const tierValue = Utils.randSeedInt(randVal - luckModifier); let tier = !isBoss From e614aec8ca7bb47d2816f07a53caf7b43b519af4 Mon Sep 17 00:00:00 2001 From: YounesM Date: Wed, 5 Jun 2024 19:10:24 +0200 Subject: [PATCH 057/129] [Bug] Fix for Dancer activating when enemy in not on field / using a 2 steps charging move (#1708) * Fixes !1686 and !1450 * Added forbidden tags * Restored original import indentations * Restored missing import --- src/data/ability.ts | 11 ++++++++--- src/phases.ts | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 31f41492ba7..3e711efc76b 100755 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -23,7 +23,7 @@ import { Command } from "../ui/command-ui-handler"; import { BerryModifierType } from "#app/modifier/modifier-type"; import { getPokeballName } from "./pokeball"; import { Species } from "./enums/species"; -import {BattlerIndex} from "#app/battle"; +import { BattlerIndex } from "#app/battle"; export class Ability implements Localizable { public id: Abilities; @@ -2764,8 +2764,12 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { * @return true if the Dancer ability was resolved */ applyPostMoveUsed(dancer: Pokemon, move: PokemonMove, source: Pokemon, targets: BattlerIndex[], args: any[]): boolean | Promise { + // List of tags that prevent the Dancer from replicating the move + const forbiddenTags = [BattlerTagType.FLYING, BattlerTagType.UNDERWATER, + BattlerTagType.UNDERGROUND, BattlerTagType.HIDDEN]; // The move to replicate cannot come from the Dancer - if (source.getBattlerIndex() !== dancer.getBattlerIndex()) { + if (source.getBattlerIndex() !== dancer.getBattlerIndex() + && !dancer.summonData.tags.some(tag => forbiddenTags.includes(tag.tagType))) { // If the move is an AttackMove or a StatusMove the Dancer must replicate the move on the source of the Dance if (move.getMove() instanceof AttackMove || move.getMove() instanceof StatusMove) { const target = this.getTarget(dancer, source, targets); @@ -2774,8 +2778,9 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { // If the move is a SelfStatusMove (ie. Swords Dance) the Dancer should replicate it on itself dancer.scene.unshiftPhase(new MovePhase(dancer.scene, dancer, [dancer.getBattlerIndex()], move, true)); } + return true; } - return true; + return false; } /** diff --git a/src/phases.ts b/src/phases.ts index c9c93ab414d..a31cd4297aa 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -2583,7 +2583,7 @@ export class MovePhase extends BattlePhase { this.scene.getPlayerField().forEach(pokemon => { applyPostMoveUsedAbAttrs(PostMoveUsedAbAttr, pokemon, this.move, this.pokemon, this.targets); }); - this.scene.getEnemyParty().forEach(pokemon => { + this.scene.getEnemyField().forEach(pokemon => { applyPostMoveUsedAbAttrs(PostMoveUsedAbAttr, pokemon, this.move, this.pokemon, this.targets); }); } From 4a9fe763a55baae0f09cf36087f1f836b7ccec59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Ricardo=20Fleury=20Oliveira?= Date: Wed, 5 Jun 2024 14:44:48 -0300 Subject: [PATCH 058/129] [ptBR] Translated text (#1821) * translated disclaimer * minor fix --- src/locales/pt_BR/game-stats-ui-handler.ts | 2 +- src/locales/pt_BR/menu.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/locales/pt_BR/game-stats-ui-handler.ts b/src/locales/pt_BR/game-stats-ui-handler.ts index dc03ba64cf2..396b30b4c78 100644 --- a/src/locales/pt_BR/game-stats-ui-handler.ts +++ b/src/locales/pt_BR/game-stats-ui-handler.ts @@ -27,7 +27,7 @@ export const gameStatsUiHandler: SimpleTranslationEntries = { "subLegendsHatched": "Sub-Lendários Chocados", "legendsSeen": "Lendários Vistos", "legendsCaught": "Lendários Capturados", - "legendsHatched": "Legendários Chocados", + "legendsHatched": "Lendários Chocados", "mythicalsSeen": "Míticos Vistos", "mythicalsCaught": "Míticos Capturados", "mythicalsHatched": "Míticos Chocados", diff --git a/src/locales/pt_BR/menu.ts b/src/locales/pt_BR/menu.ts index 2d1b7058301..000ffb1e397 100644 --- a/src/locales/pt_BR/menu.ts +++ b/src/locales/pt_BR/menu.ts @@ -49,6 +49,6 @@ export const menu: SimpleTranslationEntries = { "empty": "Vazio", "yes": "Sim", "no": "Não", - "disclaimer": "DISCLAIMER", - "disclaimerDescription": "This game is an unfinished product; it might have playability issues (including the potential loss of save data),\n change without notice, and may or may not be updated further or completed." + "disclaimer": "AVISO", + "disclaimerDescription": "Este jogo é um produto inacabado; ele pode ter problemas de jogabilidade (incluindo possíveis perdas de dados salvos),\n sofrer alterações sem aviso prévio e pode ou não ser atualizado ou concluído." } as const; From 3855b92237191f9e83af2c4263da50dfdc6e5ef4 Mon Sep 17 00:00:00 2001 From: Matthew Olker Date: Wed, 5 Jun 2024 15:41:04 -0400 Subject: [PATCH 059/129] fix weird luck when catching a fused mon --- src/data/ability.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 3e711efc76b..0a7b86e3e2b 100755 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2462,7 +2462,7 @@ export class PostTurnStatusHealAbAttr extends PostTurnAbAttr { * @returns Returns true if healed from status, false if not */ applyPostTurn(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise { - if (this.effects.includes(pokemon.status.effect)) { + if (this.effects.includes(pokemon.status?.effect)) { if (pokemon.getMaxHp() !== pokemon.hp) { const scene = pokemon.scene; const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; From 4b36d38acbdc2d80448727b08bb77ab1b33c6207 Mon Sep 17 00:00:00 2001 From: Xavion3 Date: Thu, 6 Jun 2024 05:42:15 +1000 Subject: [PATCH 060/129] Hotfix for NaN luck (#1840) Makes luck default to 0 if false-y --- src/modifier/modifier-type.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index ca3712f8295..e404781bc8d 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1875,8 +1875,9 @@ export class ModifierTypeOption { } export function getPartyLuckValue(party: Pokemon[]): integer { - return Phaser.Math.Clamp(party.map(p => p.isFainted() ? 0 : p.getLuck()) + const luck = Phaser.Math.Clamp(party.map(p => p.isFainted() ? 0 : p.getLuck()) .reduce((total: integer, value: integer) => total += value, 0), 0, 14); + return luck || 0; } export function getLuckString(luckValue: integer): string { From 46dc7e9b01ee94bd8905e1ebbe260bb523884791 Mon Sep 17 00:00:00 2001 From: Blitzy <118096277+Blitz425@users.noreply.github.com> Date: Wed, 5 Jun 2024 16:56:31 -0500 Subject: [PATCH 061/129] [Balance] Give Partner Pikachu its Signature Moves and change Cosplay's stats (#1737) * Update Cosplay Pikachu stats * Give Partner Pikachu its signature moves in its learnset * Added a "custom" note next to stats * Spread out signatures per Brain Frog's request --- src/data/pokemon-level-moves.ts | 4 +++- src/data/pokemon-species.ts | 12 ++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/data/pokemon-level-moves.ts b/src/data/pokemon-level-moves.ts index 18d83f452cf..69c661e8d3d 100644 --- a/src/data/pokemon-level-moves.ts +++ b/src/data/pokemon-level-moves.ts @@ -18539,12 +18539,14 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = { [ 8, Moves.DOUBLE_TEAM ], [ 12, Moves.ELECTRO_BALL ], [ 16, Moves.FEINT ], - [ 20, Moves.SPARK ], + [ 20, Moves.ZIPPY_ZAP ], //Custom [ 24, Moves.AGILITY ], [ 28, Moves.IRON_TAIL ], [ 32, Moves.DISCHARGE ], + [ 34, Moves.FLOATY_FALL ], //Custom [ 36, Moves.THUNDERBOLT ], [ 40, Moves.LIGHT_SCREEN ], + [ 42, Moves.SPLISHY_SPLASH ], //Custom [ 44, Moves.THUNDER ], [ 48, Moves.PIKA_PAPOW ], ], diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 793f4ff9927..562a79ea333 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -918,12 +918,12 @@ export function initSpecies() { new PokemonSpecies(Species.PIKACHU, 1, false, false, false, "Mouse Pokémon", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 320, 35, 55, 40, 50, 50, 90, 190, 50, 112, GrowthRate.MEDIUM_FAST, 50, true, true, new PokemonForm("Normal", "", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 320, 35, 55, 40, 50, 50, 90, 190, 50, 112, true, null, true), new PokemonForm("Partner", "partner", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, "", true), - new PokemonForm("Cosplay", "cosplay", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 320, 35, 55, 40, 50, 50, 90, 190, 50, 112, true, null, true), - new PokemonForm("Cool Cosplay", "cool-cosplay", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 320, 35, 55, 40, 50, 50, 90, 190, 50, 112, true, null, true), - new PokemonForm("Beauty Cosplay", "beauty-cosplay", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 320, 35, 55, 40, 50, 50, 90, 190, 50, 112, true, null, true), - new PokemonForm("Cute Cosplay", "cute-cosplay", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 320, 35, 55, 40, 50, 50, 90, 190, 50, 112, true, null, true), - new PokemonForm("Smart Cosplay", "smart-cosplay", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 320, 35, 55, 40, 50, 50, 90, 190, 50, 112, true, null, true), - new PokemonForm("Tough Cosplay", "tough-cosplay", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 320, 35, 55, 40, 50, 50, 90, 190, 50, 112, true, null, true), + new PokemonForm("Cosplay", "cosplay", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom + new PokemonForm("Cool Cosplay", "cool-cosplay", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom + new PokemonForm("Beauty Cosplay", "beauty-cosplay", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom + new PokemonForm("Cute Cosplay", "cute-cosplay", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom + new PokemonForm("Smart Cosplay", "smart-cosplay", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom + new PokemonForm("Tough Cosplay", "tough-cosplay", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.ELECTRIC, null, 21, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 420, 45, 60, 65, 100, 75, 75, 190, 50, 112), ), new PokemonSpecies(Species.RAICHU, 1, false, false, false, "Mouse Pokémon", Type.ELECTRIC, null, 0.8, 30, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 485, 60, 90, 55, 90, 80, 110, 75, 50, 243, GrowthRate.MEDIUM_FAST, 50, true), From 283714bd0fa1776ae4d408d8fcb0d75d7901fe91 Mon Sep 17 00:00:00 2001 From: flx-sta <50131232+flx-sta@users.noreply.github.com> Date: Wed, 5 Jun 2024 17:11:07 -0700 Subject: [PATCH 062/129] [Refactor] Move enums from game-data into their respective files in `src/data/enums` (#1837) * move PlayerGender enum into src/data/enums/player-gender.ts this is necessary to avoid circular dependencies which did crash tests in the past (in PRs) * Update settings.ts * Update game-data.ts * Update summary-ui-handler.ts * Update ui.ts * move Passive & GameDataType enums into own files --- src/battle-scene.ts | 3 ++- src/battle.ts | 2 +- src/data/enums/game-data-type.ts | 10 ++++++++++ src/data/enums/passive.ts | 7 +++++++ src/data/enums/player-gender.ts | 8 ++++++++ src/phases.ts | 3 ++- src/system/game-data.ts | 21 ++------------------- src/system/settings/settings.ts | 2 +- src/ui/menu-ui-handler.ts | 2 +- src/ui/starter-select-ui-handler.ts | 6 ++++-- src/ui/summary-ui-handler.ts | 2 +- src/ui/ui.ts | 2 +- 12 files changed, 40 insertions(+), 28 deletions(-) create mode 100644 src/data/enums/game-data-type.ts create mode 100644 src/data/enums/passive.ts create mode 100644 src/data/enums/player-gender.ts diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 4a21d8bd551..c68ff0dac40 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -11,7 +11,8 @@ import { Phase } from "./phase"; import { initGameSpeed } from "./system/game-speed"; import { Biome } from "./data/enums/biome"; import { Arena, ArenaBase } from "./field/arena"; -import { GameData, PlayerGender } from "./system/game-data"; +import { GameData } from "./system/game-data"; +import { PlayerGender } from "./data/enums/player-gender"; import { TextStyle, addTextObject } from "./ui/text"; import { Moves } from "./data/enums/moves"; import { allMoves } from "./data/move"; diff --git a/src/battle.ts b/src/battle.ts index f6db308d344..c857a8766ed 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -8,7 +8,7 @@ import { Moves } from "./data/enums/moves"; import { TrainerType } from "./data/enums/trainer-type"; import { GameMode } from "./game-mode"; import { BattleSpec } from "./enums/battle-spec"; -import { PlayerGender } from "./system/game-data"; +import { PlayerGender } from "./data/enums/player-gender"; import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier"; import { PokeballType } from "./data/pokeball"; import {trainerConfigs} from "#app/data/trainer-config"; diff --git a/src/data/enums/game-data-type.ts b/src/data/enums/game-data-type.ts new file mode 100644 index 00000000000..179817fe5be --- /dev/null +++ b/src/data/enums/game-data-type.ts @@ -0,0 +1,10 @@ +/** + * enum for the game data types + */ +export enum GameDataType { + SYSTEM, + SESSION, + SETTINGS, + TUTORIALS, + SEEN_DIALOGUES +} diff --git a/src/data/enums/passive.ts b/src/data/enums/passive.ts new file mode 100644 index 00000000000..b9c2dacd463 --- /dev/null +++ b/src/data/enums/passive.ts @@ -0,0 +1,7 @@ +/** + * enum for passive + */ +export enum Passive { + UNLOCKED = 1, + ENABLED = 2 +} diff --git a/src/data/enums/player-gender.ts b/src/data/enums/player-gender.ts new file mode 100644 index 00000000000..b6cd550b1f2 --- /dev/null +++ b/src/data/enums/player-gender.ts @@ -0,0 +1,8 @@ +/** + * enum for the players gender + */ +export enum PlayerGender { + UNSET, + MALE, + FEMALE +} diff --git a/src/phases.ts b/src/phases.ts index a31cd4297aa..406d47cff22 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -43,7 +43,8 @@ import { EggHatchPhase } from "./egg-hatch-phase"; import { Egg } from "./data/egg"; import { vouchers } from "./system/voucher"; import { loggedInUser, updateUserInfo } from "./account"; -import { PlayerGender, SessionSaveData } from "./system/game-data"; +import { SessionSaveData } from "./system/game-data"; +import { PlayerGender } from "./data/enums/player-gender"; import { addPokeballCaptureStars, addPokeballOpenParticles } from "./field/anims"; import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeManualTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangePreMoveTrigger } from "./data/pokemon-forms"; import { battleSpecDialogue, getCharVariantFromDialogue, miscDialogue } from "./data/dialogue"; diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 0b81c4014b5..8ed7f521507 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -36,28 +36,11 @@ import { TerrainChangedEvent, WeatherChangedEvent } from "#app/field/arena-event import { Device } from "#app/enums/devices.js"; import { EnemyAttackStatusEffectChanceModifier } from "../modifier/modifier"; import { StatusEffect } from "#app/data/status-effect.js"; +import { PlayerGender } from "#app/data/enums/player-gender"; +import { GameDataType } from "#app/data/enums/game-data-type"; const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet necessary -export enum GameDataType { - SYSTEM, - SESSION, - SETTINGS, - TUTORIALS, - SEEN_DIALOGUES -} - -export enum PlayerGender { - UNSET, - MALE, - FEMALE -} - -export enum Passive { - UNLOCKED = 1, - ENABLED = 2 -} - export function getDataTypeKey(dataType: GameDataType, slotId: integer = 0): string { switch (dataType) { case GameDataType.SYSTEM: diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index cf657d0e828..c407aff6927 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -3,7 +3,7 @@ import i18next from "i18next"; import BattleScene from "../../battle-scene"; import { hasTouchscreen } from "../../touch-controls"; import { updateWindowType } from "../../ui/ui-theme"; -import { PlayerGender } from "../game-data"; +import { PlayerGender } from "#app/data/enums/player-gender"; import { CandyUpgradeNotificationChangedEvent } from "#app/battle-scene-events.js"; import { MoneyFormat } from "../../enums/money-format"; import SettingsUiHandler from "#app/ui/settings/settings-ui-handler"; diff --git a/src/ui/menu-ui-handler.ts b/src/ui/menu-ui-handler.ts index 060cb172b05..a0b0c5da66b 100644 --- a/src/ui/menu-ui-handler.ts +++ b/src/ui/menu-ui-handler.ts @@ -4,7 +4,7 @@ import { Mode } from "./ui"; import * as Utils from "../utils"; import { addWindow } from "./ui-theme"; import MessageUiHandler from "./message-ui-handler"; -import { GameDataType } from "../system/game-data"; +import { GameDataType } from "#app/data/enums/game-data-type"; import { OptionSelectConfig, OptionSelectItem } from "./abstact-option-select-ui-handler"; import { Tutorial, handleTutorial } from "../tutorial"; import { updateUserInfo } from "../account"; diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index abba4a081df..dd7a949f275 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -20,7 +20,8 @@ import { Type } from "../data/type"; import { Button } from "../enums/buttons"; import { GameModes, gameModes } from "../game-mode"; import { TitlePhase } from "../phases"; -import { AbilityAttr, DexAttr, DexAttrProps, DexEntry, Passive as PassiveAttr, StarterFormMoveData, StarterMoveset } from "../system/game-data"; +import { AbilityAttr, DexAttr, DexAttrProps, DexEntry, StarterFormMoveData, StarterMoveset } from "../system/game-data"; +import { Passive as PassiveAttr } from "#app/data/enums/passive"; import { Tutorial, handleTutorial } from "../tutorial"; import * as Utils from "../utils"; import { OptionSelectItem } from "./abstact-option-select-ui-handler"; @@ -403,7 +404,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.valueLimitLabel.setOrigin(0.5, 0); this.starterSelectContainer.add(this.valueLimitLabel); - const startLabel = addTextObject(this.scene, 124, 162, i18next.t("starterSelectUiHandler:start"), TextStyle.TOOLTIP_CONTENT); + //TODO: back to translated version + const startLabel = addTextObject(this.scene, 124, 162, "Random", TextStyle.TOOLTIP_CONTENT); startLabel.setOrigin(0.5, 0); this.starterSelectContainer.add(startLabel); diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index 1133e7a755c..5a1f883d94f 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -17,7 +17,7 @@ import { StatusEffect } from "../data/status-effect"; import { getBiomeName } from "../data/biomes"; import { Nature, getNatureStatMultiplier } from "../data/nature"; import { loggedInUser } from "../account"; -import { PlayerGender } from "../system/game-data"; +import { PlayerGender } from "#app/data/enums/player-gender"; import { Variant, getVariantTint } from "#app/data/variant"; import {Button} from "../enums/buttons"; import { Ability } from "../data/ability.js"; diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 7640112e084..55ac06a3488 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -38,7 +38,7 @@ import OutdatedModalUiHandler from "./outdated-modal-ui-handler"; import SessionReloadModalUiHandler from "./session-reload-modal-ui-handler"; import {Button} from "../enums/buttons"; import i18next, {ParseKeys} from "i18next"; -import {PlayerGender} from "#app/system/game-data"; +import { PlayerGender } from "#app/data/enums/player-gender"; import GamepadBindingUiHandler from "./settings/gamepad-binding-ui-handler"; import SettingsKeyboardUiHandler from "#app/ui/settings/settings-keyboard-ui-handler"; import KeyboardBindingUiHandler from "#app/ui/settings/keyboard-binding-ui-handler"; From 6d35399c3154d651935e45dbe29b9be5b6805771 Mon Sep 17 00:00:00 2001 From: Tempoanon <163687446+Tempo-anon@users.noreply.github.com> Date: Wed, 5 Jun 2024 21:05:44 -0400 Subject: [PATCH 063/129] [Bug] Change confuse chance from 2/3 to 1/3 (#1827) --- src/data/battler-tags.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 63ac9fd895d..48083cba00d 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -200,6 +200,9 @@ export class InterruptedTag extends BattlerTag { } } +/** + * BattlerTag that represents the {@link https://bulbapedia.bulbagarden.net/wiki/Confusion_(status_condition)} + */ export class ConfusedTag extends BattlerTag { constructor(turnCount: integer, sourceMove: Moves) { super(BattlerTagType.CONFUSED, BattlerTagLapseType.MOVE, turnCount, sourceMove); @@ -235,7 +238,8 @@ export class ConfusedTag extends BattlerTag { pokemon.scene.queueMessage(getPokemonMessage(pokemon, " is\nconfused!")); pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION)); - if (pokemon.randSeedInt(3)) { + // 1/3 chance of hitting self with a 40 base power move + if (pokemon.randSeedInt(3) === 0) { const atk = pokemon.getBattleStat(Stat.ATK); const def = pokemon.getBattleStat(Stat.DEF); const damage = Math.ceil(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedInt(15, 85) / 100)); From c5689dfc964340c5507c59f2d698e042fd285612 Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 5 Jun 2024 21:24:47 -0400 Subject: [PATCH 064/129] dont make api calls when no server is connected in local (#1847) * dont make api calls in local without a server connected and fix fusionLuck not set by default --- src/field/pokemon.ts | 2 +- src/loading-scene.ts | 1 + src/utils.ts | 24 ++++++++++++++++++++---- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index b659923c47d..7d8f8d8dc91 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -211,8 +211,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.generateFusionSpecies(); } } - this.luck = (this.shiny ? this.variant + 1 : 0) + (this.fusionShiny ? this.fusionVariant + 1 : 0); + this.fusionLuck = this.luck; } this.generateName(); diff --git a/src/loading-scene.ts b/src/loading-scene.ts index d45aa83c740..395e4f534c5 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -29,6 +29,7 @@ export class LoadingScene extends SceneBase { } preload() { + Utils.localPing(); this.load["manifest"] = this.game["manifest"]; if (!isMobile()) { diff --git a/src/utils.ts b/src/utils.ts index 6d965369ca3..68f6e323af1 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -263,6 +263,8 @@ export const isLocal = ( // Set the server URL based on whether it's local or not export const serverUrl = isLocal ? `${window.location.hostname}:${window.location.port}` : ""; export const apiUrl = isLocal ? serverUrl : "https://api.pokerogue.net"; +// used to disable api calls when isLocal is true and a server is not found +export let isLocalServerConnected = false; export function setCookie(cName: string, cValue: string): void { const expiration = new Date(); @@ -285,8 +287,22 @@ export function getCookie(cName: string): string { return ""; } +/** + * When locally running the game, "pings" the local server + * with a GET request to verify if a server is running, + * sets isLocalServerConnected based on results + */ +export function localPing() { + if (isLocal) { + apiFetch("game/titlestats") + .then(resolved => isLocalServerConnected = true, + rejected => isLocalServerConnected = false + ); + } +} + export function apiFetch(path: string, authed: boolean = false): Promise { - return new Promise((resolve, reject) => { + return (isLocal && isLocalServerConnected) || !isLocal ? new Promise((resolve, reject) => { const request = {}; if (authed) { const sId = getCookie(sessionIdKey); @@ -297,11 +313,11 @@ export function apiFetch(path: string, authed: boolean = false): Promise resolve(response)) .catch(err => reject(err)); - }); + }) : new Promise(() => {}); } export function apiPost(path: string, data?: any, contentType: string = "application/json", authed: boolean = false): Promise { - return new Promise((resolve, reject) => { + return (isLocal && isLocalServerConnected) || !isLocal ? new Promise((resolve, reject) => { const headers = { "Accept": contentType, "Content-Type": contentType, @@ -315,7 +331,7 @@ export function apiPost(path: string, data?: any, contentType: string = "applica fetch(`${apiUrl}/${path}`, { method: "POST", headers: headers, body: data }) .then(response => resolve(response)) .catch(err => reject(err)); - }); + }) : new Promise(() => {}); } export class BooleanHolder { From 1c98106642716c800108929bf9a2b668a6c37f08 Mon Sep 17 00:00:00 2001 From: prime <10091050+prime-dialga@users.noreply.github.com> Date: Thu, 6 Jun 2024 03:28:12 +0200 Subject: [PATCH 065/129] [QoL] Move Info Overlay (#1585) * move info implemented for starter selection a move info box is displayed when editing the starter moveset. also menus have now onHover triggers. todo: - show ui when selecting TMs - show ui when selecting moves to remember (memory mushroom) * More Move Info Overlays Added overlays during Memory Mushroom use and when viewing TMs. Furthermore a settings option can enable/disable those overlays. * Added missing ko language entry ... though translation still remains necessary * updated ui also added overrides for item rewards * minor ui update moved values to the right in the tm move info box * fixed typedoc issues * removed settings in to prepare for merge * updated settings option added settings option to new settings implementation * minor changes removed unused graphic moved settings option to accessibility --- src/battle-scene.ts | 88 ++++++---- src/loading-scene.ts | 1 + src/locales/de/modifier-type.ts | 4 + src/locales/en/modifier-type.ts | 4 + src/locales/es/modifier-type.ts | 4 + src/locales/fr/modifier-type.ts | 4 + src/locales/it/modifier-type.ts | 4 + src/locales/ko/modifier-type.ts | 4 + src/locales/pt_BR/modifier-type.ts | 4 + src/locales/zh_CN/modifier-type.ts | 4 + src/locales/zh_TW/modifier-type.ts | 4 + src/modifier/modifier-type.ts | 11 +- src/overrides.ts | 8 + src/system/settings/settings.ts | 11 ++ src/ui-inputs.ts | 5 + src/ui/abstact-option-select-ui-handler.ts | 8 +- src/ui/modifier-select-ui-handler.ts | 30 +++- src/ui/move-info-overlay.ts | 194 +++++++++++++++++++++ src/ui/party-ui-handler.ts | 40 +++++ src/ui/starter-select-ui-handler.ts | 41 ++++- 20 files changed, 429 insertions(+), 44 deletions(-) create mode 100644 src/ui/move-info-overlay.ts diff --git a/src/battle-scene.ts b/src/battle-scene.ts index c68ff0dac40..53b58cb8a41 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -71,15 +71,20 @@ const expSpriteKeys: string[] = []; export let starterColors: StarterColors; interface StarterColors { - [key: string]: [string, string] + [key: string]: [string, string] } export interface PokeballCounts { - [pb: string]: integer; + [pb: string]: integer; } export type AnySound = Phaser.Sound.WebAudioSound | Phaser.Sound.HTML5AudioSound | Phaser.Sound.NoAudioSound; +export interface InfoToggle { + toggleInfo(force?: boolean): void; + isActive(): boolean; +} + export default class BattleScene extends SceneBase { public rexUI: UIPlugin; public inputController: InputsController; @@ -97,6 +102,7 @@ export default class BattleScene extends SceneBase { public showArenaFlyout: boolean = true; public showLevelUpStats: boolean = true; public enableTutorials: boolean = import.meta.env.VITE_BYPASS_TUTORIAL === "1"; + public enableMoveInfo: boolean = true; public enableRetries: boolean = false; /** * Determines the condition for a notification should be shown for Candy Upgrades @@ -120,17 +126,17 @@ export default class BattleScene extends SceneBase { public skipSeenDialogues: boolean = false; /** - * Defines the experience gain display mode. - * - * @remarks - * The `expParty` can have several modes: - * - `0` - Default: The normal experience gain display, nothing changed. - * - `1` - Level Up Notification: Displays the level up in the small frame instead of a message. - * - `2` - Skip: No level up frame nor message. - * - * Modes `1` and `2` are still compatible with stats display, level up, new move, etc. - * @default 0 - Uses the default normal experience gain display. - */ + * Defines the experience gain display mode. + * + * @remarks + * The `expParty` can have several modes: + * - `0` - Default: The normal experience gain display, nothing changed. + * - `1` - Level Up Notification: Displays the level up in the small frame instead of a message. + * - `2` - Skip: No level up frame nor message. + * + * Modes `1` and `2` are still compatible with stats display, level up, new move, etc. + * @default 0 - Uses the default normal experience gain display. + */ public expParty: integer = 0; public hpBarSpeed: integer = 0; public fusionPaletteSwaps: boolean = true; @@ -209,6 +215,8 @@ export default class BattleScene extends SceneBase { public rngSeedOverride: string = ""; public rngOffset: integer = 0; + private infoToggles: InfoToggle[] = []; + /** * Allows subscribers to listen for events * @@ -515,7 +523,7 @@ export default class BattleScene extends SceneBase { this.playTimeTimer = this.time.addEvent({ delay: Utils.fixedInt(1000), repeat: -1, - callback: () => { + callback: () => { if (this.gameData) { this.gameData.gameStats.playTime++; } @@ -599,25 +607,25 @@ export default class BattleScene extends SceneBase { /*const loadPokemonAssets: Promise[] = []; - for (let s of Object.keys(speciesStarters)) { - const species = getPokemonSpecies(parseInt(s)); - loadPokemonAssets.push(species.loadAssets(this, false, 0, false)); - } + for (let s of Object.keys(speciesStarters)) { + const species = getPokemonSpecies(parseInt(s)); + loadPokemonAssets.push(species.loadAssets(this, false, 0, false)); + } - Promise.all(loadPokemonAssets).then(() => { - const starterCandyColors = {}; - const rgbaToHexFunc = (r, g, b) => [r, g, b].map(x => x.toString(16).padStart(2, '0')).join(''); + Promise.all(loadPokemonAssets).then(() => { + const starterCandyColors = {}; + const rgbaToHexFunc = (r, g, b) => [r, g, b].map(x => x.toString(16).padStart(2, '0')).join(''); - for (let s of Object.keys(speciesStarters)) { - const species = getPokemonSpecies(parseInt(s)); + for (let s of Object.keys(speciesStarters)) { + const species = getPokemonSpecies(parseInt(s)); - starterCandyColors[species.speciesId] = species.generateCandyColors(this).map(c => rgbaToHexFunc(c[0], c[1], c[2])); - } + starterCandyColors[species.speciesId] = species.generateCandyColors(this).map(c => rgbaToHexFunc(c[0], c[1], c[2])); + } - console.log(JSON.stringify(starterCandyColors)); + console.log(JSON.stringify(starterCandyColors)); - resolve(); - });*/ + resolve(); + });*/ resolve(); }); @@ -682,6 +690,16 @@ export default class BattleScene extends SceneBase { : ret; } + // store info toggles to be accessible by the ui + addInfoToggle(infoToggle: InfoToggle): void { + this.infoToggles.push(infoToggle); + } + + // return the stored info toggles; used by ui-inputs + getInfoToggles(activeOnly: boolean = false): InfoToggle[] { + return activeOnly ? this.infoToggles.filter(t => t?.isActive()) : this.infoToggles; + } + getPokemonById(pokemonId: integer): Pokemon { const findInParty = (party: Pokemon[]) => party.find(p => p.id === pokemonId); return findInParty(this.getParty()) || findInParty(this.getEnemyParty()); @@ -728,7 +746,7 @@ export default class BattleScene extends SceneBase { const container = this.add.container(x, y); const icon = this.add.sprite(0, 0, pokemon.getIconAtlasKey(ignoreOverride)); - icon.setFrame(pokemon.getIconId(true)); + icon.setFrame(pokemon.getIconId(true)); // Temporary fix to show pokemon's default icon if variant icon doesn't exist if (icon.frame.name !== pokemon.getIconId(true)) { console.log(`${pokemon.name}'s variant icon does not exist. Replacing with default.`); @@ -1336,7 +1354,7 @@ export default class BattleScene extends SceneBase { return; } const formattedMoney = - this.moneyFormat === MoneyFormat.ABBREVIATED ? Utils.formatFancyLargeNumber(this.money, 3) : this.money.toLocaleString(); + this.moneyFormat === MoneyFormat.ABBREVIATED ? Utils.formatFancyLargeNumber(this.money, 3) : this.money.toLocaleString(); this.moneyText.setText(`₽${formattedMoney}`); this.fieldUI.moveAbove(this.moneyText, this.luckText); if (forceVisible) { @@ -1926,7 +1944,7 @@ export default class BattleScene extends SceneBase { const newItemModifier = itemModifier.clone() as PokemonHeldItemModifier; newItemModifier.pokemonId = target.id; const matchingModifier = target.scene.findModifier(m => m instanceof PokemonHeldItemModifier - && (m as PokemonHeldItemModifier).matchType(itemModifier) && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier; + && (m as PokemonHeldItemModifier).matchType(itemModifier) && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier; let removeOld = true; if (matchingModifier) { const maxStackCount = matchingModifier.getMaxStackCount(target.scene); @@ -2030,8 +2048,8 @@ export default class BattleScene extends SceneBase { } /** - * Removes all modifiers from enemy of PersistentModifier type - */ + * Removes all modifiers from enemy of PersistentModifier type + */ clearEnemyModifiers(): void { const modifiersToRemove = this.enemyModifiers.filter(m => m instanceof PersistentModifier); for (const m of modifiersToRemove) { @@ -2041,8 +2059,8 @@ export default class BattleScene extends SceneBase { } /** - * Removes all modifiers from enemy of PokemonHeldItemModifier type - */ + * Removes all modifiers from enemy of PokemonHeldItemModifier type + */ clearEnemyHeldItemModifiers(): void { const modifiersToRemove = this.enemyModifiers.filter(m => m instanceof PokemonHeldItemModifier); for (const m of modifiersToRemove) { diff --git a/src/loading-scene.ts b/src/loading-scene.ts index 395e4f534c5..34b2f9d6be7 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -127,6 +127,7 @@ export class LoadingScene extends SceneBase { this.loadImage("summary_stats_overlay_exp", "ui"); this.loadImage("summary_moves", "ui"); this.loadImage("summary_moves_effect", "ui"); + this.loadImage("summary_moves_effect_type", "ui"); this.loadImage("summary_moves_overlay_row", "ui"); this.loadImage("summary_moves_overlay_pp", "ui"); this.loadAtlas("summary_moves_cursor", "ui"); diff --git a/src/locales/de/modifier-type.ts b/src/locales/de/modifier-type.ts index 9f70c31ca63..ff9cf28632c 100644 --- a/src/locales/de/modifier-type.ts +++ b/src/locales/de/modifier-type.ts @@ -99,6 +99,10 @@ export const modifierType: ModifierTypeTranslationEntries = { name: "TM{{moveId}} - {{moveName}}", description: "Bringt einem Pokémon {{moveName}} bei", }, + "TmModifierTypeWithInfo": { + name: "TM{{moveId}} - {{moveName}}", + description: "Bringt einem Pokémon {{moveName}} bei\n(Halte C oder Shift für mehr Infos)", + }, "EvolutionItemModifierType": { description: "Erlaubt es bestimmten Pokémon sich zu entwickeln", }, diff --git a/src/locales/en/modifier-type.ts b/src/locales/en/modifier-type.ts index dac87e1d939..0f7b83d2b3e 100644 --- a/src/locales/en/modifier-type.ts +++ b/src/locales/en/modifier-type.ts @@ -99,6 +99,10 @@ export const modifierType: ModifierTypeTranslationEntries = { name: "TM{{moveId}} - {{moveName}}", description: "Teach {{moveName}} to a Pokémon", }, + "TmModifierTypeWithInfo": { + name: "TM{{moveId}} - {{moveName}}", + description: "Teach {{moveName}} to a Pokémon\n(Hold C or Shift for more info)", + }, "EvolutionItemModifierType": { description: "Causes certain Pokémon to evolve", }, diff --git a/src/locales/es/modifier-type.ts b/src/locales/es/modifier-type.ts index 7b5b1e0c90b..f6565486bb1 100644 --- a/src/locales/es/modifier-type.ts +++ b/src/locales/es/modifier-type.ts @@ -99,6 +99,10 @@ export const modifierType: ModifierTypeTranslationEntries = { name: "MT{{moveId}} - {{moveName}}", description: "Enseña {{moveName}} a un Pokémon", }, + "TmModifierTypeWithInfo": { + name: "MT{{moveId}} - {{moveName}}", + description: "Enseña {{moveName}} a un Pokémon\n(Hold C or Shift for more info)", + }, "EvolutionItemModifierType": { description: "Hace que ciertos Pokémon evolucionen", }, diff --git a/src/locales/fr/modifier-type.ts b/src/locales/fr/modifier-type.ts index 8315910adb3..dd70fd9205e 100644 --- a/src/locales/fr/modifier-type.ts +++ b/src/locales/fr/modifier-type.ts @@ -99,6 +99,10 @@ export const modifierType: ModifierTypeTranslationEntries = { name: "CT{{moveId}} - {{moveName}}", description: "Apprend la capacité {{moveName}} à un Pokémon", }, + "TmModifierTypeWithInfo": { + name: "CT{{moveId}} - {{moveName}}", + description: "Apprend la capacité {{moveName}} à un Pokémon\n(Hold C or Shift for more info)", + }, "EvolutionItemModifierType": { description: "Permet à certains Pokémon d’évoluer", }, diff --git a/src/locales/it/modifier-type.ts b/src/locales/it/modifier-type.ts index b311aa1e8fa..ac313e2444c 100644 --- a/src/locales/it/modifier-type.ts +++ b/src/locales/it/modifier-type.ts @@ -99,6 +99,10 @@ export const modifierType: ModifierTypeTranslationEntries = { name: "MT{{moveId}} - {{moveName}}", description: "Insegna {{moveName}} a un Pokémon", }, + "TmModifierTypeWithInfo": { + name: "MT{{moveId}} - {{moveName}}", + description: "Insegna {{moveName}} a un Pokémon\n(Hold C or Shift for more info)", + }, "EvolutionItemModifierType": { description: "Fa evolvere determinate specie di Pokémon", }, diff --git a/src/locales/ko/modifier-type.ts b/src/locales/ko/modifier-type.ts index 5d54018cc96..fce9d25b629 100644 --- a/src/locales/ko/modifier-type.ts +++ b/src/locales/ko/modifier-type.ts @@ -99,6 +99,10 @@ export const modifierType: ModifierTypeTranslationEntries = { name: "No.{{moveId}} {{moveName}}", description: "포켓몬에게 {{moveName}}[[를]] 가르침", }, + "TmModifierTypeWithInfo": { + name: "No.{{moveId}} {{moveName}}", + description: "포켓몬에게 {{moveName}}를(을) 가르침\n(Hold C or Shift for more info)", + }, "EvolutionItemModifierType": { description: "어느 특정 포켓몬을 진화", }, diff --git a/src/locales/pt_BR/modifier-type.ts b/src/locales/pt_BR/modifier-type.ts index 4865cfb64a2..da0364e9b6e 100644 --- a/src/locales/pt_BR/modifier-type.ts +++ b/src/locales/pt_BR/modifier-type.ts @@ -99,6 +99,10 @@ export const modifierType: ModifierTypeTranslationEntries = { name: "TM{{moveId}} - {{moveName}}", description: "Ensina {{moveName}} a um Pokémon", }, + "TmModifierTypeWithInfo": { + name: "TM{{moveId}} - {{moveName}}", + description: "Ensina {{moveName}} a um Pokémon\n(Hold C or Shift for more info)", + }, "EvolutionItemModifierType": { description: "Faz certos Pokémon evoluírem", }, diff --git a/src/locales/zh_CN/modifier-type.ts b/src/locales/zh_CN/modifier-type.ts index 7230f21e330..456cc7bb8fb 100644 --- a/src/locales/zh_CN/modifier-type.ts +++ b/src/locales/zh_CN/modifier-type.ts @@ -99,6 +99,10 @@ export const modifierType: ModifierTypeTranslationEntries = { name: "招式学习器 {{moveId}} - {{moveName}}", description: "教会一只宝可梦{{moveName}}", }, + "TmModifierTypeWithInfo": { + name: "招式学习器 {{moveId}} - {{moveName}}", + description: "教会一只宝可梦{{moveName}}\n(Hold C or Shift for more info)", + }, "EvolutionItemModifierType": { description: "使某些宝可梦进化", }, diff --git a/src/locales/zh_TW/modifier-type.ts b/src/locales/zh_TW/modifier-type.ts index 1ad51965937..a6e73ebfc28 100644 --- a/src/locales/zh_TW/modifier-type.ts +++ b/src/locales/zh_TW/modifier-type.ts @@ -98,6 +98,10 @@ export const modifierType: ModifierTypeTranslationEntries = { name: "招式學習器 {{moveId}} - {{moveName}}", description: "教會一隻寶可夢{{moveName}}", }, + TmModifierTypeWithInfo: { + name: "TM{{moveId}} - {{moveName}}", + description: "教會一隻寶可夢{{moveName}}\n(Hold C or Shift for more info)", + }, EvolutionItemModifierType: { description: "使某些寶可夢進化" }, FormChangeItemModifierType: { description: "使某些寶可夢更改形態" }, FusePokemonModifierType: { diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index e404781bc8d..3d03f1710f7 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -23,6 +23,7 @@ import { ModifierTier } from "./modifier-tier"; import { Nature, getNatureName, getNatureStatMultiplier } from "#app/data/nature"; import i18next from "#app/plugins/i18n"; import { getModifierTierTextTint } from "#app/ui/text"; +import * as Overrides from "../overrides"; const outputModifierData = false; const useMaxWeightForOutput = false; @@ -721,7 +722,7 @@ export class TmModifierType extends PokemonModifierType { } getDescription(scene: BattleScene): string { - return i18next.t("modifierType:ModifierType.TmModifierType.description", { moveName: allMoves[this.moveId].name }); + return i18next.t(scene.enableMoveInfo ? "modifierType:ModifierType.TmModifierTypeWithInfo.description" : "modifierType:ModifierType.TmModifierType.description", { moveName: allMoves[this.moveId].name }); } } @@ -1673,6 +1674,14 @@ export function getPlayerModifierTypeOptions(count: integer, party: PlayerPokemo } options.push(candidate); }); + // OVERRIDE IF NECESSARY + if (Overrides.ITEM_REWARD_OVERRIDE?.length) { + options.forEach((mod, i) => { + // @ts-ignore: keeps throwing don't use string as index error in typedoc run + const override = modifierTypes[Overrides.ITEM_REWARD_OVERRIDE[i]]?.(); + mod.type = (override instanceof ModifierTypeGenerator ? override.generateType(party) : override) || mod.type; + }); + } return options; } diff --git a/src/overrides.ts b/src/overrides.ts index 661f2d14253..148dc352ae9 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -110,3 +110,11 @@ export const OPP_MODIFIER_OVERRIDE: Array = []; export const STARTING_HELD_ITEMS_OVERRIDE: Array = []; export const OPP_HELD_ITEMS_OVERRIDE: Array = []; + +/** + * An array of items by keys as defined in the "modifierTypes" object in the "modifier/modifier-type.ts" file. + * Items listed will replace the normal rolls. + * If less items are listed than rolled, only some items will be replaced + * If more items are listed than rolled, only the first X items will be shown, where X is the number of items rolled. + */ +export const ITEM_REWARD_OVERRIDE: Array = []; diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index c407aff6927..81971a08d69 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -44,6 +44,7 @@ export const SettingKeys = { UI_Theme: "UI_THEME", Window_Type: "WINDOW_TYPE", Tutorials: "TUTORIALS", + Move_Info: "MOVE_INFO", Enable_Retries: "ENABLE_RETRIES", Skip_Seen_Dialogues: "SKIP_SEEN_DIALOGUES", Candy_Upgrade_Notification: "CANDY_UPGRADE_NOTIFICATION", @@ -132,6 +133,13 @@ export const Setting: Array = [ default: 1, type: SettingType.GENERAL }, + { + key: SettingKeys.Move_Info, + label: "Move Info", + options: OFF_ON, + default: 1, + type: SettingType.ACCESSIBILITY + }, { key: SettingKeys.Enable_Retries, label: "Enable Retries", @@ -312,6 +320,9 @@ export function setSetting(scene: BattleScene, setting: string, value: integer): case SettingKeys.Tutorials: scene.enableTutorials = Setting[index].options[value] === "On"; break; + case SettingKeys.Move_Info: + scene.enableMoveInfo = Setting[index].options[value] === "On"; + break; case SettingKeys.Enable_Retries: scene.enableRetries = Setting[index].options[value] === "On"; break; diff --git a/src/ui-inputs.ts b/src/ui-inputs.ts index d4815ad5a7c..ebe055d8de1 100644 --- a/src/ui-inputs.ts +++ b/src/ui-inputs.ts @@ -114,6 +114,11 @@ export class UiInputs { } buttonStats(pressed: boolean = true): void { + // allow access to Button.STATS as a toggle for other elements + for (const t of this.scene.getInfoToggles(true)) { + t.toggleInfo(pressed); + } + // handle normal pokemon battle ui for (const p of this.scene.getField().filter(p => p?.isActive(true))) { p.toggleStats(pressed); } diff --git a/src/ui/abstact-option-select-ui-handler.ts b/src/ui/abstact-option-select-ui-handler.ts index 925bbefc930..568c8208eac 100644 --- a/src/ui/abstact-option-select-ui-handler.ts +++ b/src/ui/abstact-option-select-ui-handler.ts @@ -14,15 +14,17 @@ export interface OptionSelectConfig { maxOptions?: integer; delay?: integer; noCancel?: boolean; + supportHover?: boolean; } export interface OptionSelectItem { label: string; handler: () => boolean; + onHover?: () => void; keepOpen?: boolean; overrideSound?: boolean; item?: string; - itemArgs?: any[] + itemArgs?: any[]; } const scrollUpLabel = "↑"; @@ -193,6 +195,10 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { } break; } + if (this.config?.supportHover) { + // handle hover code if the element supports hover-handlers and the option has the optional hover-handler set. + this.config?.options[this.cursor + (this.scrollCursor - (this.scrollCursor ? 1 : 0))]?.onHover?.(); + } } if (success && playSound) { diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index 37718243b8b..f6738a33d98 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -1,5 +1,5 @@ import BattleScene from "../battle-scene"; -import { getPlayerShopModifierTypeOptionsForWave, ModifierTypeOption } from "../modifier/modifier-type"; +import { getPlayerShopModifierTypeOptionsForWave, ModifierTypeOption, TmModifierType } from "../modifier/modifier-type"; import { getPokeballAtlasKey, PokeballType } from "../data/pokeball"; import { addTextObject, getModifierTierTextTint, getTextColor, TextStyle } from "./text"; import AwaitableUiHandler from "./awaitable-ui-handler"; @@ -7,6 +7,8 @@ import { Mode } from "./ui"; import { LockModifierTiersModifier, PokemonHeldItemModifier } from "../modifier/modifier"; import { handleTutorial, Tutorial } from "../tutorial"; import {Button} from "../enums/buttons"; +import MoveInfoOverlay from "./move-info-overlay"; +import { allMoves } from "../data/move"; export const SHOP_OPTIONS_ROW_LIMIT = 6; @@ -17,6 +19,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { private transferButtonContainer: Phaser.GameObjects.Container; private rerollCostText: Phaser.GameObjects.Text; private lockRarityButtonText: Phaser.GameObjects.Text; + private moveInfoOverlay : MoveInfoOverlay; private rowCursor: integer = 0; private player: boolean; @@ -73,6 +76,21 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.lockRarityButtonText = addTextObject(this.scene, -4, -2, "Lock Rarities", TextStyle.PARTY); this.lockRarityButtonText.setOrigin(0, 0); this.lockRarityButtonContainer.add(this.lockRarityButtonText); + + // prepare move overlay + const overlayScale = 1; + this.moveInfoOverlay = new MoveInfoOverlay(this.scene, { + delayVisibility: true, + scale: overlayScale, + onSide: true, + right: true, + x: 1, + y: -MoveInfoOverlay.getHeight(overlayScale, true) -1, + width: (this.scene.game.canvas.width / 6) - 2, + }); + ui.add(this.moveInfoOverlay); + // register the overlay to receive toggle events + this.scene.addInfoToggle(this.moveInfoOverlay); } show(args: any[]): boolean { @@ -293,6 +311,8 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.cursorObj.setScale(this.rowCursor === 1 ? 2 : this.rowCursor >= 2 ? 1.5 : 1); + // the modifier selection has been updated, always hide the overlay + this.moveInfoOverlay.clear(); if (this.rowCursor) { const sliceWidth = (this.scene.game.canvas.width / 6) / (options.length + 2); if (this.rowCursor < 2) { @@ -300,7 +320,13 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { } else { this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 16, (-this.scene.game.canvas.height / 12 - this.scene.game.canvas.height / 32) - (-16 + 28 * (this.rowCursor - (this.shopOptionsRows.length - 1)))); } - ui.showText(options[this.cursor].modifierTypeOption.type.getDescription(this.scene)); + + const type = options[this.cursor].modifierTypeOption.type; + ui.showText(type.getDescription(this.scene)); + if (type instanceof TmModifierType) { + // prepare the move overlay to be shown with the toggle + this.moveInfoOverlay.show(allMoves[type.moveId]); + } } else if (!cursor) { this.cursorObj.setPosition(6, this.lockRarityButtonContainer.visible ? -72 : -60); ui.showText("Spend money to reroll your item options."); diff --git a/src/ui/move-info-overlay.ts b/src/ui/move-info-overlay.ts new file mode 100644 index 00000000000..cba2a2a16dc --- /dev/null +++ b/src/ui/move-info-overlay.ts @@ -0,0 +1,194 @@ +import BattleScene, {InfoToggle} from "../battle-scene"; +import { TextStyle, addTextObject } from "./text"; +import { addWindow } from "./ui-theme"; +import * as Utils from "../utils"; +import Move, { MoveCategory } from "../data/move"; +import { Type } from "../data/type"; +import i18next from "i18next"; + +export interface MoveInfoOverlaySettings { + delayVisibility?: boolean; // if true, showing the overlay will only set it to active and populate the fields and the handler using this field has to manually call setVisible later. + scale?:number; // scale the box? A scale of 0.5 is recommended + top?: boolean; // should the effect box be on top? + right?: boolean; // should the effect box be on the right? + onSide?: boolean; // should the effect be on the side? ignores top argument if true + //location and width of the component; unaffected by scaling + x?: number; + y?: number; + width?: number; // default is always half the screen, regardless of scale +} + +const EFF_HEIGHT = 46; +const EFF_WIDTH = 82; +const DESC_HEIGHT = 46; +const BORDER = 8; +const GLOBAL_SCALE = 6; + +export default class MoveInfoOverlay extends Phaser.GameObjects.Container implements InfoToggle { + public active: boolean = false; + + private move: Move; + + private desc: Phaser.GameObjects.Text; + private descScroll : Phaser.Tweens.Tween = null; + + private val: Phaser.GameObjects.Container; + private pp: Phaser.GameObjects.Text; + private pow: Phaser.GameObjects.Text; + private acc: Phaser.GameObjects.Text; + private typ: Phaser.GameObjects.Sprite; + private cat: Phaser.GameObjects.Sprite; + + private options : MoveInfoOverlaySettings; + + constructor(scene: BattleScene, options?: MoveInfoOverlaySettings) { + if (options?.onSide) { + options.top = false; + } + super(scene, options?.x, options?.y); + const scale = options?.scale || 1; // set up the scale + this.setScale(scale); + this.options = options || {}; + + // prepare the description box + const width = (options?.width || MoveInfoOverlay.getWidth(scale, scene)) / scale; // divide by scale as we always want this to be half a window wide + const descBg = addWindow(scene, (options?.onSide && !options?.right ? EFF_WIDTH : 0), options?.top ? EFF_HEIGHT : 0, width - (options?.onSide ? EFF_WIDTH : 0), DESC_HEIGHT); + descBg.setOrigin(0, 0); + this.add(descBg); + + // set up the description; wordWrap uses true pixels, unaffected by any scaling, while other values are affected + this.desc = addTextObject(scene, (options?.onSide && !options?.right ? EFF_WIDTH : 0) + BORDER, (options?.top ? EFF_HEIGHT : 0) + BORDER - 2, "", TextStyle.BATTLE_INFO, { wordWrap: { width: (width - (BORDER - 2) * 2 - (options?.onSide ? EFF_WIDTH : 0)) * GLOBAL_SCALE } }); + + // limit the text rendering, required for scrolling later on + const maskPointOrigin = { + x: (options?.x || 0), + y: (options?.y || 0), + }; + if (maskPointOrigin.x < 0) { + maskPointOrigin.x += this.scene.game.canvas.width / GLOBAL_SCALE; + } + if (maskPointOrigin.y < 0) { + maskPointOrigin.y += this.scene.game.canvas.height / GLOBAL_SCALE; + } + + const moveDescriptionTextMaskRect = this.scene.make.graphics(); + moveDescriptionTextMaskRect.fillStyle(0xFF0000); + moveDescriptionTextMaskRect.fillRect( + maskPointOrigin.x + ((options?.onSide && !options?.right ? EFF_WIDTH : 0) + BORDER) * scale, maskPointOrigin.y + ((options?.top ? EFF_HEIGHT : 0) + BORDER - 2) * scale, + width - ((options?.onSide ? EFF_WIDTH : 0) - BORDER * 2) * scale, (DESC_HEIGHT - (BORDER - 2) * 2) * scale); + moveDescriptionTextMaskRect.setScale(6); + const moveDescriptionTextMask = this.createGeometryMask(moveDescriptionTextMaskRect); + + this.add(this.desc); + this.desc.setMask(moveDescriptionTextMask); + + // prepare the effect box + this.val = new Phaser.GameObjects.Container(scene, options?.right ? width - EFF_WIDTH : 0, options?.top || options?.onSide ? 0 : DESC_HEIGHT); + this.add(this.val); + + const valuesBg = addWindow(scene, 0, 0, EFF_WIDTH, EFF_HEIGHT); + valuesBg.setOrigin(0, 0); + this.val.add(valuesBg); + + this.typ = this.scene.add.sprite(25, EFF_HEIGHT - 35,`types${Utils.verifyLang(i18next.language) ? `_${i18next.language}` : ""}` , "unknown"); + this.typ.setScale(0.8); + this.val.add(this.typ); + + this.cat = this.scene.add.sprite(57, EFF_HEIGHT - 35, "categories", "physical"); + this.val.add(this.cat); + + const ppTxt = addTextObject(scene, 12, EFF_HEIGHT - 25, "PP", TextStyle.MOVE_INFO_CONTENT); + ppTxt.setOrigin(0.0, 0.5); + ppTxt.setText(i18next.t("fightUiHandler:pp")); + this.val.add(ppTxt); + + this.pp = addTextObject(scene, 70, EFF_HEIGHT - 25, "--", TextStyle.MOVE_INFO_CONTENT); + this.pp.setOrigin(1, 0.5); + this.val.add(this.pp); + + const powTxt = addTextObject(scene, 12, EFF_HEIGHT - 17, "POWER", TextStyle.MOVE_INFO_CONTENT); + powTxt.setOrigin(0.0, 0.5); + powTxt.setText(i18next.t("fightUiHandler:power")); + this.val.add(powTxt); + + this.pow = addTextObject(scene, 70, EFF_HEIGHT - 17, "---", TextStyle.MOVE_INFO_CONTENT); + this.pow.setOrigin(1, 0.5); + this.val.add(this.pow); + + const accTxt = addTextObject(scene, 12, EFF_HEIGHT - 9, "ACC", TextStyle.MOVE_INFO_CONTENT); + accTxt.setOrigin(0.0, 0.5); + accTxt.setText(i18next.t("fightUiHandler:accuracy")); + this.val.add(accTxt); + + this.acc = addTextObject(scene, 70, EFF_HEIGHT - 9, "---", TextStyle.MOVE_INFO_CONTENT); + this.acc.setOrigin(1, 0.5); + this.val.add(this.acc); + + // hide this component for now + this.setVisible(false); + } + + // show this component with infos for the specific move + show(move : Move):boolean { + if (!(this.scene as BattleScene).enableMoveInfo) { + return; // move infos have been disabled + } + this.move = move; + this.pow.setText(move.power >= 0 ? move.power.toString() : "---"); + this.acc.setText(move.accuracy >= 0 ? move.accuracy.toString() : "---"); + this.pp.setText(move.pp >= 0 ? move.pp.toString() : "---"); + this.typ.setTexture(`types${Utils.verifyLang(i18next.language) ? `_${i18next.language}` : ""}`, Type[move.type].toLowerCase()); + this.cat.setFrame(MoveCategory[move.category].toLowerCase()); + + this.desc.setText(move?.effect || ""); + + // stop previous scrolling effects + if (this.descScroll) { + this.descScroll.remove(); + this.descScroll = null; + } + + // determine if we need to add new scrolling effects + const moveDescriptionLineCount = Math.floor(this.desc.displayHeight * (96 / 72) / 14.83); + if (moveDescriptionLineCount > 3) { + // generate scrolling effects + this.descScroll = this.scene.tweens.add({ + targets: this.desc, + delay: Utils.fixedInt(2000), + loop: -1, + hold: Utils.fixedInt(2000), + duration: Utils.fixedInt((moveDescriptionLineCount - 3) * 2000), + y: `-=${14.83 * (72 / 96) * (moveDescriptionLineCount - 3)}` + }); + } + + if (!this.options.delayVisibility) { + this.setVisible(true); + } + this.active = true; + return true; + } + + clear() { + this.setVisible(false); + this.active = false; + } + + toggleInfo(force?: boolean): void { + this.setVisible(force ?? !this.visible); + } + + isActive(): boolean { + return this.active; + } + + // width of this element + static getWidth(scale:number, scene: BattleScene):number { + return scene.game.canvas.width / GLOBAL_SCALE / 2; + } + + // height of this element + static getHeight(scale:number, onSide?: boolean):number { + return (onSide ? Math.max(EFF_HEIGHT, DESC_HEIGHT) : (EFF_HEIGHT + DESC_HEIGHT)) * scale; + } +} diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index b62242e3c18..c51fea747a9 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -17,6 +17,7 @@ import { addWindow } from "./ui-theme"; import { SpeciesFormChangeItemTrigger } from "../data/pokemon-forms"; import { getVariantTint } from "#app/data/variant"; import {Button} from "../enums/buttons"; +import MoveInfoOverlay from "./move-info-overlay"; const defaultMessage = "Choose a Pokémon."; @@ -73,6 +74,7 @@ export default class PartyUiHandler extends MessageUiHandler { private partySlots: PartySlot[]; private partyCancelButton: PartyCancelButton; private partyMessageBox: Phaser.GameObjects.NineSlice; + private moveInfoOverlay: MoveInfoOverlay; private optionsMode: boolean; private optionsScroll: boolean; @@ -179,6 +181,17 @@ export default class PartyUiHandler extends MessageUiHandler { this.iconAnimHandler = new PokemonIconAnimHandler(); this.iconAnimHandler.setup(this.scene); + // prepare move overlay. in case it appears to be too big, set the overlayScale to .5 + const overlayScale = 1; + this.moveInfoOverlay = new MoveInfoOverlay(this.scene, { + scale: overlayScale, + top: true, + x: 1, + y: -MoveInfoOverlay.getHeight(overlayScale) - 1, //this.scene.game.canvas.height / 6 - MoveInfoOverlay.getHeight(overlayScale) - 29, + width: this.scene.game.canvas.width / 12 - 30, + }); + ui.add(this.moveInfoOverlay); + this.options = []; this.partySlots = []; @@ -191,6 +204,9 @@ export default class PartyUiHandler extends MessageUiHandler { super.show(args); + // reset the infoOverlay + this.moveInfoOverlay.clear(); + this.partyUiMode = args[0] as PartyUiMode; this.fieldIndex = args.length > 1 ? args[1] as integer : -1; @@ -244,6 +260,8 @@ export default class PartyUiHandler extends MessageUiHandler { ui.playSelect(); return true; } else if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER && option !== PartyOption.CANCEL) { + // clear overlay on cancel + this.moveInfoOverlay.clear(); const filterResult = (this.selectFilter as PokemonSelectFilter)(pokemon); if (filterResult === null) { this.selectCallback(this.cursor, option); @@ -408,6 +426,19 @@ export default class PartyUiHandler extends MessageUiHandler { success = this.setCursor(this.optionsCursor < this.options.length - 1 ? this.optionsCursor + 1 : 0); /** Move cursor */ break; } + + // show move description + if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER) { + const option = this.options[this.optionsCursor]; + const pokemon = this.scene.getParty()[this.cursor]; + const move = allMoves[pokemon.getLearnableLevelMoves()[option]]; + if (move) { + this.moveInfoOverlay.show(move); + } else { + // or hide the overlay, in case it's the cancel button + this.moveInfoOverlay.clear(); + } + } } } else { if (button === Button.ACTION) { @@ -625,6 +656,11 @@ export default class PartyUiHandler extends MessageUiHandler { ? pokemon.getLearnableLevelMoves() : null; + if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER && learnableLevelMoves?.length) { + // show the move overlay with info for the first move + this.moveInfoOverlay.show(allMoves[learnableLevelMoves[0]]); + } + const itemModifiers = this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER ? this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).getTransferrable(true) && (m as PokemonHeldItemModifier).pokemonId === pokemon.id) as PokemonHeldItemModifier[] @@ -874,6 +910,8 @@ export default class PartyUiHandler extends MessageUiHandler { } clearOptions() { + // hide the overlay + this.moveInfoOverlay.clear(); this.optionsMode = false; this.optionsScroll = false; this.optionsScrollCursor = 0; @@ -895,6 +933,8 @@ export default class PartyUiHandler extends MessageUiHandler { clear() { super.clear(); + // hide the overlay + this.moveInfoOverlay.clear(); this.partyContainer.setVisible(false); this.clearPartySlots(); } diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index dd7a949f275..d30d4c4e14c 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -31,6 +31,7 @@ import { StatsContainer } from "./stats-container"; import { TextStyle, addBBCodeTextObject, addTextObject } from "./text"; import { Mode } from "./ui"; import { addWindow } from "./ui-theme"; +import MoveInfoOverlay from "./move-info-overlay"; export type StarterSelectCallback = (starters: Starter[]) => void; @@ -192,6 +193,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { private starterSelectMessageBoxContainer: Phaser.GameObjects.Container; private statsContainer: StatsContainer; private pokemonFormText: Phaser.GameObjects.Text; + private moveInfoOverlay : MoveInfoOverlay; private genMode: boolean; private statsMode: boolean; @@ -661,6 +663,15 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.message.setOrigin(0, 0); this.starterSelectMessageBoxContainer.add(this.message); + const overlayScale = 1; // scale for the move info. "2/3" might be another good option... + this.moveInfoOverlay = new MoveInfoOverlay(this.scene, { + scale: overlayScale, + top: true, + x: 1, + y: this.scene.game.canvas.height / 6 - MoveInfoOverlay.getHeight(overlayScale) - 29, + }); + this.starterSelectContainer.add(this.moveInfoOverlay); + const date = new Date(); date.setUTCHours(0, 0, 0, 0); @@ -1058,6 +1069,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const showSwapOptions = (moveset: StarterMoveset) => { ui.setMode(Mode.STARTER_SELECT).then(() => { ui.showText(i18next.t("starterSelectUiHandler:selectMoveSwapOut"), null, () => { + this.moveInfoOverlay.show(allMoves[moveset[0]]); + ui.setModeWithoutClear(Mode.OPTION_SELECT, { options: moveset.map((m: Moves, i: number) => { const option: OptionSelectItem = { @@ -1065,8 +1078,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler { handler: () => { ui.setMode(Mode.STARTER_SELECT).then(() => { ui.showText(`${i18next.t("starterSelectUiHandler:selectMoveSwapWith")} ${allMoves[m].name}.`, null, () => { + const possibleMoves = this.speciesStarterMoves.filter((sm: Moves) => sm !== m); + this.moveInfoOverlay.show(allMoves[possibleMoves[0]]); + ui.setModeWithoutClear(Mode.OPTION_SELECT, { - options: this.speciesStarterMoves.filter((sm: Moves) => sm !== m).map(sm => { + options: possibleMoves.map(sm => { // make an option for each available starter move const option = { label: allMoves[sm].name, @@ -1074,7 +1090,10 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.switchMoveHandler(i, sm, m); showSwapOptions(this.starterMoveset); return true; - } + }, + onHover: () => { + this.moveInfoOverlay.show(allMoves[sm]); + }, }; return option; }).concat({ @@ -1082,25 +1101,37 @@ export default class StarterSelectUiHandler extends MessageUiHandler { handler: () => { showSwapOptions(this.starterMoveset); return true; - } + }, + onHover: () => { + this.moveInfoOverlay.clear(); + }, }), + supportHover: true, maxOptions: 8, yOffset: 19 }); }); }); return true; - } + }, + onHover: () => { + this.moveInfoOverlay.show(allMoves[m]); + }, }; return option; }).concat({ label: i18next.t("menu:cancel"), handler: () => { + this.moveInfoOverlay.clear(); this.clearText(); ui.setMode(Mode.STARTER_SELECT); return true; - } + }, + onHover: () => { + this.moveInfoOverlay.clear(); + }, }), + supportHover: true, maxOptions: 8, yOffset: 19 }); From 19114e4fd39802d5d2498e7cc8ef7160cdc87782 Mon Sep 17 00:00:00 2001 From: sodam <66295123+sodaMelon@users.noreply.github.com> Date: Thu, 6 Jun 2024 10:52:30 +0900 Subject: [PATCH 066/129] [Localization] #1761 Korean trainer dialogue (Gym leader in Hoenn region) (#1830) * localized to korean (Gym leader's dialouge in Houenn region) * modified the spacing Co-authored-by: returntoice * modified the spacing Co-authored-by: returntoice * modified the spelling Co-authored-by: returntoice --------- Co-authored-by: returntoice --- src/locales/ko/dialogue.ts | 168 ++++++++++++++++++------------------- 1 file changed, 84 insertions(+), 84 deletions(-) diff --git a/src/locales/ko/dialogue.ts b/src/locales/ko/dialogue.ts index 7e65d9e0f09..040fff8cbdc 100644 --- a/src/locales/ko/dialogue.ts +++ b/src/locales/ko/dialogue.ts @@ -512,158 +512,158 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "roxanne": { "encounter": { - 1: "Would you kindly demonstrate how you battle?", - 2: "You can learn many things by battling many trainers.", - 3: "Oh, you caught me strategizing.\nWould you like to battle?" + 1: "당신이 어떻게 싸우는지 보여주시겠어요?", + 2: "당신은 여러 트레이너와 싸우면서 많은 것을 배울 수 있을거예요.", + 3: "아, 전략짜는 거 들켰네요.\n배틀할까요?" }, "victory": { - 1: "Oh, I appear to have lost.\nI understand.", - 2: "It seems that I still have so much more to learn when it comes to battle.", - 3: "I'll take what I learned here today to heart." + 1: "아, 제가 진 것 같네요.\n승복하겠습니다.", + 2: "전 아직도 포켓몬 배틀에 대해서 한참 더 배워야할 것 같네요.", + 3: "오늘 여기서 배운 것들을 마음에 담아둬야겠어요." }, "defeat": { - 1: "I have learned many things from our battle.\nI hope you have too.", - 2: "I look forward to battling you again.\nI hope you'll use what you've learned here.", - 3: "I won due to everything I have learned." + 1: "전 방금 승부에서 많은 것을 배웠습니다.\n당신도 그랬길 바래요.", + 2: "다시 붙을 날이 기대되네요.\n당신이 여기서 배운 걸 활용할 수 있길 바랍니다.", + 3: "여태까지 공부해온 것 덕분에 이겼네요." } }, "brawly": { "encounter": { - 1: "Oh man, a challenger!\nLet's see what you can do!", - 2: "You seem like a big splash.\nLet's battle!", - 3: "Time to create a storm!\nLet's go!" + 1: "오, 도전자잖아!\n어디 한 번 볼까!", + 2: "넌 큰 파란을 일으킬 것 같군.\n승부다!", + 3: "폭풍을 일으킬 시간이야!\n가자!" }, "victory": { - 1: "Oh woah, you've washed me out!", - 2: "You surfed my wave and crashed me down!", - 3: "I feel like I'm lost in Granite Cave!" + 1: "우와, 너 날 씻겨버렸네!", + 2: "내 파도를 타고, 나까지 밀어내다니!", + 3: "바위 동굴에서 길을 잃은 기분이야!" }, "defeat": { - 1: "Haha, I surfed the big wave!\nChallenge me again sometime.", - 2: "Surf with me again some time!", - 3: "Just like the tides come in and out, I hope you return to challenge me again." + 1: "하핫, 난 큰 파도를 탔다고!\n언제 또 도전해주라.", + 2: "언젠가 또 같이 서핑하자고!", + 3: "파도가 밀려왔다가 밀려나듯, 언젠가 너도 다시 도전하러 와." } }, "wattson": { "encounter": { - 1: "Time to get shocked!\nWahahahaha!", - 2: "I'll make sparks fly!\nWahahahaha!", - 3: "I hope you brought Paralyz Heal!\nWahahahaha!" + 1: "찌릿찌릿해질 때가 됐군!\n와하하하핫!", + 2: "스파크가 튀도록 해주마!\n와하하하하!", + 3: "와하하하하!\n마비 치료제를 가져왔길 바라네!" }, "victory": { - 1: "Seems like I'm out of charge!\nWahahahaha!", - 2: "You've completely grounded me!\nWahahahaha!", - 3: "Thanks for the thrill!\nWahahahaha!" + 1: "이 몸 배터리가 다 됐군!\n와하하하하!", + 2: "자네 완전히 날 좌초시켰군!\n와하하하핫!", + 3: "스릴 넘치는 배틀, 고맙네!\n와하하하하하!" }, "defeat": { - 1: "Recharge your batteries and challenge me again sometime!\nWahahahaha!", - 2: "I hope you found our battle electrifying!\nWahahahaha!", - 3: "Aren't you shocked I won?\nWahahahaha!" + 1: "자네의 배터리 재충전하게. 그리고 나에게 도전하러 돌아오도록!\n와하하하핫!", + 2: "방금 배틀이 자네에게 짜릿짜릿했길 바란다네!\n와하하하하!", + 3: "자네 혹시 내가 이겨서 충격 받았나?\n와하하하핫!" } }, "flannery": { "encounter": { - 1: "Nice to meet you! Wait, no…\nI will crush you!", - 2: "I've only been a leader for a little while, but I'll smoke you!", - 3: "It's time to demonstrate the moves my grandfather has taught me! Let's battle!" + 1: "어서오세요! 잠깐, 아냐…\n너를 무너뜨려줄게!", + 2: "난 체육관 관장이 된지는 얼마 안됐지만, 널 태워버릴거야!", + 3: "할아버지에게 배운 기술을 한 수 보여줄게! 승부다!" }, "victory": { - 1: "You remind me of my grandfather…\nNo wonder I lost.", - 2: "Am I trying too hard?\nI should relax, can't get too heated.", - 3: "Losing isn't going to smother me out.\nTime to reignite training!" + 1: "너 우리 할아버지를 생각나게 하네…\n내가 진 게 놀랍진 않아.", + 2: "나 너무 열심히 하는 건가?\n너무 열 올리면 안되니깐, 진정해야겠어.", + 3: "패배는 날 꺼뜨릴 수 없어.\n트레이닝으로 다시 불을 붙일 때야!" }, "defeat": { - 1: "I hope I've made my grandfather proud…\nLet's battle again some time.", - 2: "I…I can't believe I won!\nDoing things my way worked!", - 3: "Let's exchange burning hot moves again soon!" + 1: "할아버지가 자랑스러워하시길…\n언젠가 다시 배틀하자.", + 2: "내…내가 이기다니!\n내 방식대로 한 게 통했어!", + 3: "조만간 다시 뜨겁게 불타오르는 배틀을 하자!" } }, "norman": { "encounter": { - 1: "I'm surprised you managed to get here.\nLet's battle.", - 2: "I'll do everything in my power as a Gym Leader to win.\nLet's go!", - 3: "You better give this your all.\nIt's time to battle!" + 1: "여기까지 오다니 놀랍군.\n한 번 겨뤄볼까.", + 2: "관장으로서 최선을 다해 널 이길 거란다.\n가자!", + 3: "최선을 다하는 게 좋을 거야.\n승부할 시간이다!" }, "victory": { - 1: "I lost to you…?\nRules are rules, though.", - 2: "Was moving from Olivine a mistake…?", - 3: "I can't believe it.\nThat was a great match." + 1: "내가 지다니…?\n규칙은 규칙이니, 흐음.", + 2: "담청시티에서 이사한 게 문제였나…?", + 3: "믿을 수 없구나.\n훌륭한 승부였어." }, "defeat": { - 1: "We both tried our best.\nI hope we can battle again soon.", - 2: "You should try challenging my kid instead.\nYou might learn something!", - 3: "Thank you for the excellent battle.\nBetter luck next time." + 1: "우린 둘 다 최선을 다했지.\n다시 대결할 수 있었으면 좋겠구나.", + 2: "우리 집 꼬마에게 도전해보는 것도 좋겠군.\n아마 뭔가 배울 수 있을거다!", + 3: "방금 전 배틀 완벽했어.\n다음에도 행운이 함께하길." } }, "winona": { "encounter": { - 1: "I've been soaring the skies looking for prey…\nAnd you're my target!", - 2: "No matter how our battle is, my Flying Pokémon and I will triumph with grace. Let's battle!", - 3: "I hope you aren't scared of heights.\nLet's ascend!" + 1: "저는 먹이를 찾아서 하늘을 날아다녔어요…\n그리고 당신은 제 타겟입니다!", + 2: "배틀이 어떻게 되든, 전 제 비행 포켓몬과 우아하게 승리하겠어요. 승부합시다!", + 3: "당신이 높은 곳을 무서워하지 않기를.\n자, 날아올라요!" }, "victory": { - 1: "You're the first Trainer I've seen with more grace than I.\nExcellently played.", - 2: "Oh, my Flying Pokémon have plummeted!\nVery well.", - 3: "Though I may have fallen, my Pokémon will continue to fly!" + 1: "저보다 우아하게 나서는 트레이너는 처음 봤습니다.\n훌륭하시네요.", + 2: "이런, 제 비행 포켓몬이 추락해버렸네요!\n훌륭한 배틀이었습니다.", + 3: "비록 전 떨어졌지만, 제 포켓몬은 다시 날아갈 겁니다!" }, "defeat": { - 1: "My Flying Pokémon and I will forever dance elegantly!", - 2: "I hope you enjoyed our show.\nOur graceful dance is finished.", - 3: "Won't you come see our elegant choreography again?" + 1: "제 비행 포켓몬과 영원히 우아하게 춤출게요.", + 2: "우리의 쇼가 즐거웠길 바라요.\우아한 춤은 끝났습니다.", + 3: "우리의 엘레강스한 안무를 다시 보러오지 않을래요?" } }, "tate": { "encounter": { - 1: "Hehehe…\nWere you surprised to see me without my sister?", - 2: "I can see what you're thinking…\nYou want to battle!", - 3: "How can you defeat someone…\nWho knows your every move?" + 1: "헤헤헤…\n내가 란과 같이 있지 않아서 놀랐지?", + 2: "네가 무슨 생각을 하는지 알아…\n승부하고 싶은거지!", + 3: "네 움직임을 모두 알고 있는데…\n어떻게 이기려고?" }, "victory": { - 1: "It can't be helped…\nI miss Liza…", - 2: "Your bond with your Pokémon was stronger than mine.", - 3: "If I were with Liza, we would have won.\nWe can finish each other's thoughts!" + 1: "어쩔 수 없지…\n란이 보고싶다아…", + 2: "너와 네 포켓몬과의 유대, 나보다 더 견고한걸.", + 3: "란이랑 함께였다면, 우리가 이겼어.\n둘이선 더 잘 할 수 있다구!" }, "defeat": { - 1: "My Pokémon and I are superior!", - 2: "If you can't even defeat me, you'll never be able to defeat Liza either.", - 3: "It's all thanks to my strict training with Liza.\nI can make myself one with Pokémon." + 1: "내 포켓몬과 나는 우수하다구!", + 2: "날 못 이긴다면, 넌 란한테도 절대로 못 이겨.", + 3: "란과 함께한 엄격한 훈련 덕이야.\n덕분에 포켓몬과 하나가 될 수 있었어." } }, "liza": { "encounter": { - 1: "Fufufu…\nWere you surprised to see me without my brother?", - 2: "I can determine what you desire…\nYou want to battle, don't you?", - 3: "How can you defeat someone…\nWho's one with their Pokémon?" + 1: "후후후…\n내가 풍과 같이 있지 않아서 놀랐지?", + 2: "네가 무얼 바라는지 알아…\n포켓몬 배틀, 맞지?", + 3: "포켓몬과 하나가 된 사람…\n어떻게 이기려고?" }, "victory": { - 1: "It can't be helped…\nI miss Tate…", - 2: "Your bond with your Pokémon…\nIt's stronger than mine.", - 3: "If I were with Tate, we would have won.\nWe can finish each other's sentences!" + 1: "어쩔 수 없지…\n풍이 보고싶다아…", + 2: "너와 네 포켓몬과의 유대, 나보다 더 견고한걸.", + 3: "풍이랑 함께였다면, 우리가 이겼어.\n둘이선 더 잘 할 수 있다구!" }, "defeat": { - 1: "My Pokémon and I are victorious.", - 2: "If you can't even defeat me, you'll never be able to defeat Tate either.", - 3: "It's all thanks to my strict training with Tate.\nI can synchronize myself with my Pokémon." + 1: "내 포켓몬과 내가 승리한거야.", + 2: "날 못 이긴다면, 넌 풍한테도 절대로 못 이겨.", + 3: "풍과 함께한 엄격한 훈련 덕이야.\n덕분에 포켓몬과 싱크로 될 수 있었어." } }, "juan": { "encounter": { - 1: "Now's not the time to act coy.\nLet's battle!", - 2: "Ahahaha, You'll be witness to my artistry with Water Pokémon!", - 3: "A typhoon approaches!\nWill you be able to test me?", - 4: "Please, you shall bear witness to our artistry.\nA grand illusion of water sculpted by my Pokémon and myself!" + 1: "지금은 겸양을 부릴 때가 아니군요.\n승부합시다!", + 2: "아하하하, 물 포켓몬과 함께 아트를 보여드리겠습니다!", + 3: "태풍이 다가오는군요!\n저를 테스트해주시겠습니까?", + 4: "자, 마음껏 봐주십시오.\n저와 포켓몬이 이루어내는 물의 일루전을!" }, "victory": { - 1: "You may be a genius who can take on Wallace!", - 2: "I focused on elegance while you trained.\nIt's only natural that you defeated me.", - 3: "Ahahaha!\nVery well, You have won this time.", - 4: "From you, I sense the brilliant shine of skill that will overcome all." + 1: "당신은 윤진 관장을 뛰어넘을 지니어스군요!", + 2: "당신이 훈련할 때 저는 엘레강스에 집중했습니다.\n당신이 이기는 건 당연하죠.", + 3: "아하하하하!\n잘했습니다, 이번엔 당신이 이겼네요.", + 4: "모든 것을 극복하는 브릴리언트 스킬, 당신에게 느껴지네요." }, "defeat": { - 1: "My Pokémon and I have sculpted an illusion of Water and come out victorious.", - 2: "Ahahaha, I have won, and you have lost.", - 3: "Shall I loan you my outfit? It may help you battle!\nAhahaha, I jest!", - 4: "I'm the winner! Which is to say, you lost." + 1: "저와 포켓몬이 이루어내는 물의 일루전이 승리했습니다.", + 2: "아하하핫, 저는 이겼고, 당신은 졌습니다.", + 3: "겉옷 빌려드릴까요? 아마도 배틀에 도움이 될겁니다!\n아하하하, 농담입니다!", + 4: "제가 승리자군요! 그리고, 당신은 졌네요." } }, "crasher_wake": { From 6b31db0bc52712da489a69bea6b9a3f6c0887884 Mon Sep 17 00:00:00 2001 From: returntoice Date: Thu, 6 Jun 2024 10:54:08 +0900 Subject: [PATCH 067/129] [Localization] Paldean gym leaders and elite 4 dialogue Korean translation (#1838) * Your commit message * localization --- src/locales/ko/dialogue.ts | 82 +++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/locales/ko/dialogue.ts b/src/locales/ko/dialogue.ts index 040fff8cbdc..6824cc506ef 100644 --- a/src/locales/ko/dialogue.ts +++ b/src/locales/ko/dialogue.ts @@ -1046,24 +1046,24 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "kofu": { "encounter": { - 1: "I'mma serve you a full course o' Water-type Pokémon! Don't try to eat 'em, though!" + 1: "물포켓몬의 풀코스를! 배 터지게 먹여 주도록 하마!" }, "victory": { - 1: "Vaultin' Veluza! Yer a lively one, aren't ya! A little TOO lively, if I do say so myself!" + 1: "우옷! 우오오옷! 이렇게 팔팔한 트레이너가 다 있다니!" }, "defeat": { - 1: "You come back to see me again now, ya hear?" + 1: "젊은 친구! 다음에 또 만나기를 기대하고 있으마!" } }, "tulip": { "encounter": { - 1: "Allow me to put my skills to use to make your cute little Pokémon even more beautiful!" + 1: "리파의 기술로 너의 포켓몬들을 지금보다 훨~씬 아름답게 만들어 줄게!" }, "victory": { - 1: "Your strength has a magic to it that cannot be washed away." + 1: "너의 강함은 풀 수 없는 매직이구나." }, "defeat": { - 1: "You know, in my line of work, people who lack talent in one area or the other often fade away quickly—never to be heard of again." + 1: "…리파의 업계에서는 어중간한 재능을 가진 사람은 대체로 금방 사라져 버려." } }, "sidney": { @@ -1193,13 +1193,13 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "rika": { "encounter": { - 1: "I'd say I'll go easy on you, but… I'd be lying! Think fast!" + 1: "실컷 귀여워해 줄 테니까… 한 번 열심히 해 보라고!" }, "victory": { - 1: "Not bad, kiddo." + 1: "너, 꽤 하는구나!" }, "defeat": { - 1: "Nahahaha! You really are something else, kiddo!" + 1: "아하하! 제법인데! 역시 너는 재밌는 녀석이라니까!" } }, "bruno": { @@ -1295,14 +1295,14 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "poppy": { "encounter": { - 1: "Oooh! Do you wanna have a Pokémon battle with me?" + 1: "우와~! 뽀삐와 포켓몬 승부가 하고 싶으세요?" }, "victory": { - 1: "Uagh?! Mmmuuuggghhh…" + 1: "훌쩍, 으에엥~" }, "defeat": { - 1: `Yaaay! I did it! I de-feet-ed you! You can come for… For… An avenge match? - $Come for an avenge match anytime you want!`, + 1: `만세~! 만세~ 목수, 성공했어요! + $에헴! 리벤지 매치는 언제든지 받아 줄게요!`, } }, "agatha": { @@ -1390,14 +1390,14 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "larry_elite": { "encounter": { - 1: `Hello there… It's me, Larry. - $I serve as a member of the Elite Four too, yes… Unfortunately for me.`, + 1: `…안녕하십니까, 청목입니다. + $귀찮게도 저는 사천왕도 겸임하고 있습니다.`, }, "victory": { - 1: "Well, that took the wind from under our wings…" + 1: "날고 있는 새포켓몬도 떨어뜨릴 기세로군요." }, "defeat": { - 1: "It's time for a meeting with the boss." + 1: "치프와 만나기로 한 시각이군요." } }, "lance": { @@ -1483,14 +1483,14 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "hassel": { "encounter": { - 1: "Prepare to learn firsthand how the fiery breath of ferocious battle feels!" + 1: "맹렬하게 몰아치는 승부의 숨결을 직접 가르쳐 드리겠습니다!!" }, "victory": { - 1: `Fortune smiled on me this time, but… - $Judging from how the match went, who knows if I will be so lucky next time.`, + 1: `이번에는 당신이 승리를 쟁취했군요. + $하지만, 시합의 흐름을 보니… 다음 승부는 또 어떻게 될지 모르겠네요.`, }, "defeat": { - 1: "That was an ace!" + 1: "저에게 더 배우고 싶은 것이 있으시다면 또 승부하도록 하죠." } }, "blue": { @@ -1662,13 +1662,13 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "katy": { "encounter": { - 1: "Don't let your guard down unless you would like to find yourself knocked off your feet!" + 1: "쓰러지고 싶지 않다면 방심하지 말고 열심히 해 봐~" }, "victory": { - 1: "All of my sweet little Pokémon dropped like flies!" + 1: "내 포켓몬들 모두 지쳐서 헤벌레~ 해졌어~" }, "defeat": { - 1: "Eat up, my cute little Vivillon!" + 1: "비비용~ 많~이 먹으렴~" } }, "pryce": { @@ -1969,60 +1969,60 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "brassius": { "encounter": { - 1: "I assume you are ready? Let our collaborative work of art begin!" + 1: "준비는 됐겠지!? 그럼, 우리 둘의 예술적인 합작품을 한번 만들어 보도록 할까!" }, "victory": { - 1: "Ahhh…vant-garde!" + 1: "아… 아방가르드!!" }, "defeat": { - 1: "I will begin on a new piece at once!" + 1: "바로 신작을 만들러 가야 하니 이만 실례하겠다!" } }, "iono": { "encounter": { - 1: `How're ya feelin' about this battle? + 1: `자~ 오늘의 각오는~ 모야모야~? $... - $Let's get this show on the road! How strong is our challenger? - $I 'unno! Let's find out together!`, + $그럼, 이제 시작해 볼까! + $도전자님의 실력은 과연 과연~!?`, }, "victory": { - 1: "You're as flashy and bright as a 10,000,000-volt Thunderbolt, friendo!" + 1: "너의 반짝임은 1000만볼트!" }, "defeat": { - 1: "Your eyeballs are MINE!" + 1: "당신의 눈길을 일렉트릭네트로 뾰로롱!" } }, "larry": { "encounter": { - 1: "When all's said and done, simplicity is strongest." + 1: "그렇습니다. 심플한 것이 가장 강한 것입니다!" }, "victory": { - 1: "A serving of defeat, huh?" + 1: "허, 이걸로 한 방 먹은 게 되었군요." }, "defeat": { - 1: "I'll call it a day." + 1: "오늘은 저는 이만 실례하겠습니다." } }, "ryme": { "encounter": { - 1: "Come on, baby! Rattle me down to the bone!" + 1: "나의 영혼 흔들어 봐 Come On!" }, "victory": { - 1: "You're cool, my friend—you move my SOUL!" + 1: "너의 Cool한 Youth 나의 Soul이 Move!" }, "defeat": { - 1: "Later, baby!" + 1: "Bye Bye Baby~!" } }, "grusha": { "encounter": { - 1: "All I need to do is make sure the power of my Pokémon chills you to the bone!" + 1: "내가 너를 철저하게 얼려 버리면 고민할 것도 없겠지!" }, "victory": { - 1: "Your burning passion… I kinda like it, to be honest." + 1: "너의 그 열기… 싫지 않아." }, "defeat": { - 1: "Things didn't heat up for you." + 1: "너에겐 아직 열기가 부족하구나." } }, "marnie_elite": { From c1b4be83d0291984705dc730f803c47e6190e3cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Ricardo=20Fleury=20Oliveira?= Date: Wed, 5 Jun 2024 23:52:59 -0300 Subject: [PATCH 068/129] translations (#1850) --- src/locales/pt_BR/modifier-type.ts | 2 +- src/locales/pt_BR/tutorial.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/locales/pt_BR/modifier-type.ts b/src/locales/pt_BR/modifier-type.ts index da0364e9b6e..5904278c401 100644 --- a/src/locales/pt_BR/modifier-type.ts +++ b/src/locales/pt_BR/modifier-type.ts @@ -101,7 +101,7 @@ export const modifierType: ModifierTypeTranslationEntries = { }, "TmModifierTypeWithInfo": { name: "TM{{moveId}} - {{moveName}}", - description: "Ensina {{moveName}} a um Pokémon\n(Hold C or Shift for more info)", + description: "Ensina {{moveName}} a um Pokémon\n(Segure C ou Shift para mais informações)", }, "EvolutionItemModifierType": { description: "Faz certos Pokémon evoluírem", diff --git a/src/locales/pt_BR/tutorial.ts b/src/locales/pt_BR/tutorial.ts index 4d71d0b7e53..bc0c5610e60 100644 --- a/src/locales/pt_BR/tutorial.ts +++ b/src/locales/pt_BR/tutorial.ts @@ -9,7 +9,7 @@ export const tutorial: SimpleTranslationEntries = { $Se o jogo estiver rodando lentamente, certifique-se de que a 'Aceleração de hardware' esteja ativada $nas configurações do seu navegador.`, - "accessMenu": `Para acessar o menu, aperte M ou Esc. + "accessMenu": `Para acessar o menu, pressione M ou Esc. $O menu contém configurações e diversas funções.`, "menu": `A partir deste menu, você pode acessar as configurações. From daa9e1ef0ff3c5abf831d3e5a7e78121e725ed2a Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Wed, 5 Jun 2024 23:07:47 -0400 Subject: [PATCH 069/129] [BUG] Fix fullheal, burn/poison, and endure tokens in existing saves not updating after rebalance (#1848) * Fix tokens not actually updating * Remove changes to getArgs * Added parentheses around conditional for safety * Laid a space betwixt the two and its respective one at the behest of Temp --- src/modifier/modifier.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 6f296c997ca..26d68d74e87 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -2195,7 +2195,8 @@ export class EnemyAttackStatusEffectChanceModifier extends EnemyPersistentModifi super(type, stackCount); this.effect = effect; - this.chance = (chancePercent || 5) / 100; + //Hardcode temporarily + this.chance = .025 * ((this.effect === StatusEffect.BURN || this.effect === StatusEffect.POISON) ? 2 : 1); } match(modifier: Modifier): boolean { @@ -2230,7 +2231,8 @@ export class EnemyStatusEffectHealChanceModifier extends EnemyPersistentModifier constructor(type: ModifierType, chancePercent: number, stackCount?: integer) { super(type, stackCount); - this.chance = (chancePercent || 2.5) / 100; + //Hardcode temporarily + this.chance = .025; } match(modifier: Modifier): boolean { @@ -2268,7 +2270,8 @@ export class EnemyEndureChanceModifier extends EnemyPersistentModifier { constructor(type: ModifierType, chancePercent?: number, stackCount?: integer) { super(type, stackCount || 10); - this.chance = (chancePercent || 2) / 100; + //Hardcode temporarily + this.chance = .02; } match(modifier: Modifier) { From 5e52be676f9bc7259baeee9c2d8c4d50debd0e2f Mon Sep 17 00:00:00 2001 From: Benjamin Odom Date: Wed, 5 Jun 2024 22:57:55 -0500 Subject: [PATCH 070/129] [QoL] Add Time of Day Widget to Arena Flyout (#1846) * Time of Day Sample * Add Proper Time of Day Tracking * Add Settings --- public/images/ui/dawn_icon.png | Bin 581 -> 0 bytes public/images/ui/dawn_icon_bg.png | Bin 0 -> 186 bytes public/images/ui/dawn_icon_fg.png | Bin 0 -> 347 bytes public/images/ui/dawn_icon_mg.png | Bin 0 -> 273 bytes public/images/ui/day_icon.png | Bin 593 -> 0 bytes public/images/ui/day_icon_bg.png | Bin 0 -> 186 bytes public/images/ui/day_icon_fg.png | Bin 0 -> 398 bytes public/images/ui/day_icon_mg.png | Bin 0 -> 245 bytes public/images/ui/dusk_icon.png | Bin 534 -> 0 bytes public/images/ui/dusk_icon_bg.png | Bin 0 -> 186 bytes public/images/ui/dusk_icon_fg.png | Bin 0 -> 388 bytes public/images/ui/dusk_icon_mg.png | Bin 0 -> 235 bytes public/images/ui/legacy/dawn_icon.png | Bin 327 -> 0 bytes public/images/ui/legacy/dawn_icon_bg.png | Bin 0 -> 150 bytes public/images/ui/legacy/dawn_icon_fg.png | Bin 0 -> 249 bytes public/images/ui/legacy/dawn_icon_mg.png | Bin 0 -> 239 bytes public/images/ui/legacy/day_icon.png | Bin 285 -> 0 bytes public/images/ui/legacy/day_icon_bg.png | Bin 0 -> 186 bytes public/images/ui/legacy/day_icon_fg.png | Bin 0 -> 255 bytes public/images/ui/legacy/day_icon_mg.png | Bin 0 -> 151 bytes public/images/ui/legacy/dusk_icon.png | Bin 300 -> 0 bytes public/images/ui/legacy/dusk_icon_bg.png | Bin 0 -> 150 bytes public/images/ui/legacy/dusk_icon_fg.png | Bin 0 -> 252 bytes public/images/ui/legacy/dusk_icon_mg.png | Bin 0 -> 219 bytes public/images/ui/legacy/night_icon.png | Bin 288 -> 0 bytes public/images/ui/legacy/night_icon_bg.png | Bin 0 -> 150 bytes public/images/ui/legacy/night_icon_fg.png | Bin 0 -> 240 bytes public/images/ui/legacy/night_icon_mg.png | Bin 0 -> 120 bytes public/images/ui/night_icon.png | Bin 686 -> 0 bytes public/images/ui/night_icon_bg.png | Bin 0 -> 186 bytes public/images/ui/night_icon_fg.png | Bin 0 -> 440 bytes public/images/ui/night_icon_mg.png | Bin 0 -> 271 bytes src/battle-scene-events.ts | 14 ++ src/battle-scene.ts | 3 + src/loading-scene.ts | 16 +- src/phases.ts | 4 +- src/system/settings/settings.ts | 24 +++ src/ui/arena-flyout.ts | 30 +--- src/ui/enums/ease-type.ts | 15 ++ src/ui/time-of-day-widget.ts | 172 ++++++++++++++++++++++ 40 files changed, 251 insertions(+), 27 deletions(-) delete mode 100644 public/images/ui/dawn_icon.png create mode 100644 public/images/ui/dawn_icon_bg.png create mode 100644 public/images/ui/dawn_icon_fg.png create mode 100644 public/images/ui/dawn_icon_mg.png delete mode 100644 public/images/ui/day_icon.png create mode 100644 public/images/ui/day_icon_bg.png create mode 100644 public/images/ui/day_icon_fg.png create mode 100644 public/images/ui/day_icon_mg.png delete mode 100644 public/images/ui/dusk_icon.png create mode 100644 public/images/ui/dusk_icon_bg.png create mode 100644 public/images/ui/dusk_icon_fg.png create mode 100644 public/images/ui/dusk_icon_mg.png delete mode 100644 public/images/ui/legacy/dawn_icon.png create mode 100644 public/images/ui/legacy/dawn_icon_bg.png create mode 100644 public/images/ui/legacy/dawn_icon_fg.png create mode 100644 public/images/ui/legacy/dawn_icon_mg.png delete mode 100644 public/images/ui/legacy/day_icon.png create mode 100644 public/images/ui/legacy/day_icon_bg.png create mode 100644 public/images/ui/legacy/day_icon_fg.png create mode 100644 public/images/ui/legacy/day_icon_mg.png delete mode 100644 public/images/ui/legacy/dusk_icon.png create mode 100644 public/images/ui/legacy/dusk_icon_bg.png create mode 100644 public/images/ui/legacy/dusk_icon_fg.png create mode 100644 public/images/ui/legacy/dusk_icon_mg.png delete mode 100644 public/images/ui/legacy/night_icon.png create mode 100644 public/images/ui/legacy/night_icon_bg.png create mode 100644 public/images/ui/legacy/night_icon_fg.png create mode 100644 public/images/ui/legacy/night_icon_mg.png delete mode 100644 public/images/ui/night_icon.png create mode 100644 public/images/ui/night_icon_bg.png create mode 100644 public/images/ui/night_icon_fg.png create mode 100644 public/images/ui/night_icon_mg.png create mode 100644 src/ui/enums/ease-type.ts create mode 100644 src/ui/time-of-day-widget.ts diff --git a/public/images/ui/dawn_icon.png b/public/images/ui/dawn_icon.png deleted file mode 100644 index e04e6024aa0b097dff236c4b4a1ca56e24bd4531..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 581 zcmV-L0=oT)P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0oqAKK~y+Tjgw7F z+)xyT-z3KIYf#5lXmn6ysHiY4rh?E`L2PFyYFF*bh4TYusNk>AAK=EFZrluX;ihf` z1(niL3$4;BiYZmckCExL(ZrmSA2^(Ia_4>TxpNZL^A#r@6w^~U;zcVG<9rvtKhI!m^CQgsn#9(Ms+}HZe|Bkp5;l z?D7Z;eB|sVj34tXc1T;M;l9cQAI1Bag46iy?X37hBJJ$XACoZf2^!sk5V`=D1CXre ThUT}f00000NkvXXu0mjfz();` diff --git a/public/images/ui/dawn_icon_bg.png b/public/images/ui/dawn_icon_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..2983c2744a3b8a3f53da20bf5a3b94fe9dc46e29 GIT binary patch literal 186 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBufiR<}hF1en(BIR=F~q_@d4Y(5Q>MrU?b1{A8p5nQ&Yn9M z(67|st!(9B#&J#|%$Vl@Pa@N24TgkZkFBz74(k@&U|r0{GpmGwSwuB8rQx(fN^b}g b4-bRRAsMmjTW3rI8qMJ8>gTe~DWM4fL!dHc literal 0 HcmV?d00001 diff --git a/public/images/ui/dawn_icon_fg.png b/public/images/ui/dawn_icon_fg.png new file mode 100644 index 0000000000000000000000000000000000000000..e6c195bd371e5198ee92f02d39d7cbd12acfc71f GIT binary patch literal 347 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBufiR<}hF1en@Pns|V~B-+atcG!|NsA+H7o+2PjnM)Xqp{A zujy(tgR=4*PD6&4t2>tdb1sw%;o@^hGN_zUW#DkRXQ80%0iGslr>-;!2Q!vDhLFAt z2IB@!sf3>biIW`cBJ?e{9cJ**7oOs4CZWQ{@YktCNS%G}T0G|-o%ViVaFWB+4q4(9ag)fdi`Z9UK*CoAQHq?JV+xqEd2~ZX5ku~W+ zin}DpFZe$kFnIYhegX<}7I;J!GcfQS0%1l`4X*~EV3MbcV~9p@?72Xp1_ch5fF(gs z{;~Jn)y;l#c;fS}s617JX!aW+(O1QG{C>DVeu0Aef%RIQLN7e8S!`HP-lW8;oTQ=j z!Z1CA?Z86)2bpR``zPHGs1b`;n`gr*V1I1wwa}U7K_A)tngvw$0BvLNboFyt=akR{ E0Fkj~JOBUy literal 0 HcmV?d00001 diff --git a/public/images/ui/day_icon.png b/public/images/ui/day_icon.png deleted file mode 100644 index fe41acffd9cfb2096a87a86cadbbc7f57ea136a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 593 zcmV-X0Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!T|WK~y+TV`M-B ztSZKZK&lVI!vICU@0{(2vcUju0J28!qzndI{}2!p4i0Zz!?1PU3@8s_5ZC~iX6L9B z2G{TikPuF=d*up-z02l+m|#PgK`dq=spIIHRrx+L_(&aP&=;4!@HjxU~y#~ z9fto*Yz+4=pL75B?s+3K)OPpC(q;yB4i>N?kY?*wa~Zg=En#4Lc#J`k;UmLWAuYHe zqOz(Chu1FyS^S9!$Ok(e8CVLRVc>eTn}PBDTQCN(L1M@Ns;3WR0E4+lAc!If!1RC( zKnKU)nlOA5b7lB1C=A9RHb@K`z-qwlPy7r=#gZAm*q4AYhz%0M22c$6^zkQ@1uTAV>bk`KybV0iKT6T{M@FByKWEnr~y$4~@GiL%E3nKe8a zSQMK{C;?r5t0)5r9S0kT0@ f09+#kFfafBum-cC{w~Yc00000NkvXXu0mjf3PJjV diff --git a/public/images/ui/day_icon_bg.png b/public/images/ui/day_icon_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..9028e0309f7d26c386f1e8fb1a89c3ca1ec2834c GIT binary patch literal 186 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBuf-vKbiP>*~g8rT^jv*HQ$qPgboH9iMw*9?WuOZA@Qh6$I zg}dN^rGl~w(k$r$t0mbI+6)>iMHn_{F1f|WtPmdXrYVq_&DW}dv1^i%(ScNfO>SC@ bY-|h@lVrqpmbFd+8qMJ8>gTe~DWM4f)!s8C literal 0 HcmV?d00001 diff --git a/public/images/ui/day_icon_fg.png b/public/images/ui/day_icon_fg.png new file mode 100644 index 0000000000000000000000000000000000000000..523f7b1be7e3043b445bea6dc53bf6cd1606cd56 GIT binary patch literal 398 zcmV;90df9`P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!2kdb!2!6DYwZ940V7F7K~y+Tos%(7 z0znXkXAdPYoOVPi={$OBt^5O)hLT)P5I zHrNMrY8$rctJ=BApnXTGb;Ac7y?l7}Z%4;TyI=kK87dgXjYO-?$Zc&=FpCGz$m&6l zyOh71PaBKqB1&$l(!54V%la9QyU?#wqL(LHd}P)@y6{avXms@XO{@8g{?duP-N-k; smY8538UYak5d+RZ3g8=s_(BWO8rl_+IyPPD!TNS%G|s0G|-oY1dxVAA7Os@q^dj*1Wph`suLm_f1+r$(wab1we|UB*-uLKLjw` zYu+0P6yq%Lh%9Dc;5!Jyj5{V~zXb{gdAc};Xau*OCGucnxrM|2gfPA{fli+6QwzLJ9ZqxBTZQa> eIMe>b9)@fCnGbH`Ff#_4&fw|l=d#Wzp$PydrdNId literal 0 HcmV?d00001 diff --git a/public/images/ui/dusk_icon.png b/public/images/ui/dusk_icon.png deleted file mode 100644 index e848fa313459ce44809049ffe80f8a0ce298da7c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 534 zcmV+x0_pvUP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0jo(wK~y+Tjg!kt z!%z^0|J-OmisGeGQ9-*EM2ZVVUA73~6Da5-vnB%CR}p2kH{Vt{ur@=C(bPW6(Vf9i9@dVitK?fa!ot~o5cNzb&O5LAnTt{_I8`Z zN{_#$v(qOw5eAaX)x}(sSbhN3mLW~YSr`-(^9y~#(dk_Bs{hw|P%QU4W8>NHLMEq9 z#M(HZPe>e;dtJ!xd|RlpHY~O)oZz6`>!u@vc64MwYc|U^P&?wFoSqRf9Sk}oP1H0V z>aWhJA>3>#BVC4ZdjE|?tswrcuH*3Cgs6yJl-lL77`>jjlBOe1qm9p5)?WB YKRnE+uEonOR{#J207*qoM6N<$g63c9P5=M^ diff --git a/public/images/ui/dusk_icon_bg.png b/public/images/ui/dusk_icon_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..57d22ec2b21bb8dbf52ab55fb766c3a3d9ddfce5 GIT binary patch literal 186 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBufiR<}hF1en(BIR=F~q_@d4Y(5Q>Ms=1y}ysPw8Y5*;Ssj z!d>vdQbAb-X_j<>)sk!pZ3c~%A`BZem)zoGRtOJx(-g?e=4;i!*fq(>=s>E#CO0ic bHZ}&~Su$dYJ|R9pqZvG1{an^LB{Ts5SSc|E literal 0 HcmV?d00001 diff --git a/public/images/ui/dusk_icon_fg.png b/public/images/ui/dusk_icon_fg.png new file mode 100644 index 0000000000000000000000000000000000000000..e0ca1bce79b04db334d537124cfda67f6e906a34 GIT binary patch literal 388 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBufiR<}hF1engQTa6V~B-+a>@awD=jLTMA1r2eggjwIu&;9lP;7-~9^0CqFcf~Xu%vj#2 z3#6x&i#IP?Xv8dh+?*|K*>r_OffZA(op{r*DUiW~-@+$hoAc35wu0~jbDa1XUZyr} zI?2|2gdsW1^AfY*s`iin|NrN)QF`@%>!Ea`J?%$cg(=)CO`mmGMSxv$MT+bR;glct zf=BjkGn)JG^z7L^39@$%W&J<;b(6#o)l!ETHZjIDgI?}Ed+R1Xe7ZD~C;tAuB$nL_ zEDq~{ZezK?`|I;V{lmwfhaZ@Aa2xYm_T3CO1q6EY1UL?uG%ceF_}Fc~;IFr*1s fHQelC;Nf8~Qpsm-;;`Tbh8%;ZtDnm{r-UW|jDC<= literal 0 HcmV?d00001 diff --git a/public/images/ui/dusk_icon_mg.png b/public/images/ui/dusk_icon_mg.png new file mode 100644 index 0000000000000000000000000000000000000000..4e6a880b37fb6531b577159342108f698b7e9258 GIT binary patch literal 235 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6T`Z5GB1G~mUKs7M+SzC z{oH>NS%G}f0G|-or)};prn#B|(0{|G|Jk%A45@ zD8yOd5n0T@z;_6Q8AUa`8i0a6o-U3d8o{;a0=XCzIamUWKmT*TmuWIlY*v4foJwcB zraPx#|4BqaR5(v#WWWyo;}B;=7e^NWYdloaj-u}J-REF__WDUMakxHY1O7joJQ=R> z&ciQoHp~E+0MsC`UYG$WnqdlwF$7`2;>ipQA5eXWZa`p;D#Q9Iw~@WW2r}URGmvH& zKr#fM3t(XaHsHth5U}=lZ|ot)Kn)?ufPjM6V7<@Yy#v!z;-j#}1hOC~Fu^YHP`8I` zM2}~XLFlmn3k$dbdZM~G(hWp7-Nq5CJlp_FWm~v{ub&_Rz|ALsqA5nt5X@h4={#Kg z_}#;ZbbxFKUgtn1L7GA72%ZioH2`GVe_{=R*$#FBnsdRK370`ojVSssvM1C)qO&sq ZXBK-4;%UQk00000NkvXXu0mjf008%?gY^Ia diff --git a/public/images/ui/legacy/dawn_icon_bg.png b/public/images/ui/legacy/dawn_icon_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..87c4d75cd942ab4e9b8d900562ff63a360331d00 GIT binary patch literal 150 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`v7RoDAr^wk2@3=ak}lhu8oakR z`}pSTWP>YdQ9TLMhQ`DROPC|} x2+fvDSShfC{~hZLMFpwpjwhrhUJX9N#&G1cj9B*HERYKrJYD@<);T3K0RX_uFbe_gEm^MciGyA@fK8$JGwS{xt$Bo!-j3juvb+;9Zi@CdqcqS(N$2M%i~<6S!9- zH?;4vwP=~Yx0>DT@1@tLkFdO`-@t8ixB(7Jty z_s6f7%rnlORc}ao@o{(i!MIH!+gg`jdR)$bSeUO%v0^UsRF+IPPxce*6pk}YkXsbX xxLNiMi^=T+{|-NuTHz*A+fpRE@Xl!lh7=`x?n|PJF9V&=;OXk;vd$@?2>_pPV0Zuk literal 0 HcmV?d00001 diff --git a/public/images/ui/legacy/dawn_icon_mg.png b/public/images/ui/legacy/dawn_icon_mg.png new file mode 100644 index 0000000000000000000000000000000000000000..442cb1b674c8ad854c662d79a7189833115e242d GIT binary patch literal 239 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`+dN$yLo5W76BY;N0 literal 0 HcmV?d00001 diff --git a/public/images/ui/legacy/day_icon.png b/public/images/ui/legacy/day_icon.png deleted file mode 100644 index 310ba50dcf306bc4fb559b4a96ae9da8d53bc40b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 285 zcmV+&0pk9NP)Px#*GWV{R5(v#WWWNXm0bQ~6MuSd4Lki*gF1_po zv-QNwQM@3Ur2*!@o^}H+etu0q13Vg$4I$`x1_lO@W>8|HjsdXLNUSShwuAIx%}lrq j0%^pVnNUKXgzO9eAn$t--D6GR00000NkvXXu0mjfSW0br diff --git a/public/images/ui/legacy/day_icon_bg.png b/public/images/ui/legacy/day_icon_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..3db0dab4589360e5f7e76862f65e1ddc973d85b1 GIT binary patch literal 186 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucL4Qvd#}Etu~ bJUk2wPsxbQN#?ol-tu literal 0 HcmV?d00001 diff --git a/public/images/ui/legacy/day_icon_fg.png b/public/images/ui/legacy/day_icon_fg.png new file mode 100644 index 0000000000000000000000000000000000000000..657c82e5dbda1b7653c66acf9dad30c5887bff99 GIT binary patch literal 255 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|wtKobhFJI~ zryO8fGBxI3vT%J>KELuVi!KHqWwTa=-WyLIvNfm_X1Z(_jnuut5M*+gr}^c=I}AlR z?q#gXS9vCMZ~v{I`+-$1?l|}PL>I|z2UvDD-25gU!*^3)f`VPMLzN+q!#3752|ecr zY!YoEhrY&E-Q-Mr-8p^njtcMT<~O|UQllTP{PJg39b1Kol5~@<n`M{qXQv%X74T5zPf{7F`TJ$y57S%>MG^hQ~D=oyglH&2Xv5d-1YO8eNSWrS%W< zGDzD(vPBlle_=Ij|i#u**btO<>n`h6yTW&MwS94z}&R_d(F-aBRZD zWY+CZA1$s<*tSn5tKlnKmr(;xNB4f_@ajjW4B0A7l%$(3pY;$@IJ-c^QbO8@`> diff --git a/public/images/ui/legacy/dusk_icon_bg.png b/public/images/ui/legacy/dusk_icon_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..7610a2e67f8ea404dcf0545b488240feeb3ec807 GIT binary patch literal 150 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`v7RoDAr^wk2@3=ak}lg{4S8*E z_VLZ->jqaAb3C}zkteaqv4FYbDvvQw8i%k?LyXiLrl8#RNVX!@qk0mi4ULHtmM}-` y5t=QRuu@f{7F`TJ%4V$!y*HjbWNT0<%yii<8mW7OA;{z~PxH%#cNmIt z+{;*%ukr*4UT<=jl~*@Eypw&o;Ub>sgr?X7C)FF3*o_$2K1i}$>b}Y_AuWlgQ95B^ zGVAt_j}})aY}+?SsB87LdH0yU1#MW#e`mG&I+^4KcjZlrHzpbGlJGFrco=w`AtFoV znyA5T$6p)L)MD5gO_~^r<{gx}z9c1`;mH47|69f(i-GQE@O1TaS?83{#J~Ump$1%% literal 0 HcmV?d00001 diff --git a/public/images/ui/legacy/dusk_icon_mg.png b/public/images/ui/legacy/dusk_icon_mg.png new file mode 100644 index 0000000000000000000000000000000000000000..dc603f8ca79a7289b34f1daae36664014d831ef0 GIT binary patch literal 219 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`i#%N%Lo5W76C^SYc=RxTbG`B3 zJ?#0GM+!0Wa~jt$9CN$QATNm?*D4{E8`N*3>UkCVVy02sxXc;wZ>i zv?0^&ddC{}<>ColA2QiiDrPX8QHU||Wb0u2VBBC}bRe5S@p&`PAr6<6%NK91H?L{o zZcJi0a`1Ii_4IcKO&=sBNHBOO?l5UMlDMt;Muf1C2a~1kfkRA@${xI-3=CG=6s-Tg SH%$S$mci52&t;ucLK6VZcTq_gEm^MciGSA)Dt@#=%wfcl)GfSj5P0V*Fp28MEV1r=new{GEUmjtP4WmwjJPUkYp-i46Ji6XKZ6^@Aq^6xNdE! zn%?`xENVAc7wx%ZA)0K=A?CNBKp}(SL`uxMMrj504BqmC-|hJi|i{S)h5Yi+`_5mpvPOrxN_TJ9p;@)N#Z?H2M!o`Tw>IT x>-6Q{5W;bZ{X5ec0fFAr3MsuOUTG#VGj#5j5tFm%@&H=I;OXk;vd$@?2>^3iE}j4Y literal 0 HcmV?d00001 diff --git a/public/images/ui/legacy/night_icon_fg.png b/public/images/ui/legacy/night_icon_fg.png new file mode 100644 index 0000000000000000000000000000000000000000..737cca3305e838b29ff0485279af7021b994b5a7 GIT binary patch literal 240 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|R(QHNhFJI~ zrySt>_gEm^MciGyA@fK8$JGwS{xt$Bo!-j3juvb+;9Zi@CdqcqS(N$2M%i~<6S!9- zH)yOk@w&42-=8Tn82<8?H3GSP8R-cM2?B}C4r-4LS&|y&EHJ&pdjG!-!)D7!k${-5 zFa8E@nEr%i?Zdd*FaI=b9xjk*x@_+uq;OW_CBse8ClU;ooVREvY-^s|kS2Yjen$-R l)s98|0qc~fR7*1SFk5mfmG+ka-OS+W>gTe~DWQph0RR^{SeO6+ literal 0 HcmV?d00001 diff --git a/public/images/ui/legacy/night_icon_mg.png b/public/images/ui/legacy/night_icon_mg.png new file mode 100644 index 0000000000000000000000000000000000000000..f5fdff51cc87e6bf6f7f3eebe5428b3891c8b3a2 GIT binary patch literal 120 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`_MR?|Ar^wk2@*mNKDQ_TfB8Hh zX4~HSy)W!Jf}aTGuo*lw7QAVC!{qU0?`X@E>4Epva+W0vFWz_7#IP{KLYCnfhkE;( TgU2|5MlpE0`njxgN@xNAOcW?i literal 0 HcmV?d00001 diff --git a/public/images/ui/night_icon.png b/public/images/ui/night_icon.png deleted file mode 100644 index 1782303f73d853f90b38fe94dff34cdbe9593faf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 686 zcmV;f0#W^mP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!2kdb!2!6DYwZ940z*keK~y+Tjgw7G z6HySy|Ld})w8R#%SgRFjfY?|yp%)U2(Sz(wC4rj>Q9P?`ylB+mO%thJ^lH3#FkHPE zB|S)uDORQ0sA(!32Q^=kB% zz9&q9=0tnD6KxJJ-a|*^` z>BVPrF;*py@$r+mGkqFD%NB}#SuCNWvlYI6H(o{(;9ZF9F~_S*QY&V{*YWWFEG1i5 zZ{oN|i^COp*N`Y_`p0NnrZMZH%57 zqHp9#76)o7aCBJ4V97bNM6umd$gO1LPIos7`Jc3U1Ag=m?4`9@Eh3jmLumY_#G#R6aCvuP_R#~brD%h+ zHK^|B*$1!B&t2G^HnJRVw@=>z>BS`3#C#NAQ*4k{*dXzMX!8_`xvZ?RpW^6nu?efw zvREavE56FP3X*SMQ!+zoRP<5iurAEY)ZC3hI{D5>bP0l+XkKO5QTv literal 0 HcmV?d00001 diff --git a/public/images/ui/night_icon_fg.png b/public/images/ui/night_icon_fg.png new file mode 100644 index 0000000000000000000000000000000000000000..78741584312c1ed37ad11a1a2a23330ed1af4409 GIT binary patch literal 440 zcmV;p0Z0CcP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGizyJUazyWI3i3tDz0ZmCnK~y+TV`PAW z|Ns9(Squz3eCh}e68Q4zCR{xuBb*7*48q*JYX3o)nN6(_Dg_51F_=6|Js2Qs#-oym8?M!_nP`z+Pg4NHH)F(hLHV>PoVJ z)N~C44Tt~;5bJbw0P_mi0H_$r0Hhc01)%@_L;Xk+02`1|+4BF__a7i;Cdr0S>IFt_ zF}43vTJ{XiE*?-}V2m(vz+M17YIoQ;_Clxy)&Rv3=9g&@=zA4 zVX19f^O*nrma;3<>gICf(E%lRNjg~URs5LQ;-WJ1P925fxv^i4;gM;xy11L z;W3B-AQylD$Q3X{U;y2)@9)1cJbZcwt{Ef-CXfvQ(dY(&08AqYz%>Is0MC;!LqGs# i5ITTqgaJlIMg{NS%G}T0G|+783i8&?S$T`2R;0&{{R1f^w9$`G27#3o=)rCKYh|UpsE+oIBozb z?vfzCV4wmhVDR#1`~(!{EbxddW?)>$04j2JLz3 z>AF*EGi$XMH>`E}Ak#26q+-9tl)~2TKbPbUb~7v!WnJhuTW>GWE(T9mKbLh*2~7a# Cs$)9< literal 0 HcmV?d00001 diff --git a/src/battle-scene-events.ts b/src/battle-scene-events.ts index 74fac97d2b7..aaeb590f8ba 100644 --- a/src/battle-scene-events.ts +++ b/src/battle-scene-events.ts @@ -20,6 +20,11 @@ export enum BattleSceneEventType { */ BERRY_USED = "onBerryUsed", + /** + * Triggers at the start of each new encounter + * @see {@linkcode EncounterPhaseEvent} + */ + ENCOUNTER_PHASE = "onEncounterPhase", /** * Triggers on the first turn of a new battle * @see {@linkcode TurnInitEvent} @@ -85,6 +90,15 @@ export class BerryUsedEvent extends Event { } } +/** + * Container class for {@linkcode BattleSceneEventType.ENCOUNTER_PHASE} events + * @extends Event +*/ +export class EncounterPhaseEvent extends Event { + constructor() { + super(BattleSceneEventType.ENCOUNTER_PHASE); + } +} /** * Container class for {@linkcode BattleSceneEventType.TURN_INIT} events * @extends Event diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 53b58cb8a41..38882cf3e68 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -60,6 +60,7 @@ import {UiInputs} from "./ui-inputs"; import { MoneyFormat } from "./enums/money-format"; import { NewArenaEvent } from "./battle-scene-events"; import ArenaFlyout from "./ui/arena-flyout"; +import { EaseType } from "./ui/enums/ease-type"; export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1"; @@ -100,6 +101,8 @@ export default class BattleScene extends SceneBase { public reroll: boolean = false; public showMovesetFlyout: boolean = true; public showArenaFlyout: boolean = true; + public showTimeOfDayWidget: boolean = true; + public timeOfDayAnimation: EaseType = EaseType.NONE; public showLevelUpStats: boolean = true; public enableTutorials: boolean = import.meta.env.VITE_BYPASS_TUTORIAL === "1"; public enableMoveInfo: boolean = true; diff --git a/src/loading-scene.ts b/src/loading-scene.ts index 34b2f9d6be7..522962d5829 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -96,10 +96,18 @@ export class LoadingScene extends SceneBase { this.loadImage("type_tera", "ui"); this.loadAtlas("type_bgs", "ui"); - this.loadImage("dawn_icon", "ui"); - this.loadImage("day_icon", "ui"); - this.loadImage("dusk_icon", "ui"); - this.loadImage("night_icon", "ui"); + this.loadImage("dawn_icon_fg", "ui"); + this.loadImage("dawn_icon_mg", "ui"); + this.loadImage("dawn_icon_bg", "ui"); + this.loadImage("day_icon_fg", "ui"); + this.loadImage("day_icon_mg", "ui"); + this.loadImage("day_icon_bg", "ui"); + this.loadImage("dusk_icon_fg", "ui"); + this.loadImage("dusk_icon_mg", "ui"); + this.loadImage("dusk_icon_bg", "ui"); + this.loadImage("night_icon_fg", "ui"); + this.loadImage("night_icon_mg", "ui"); + this.loadImage("night_icon_bg", "ui"); this.loadImage("pb_tray_overlay_player", "ui"); this.loadImage("pb_tray_overlay_enemy", "ui"); diff --git a/src/phases.ts b/src/phases.ts index 406d47cff22..e9becbe485d 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -62,7 +62,7 @@ import { Abilities } from "./data/enums/abilities"; import * as Overrides from "./overrides"; import { TextStyle, addTextObject } from "./ui/text"; import { Type } from "./data/type"; -import { BerryUsedEvent, MoveUsedEvent, TurnEndEvent, TurnInitEvent } from "./battle-scene-events"; +import { BerryUsedEvent, EncounterPhaseEvent, MoveUsedEvent, TurnEndEvent, TurnInitEvent } from "./battle-scene-events"; export class LoginPhase extends Phase { @@ -741,6 +741,8 @@ export class EncounterPhase extends BattlePhase { this.scene.initSession(); + this.scene.eventTarget.dispatchEvent(new EncounterPhaseEvent()); + // Failsafe if players somehow skip floor 200 in classic mode if (this.scene.gameMode.isClassic && this.scene.currentBattle.waveIndex > 200) { this.scene.unshiftPhase(new GameOverPhase(this.scene)); diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index 81971a08d69..b2175275eef 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -7,6 +7,7 @@ import { PlayerGender } from "#app/data/enums/player-gender"; import { CandyUpgradeNotificationChangedEvent } from "#app/battle-scene-events.js"; import { MoneyFormat } from "../../enums/money-format"; import SettingsUiHandler from "#app/ui/settings/settings-ui-handler"; +import { EaseType } from "#app/ui/enums/ease-type.js"; const MUTE = "Mute"; const VOLUME_OPTIONS = new Array(11).fill(null).map((_, i) => i ? (i * 10).toString() : MUTE); @@ -54,6 +55,8 @@ export const SettingKeys = { Move_Animations: "MOVE_ANIMATIONS", Show_Moveset_Flyout: "SHOW_MOVESET_FLYOUT", Show_Arena_Flyout: "SHOW_ARENA_FLYOUT", + Show_Time_Of_Day_Widget: "SHOW_TIME_OF_DAY_WIDGET", + Time_Of_Day_Animation: "TIME_OF_DAY_ANIMATION", Show_Stats_on_Level_Up: "SHOW_LEVEL_UP_STATS", EXP_Gains_Speed: "EXP_GAINS_SPEED", EXP_Party_Display: "EXP_PARTY_DISPLAY", @@ -205,6 +208,21 @@ export const Setting: Array = [ default: 1, type: SettingType.ACCESSIBILITY }, + { + key: SettingKeys.Show_Time_Of_Day_Widget, + label: "Show Time of Day Widget", + options: OFF_ON, + default: 1, + type: SettingType.ACCESSIBILITY, + requireReload: true, + }, + { + key: SettingKeys.Time_Of_Day_Animation, + label: "Time of Day Animation", + options: ["Bounce", "Back"], + default: 0, + type: SettingType.ACCESSIBILITY + }, { key: SettingKeys.Show_Stats_on_Level_Up, label: "Show Stats on Level Up", @@ -365,6 +383,12 @@ export function setSetting(scene: BattleScene, setting: string, value: integer): case SettingKeys.Show_Arena_Flyout: scene.showArenaFlyout = Setting[index].options[value] === "On"; break; + case SettingKeys.Show_Time_Of_Day_Widget: + scene.showTimeOfDayWidget = Setting[index].options[value] === "On"; + break; + case SettingKeys.Time_Of_Day_Animation: + scene.timeOfDayAnimation = Setting[index].options[value] === "Bounce" ? EaseType.BOUNCE : EaseType.BACK; + break; case SettingKeys.Show_Stats_on_Level_Up: scene.showLevelUpStats = Setting[index].options[value] === "On"; break; diff --git a/src/ui/arena-flyout.ts b/src/ui/arena-flyout.ts index 77996625fed..e854de2006b 100644 --- a/src/ui/arena-flyout.ts +++ b/src/ui/arena-flyout.ts @@ -1,4 +1,3 @@ -import * as Utils from "../utils"; import { addTextObject, TextStyle } from "./text"; import BattleScene from "#app/battle-scene.js"; import { ArenaTagSide } from "#app/data/arena-tag.js"; @@ -8,7 +7,8 @@ import { addWindow, WindowVariant } from "./ui-theme"; import { ArenaEvent, ArenaEventType, TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/field/arena-events.js"; import { BattleSceneEventType, TurnEndEvent } from "#app/battle-scene-events.js"; import { ArenaTagType } from "#app/data/enums/arena-tag-type.js"; -import { TimeOfDay } from "#app/data/enums/time-of-day.js"; +import TimeOfDayWidget from "./time-of-day-widget"; +import * as Utils from "../utils"; /** Enum used to differentiate {@linkcode Arena} effects */ enum ArenaEffectType { @@ -60,8 +60,7 @@ export default class ArenaFlyout extends Phaser.GameObjects.Container { /** The {@linkcode Phaser.GameObjects.Text} that goes inside of the header */ private flyoutTextHeader: Phaser.GameObjects.Text; - /** The {@linkcode Phaser.GameObjects.Sprite} that represents the current time of day */ - private timeOfDayIcon: Phaser.GameObjects.Sprite; + private timeOfDayWidget: TimeOfDayWidget; /** The {@linkcode Phaser.GameObjects.Text} header used to indicate the player's effects */ private flyoutTextHeaderPlayer: Phaser.GameObjects.Text; @@ -82,7 +81,6 @@ export default class ArenaFlyout extends Phaser.GameObjects.Container { // Stores callbacks in a variable so they can be unsubscribed from when destroyed private readonly onNewArenaEvent = (event: Event) => this.onNewArena(event); - private readonly onTurnInitEvent = (event: Event) => this.onTurnInit(event); private readonly onTurnEndEvent = (event: Event) => this.onTurnEnd(event); private readonly onFieldEffectChangedEvent = (event: Event) => this.onFieldEffectChanged(event); @@ -117,10 +115,8 @@ export default class ArenaFlyout extends Phaser.GameObjects.Container { this.flyoutContainer.add(this.flyoutTextHeader); - this.timeOfDayIcon = this.scene.add.sprite((this.flyoutWidth / 2) + (this.flyoutWindowHeader.displayWidth / 2), 0, "dawn_icon").setOrigin(); - this.timeOfDayIcon.setVisible(false); - - this.flyoutContainer.add(this.timeOfDayIcon); + this.timeOfDayWidget = new TimeOfDayWidget(this.scene, (this.flyoutWidth / 2) + (this.flyoutWindowHeader.displayWidth / 2)); + this.flyoutContainer.add(this.timeOfDayWidget); this.flyoutTextHeaderPlayer = addTextObject(this.scene, 6, 5, "Player", TextStyle.SUMMARY_BLUE); this.flyoutTextHeaderPlayer.setFontSize(54); @@ -172,18 +168,9 @@ export default class ArenaFlyout extends Phaser.GameObjects.Container { // Subscribes to required events available on game start this.battleScene.eventTarget.addEventListener(BattleSceneEventType.NEW_ARENA, this.onNewArenaEvent); - this.battleScene.eventTarget.addEventListener(BattleSceneEventType.TURN_INIT, this.onTurnInitEvent); this.battleScene.eventTarget.addEventListener(BattleSceneEventType.TURN_END, this.onTurnEndEvent); } - private setTimeOfDayIcon() { - this.timeOfDayIcon.setTexture(TimeOfDay[this.battleScene.arena.getTimeOfDay()].toLowerCase() + "_icon"); - } - - private onTurnInit(event: Event) { - this.setTimeOfDayIcon(); - } - private onNewArena(event: Event) { this.fieldEffectInfo.length = 0; @@ -192,8 +179,6 @@ export default class ArenaFlyout extends Phaser.GameObjects.Container { this.battleScene.arena.eventTarget.addEventListener(ArenaEventType.TERRAIN_CHANGED, this.onFieldEffectChangedEvent); this.battleScene.arena.eventTarget.addEventListener(ArenaEventType.TAG_ADDED, this.onFieldEffectChangedEvent); this.battleScene.arena.eventTarget.addEventListener(ArenaEventType.TAG_REMOVED, this.onFieldEffectChangedEvent); - - this.setTimeOfDayIcon(); } /** @@ -360,17 +345,18 @@ export default class ArenaFlyout extends Phaser.GameObjects.Container { * Animates the flyout to either show or hide it by applying a fade and translation * @param visible Should the flyout be shown? */ - toggleFlyout(visible: boolean): void { + public toggleFlyout(visible: boolean): void { this.scene.tweens.add({ targets: this.flyoutParent, x: visible ? this.anchorX : this.anchorX - this.translationX, duration: Utils.fixedInt(125), ease: "Sine.easeInOut", alpha: visible ? 1 : 0, + onComplete: () => this.timeOfDayWidget.parentVisible = visible, }); } - destroy(fromScene?: boolean): void { + public destroy(fromScene?: boolean): void { this.battleScene.eventTarget.removeEventListener(BattleSceneEventType.NEW_ARENA, this.onNewArenaEvent); this.battleScene.eventTarget.removeEventListener(BattleSceneEventType.TURN_END, this.onTurnEndEvent); diff --git a/src/ui/enums/ease-type.ts b/src/ui/enums/ease-type.ts new file mode 100644 index 00000000000..fbe06fd536d --- /dev/null +++ b/src/ui/enums/ease-type.ts @@ -0,0 +1,15 @@ +export enum EaseType { + NONE, + LINEAR = "Linear", + QUADRATIC = "Quad", + CUBIC = "Cubic", + QUARTIC = "Quart", + QUINTIC = "Quint", + SINUSOIDAL = "Sine", + EXPONENTIAL = "Expo", + CIRCULAR = "Circ", + ELASTIC = "Elastic", + BACK = "Back", + BOUNCE = "Bounce", + STEPPED = "Stepped", +} diff --git a/src/ui/time-of-day-widget.ts b/src/ui/time-of-day-widget.ts new file mode 100644 index 00000000000..fd5a8b0b15e --- /dev/null +++ b/src/ui/time-of-day-widget.ts @@ -0,0 +1,172 @@ +import * as Utils from "../utils"; +import BattleScene from "#app/battle-scene.js"; +import { TimeOfDay } from "#app/data/enums/time-of-day.js"; +import { BattleSceneEventType } from "#app/battle-scene-events.js"; +import { EaseType } from "./enums/ease-type"; + +/** A small self contained UI element that displays the time of day as an icon */ +export default class TimeOfDayWidget extends Phaser.GameObjects.Container { + /** An alias for the scene typecast to a {@linkcode BattleScene} */ + private battleScene: BattleScene; + + /** The {@linkcode Phaser.GameObjects.Sprite} that represents the foreground of the current time of day */ + private readonly timeOfDayIconFgs: Phaser.GameObjects.Sprite[] = new Array(2); + /** The {@linkcode Phaser.GameObjects.Sprite} that represents the middle-ground of the current time of day */ + private readonly timeOfDayIconMgs: Phaser.GameObjects.Sprite[] = new Array(2); + /** The {@linkcode Phaser.GameObjects.Sprite} that represents the background of the current time of day */ + private readonly timeOfDayIconBgs: Phaser.GameObjects.Sprite[] = new Array(2); + + /** An array containing all timeOfDayIcon objects for easier iteration */ + private timeOfDayIcons: Phaser.GameObjects.Sprite[]; + + /** A map containing all timeOfDayIcon arrays with a matching string key for easier iteration */ + private timeOfDayIconPairs: Map = new Map([ + ["bg", this.timeOfDayIconBgs], + ["mg", this.timeOfDayIconMgs], + ["fg", this.timeOfDayIconFgs],]); + + /** The current time of day */ + private currentTime: TimeOfDay = TimeOfDay.ALL; + /** The previous time of day */ + private previousTime: TimeOfDay = TimeOfDay.ALL; + + // Subscribes to required events available on game start + private readonly onEncounterPhaseEvent = (event: Event) => this.onEncounterPhase(event); + + private _parentVisible: boolean; + /** Is the parent object visible? */ + public get parentVisible(): boolean { + return this._parentVisible; + } + /** On set, resumes any paused tweens if true */ + public set parentVisible(visible: boolean) { + if (visible && !this._parentVisible) { // Only resume the tweens if parent is newly visible + this.timeOfDayIcons?.forEach( + icon => this.scene.tweens.getTweensOf(icon).forEach( + tween => tween.resume())); + } + + this._parentVisible = visible; + } + + constructor(scene: Phaser.Scene, x: number = 0, y: number = 0) { + super(scene, x, y); + this.battleScene = this.scene as BattleScene; + + this.setVisible(this.battleScene.showTimeOfDayWidget); + if (!this.battleScene.showTimeOfDayWidget) { + return; + } + + // Initialize all sprites + this.timeOfDayIconPairs.forEach( + (icons, key) => { + for (let i = 0; i < icons.length; i++) { + icons[i] = this.scene.add.sprite(0, 0, "dawn_icon_" + key).setOrigin(); + } + }); + // Store a flat array of all icons for later + this.timeOfDayIcons = [this.timeOfDayIconBgs, this.timeOfDayIconMgs, this.timeOfDayIconFgs].flat(); + this.add(this.timeOfDayIcons); + + this.battleScene.eventTarget.addEventListener(BattleSceneEventType.ENCOUNTER_PHASE, this.onEncounterPhaseEvent); + } + + /** + * Creates a tween animation based on the 'Back' ease algorithm + * @returns an array of all tweens in the animation + */ + private getBackTween(): Phaser.Types.Tweens.TweenBuilderConfig[] { + const rotate = { + targets: [this.timeOfDayIconMgs[0], this.timeOfDayIconMgs[1]], + angle: "+=90", + duration: Utils.fixedInt(1500), + ease: "Back.easeOut", + paused: !this.parentVisible, + }; + const fade = { + targets: [this.timeOfDayIconBgs[1], this.timeOfDayIconMgs[1], this.timeOfDayIconFgs[1]], + alpha: 0, + duration: Utils.fixedInt(500), + ease: "Linear", + paused: !this.parentVisible, + }; + + return [rotate, fade]; + } + + /** + * Creates a tween animation based on the 'Bounce' ease algorithm + * @returns an array of all tweens in the animation + */ + private getBounceTween(): Phaser.Types.Tweens.TweenBuilderConfig[] { + const bounce = { + targets: [this.timeOfDayIconMgs[0], this.timeOfDayIconMgs[1]], + angle: "+=90", + duration: Utils.fixedInt(2000), + ease: "Bounce.easeOut", + paused: !this.parentVisible, + }; + const fade = { + targets: [this.timeOfDayIconBgs[1], this.timeOfDayIconMgs[1], this.timeOfDayIconFgs[1]], + alpha: 0, + duration: Utils.fixedInt(800), + ease: "Linear", + paused: !this.parentVisible, + }; + + return [bounce, fade]; + } + + /** Resets all icons to the proper depth, texture, and alpha so they are ready to tween */ + private resetIcons() { + this.moveBelow(this.timeOfDayIconBgs[0], this.timeOfDayIconBgs[1]); + this.moveBelow(this.timeOfDayIconMgs[0], this.timeOfDayIconBgs[1]); + this.moveBelow(this.timeOfDayIconFgs[0], this.timeOfDayIconFgs[1]); + + this.timeOfDayIconPairs.forEach( + (icons, key) => { + icons[0].setTexture(TimeOfDay[this.currentTime].toLowerCase() + "_icon_" + key); + icons[1].setTexture(TimeOfDay[this.previousTime].toLowerCase() + "_icon_" + key); + }); + this.timeOfDayIconMgs[0].setRotation(-90 * (3.14/180)); + + this.timeOfDayIcons.forEach(icon => icon.setAlpha(1)); + } + + /** Adds the proper tween for all icons */ + private tweenTimeOfDayIcon() { + this.scene.tweens.killTweensOf(this.timeOfDayIcons); + + this.resetIcons(); + + // Tween based on the player setting + (this.battleScene.timeOfDayAnimation === EaseType.BACK ? this.getBackTween() : this.getBounceTween()) + .forEach(tween => this.scene.tweens.add(tween)); + + // Swaps all elements of the icon arrays by shifting the first element onto the end of the array + // This ensures index[0] is always the new time of day icon and index[1] is always the current one + this.timeOfDayIconPairs.forEach( + icons => icons.push(icons.shift())); + } + + /** + * Grabs the current time of day from the arena and calls {@linkcode tweenTimeOfDayIcon} + * @param event {@linkcode Event} being sent + */ + private onEncounterPhase(event: Event) { + const newTime = this.battleScene.arena.getTimeOfDay(); + + if (this.currentTime === newTime) { + return; + } + + this.currentTime = newTime; + this.previousTime = this.currentTime - 1; + if (this.previousTime < TimeOfDay.DAWN) { + this.previousTime = TimeOfDay.NIGHT; + } + + this.tweenTimeOfDayIcon(); + } +} From a8489cc70748e9eeb48033b9f9764045e788807b Mon Sep 17 00:00:00 2001 From: flx-sta <50131232+flx-sta@users.noreply.github.com> Date: Wed, 5 Jun 2024 22:18:54 -0700 Subject: [PATCH 071/129] [Bug] Revert start-label text to translation (#1855) --- src/ui/starter-select-ui-handler.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index d30d4c4e14c..f799efe96b5 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -406,8 +406,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.valueLimitLabel.setOrigin(0.5, 0); this.starterSelectContainer.add(this.valueLimitLabel); - //TODO: back to translated version - const startLabel = addTextObject(this.scene, 124, 162, "Random", TextStyle.TOOLTIP_CONTENT); + const startLabel = addTextObject(this.scene, 124, 162, i18next.t("starterSelectUiHandler:start"), TextStyle.TOOLTIP_CONTENT); startLabel.setOrigin(0.5, 0); this.starterSelectContainer.add(startLabel); From 0e9bcfb4fdaeeb2dc405266e6b34dbf55bae9f5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Ricardo=20Fleury=20Oliveira?= Date: Thu, 6 Jun 2024 02:23:44 -0300 Subject: [PATCH 072/129] [Bug] Minor ptBR mint fix (#1852) --- src/locales/pt_BR/modifier-type.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locales/pt_BR/modifier-type.ts b/src/locales/pt_BR/modifier-type.ts index 5904278c401..adae36adc26 100644 --- a/src/locales/pt_BR/modifier-type.ts +++ b/src/locales/pt_BR/modifier-type.ts @@ -46,7 +46,7 @@ export const modifierType: ModifierTypeTranslationEntries = { }, "PokemonNatureChangeModifierType": { name: "Hortelã {{natureName}}", - description: "Muda a natureza de um Pokémon para {{natureName}} e a desbloqueia permanentemente para seu inicial", + description: "Muda a natureza do Pokémon para {{natureName}} e a desbloqueia permanentemente", }, "DoubleBattleChanceBoosterModifierType": { description: "Dobra as chances de encontrar uma batalha em dupla por {{battleCount}} batalhas", From 40328d5712272bf055ae223953e76c5a03369802 Mon Sep 17 00:00:00 2001 From: Frede Date: Thu, 6 Jun 2024 15:22:37 +0200 Subject: [PATCH 073/129] [BUG] Fix Move Info Overlay Scroll Bug (#1856) * Added "Skip Dialogues" option (if at least 1 classic win) * Removed error sound and hide option instead when classic wins = 0 * Add skip dialogues option to Unlockables and show unlocked message on first classic win * Only skips seen dialogues, removed dialogue option from unlockables, seen dialogues get saved to local storage * oops * dont show charSprite when skipping a dialogue, small fixes * correctly reset move description scrolling when changing move * override fix --------- Co-authored-by: Frederik Hobein --- src/ui/move-info-overlay.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ui/move-info-overlay.ts b/src/ui/move-info-overlay.ts index cba2a2a16dc..3b947cb842d 100644 --- a/src/ui/move-info-overlay.ts +++ b/src/ui/move-info-overlay.ts @@ -142,10 +142,11 @@ export default class MoveInfoOverlay extends Phaser.GameObjects.Container implem this.desc.setText(move?.effect || ""); - // stop previous scrolling effects + // stop previous scrolling effects and reset y position if (this.descScroll) { this.descScroll.remove(); this.descScroll = null; + this.desc.y = (this.options?.top ? EFF_HEIGHT : 0) + BORDER - 2; } // determine if we need to add new scrolling effects From f53dce432be4c16d9b89356ef16d859a0e94b44f Mon Sep 17 00:00:00 2001 From: Laeticia PIERRE Date: Thu, 6 Jun 2024 15:36:12 +0200 Subject: [PATCH 074/129] useMove + pokemon affix localization (#1276) * useMove + pokemonPrefix localization * Rename prefix to affix + line break fr * getPokemonAffix to getPokemonNameWithAffix + remove space + replace * Better getPokemonNameWithAffix switch * Ko locale + fix es locale * Doc getPokemonNameWithAffix + getPokemonMessage + fix * Ko translate / missing weather changes * Fix conflicts getPokemonPrefix --------- Co-authored-by: Benjamin Odom --- src/data/ability.ts | 8 ++++---- src/data/battler-tags.ts | 6 +++--- src/data/weather.ts | 6 +++--- src/locales/de/battle.ts | 3 +++ src/locales/de/weather.ts | 4 ++-- src/locales/en/battle.ts | 3 +++ src/locales/en/weather.ts | 4 ++-- src/locales/es/battle.ts | 3 +++ src/locales/es/weather.ts | 4 ++-- src/locales/fr/battle.ts | 3 +++ src/locales/fr/weather.ts | 4 ++-- src/locales/it/battle.ts | 3 +++ src/locales/it/weather.ts | 4 ++-- src/locales/ko/battle.ts | 5 ++++- src/locales/ko/weather.ts | 4 ++-- src/locales/pt_BR/battle.ts | 3 +++ src/locales/pt_BR/weather.ts | 4 ++-- src/locales/zh_CN/battle.ts | 3 +++ src/locales/zh_CN/weather.ts | 4 ++-- src/locales/zh_TW/battle.ts | 3 +++ src/locales/zh_TW/weather.ts | 4 ++-- src/messages.ts | 37 ++++++++++++++++++++++++++++-------- src/phases.ts | 14 ++++++++++---- 23 files changed, 95 insertions(+), 41 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 0a7b86e3e2b..4bb4f36350e 100755 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -3,7 +3,7 @@ import { Type } from "./type"; import * as Utils from "../utils"; import { BattleStat, getBattleStatName } from "./battle-stat"; import { MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase } from "../phases"; -import { getPokemonMessage, getPokemonPrefix } from "../messages"; +import { getPokemonMessage, getPokemonNameWithAffix } from "../messages"; import { Weather, WeatherType } from "./weather"; import { BattlerTag } from "./battler-tags"; import { BattlerTagType } from "./enums/battler-tag-type"; @@ -156,7 +156,7 @@ export class BlockRecoilDamageAttr extends AbAttr { } getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]) { - return i18next.t("abilityTriggers:blockRecoilDamage", {pokemonName: `${getPokemonPrefix(pokemon)}${pokemon.name}`, abilityName: abilityName}); + return i18next.t("abilityTriggers:blockRecoilDamage", {pokemonName: getPokemonNameWithAffix(pokemon), abilityName: abilityName}); } } @@ -878,7 +878,7 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr { } getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { - return i18next.t("abilityTriggers:perishBody", {pokemonName: `${getPokemonPrefix(pokemon)}${pokemon.name}`, abilityName: abilityName}); + return i18next.t("abilityTriggers:perishBody", {pokemonName: getPokemonNameWithAffix(pokemon), abilityName: abilityName}); } } @@ -2666,7 +2666,7 @@ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr { for (const opp of pokemon.getOpponents()) { if (opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(Abilities.COMATOSE)) { opp.damageAndUpdate(Math.floor(Math.max(1, opp.getMaxHp() / 8)), HitResult.OTHER); - pokemon.scene.queueMessage(i18next.t("abilityTriggers:badDreams", {pokemonName: `${getPokemonPrefix(opp)}${opp.name}`})); + pokemon.scene.queueMessage(i18next.t("abilityTriggers:badDreams", {pokemonName: getPokemonNameWithAffix(opp)})); hadEffect = true; } diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 48083cba00d..50d4b62decb 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1,6 +1,6 @@ import { CommonAnim, CommonBattleAnim } from "./battle-anims"; import { CommonAnimPhase, MoveEffectPhase, MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase } from "../phases"; -import { getPokemonMessage, getPokemonPrefix } from "../messages"; +import { getPokemonMessage, getPokemonNameWithAffix } from "../messages"; import Pokemon, { MoveResult, HitResult } from "../field/pokemon"; import { Stat, getStatName } from "./pokemon-stat"; import { StatusEffect } from "./status-effect"; @@ -803,7 +803,7 @@ export class ThunderCageTag extends DamagingTrapTag { } getTrapMessage(pokemon: Pokemon): string { - return getPokemonMessage(pokemon.scene.getPokemonById(this.sourceId), ` trapped\n${getPokemonPrefix(pokemon).toLowerCase()}${pokemon.name}!`); + return getPokemonMessage(pokemon.scene.getPokemonById(this.sourceId), ` trapped\n${getPokemonNameWithAffix(pokemon)}!`); } } @@ -813,7 +813,7 @@ export class InfestationTag extends DamagingTrapTag { } getTrapMessage(pokemon: Pokemon): string { - return getPokemonMessage(pokemon, ` has been afflicted \nwith an infestation by ${getPokemonPrefix(pokemon.scene.getPokemonById(this.sourceId))}${pokemon.scene.getPokemonById(this.sourceId).name}!`); + return getPokemonMessage(pokemon, ` has been afflicted \nwith an infestation by ${getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId))}!`); } } diff --git a/src/data/weather.ts b/src/data/weather.ts index f2b4136f51c..1df94b02da1 100644 --- a/src/data/weather.ts +++ b/src/data/weather.ts @@ -1,5 +1,5 @@ import { Biome } from "./enums/biome"; -import { getPokemonMessage, getPokemonPrefix } from "../messages"; +import { getPokemonMessage, getPokemonNameWithAffix } from "../messages"; import Pokemon from "../field/pokemon"; import { Type } from "./type"; import Move, { AttackMove } from "./move"; @@ -180,9 +180,9 @@ export function getWeatherLapseMessage(weatherType: WeatherType): string { export function getWeatherDamageMessage(weatherType: WeatherType, pokemon: Pokemon): string { switch (weatherType) { case WeatherType.SANDSTORM: - return i18next.t("weather:sandstormDamageMessage", {pokemonPrefix: getPokemonPrefix(pokemon), pokemonName: pokemon.name}); + return i18next.t("weather:sandstormDamageMessage", {pokemonNameWithAffix: getPokemonNameWithAffix(pokemon)}); case WeatherType.HAIL: - return i18next.t("weather:hailDamageMessage", {pokemonPrefix: getPokemonPrefix(pokemon), pokemonName: pokemon.name}); + return i18next.t("weather:hailDamageMessage", {pokemonNameWithAffix: getPokemonNameWithAffix(pokemon)}); } return null; diff --git a/src/locales/de/battle.ts b/src/locales/de/battle.ts index 8c153aa77eb..d588fb327c6 100644 --- a/src/locales/de/battle.ts +++ b/src/locales/de/battle.ts @@ -56,6 +56,9 @@ export const battle: SimpleTranslationEntries = { "notDisabled": "{{pokemonName}}'s {{moveName}} ist\nnicht mehr deaktiviert!", "eggHatching": "Oh?", "ivScannerUseQuestion": "IV-Scanner auf {{pokemonName}} benutzen?", + "wildPokemonWithAffix": "{{pokemonName}} (wild)", + "foePokemonWithAffix": "{{pokemonName}} (Gegner)", + "useMove": "{{pokemonNameWithAffix}} setzt {{moveName}} ein!", "drainMessage": "{{pokemonName}} wurde Energie abgesaugt", "regainHealth": "KP von {{pokemonName}} wurden wieder aufgefrischt!" } as const; diff --git a/src/locales/de/weather.ts b/src/locales/de/weather.ts index f6a6864bec7..ab1dde97639 100644 --- a/src/locales/de/weather.ts +++ b/src/locales/de/weather.ts @@ -15,12 +15,12 @@ export const weather: SimpleTranslationEntries = { "sandstormStartMessage": "Ein Sandsturm kommt auf!", "sandstormLapseMessage": "Der Sandsturm tobt.", "sandstormClearMessage": "Der Sandsturm legt sich.", - "sandstormDamageMessage": " Der Sandsturm fügt {{pokemonPrefix}}{{pokemonName}} Schaden zu!", + "sandstormDamageMessage": " Der Sandsturm fügt {{pokemonNameWithAffix}} Schaden zu!", "hailStartMessage": "Es fängt an zu hageln!", "hailLapseMessage": "Der Hagelsturm tobt.", "hailClearMessage": "Der Hagelsturm legt sich.", - "hailDamageMessage": "{{pokemonPrefix}}{{pokemonName}} wird von Hagelkörnern getroffen!", + "hailDamageMessage": "{{pokemonNameWithAffix}} wird von Hagelkörnern getroffen!", "snowStartMessage": "Es fängt an zu schneien!", "snowLapseMessage": "Der Schneesturm tobt.", diff --git a/src/locales/en/battle.ts b/src/locales/en/battle.ts index a3fa41d9b76..083de089961 100644 --- a/src/locales/en/battle.ts +++ b/src/locales/en/battle.ts @@ -56,6 +56,9 @@ export const battle: SimpleTranslationEntries = { "skipItemQuestion": "Are you sure you want to skip taking an item?", "eggHatching": "Oh?", "ivScannerUseQuestion": "Use IV Scanner on {{pokemonName}}?", + "wildPokemonWithAffix": "Wild {{pokemonName}}", + "foePokemonWithAffix": "Foe {{pokemonName}}", + "useMove": "{{pokemonNameWithAffix}} used {{moveName}}!", "drainMessage": "{{pokemonName}} had its\nenergy drained!", "regainHealth": "{{pokemonName}} regained\nhealth!" } as const; diff --git a/src/locales/en/weather.ts b/src/locales/en/weather.ts index 1e4602f362c..513c2181b00 100644 --- a/src/locales/en/weather.ts +++ b/src/locales/en/weather.ts @@ -15,12 +15,12 @@ export const weather: SimpleTranslationEntries = { "sandstormStartMessage": "A sandstorm brewed!", "sandstormLapseMessage": "The sandstorm rages.", "sandstormClearMessage": "The sandstorm subsided.", - "sandstormDamageMessage": "{{pokemonPrefix}}{{pokemonName}} is buffeted\nby the sandstorm!", + "sandstormDamageMessage": "{{pokemonNameWithAffix}} is buffeted\nby the sandstorm!", "hailStartMessage": "It started to hail!", "hailLapseMessage": "Hail continues to fall.", "hailClearMessage": "The hail stopped.", - "hailDamageMessage": "{{pokemonPrefix}}{{pokemonName}} is pelted\nby the hail!", + "hailDamageMessage": "{{pokemonNameWithAffix}} is pelted\nby the hail!", "snowStartMessage": "It started to snow!", "snowLapseMessage": "The snow is falling down.", diff --git a/src/locales/es/battle.ts b/src/locales/es/battle.ts index af090153a04..c4d79cfdb93 100644 --- a/src/locales/es/battle.ts +++ b/src/locales/es/battle.ts @@ -56,6 +56,9 @@ export const battle: SimpleTranslationEntries = { "skipItemQuestion": "¿Estás seguro de que no quieres coger un objeto?", "eggHatching": "¿Y esto?", "ivScannerUseQuestion": "¿Quieres usar el Escáner de IVs en {{pokemonName}}?", + "wildPokemonWithAffix": "Wild {{pokemonName}}", + "foePokemonWithAffix": "Foe {{pokemonName}}", + "useMove": "{{pokemonNameWithAffix}} used {{moveName}}!", "drainMessage": "{{pokemonName}} had its\nenergy drained!", "regainHealth": "{{pokemonName}} regained\nhealth!" } as const; diff --git a/src/locales/es/weather.ts b/src/locales/es/weather.ts index a5ba94d1e7d..1e40544accd 100644 --- a/src/locales/es/weather.ts +++ b/src/locales/es/weather.ts @@ -15,12 +15,12 @@ export const weather: SimpleTranslationEntries = { "sandstormStartMessage": "¡Se ha desatado una tormenta de arena!", "sandstormLapseMessage": "La tormenta de arena arrecia...", "sandstormClearMessage": "La tormenta de arena termino.", - "sandstormDamageMessage": "¡La tormenta de arena zarandea al\n{{pokemonName}} {{pokemonPrefix}}!", + "sandstormDamageMessage": "¡La tormenta de arena zarandea al\n{{pokemonNameWithAffix}}!", "hailStartMessage": "¡Ha empezado a granizar!", "hailLapseMessage": "Sigue granizando...", "hailClearMessage": "Had dejado de granizar.", - "hailDamageMessage": "El granizo golpea al\n{{pokemonName}} {{pokemonPrefix}}!", + "hailDamageMessage": "El granizo golpea al\n{{pokemonNameWithAffix}}!", "snowStartMessage": "¡Ha empezado a nevar!", "snowLapseMessage": "Sigue nevando...", diff --git a/src/locales/fr/battle.ts b/src/locales/fr/battle.ts index 535ed4b6ade..181b85f7cc8 100644 --- a/src/locales/fr/battle.ts +++ b/src/locales/fr/battle.ts @@ -56,6 +56,9 @@ export const battle: SimpleTranslationEntries = { "skipItemQuestion": "Êtes-vous sûr·e de ne pas vouloir prendre d’objet ?", "eggHatching": "Oh ?", "ivScannerUseQuestion": "Utiliser le Scanner d’IV sur {{pokemonName}} ?", + "wildPokemonWithAffix": "{{pokemonName}} sauvage", + "foePokemonWithAffix": "{{pokemonName}} ennemi", + "useMove": "{{pokemonNameWithAffix}} utilise\n{{moveName}} !", "drainMessage": "{{pokemonName}} had its\nenergy drained!", "regainHealth": "{{pokemonName}} regained\nhealth!" } as const; diff --git a/src/locales/fr/weather.ts b/src/locales/fr/weather.ts index 76d56887578..5c483d71b44 100644 --- a/src/locales/fr/weather.ts +++ b/src/locales/fr/weather.ts @@ -15,12 +15,12 @@ export const weather: SimpleTranslationEntries = { "sandstormStartMessage": "Une tempête de sable se prépare !", "sandstormLapseMessage": "La tempête de sable fait rage !", "sandstormClearMessage": "La tempête de sable se calme !", - "sandstormDamageMessage": "La tempête de sable inflige des dégâts\nà {{pokemonPrefix}}{{pokemonName}} !", + "sandstormDamageMessage": "La tempête de sable inflige des dégâts\nà {{pokemonNameWithAffix}} !", "hailStartMessage": "Il commence à grêler !", "hailLapseMessage": "La grêle continue de tomber !", "hailClearMessage": "La grêle s’est arrêtée !", - "hailDamageMessage": "La grêle inflige des dégâts\nà {{pokemonPrefix}}{{pokemonName}} !", + "hailDamageMessage": "La grêle inflige des dégâts\nà {{pokemonNameWithAffix}} !", "snowStartMessage": "Il commence à neiger !", "snowLapseMessage": "Il y a une tempête de neige !", diff --git a/src/locales/it/battle.ts b/src/locales/it/battle.ts index 5b8089e6677..3ea527339f0 100644 --- a/src/locales/it/battle.ts +++ b/src/locales/it/battle.ts @@ -56,6 +56,9 @@ export const battle: SimpleTranslationEntries = { "skipItemQuestion": "Sei sicuro di non voler prendere nessun oggetto?", "eggHatching": "Oh!", "ivScannerUseQuestion": "Vuoi usare lo scanner di IV su {{pokemonName}}?", + "wildPokemonWithAffix": "Wild {{pokemonName}}", + "foePokemonWithAffix": "Foe {{pokemonName}}", + "useMove": "{{pokemonNameWithAffix}} used {{moveName}}!", "drainMessage": "{{pokemonName}} had its\nenergy drained!", "regainHealth": "{{pokemonName}} regained\nhealth!" } as const; diff --git a/src/locales/it/weather.ts b/src/locales/it/weather.ts index 3895fcebc46..ed5d41a80af 100644 --- a/src/locales/it/weather.ts +++ b/src/locales/it/weather.ts @@ -15,12 +15,12 @@ export const weather: SimpleTranslationEntries = { "sandstormStartMessage": "Si è scatenata una tempesta di sabbia!", "sandstormLapseMessage": "La tempesta di sabbia infuria.", "sandstormClearMessage": "La tempesta di sabbia si è placata.", - "sandstormDamageMessage": "{{pokemonPrefix}}{{pokemonName}} è stato colpito\ndalla tempesta di sabbia!", + "sandstormDamageMessage": "{{pokemonNameWithAffix}} è stato colpito\ndalla tempesta di sabbia!", "hailStartMessage": "Ha iniziato a grandinare!", "hailLapseMessage": "La grandine continua a cadere.", "hailClearMessage": "Ha smesso di grandinare.", - "hailDamageMessage": "{{pokemonPrefix}}{{pokemonName}} è stato colpito\ndalla grandine!", + "hailDamageMessage": "{{pokemonNameWithAffix}} è stato colpito\ndalla grandine!", "snowStartMessage": "Ha iniziato a nevicare!", "snowLapseMessage": "La neve sta continuando a cadere.", diff --git a/src/locales/ko/battle.ts b/src/locales/ko/battle.ts index c6288d3d9f2..cc91141718c 100644 --- a/src/locales/ko/battle.ts +++ b/src/locales/ko/battle.ts @@ -55,5 +55,8 @@ export const battle: SimpleTranslationEntries = { "notDisabled": "{{pokemonName}}의\n{{moveName}} 사슬묶기가 풀렸다!", "skipItemQuestion": "아이템을 받지 않고 넘어가시겠습니까?", "eggHatching": "어라…?", - "ivScannerUseQuestion": "{{pokemonName}}에게 개체값탐지기를 사용하시겠습니까?" + "ivScannerUseQuestion": "{{pokemonName}}에게 개체값탐지기를 사용하시겠습니까?", + "wildPokemonWithAffix": "야생 {{pokemonName}}", + "foePokemonWithAffix": "상대 {{pokemonName}}", + "useMove": "{{pokemonNameWithAffix}}의 {{moveName}}!" } as const; diff --git a/src/locales/ko/weather.ts b/src/locales/ko/weather.ts index 70654d247b6..81340d9567a 100644 --- a/src/locales/ko/weather.ts +++ b/src/locales/ko/weather.ts @@ -15,12 +15,12 @@ export const weather: SimpleTranslationEntries = { "sandstormStartMessage": "모래바람이 불기 시작했다!", "sandstormLapseMessage": "모래바람이 세차게 분다", "sandstormClearMessage": "모래바람이 가라앉았다!", - "sandstormDamageMessage": "모래바람이\n{{pokemonPrefix}}{{pokemonName}}[[를]] 덮쳤다!", + "sandstormDamageMessage": "모래바람이\n{{pokemonNameWithAffix}}[[를]] 덮쳤다!", "hailStartMessage": "싸라기눈이 내리기 시작했다!", "hailLapseMessage": "싸라기눈이 계속 내리고 있다", "hailClearMessage": "싸라기눈이 그쳤다!", - "hailDamageMessage": "싸라기눈이\n{{pokemonPrefix}}{{pokemonName}}[[를]] 덮쳤다!", + "hailDamageMessage": "싸라기눈이\n{{pokemonNameWithAffix}}[[를]] 덮쳤다!", "snowStartMessage": "눈이 내리기 시작했다!", "snowLapseMessage": "눈이 계속 내리고 있다", diff --git a/src/locales/pt_BR/battle.ts b/src/locales/pt_BR/battle.ts index f6f471e838a..810aca36b6c 100644 --- a/src/locales/pt_BR/battle.ts +++ b/src/locales/pt_BR/battle.ts @@ -56,6 +56,9 @@ export const battle: SimpleTranslationEntries = { "skipItemQuestion": "Tem certeza de que não quer escolher um item?", "eggHatching": "Opa?", "ivScannerUseQuestion": "Quer usar o Scanner de IVs em {{pokemonName}}?", + "wildPokemonWithAffix": "Wild {{pokemonName}}", + "foePokemonWithAffix": "Foe {{pokemonName}}", + "useMove": "{{pokemonNameWithAffix}} used {{moveName}}!", "drainMessage": "{{pokemonName}} teve sua\nenergia drenada!", "regainHealth": "{{pokemonName}} recuperou\npontos de saúde!" } as const; diff --git a/src/locales/pt_BR/weather.ts b/src/locales/pt_BR/weather.ts index 269ba0e3726..07854512fdc 100644 --- a/src/locales/pt_BR/weather.ts +++ b/src/locales/pt_BR/weather.ts @@ -15,12 +15,12 @@ export const weather: SimpleTranslationEntries = { "sandstormStartMessage": "Uma tempestade de areia se formou!", "sandstormLapseMessage": "A tempestade de areia é violenta.", "sandstormClearMessage": "A tempestade de areia diminuiu.", - "sandstormDamageMessage": "{{pokemonPrefix}}{{pokemonName}} é atingido\npela tempestade de areia!", + "sandstormDamageMessage": "{{pokemonNameWithAffix}} é atingido\npela tempestade de areia!", "hailStartMessage": "Começou a chover granizo!", "hailLapseMessage": "Granizo cai do céu.", "hailClearMessage": "O granizo parou.", - "hailDamageMessage": "{{pokemonPrefix}}{{pokemonName}} é atingido\npelo granizo!", + "hailDamageMessage": "{{pokemonNameWithAffix}} é atingido\npelo granizo!", "snowStartMessage": "Começou a nevar!", "snowLapseMessage": "A neve continua caindo.", diff --git a/src/locales/zh_CN/battle.ts b/src/locales/zh_CN/battle.ts index cd6357608ac..0fdb962c679 100644 --- a/src/locales/zh_CN/battle.ts +++ b/src/locales/zh_CN/battle.ts @@ -56,6 +56,9 @@ export const battle: SimpleTranslationEntries = { "skipItemQuestion": "你确定要跳过拾取道具吗?", "eggHatching": "咦?", "ivScannerUseQuestion": "对 {{pokemonName}} 使用个体值扫描仪?", + "wildPokemonWithAffix": "Wild {{pokemonName}}", + "foePokemonWithAffix": "Foe {{pokemonName}}", + "useMove": "{{pokemonNameWithAffix}} used {{moveName}}!", "drainMessage": "{{pokemonName}} had its\nenergy drained!", "regainHealth": "{{pokemonName}} regained\nhealth!" } as const; diff --git a/src/locales/zh_CN/weather.ts b/src/locales/zh_CN/weather.ts index 2049b8a429c..874b17f8fe8 100644 --- a/src/locales/zh_CN/weather.ts +++ b/src/locales/zh_CN/weather.ts @@ -15,12 +15,12 @@ export const weather: SimpleTranslationEntries = { "sandstormStartMessage": "开始刮沙暴了!", "sandstormLapseMessage": "沙暴肆虐。", "sandstormClearMessage": "沙暴停止了!", - "sandstormDamageMessage": "沙暴袭击了{{pokemonPrefix}}{{pokemonName}}!", + "sandstormDamageMessage": "沙暴袭击了{{pokemonNameWithAffix}}!", "hailStartMessage": "开始下冰雹了!", "hailLapseMessage": "冰雹继续肆虐。", "hailClearMessage": "冰雹不再下了。", - "hailDamageMessage": "冰雹袭击了{{pokemonPrefix}}{{pokemonName}}!", + "hailDamageMessage": "冰雹袭击了{{pokemonNameWithAffix}}!", "snowStartMessage": "开始下雪了!", "snowLapseMessage": "雪继续下。", diff --git a/src/locales/zh_TW/battle.ts b/src/locales/zh_TW/battle.ts index c0846e1cb18..4255aeaef99 100644 --- a/src/locales/zh_TW/battle.ts +++ b/src/locales/zh_TW/battle.ts @@ -53,6 +53,9 @@ export const battle: SimpleTranslationEntries = { "skipItemQuestion": "你要跳過拾取道具嗎?", "eggHatching": "咦?", "ivScannerUseQuestion": "對 {{pokemonName}} 使用個體值掃描?", + "wildPokemonWithAffix": "Wild {{pokemonName}}", + "foePokemonWithAffix": "Foe {{pokemonName}}", + "useMove": "{{pokemonNameWithAffix}} used {{moveName}}!", "drainMessage": "{{pokemonName}} had its\nenergy drained!", "regainHealth": "{{pokemonName}} regained\nhealth!" } as const; diff --git a/src/locales/zh_TW/weather.ts b/src/locales/zh_TW/weather.ts index 0a235b3b10c..03f3425aa43 100644 --- a/src/locales/zh_TW/weather.ts +++ b/src/locales/zh_TW/weather.ts @@ -15,12 +15,12 @@ export const weather: SimpleTranslationEntries = { "sandstormStartMessage": "開始刮沙暴了!", "sandstormLapseMessage": "沙暴肆虐。", "sandstormClearMessage": "沙暴停止了。", - "sandstormDamageMessage": "沙暴襲擊了{{pokemonPrefix}}{{pokemonName}}!", + "sandstormDamageMessage": "沙暴襲擊了{{pokemonNameWithAffix}}!", "hailStartMessage": "開始下冰雹了!", "hailLapseMessage": "冰雹繼續肆虐。", "hailClearMessage": "冰雹不再下了。", - "hailDamageMessage": "冰雹襲擊了{{pokemonPrefix}}{{pokemonName}}!", + "hailDamageMessage": "冰雹襲擊了{{pokemonNameWithAffix}}!", "snowStartMessage": "開始下雪了!", "snowLapseMessage": "雪繼續下。", diff --git a/src/messages.ts b/src/messages.ts index a8549c7356b..e2387094502 100644 --- a/src/messages.ts +++ b/src/messages.ts @@ -1,19 +1,40 @@ import { BattleSpec } from "./enums/battle-spec"; import Pokemon from "./field/pokemon"; +import i18next from "./plugins/i18n"; +/** + * Builds a message by concatenating the Pokemon name with its potential affix and the given text + * @param pokemon {@linkcode Pokemon} name and battle context will be retrieved from this instance for {@linkcode getPokemonNameWithAffix} + * @param {string} content any text + * @returns {string} ex: "Wild Gengar fainted!", "Ectoplasma sauvage est K.O!" + * @see {@linkcode getPokemonNameWithAffix} for the Pokemon's name and potentiel affix + */ export function getPokemonMessage(pokemon: Pokemon, content: string): string { - return `${getPokemonPrefix(pokemon)}${pokemon.name}${content}`; + return `${getPokemonNameWithAffix(pokemon)}${content}`; } -export function getPokemonPrefix(pokemon: Pokemon): string { - let prefix: string; +/** + * Retrieves the Pokemon's name, potentially with an affix indicating its role (wild or foe) in the current battle context, translated + * @param pokemon {@linkcode Pokemon} name and battle context will be retrieved from this instance + * @returns {string} ex: "Wild Gengar", "Ectoplasma sauvage" + */ +export function getPokemonNameWithAffix(pokemon: Pokemon): string { switch (pokemon.scene.currentBattle.battleSpec) { case BattleSpec.DEFAULT: - prefix = !pokemon.isPlayer() ? pokemon.hasTrainer() ? "Foe " : "Wild " : ""; - break; + return !pokemon.isPlayer() + ? pokemon.hasTrainer() + ? i18next.t("battle:foePokemonWithAffix", { + pokemonName: pokemon.name, + }) + : i18next.t("battle:wildPokemonWithAffix", { + pokemonName: pokemon.name, + }) + : pokemon.name; case BattleSpec.FINAL_BOSS: - prefix = !pokemon.isPlayer() ? "Foe " : ""; - break; + return !pokemon.isPlayer() + ? i18next.t("battle:foePokemonWithAffix", { pokemonName: pokemon.name }) + : pokemon.name; + default: + return pokemon.name; } - return prefix; } diff --git a/src/phases.ts b/src/phases.ts index e9becbe485d..45acc79ca01 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -23,7 +23,7 @@ import { FusePokemonModifierType, ModifierPoolType, ModifierType, ModifierTypeFu import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import { BattlerTagLapseType, EncoreTag, HideSpriteTag as HiddenTag, ProtectedTag, TrappedTag } from "./data/battler-tags"; import { BattlerTagType } from "./data/enums/battler-tag-type"; -import { getPokemonMessage, getPokemonPrefix } from "./messages"; +import { getPokemonMessage, 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"; @@ -2276,7 +2276,7 @@ export class TurnEndPhase extends FieldPhase { pokemon.lapseTags(BattlerTagLapseType.TURN_END); if (pokemon.summonData.disabledMove && !--pokemon.summonData.disabledTurns) { - this.scene.pushPhase(new MessagePhase(this.scene, i18next.t("battle:notDisabled", { pokemonName: `${getPokemonPrefix(pokemon)}${pokemon.name}`, moveName: allMoves[pokemon.summonData.disabledMove].name }))); + this.scene.pushPhase(new MessagePhase(this.scene, i18next.t("battle:notDisabled", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: allMoves[pokemon.summonData.disabledMove].name }))); pokemon.summonData.disabledMove = Moves.NONE; } @@ -2643,7 +2643,10 @@ export class MovePhase extends BattlePhase { if (this.move.getMove().hasAttr(ChargeAttr)) { const lastMove = this.pokemon.getLastXMoves() as TurnMove[]; if (!lastMove.length || lastMove[0].move !== this.move.getMove().id || lastMove[0].result !== MoveResult.OTHER) { - this.scene.queueMessage(getPokemonMessage(this.pokemon, ` used\n${this.move.getName()}!`), 500); + this.scene.queueMessage(i18next.t("battle:useMove", { + pokemonNameWithAffix: getPokemonNameWithAffix(this.pokemon), + moveName: this.move.getName() + }), 500); return; } } @@ -2652,7 +2655,10 @@ export class MovePhase extends BattlePhase { return; } - this.scene.queueMessage(getPokemonMessage(this.pokemon, ` used\n${this.move.getName()}!`), 500); + this.scene.queueMessage(i18next.t("battle:useMove", { + pokemonNameWithAffix: getPokemonNameWithAffix(this.pokemon), + moveName: this.move.getName() + }), 500); applyMoveAttrs(PreMoveMessageAttr, this.pokemon, this.pokemon.getOpponents().find(() => true), this.move.getMove()); } From 5764324f61ca882d21e87356f6aba7d41d9a044e Mon Sep 17 00:00:00 2001 From: Greenlamp2 <44787002+Greenlamp2@users.noreply.github.com> Date: Thu, 6 Jun 2024 15:47:08 +0200 Subject: [PATCH 075/129] fix display touch control V in battle (#1869) --- index.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.css b/index.css index 9ca2cd60dff..5497d3b9398 100644 --- a/index.css +++ b/index.css @@ -146,8 +146,8 @@ body { margin-left: 10%; } -#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_ACCESSIBILITY']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadRectBtnContainer > .apadSqBtn, -#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_ACCESSIBILITY']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadSqBtnContainer +#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_ACCESSIBILITY']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadRectBtnContainer > .apadSqBtn:not(.apadBattle), +#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_ACCESSIBILITY']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadSqBtnContainer > .apadSqBtn:not(.apadBattle) { display: none; } From 9c4c4005ae5defee9420ec22e59074a2831f134f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Ricardo=20Fleury=20Oliveira?= Date: Thu, 6 Jun 2024 11:12:40 -0300 Subject: [PATCH 076/129] [Localization] ptBr battle.ts translations (#1870) --- src/locales/pt_BR/battle.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/locales/pt_BR/battle.ts b/src/locales/pt_BR/battle.ts index 810aca36b6c..11238d1902c 100644 --- a/src/locales/pt_BR/battle.ts +++ b/src/locales/pt_BR/battle.ts @@ -56,9 +56,9 @@ export const battle: SimpleTranslationEntries = { "skipItemQuestion": "Tem certeza de que não quer escolher um item?", "eggHatching": "Opa?", "ivScannerUseQuestion": "Quer usar o Scanner de IVs em {{pokemonName}}?", - "wildPokemonWithAffix": "Wild {{pokemonName}}", - "foePokemonWithAffix": "Foe {{pokemonName}}", - "useMove": "{{pokemonNameWithAffix}} used {{moveName}}!", + "wildPokemonWithAffix": "{{pokemonName}} Selvagem", + "foePokemonWithAffix": "{{pokemonName}} Adversário", + "useMove": "{{pokemonNameWithAffix}} usou {{moveName}}!", "drainMessage": "{{pokemonName}} teve sua\nenergia drenada!", "regainHealth": "{{pokemonName}} recuperou\npontos de saúde!" } as const; From 63ce24afb212a8e9a07a61d99860f1f1a9d9a03e Mon Sep 17 00:00:00 2001 From: dorri-riddo <59874084+dorri-riddo@users.noreply.github.com> Date: Thu, 6 Jun 2024 23:22:46 +0900 Subject: [PATCH 077/129] [Localization] #1761 Korean trainer dialogue (ramos, viola) (#1868) --- src/locales/ko/dialogue.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/locales/ko/dialogue.ts b/src/locales/ko/dialogue.ts index 6824cc506ef..83e49e33923 100644 --- a/src/locales/ko/dialogue.ts +++ b/src/locales/ko/dialogue.ts @@ -943,31 +943,31 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "ramos": { "encounter": { - 1: `Did yeh enjoy the garden playground I made with all these sturdy plants o' mine? - $Their strength is a sign o' my strength as a gardener and a Gym Leader! Yeh sure yer up to facing all that?`, + 1: `그래, 올곧게 자란 초목을 모아서 만든 풀 정글짐은 어땠는가? + $자네가 느낀 그것이 나의 체육관 관장으로서의 실력이네! 한번 확인해 보겠나?`, }, "victory": { - 1: "Yeh believe in yer Pokémon… And they believe in yeh, too… It was a fine battle, sprout." + 1: "포켓몬은 자네를 믿고, 자네는 그들을 믿는다…가슴이 후련해지는 승부였구먼." }, "defeat": { - 1: "Hohoho… Indeed. Frail little blades o' grass'll break through even concrete." + 1: "호호호…연약해 보이는 풀잎은 콘크리트도 뚫을 수 있다네." } }, "viola": { "encounter": { - 1: `Whether it's the tears of frustration that follow a loss or the blossoming of joy that comes with victory… - $They're both great subjects for my camera! Fantastic! This'll be just fantastic! - $Now come at me!`, - 2: "My lens is always focused on victory--I won't let anything ruin this shot!" + 1: `패배의 분함도 승리의 순간도… + $둘 다 최고의 피사체야! 정말 멋져 멋져! + $자, 그럼 덤비렴!`, + 2: "나 비올라는 셔트 찬스를 노리는 것처럼--승리를 노릴 거야!" }, "victory": { - 1: "You and your Pokémon have shown me a whole new depth of field! Fantastic! Just fantastic!", - 2: `The world you see through a lens, and the world you see with a Pokémon by your side… - $The same world can look entirely different depending on your view.` + 1: "너와 네 포켓몬은 최고의 콤비구나! 정말 멋져 멋져!", + 2: `렌즈 너머의 세계와 포켓몬의 마음으로 보는 세계… + $똑같이 보이는 풍경이지만 다양한 세계가 겹쳐져 있는 거야.` }, "defeat": { - 1: "The photo from the moment of my victory will be a real winner, all right!", - 2: "Yes! I took some great photos!" + 1: "내가 승리한 순간을 찍은 사진은 정말 멋져 멋져!", + 2: "좋아! 멋진 사진을 찍었어!" } }, "candice": { From 5ac1b7245f68820ffc92471ec84685fc7dc23e11 Mon Sep 17 00:00:00 2001 From: SeafoamQueen <167576411+SeafoamQueen@users.noreply.github.com> Date: Thu, 6 Jun 2024 10:38:54 -0400 Subject: [PATCH 078/129] [Feature] Refactored Game Settings UI navigation menu and sorting (#1860) * Refactored settings UI menu options and added the battle style setting * Removed the new Battle Style setting. It will be added in a seperate PR. * Fixed typo and spacing --- index.css | 4 +- src/system/settings/settings.ts | 357 +++++++++--------- src/ui-inputs.ts | 5 +- src/ui/settings/navigationMenu.ts | 5 +- ...andler.ts => settings-audio-ui-handler.ts} | 6 +- .../settings/settings-display-ui-handler.ts | 20 + src/ui/ui.ts | 12 +- 7 files changed, 218 insertions(+), 191 deletions(-) rename src/ui/settings/{settings-accessiblity-ui-handler.ts => settings-audio-ui-handler.ts} (81%) create mode 100644 src/ui/settings/settings-display-ui-handler.ts diff --git a/index.css b/index.css index 5497d3b9398..c0791259002 100644 --- a/index.css +++ b/index.css @@ -146,8 +146,8 @@ body { margin-left: 10%; } -#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_ACCESSIBILITY']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadRectBtnContainer > .apadSqBtn:not(.apadBattle), -#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_ACCESSIBILITY']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadSqBtnContainer > .apadSqBtn:not(.apadBattle) +#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_DISPLAY']):not([data-ui-mode='SETTINGS_AUDIO']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadRectBtnContainer > .apadSqBtn, +#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_DISPLAY']):not([data-ui-mode='SETTINGS_AUDIO']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadSqBtnContainer { display: none; } diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index b2175275eef..3cd578dbaf4 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -19,7 +19,8 @@ const AUTO_DISABLED = ["Auto", "Disabled"]; */ export enum SettingType { GENERAL, - ACCESSIBILITY + DISPLAY, + AUDIO } export interface Setting { @@ -37,34 +38,34 @@ export interface Setting { */ export const SettingKeys = { Game_Speed: "GAME_SPEED", - Master_Volume: "MASTER_VOLUME", - BGM_Volume: "BGM_VOLUME", - SE_Volume: "SE_VOLUME", + HP_Bar_Speed: "HP_BAR_SPEED", + EXP_Gains_Speed: "EXP_GAINS_SPEED", + EXP_Party_Display: "EXP_PARTY_DISPLAY", + Skip_Seen_Dialogues: "SKIP_SEEN_DIALOGUES", + Enable_Retries: "ENABLE_RETRIES", + Tutorials: "TUTORIALS", + Touch_Controls: "TOUCH_CONTROLS", + Vibration: "VIBRATION", Language: "LANGUAGE", - Damage_Numbers: "DAMAGE_NUMBERS", UI_Theme: "UI_THEME", Window_Type: "WINDOW_TYPE", - Tutorials: "TUTORIALS", - Move_Info: "MOVE_INFO", - Enable_Retries: "ENABLE_RETRIES", - Skip_Seen_Dialogues: "SKIP_SEEN_DIALOGUES", + Money_Format: "MONEY_FORMAT", + Damage_Numbers: "DAMAGE_NUMBERS", + Move_Animations: "MOVE_ANIMATIONS", + Show_Stats_on_Level_Up: "SHOW_LEVEL_UP_STATS", Candy_Upgrade_Notification: "CANDY_UPGRADE_NOTIFICATION", Candy_Upgrade_Display: "CANDY_UPGRADE_DISPLAY", - Money_Format: "MONEY_FORMAT", - Sprite_Set: "SPRITE_SET", - Move_Animations: "MOVE_ANIMATIONS", + Move_Info: "MOVE_INFO", Show_Moveset_Flyout: "SHOW_MOVESET_FLYOUT", Show_Arena_Flyout: "SHOW_ARENA_FLYOUT", Show_Time_Of_Day_Widget: "SHOW_TIME_OF_DAY_WIDGET", Time_Of_Day_Animation: "TIME_OF_DAY_ANIMATION", - Show_Stats_on_Level_Up: "SHOW_LEVEL_UP_STATS", - EXP_Gains_Speed: "EXP_GAINS_SPEED", - EXP_Party_Display: "EXP_PARTY_DISPLAY", - HP_Bar_Speed: "HP_BAR_SPEED", + Sprite_Set: "SPRITE_SET", Fusion_Palette_Swaps: "FUSION_PALETTE_SWAPS", Player_Gender: "PLAYER_GENDER", - Touch_Controls: "TOUCH_CONTROLS", - Vibration: "VIBRATION" + Master_Volume: "MASTER_VOLUME", + BGM_Volume: "BGM_VOLUME", + SE_Volume: "SE_VOLUME" }; /** @@ -79,155 +80,10 @@ export const Setting: Array = [ type: SettingType.GENERAL }, { - key: SettingKeys.Master_Volume, - label: "Master Volume", - options: VOLUME_OPTIONS, - default: 5, - type: SettingType.GENERAL - }, - { - key: SettingKeys.BGM_Volume, - label: "BGM Volume", - options: VOLUME_OPTIONS, - default: 10, - type: SettingType.GENERAL - }, - { - key: SettingKeys.SE_Volume, - label: "SE Volume", - options: VOLUME_OPTIONS, - default: 10, - type: SettingType.GENERAL - }, - { - key: SettingKeys.Language, - label: "Language", - options: ["English", "Change"], + key: SettingKeys.HP_Bar_Speed, + label: "HP Bar Speed", + options: ["Normal", "Fast", "Faster", "Skip"], default: 0, - type: SettingType.GENERAL, - requireReload: true - }, - { - key: SettingKeys.Damage_Numbers, - label: "Damage Numbers", - options: ["Off", "Simple", "Fancy"], - default: 0, - type: SettingType.GENERAL - }, - { - key: SettingKeys.UI_Theme, - label: "UI Theme", - options: ["Default", "Legacy"], - default: 0, - type: SettingType.GENERAL, - requireReload: true - }, - { - key: SettingKeys.Window_Type, - label: "Window Type", - options: new Array(5).fill(null).map((_, i) => (i + 1).toString()), - default: 0, - type: SettingType.GENERAL - }, - { - key: SettingKeys.Tutorials, - label: "Tutorials", - options: OFF_ON, - default: 1, - type: SettingType.GENERAL - }, - { - key: SettingKeys.Move_Info, - label: "Move Info", - options: OFF_ON, - default: 1, - type: SettingType.ACCESSIBILITY - }, - { - key: SettingKeys.Enable_Retries, - label: "Enable Retries", - options: OFF_ON, - default: 0, - type: SettingType.ACCESSIBILITY - }, - { - key: SettingKeys.Skip_Seen_Dialogues, - label: "Skip Seen Dialogues", - options: OFF_ON, - default: 0, - type: SettingType.GENERAL - }, - { - key: SettingKeys.Candy_Upgrade_Notification, - label: "Candy Upgrade Notification", - options: ["Off", "Passives Only", "On"], - default: 0, - type: SettingType.ACCESSIBILITY - }, - { - key: SettingKeys.Candy_Upgrade_Display, - label: "Candy Upgrade Display", - options: ["Icon", "Animation"], - default: 0, - type: SettingType.ACCESSIBILITY, - requireReload: true - }, - { - key: SettingKeys.Money_Format, - label: "Money Format", - options: ["Normal", "Abbreviated"], - default: 0, - type: SettingType.ACCESSIBILITY - }, - { - key: SettingKeys.Sprite_Set, - label: "Sprite Set", - options: ["Consistent", "Mixed Animated"], - default: 0, - type: SettingType.GENERAL, - requireReload: true - }, - { - key: SettingKeys.Move_Animations, - label: "Move Animations", - options: OFF_ON, - default: 1, - type: SettingType.GENERAL - }, - { - key: SettingKeys.Show_Moveset_Flyout, - label: "Show Moveset Flyout", - options: OFF_ON, - default: 1, - type: SettingType.ACCESSIBILITY - }, - { - key: SettingKeys.Show_Arena_Flyout, - label: "Show Battle Effects Flyout", - options: OFF_ON, - default: 1, - type: SettingType.ACCESSIBILITY - }, - { - key: SettingKeys.Show_Time_Of_Day_Widget, - label: "Show Time of Day Widget", - options: OFF_ON, - default: 1, - type: SettingType.ACCESSIBILITY, - requireReload: true, - }, - { - key: SettingKeys.Time_Of_Day_Animation, - label: "Time of Day Animation", - options: ["Bounce", "Back"], - default: 0, - type: SettingType.ACCESSIBILITY - }, - { - key: SettingKeys.Show_Stats_on_Level_Up, - label: "Show Stats on Level Up", - options: OFF_ON, - default: 1, type: SettingType.GENERAL }, { @@ -245,26 +101,26 @@ export const Setting: Array = [ type: SettingType.GENERAL }, { - key: SettingKeys.HP_Bar_Speed, - label: "HP Bar Speed", - options: ["Normal", "Fast", "Faster", "Skip"], + key: SettingKeys.Skip_Seen_Dialogues, + label: "Skip Seen Dialogues", + options: OFF_ON, default: 0, type: SettingType.GENERAL }, { - key: SettingKeys.Fusion_Palette_Swaps, - label: "Fusion Palette Swaps", + key: SettingKeys.Enable_Retries, + label: "Enable Retries", + options: OFF_ON, + default: 0, + type: SettingType.GENERAL + }, + { + key: SettingKeys.Tutorials, + label: "Tutorials", options: OFF_ON, default: 1, type: SettingType.GENERAL }, - { - key: SettingKeys.Player_Gender, - label: "Player Gender", - options: ["Boy", "Girl"], - default: 0, - type: SettingType.GENERAL - }, { key: SettingKeys.Touch_Controls, label: "Touch Controls", @@ -278,6 +134,151 @@ export const Setting: Array = [ options: AUTO_DISABLED, default: 0, type: SettingType.GENERAL + }, + { + key: SettingKeys.Language, + label: "Language", + options: ["English", "Change"], + default: 0, + type: SettingType.DISPLAY, + requireReload: true + }, + { + key: SettingKeys.UI_Theme, + label: "UI Theme", + options: ["Default", "Legacy"], + default: 0, + type: SettingType.DISPLAY, + requireReload: true + }, + { + key: SettingKeys.Window_Type, + label: "Window Type", + options: new Array(5).fill(null).map((_, i) => (i + 1).toString()), + default: 0, + type: SettingType.DISPLAY + }, + { + key: SettingKeys.Money_Format, + label: "Money Format", + options: ["Normal", "Abbreviated"], + default: 0, + type: SettingType.DISPLAY + }, + { + key: SettingKeys.Damage_Numbers, + label: "Damage Numbers", + options: ["Off", "Simple", "Fancy"], + default: 0, + type: SettingType.DISPLAY + }, + { + key: SettingKeys.Move_Animations, + label: "Move Animations", + options: OFF_ON, + default: 1, + type: SettingType.DISPLAY + }, + { + key: SettingKeys.Show_Stats_on_Level_Up, + label: "Show Stats on Level Up", + options: OFF_ON, + default: 1, + type: SettingType.DISPLAY + }, + { + key: SettingKeys.Candy_Upgrade_Notification, + label: "Candy Upgrade Notification", + options: ["Off", "Passives Only", "On"], + default: 0, + type: SettingType.DISPLAY + }, + { + key: SettingKeys.Candy_Upgrade_Display, + label: "Candy Upgrade Display", + options: ["Icon", "Animation"], + default: 0, + type: SettingType.DISPLAY, + requireReload: true + }, + { + key: SettingKeys.Move_Info, + label: "Move Info", + options: OFF_ON, + default: 1, + type: SettingType.DISPLAY + }, + { + key: SettingKeys.Show_Moveset_Flyout, + label: "Show Moveset Flyout", + options: OFF_ON, + default: 1, + type: SettingType.DISPLAY + }, + { + key: SettingKeys.Show_Arena_Flyout, + label: "Show Battle Effects Flyout", + options: OFF_ON, + default: 1, + type: SettingType.DISPLAY + }, + { + key: SettingKeys.Show_Time_Of_Day_Widget, + label: "Show Time of Day Widget", + options: OFF_ON, + default: 1, + type: SettingType.DISPLAY, + requireReload: true, + }, + { + key: SettingKeys.Time_Of_Day_Animation, + label: "Time of Day Animation", + options: ["Bounce", "Back"], + default: 0, + type: SettingType.DISPLAY + }, + { + key: SettingKeys.Sprite_Set, + label: "Sprite Set", + options: ["Consistent", "Mixed Animated"], + default: 0, + type: SettingType.DISPLAY, + requireReload: true + }, + { + key: SettingKeys.Fusion_Palette_Swaps, + label: "Fusion Palette Swaps", + options: OFF_ON, + default: 1, + type: SettingType.DISPLAY + }, + { + key: SettingKeys.Player_Gender, + label: "Player Gender", + options: ["Boy", "Girl"], + default: 0, + type: SettingType.DISPLAY + }, + { + key: SettingKeys.Master_Volume, + label: "Master Volume", + options: VOLUME_OPTIONS, + default: 5, + type: SettingType.AUDIO + }, + { + key: SettingKeys.BGM_Volume, + label: "BGM Volume", + options: VOLUME_OPTIONS, + default: 10, + type: SettingType.AUDIO + }, + { + key: SettingKeys.SE_Volume, + label: "SE Volume", + options: VOLUME_OPTIONS, + default: 10, + type: SettingType.AUDIO } ]; diff --git a/src/ui-inputs.ts b/src/ui-inputs.ts index ebe055d8de1..d068cbebde7 100644 --- a/src/ui-inputs.ts +++ b/src/ui-inputs.ts @@ -9,7 +9,8 @@ import {Button} from "./enums/buttons"; import SettingsGamepadUiHandler from "./ui/settings/settings-gamepad-ui-handler"; import SettingsKeyboardUiHandler from "#app/ui/settings/settings-keyboard-ui-handler"; import BattleScene from "./battle-scene"; -import SettingsAccessibilityUiHandler from "./ui/settings/settings-accessiblity-ui-handler"; +import SettingsDisplayUiHandler from "./ui/settings/settings-display-ui-handler"; +import SettingsAudioUiHandler from "./ui/settings/settings-audio-ui-handler"; type ActionKeys = Record void>; @@ -169,7 +170,7 @@ export class UiInputs { } buttonCycleOption(button: Button): void { - const whitelist = [StarterSelectUiHandler, SettingsUiHandler, SettingsAccessibilityUiHandler, SettingsGamepadUiHandler, SettingsKeyboardUiHandler]; + const whitelist = [StarterSelectUiHandler, SettingsUiHandler, SettingsDisplayUiHandler, SettingsAudioUiHandler, SettingsGamepadUiHandler, SettingsKeyboardUiHandler]; const uiHandler = this.scene.ui?.getHandler(); if (whitelist.some(handler => uiHandler instanceof handler)) { this.scene.ui.processInput(button); diff --git a/src/ui/settings/navigationMenu.ts b/src/ui/settings/navigationMenu.ts index d9664276872..c35e60d3ac7 100644 --- a/src/ui/settings/navigationMenu.ts +++ b/src/ui/settings/navigationMenu.ts @@ -27,11 +27,12 @@ export class NavigationManager { constructor() { this.modes = [ Mode.SETTINGS, - Mode.SETTINGS_ACCESSIBILITY, + Mode.SETTINGS_DISPLAY, + Mode.SETTINGS_AUDIO, Mode.SETTINGS_GAMEPAD, Mode.SETTINGS_KEYBOARD, ]; - this.labels = ["General", "Accessibility", "Gamepad", "Keyboard"]; + this.labels = ["General", "Display", "Audio", "Gamepad", "Keyboard"]; } public reset() { diff --git a/src/ui/settings/settings-accessiblity-ui-handler.ts b/src/ui/settings/settings-audio-ui-handler.ts similarity index 81% rename from src/ui/settings/settings-accessiblity-ui-handler.ts rename to src/ui/settings/settings-audio-ui-handler.ts index ef700a7a9ab..47606d3c54c 100644 --- a/src/ui/settings/settings-accessiblity-ui-handler.ts +++ b/src/ui/settings/settings-audio-ui-handler.ts @@ -4,7 +4,7 @@ import { Mode } from "../ui"; import AbstractSettingsUiHandler from "./abstract-settings-ui-handler"; import { Setting, SettingType } from "#app/system/settings/settings"; -export default class SettingsAccessibilityUiHandler extends AbstractSettingsUiHandler { +export default class SettingsAudioUiHandler extends AbstractSettingsUiHandler { /** * Creates an instance of SettingsGamepadUiHandler. * @@ -13,8 +13,8 @@ export default class SettingsAccessibilityUiHandler extends AbstractSettingsUiHa */ constructor(scene: BattleScene, mode?: Mode) { super(scene, mode); - this.title = "Accessibility"; - this.settings = Setting.filter(s => s.type === SettingType.ACCESSIBILITY); + this.title = "Audio"; + this.settings = Setting.filter(s => s.type === SettingType.AUDIO); this.localStorageKey = "settings"; } } diff --git a/src/ui/settings/settings-display-ui-handler.ts b/src/ui/settings/settings-display-ui-handler.ts new file mode 100644 index 00000000000..9e61c2ba0b2 --- /dev/null +++ b/src/ui/settings/settings-display-ui-handler.ts @@ -0,0 +1,20 @@ +import BattleScene from "../../battle-scene"; +import { Mode } from "../ui"; +"#app/inputs-controller.js"; +import AbstractSettingsUiHandler from "./abstract-settings-ui-handler"; +import { Setting, SettingType } from "#app/system/settings/settings"; + +export default class SettingsDisplayUiHandler extends AbstractSettingsUiHandler { + /** + * Creates an instance of SettingsGamepadUiHandler. + * + * @param scene - The BattleScene instance. + * @param mode - The UI mode, optional. + */ + constructor(scene: BattleScene, mode?: Mode) { + super(scene, mode); + this.title = "Display"; + this.settings = Setting.filter(s => s.type === SettingType.DISPLAY); + this.localStorageKey = "settings"; + } +} diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 55ac06a3488..aaf764f501f 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -42,7 +42,8 @@ import { PlayerGender } from "#app/data/enums/player-gender"; import GamepadBindingUiHandler from "./settings/gamepad-binding-ui-handler"; import SettingsKeyboardUiHandler from "#app/ui/settings/settings-keyboard-ui-handler"; import KeyboardBindingUiHandler from "#app/ui/settings/keyboard-binding-ui-handler"; -import SettingsAccessibilityUiHandler from "./settings/settings-accessiblity-ui-handler"; +import SettingsDisplayUiHandler from "./settings/settings-display-ui-handler"; +import SettingsAudioUiHandler from "./settings/settings-audio-ui-handler"; export enum Mode { MESSAGE, @@ -63,7 +64,8 @@ export enum Mode { MENU, MENU_OPTION_SELECT, SETTINGS, - SETTINGS_ACCESSIBILITY, + SETTINGS_DISPLAY, + SETTINGS_AUDIO, SETTINGS_GAMEPAD, GAMEPAD_BINDING, SETTINGS_KEYBOARD, @@ -101,7 +103,8 @@ const noTransitionModes = [ Mode.GAMEPAD_BINDING, Mode.KEYBOARD_BINDING, Mode.SETTINGS, - Mode.SETTINGS_ACCESSIBILITY, + Mode.SETTINGS_AUDIO, + Mode.SETTINGS_DISPLAY, Mode.SETTINGS_GAMEPAD, Mode.SETTINGS_KEYBOARD, Mode.ACHIEVEMENTS, @@ -154,7 +157,8 @@ export default class UI extends Phaser.GameObjects.Container { new MenuUiHandler(scene), new OptionSelectUiHandler(scene, Mode.MENU_OPTION_SELECT), new SettingsUiHandler(scene), - new SettingsAccessibilityUiHandler(scene), + new SettingsDisplayUiHandler(scene), + new SettingsAudioUiHandler(scene), new SettingsGamepadUiHandler(scene), new GamepadBindingUiHandler(scene), new SettingsKeyboardUiHandler(scene), From e0401a93aa06399095fc231b6c78da8a45a60906 Mon Sep 17 00:00:00 2001 From: c4vv <132619649+c4vv@users.noreply.github.com> Date: Thu, 6 Jun 2024 10:55:50 -0400 Subject: [PATCH 079/129] [Bug] Fix generateVariant to account for forms (#1783) * Add form check to generateVaraint * Add index check * Fix for typedoc --- src/field/pokemon.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 7d8f8d8dc91..86909f056aa 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1277,7 +1277,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns the shiny variant */ generateVariant(): Variant { - if (!this.shiny || !variantData.hasOwnProperty(this.species.speciesId)) { + const formIndex: number = this.formIndex; + let variantDataIndex: string | number = this.species.speciesId; + if (this.species.forms.length > 0) { + const formKey = this.species.forms[formIndex]?.formKey; + if (formKey) { + variantDataIndex = `${variantDataIndex}-${formKey}`; + } + } + // Checks if there is no variant data for both the index or index with form + if (!this.shiny || (!variantData.hasOwnProperty(variantDataIndex) && !variantData.hasOwnProperty(this.species.speciesId))) { return 0; } const rand = Utils.randSeedInt(10); From 081d8135404918f96ae117531e531b926b4266bb Mon Sep 17 00:00:00 2001 From: Lee ByungHoon Date: Fri, 7 Jun 2024 00:12:19 +0900 Subject: [PATCH 080/129] [Feature] Move to start button when you can't add party anymore (#1673) * [Feature] Move to start button when you can't add party anymore * Add comment about #1673 * Update starter-select-ui-handler.ts --------- Co-authored-by: Benjamin Odom --- src/ui/starter-select-ui-handler.ts | 45 ++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index f799efe96b5..1dbb60915ca 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -224,6 +224,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { private canCycleNature: boolean; private canCycleVariant: boolean; private value: integer = 0; + private canAddParty: boolean; private assetLoadCancelled: Utils.BooleanHolder; private cursorObj: Phaser.GameObjects.Image; @@ -1047,6 +1048,16 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.tryStart(); } this.updateInstructions(); + + /** + * If the user can't select a pokemon anymore, + * go to start button. + */ + if (!this.canAddParty) { + this.startCursorObj.setVisible(true); + this.setGenMode(true); + } + ui.playSelect(); } else { ui.playError(); @@ -2131,11 +2142,43 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.scene.time.delayedCall(Utils.fixedInt(500), () => this.tryUpdateValue()); return false; } + + /** + * this loop is used to set the Sprite's alpha value and check if the user can select other pokemon more. + */ + this.canAddParty = false; + const remainValue = valueLimit - newValue; for (let g = 0; g < this.genSpecies.length; g++) { for (let s = 0; s < this.genSpecies[g].length; s++) { - (this.starterSelectGenIconContainers[g].getAt(s) as Phaser.GameObjects.Sprite).setAlpha((newValue + this.scene.gameData.getSpeciesStarterValue(this.genSpecies[g][s].speciesId)) > valueLimit ? 0.375 : 1); + /** Cost of pokemon species */ + const speciesStarterValue = this.scene.gameData.getSpeciesStarterValue(this.genSpecies[g][s].speciesId); + /** Used to detect if this pokemon is registered in starter */ + const speciesStarterDexEntry = this.scene.gameData.dexData[this.genSpecies[g][s].speciesId]; + /** {@linkcode Phaser.GameObjects.Sprite} object of Pokémon for setting the alpha value */ + const speciesSprite = this.starterSelectGenIconContainers[g].getAt(s) as Phaser.GameObjects.Sprite; + + /** + * If remainValue greater than or equal pokemon species, the user can select. + * so that the alpha value of pokemon sprite set 1. + * + * If speciesStarterDexEntry?.caughtAttr is true, this species registered in stater. + * we change to can AddParty value to true since the user has enough cost to choose this pokemon and this pokemon registered too. + */ + if (remainValue >= speciesStarterValue) { + speciesSprite.setAlpha(1); + if (speciesStarterDexEntry?.caughtAttr) { + this.canAddParty = true; + } + } else { + /** + * If remainValue less than pokemon, the use can't select. + * so that the alpha value of pokemon sprite set 0.375. + */ + speciesSprite.setAlpha(0.375); + } } } + this.value = newValue; return true; } From 94099e0fbaa6cb4b794e95fa0b197cac34591cff Mon Sep 17 00:00:00 2001 From: h44451890 <139263319+h44451890@users.noreply.github.com> Date: Thu, 6 Jun 2024 23:17:53 +0800 Subject: [PATCH 081/129] Fix all ability name and description of zh_tw (#1478) --- src/locales/zh_TW/ability.ts | 494 +++++++++++++++++------------------ 1 file changed, 247 insertions(+), 247 deletions(-) diff --git a/src/locales/zh_TW/ability.ts b/src/locales/zh_TW/ability.ts index f760a9614f6..a99d0acd10c 100644 --- a/src/locales/zh_TW/ability.ts +++ b/src/locales/zh_TW/ability.ts @@ -3,58 +3,59 @@ import { AbilityTranslationEntries } from "#app/plugins/i18n.js"; export const ability: AbilityTranslationEntries = { stench: { name: "惡臭", - description: "通過釋放臭臭的氣味,在攻\n擊的時候,有時會使對手畏\n縮。", + description: "發出臭氣,在攻擊的時候,\n有時會使對手畏縮。", }, - drizzle: { name: "降雨", description: "出場時,會將天氣變爲下雨\n。" }, + drizzle: { name: "降雨", description: "出場時,會將天氣變為下雨\n。" }, speedBoost: { name: "加速", description: "每一回合速度會變快。" }, battleArmor: { name: "戰鬥盔甲", - description: "被堅硬的甲殼守護着,不會\n被對手的攻擊擊中要害。", + description: "被堅硬的甲殼守護著,不會\n被對手的攻擊擊中要害。", }, sturdy: { name: "結實", description: - "在HP全滿時,即使受到招\n式攻擊,也不會被一擊打倒\n。一擊必殺的招式也沒有效\n果。", + "在HP全滿時受到招式攻擊\n不會被一擊打倒。一擊必殺\n的招式也沒有效果。", + }, damp: { - name: "溼氣", - description: "通過把周圍都弄溼,使誰都\n無法使用自爆等爆炸類的招\n式。", + name: "濕氣", + description: "透過把周圍都弄溼,使誰都\n無法使用自爆等爆炸類的招\n式。", }, - limber: { name: "柔軟", description: "因爲身體柔軟,不會變爲麻\n痹狀態。" }, - sandVeil: { name: "沙隱", description: "在沙暴的時候,閃避率會提\n高。" }, + limber: { name: "柔軟", description: "因為身體柔軟,不會變為麻\n痹狀態。" }, + sandVeil: { name: "沙隱", description: "在沙暴中閃避率會提高。" }, static: { name: "靜電", - description: "身上帶有靜電,有時會讓接\n觸到的對手麻痹。", + description: "身上帶有靜電,有時會令接\n觸到的對手麻痹。", }, voltAbsorb: { name: "蓄電", - description: "受到電屬性的招式攻擊時,\n不會受到傷害,而是會回覆。", + description: "受到電屬性的招式攻擊時,\n不會受到傷害,而是會回復。", }, waterAbsorb: { name: "儲水", - description: "受到水屬性的招式攻擊時,\n不會受到傷害,而是會回覆。", + description: "受到水屬性的招式攻擊時,\n不會受到傷害,而是會回復。", }, oblivious: { name: "遲鈍", description: - "因爲感覺遲鈍,不會變爲着\n迷和被挑釁狀態。對威嚇也\n毫不動搖。", + "感覺遲鈍,不會陷入著迷和\n被挑釁狀態。面對威嚇也不\n會動搖。", }, cloudNine: { name: "無關天氣", description: "任何天氣的影響都會消失。" }, compoundEyes: { name: "複眼", - description: "因爲擁有複眼,招式的命中\n率會提高。", + description: "因為擁有複眼,會提高招式\n的命中率。", }, insomnia: { name: "不眠", - description: "因爲有着睡不着的體質,所\n以不會陷入睡眠狀態。", + description: "因為有著睡不著的體質,所\n以不會陷入睡眠狀態。", }, colorChange: { name: "變色", - description: "自己的屬性會變爲從對手處\n所受招式的屬性。", + description: "自己的屬性會變為擊中自己\n的對手招式的屬性。", }, immunity: { name: "免疫", - description: "因爲體內擁有免疫能力,不\n會變爲中毒狀態。", + description: "因為體內擁有免疫能力,不\n會變為中毒狀態。", }, flashFire: { name: "引火", @@ -63,19 +64,19 @@ export const ability: AbilityTranslationEntries = { }, shieldDust: { name: "鱗粉", - description: "被鱗粉守護着,不會受到招\n式的追加效果影響。", + description: "被鱗粉守護著,不會受到招\n式的追加效果影響。", }, ownTempo: { name: "我行我素", - description: "因爲我行我素,不會變爲混\n亂狀態。對威嚇也毫不動搖。", + description: "因為我行我素,不會陷入混\n亂狀態。面對威嚇也不會動\n搖。", }, suctionCups: { name: "吸盤", - description: "用吸盤牢牢貼在地面上,讓\n替換寶可夢的招式和道具無\n效。", + description: "用吸盤將自己牢牢吸附在地\n面上,讓替換寶可夢的招式\n和道具失效。", }, intimidate: { name: "威嚇", - description: "出場時威嚇對手,讓其退縮\n,降低對手的攻擊。", + description: "出場時威嚇對手,使其退縮\n,從而降低對手的攻擊。", }, shadowTag: { name: "踩影", @@ -96,7 +97,7 @@ export const ability: AbilityTranslationEntries = { effectSpore: { name: "孢子", description: - "受到攻擊時,有時會把接觸\n到自己的對手變爲中毒、麻\n痹或睡眠狀態。", + "受到攻擊時,有時會把接觸\n到自己的對手變為中毒、麻\n痹或睡眠狀態。", }, synchronize: { name: "同步", @@ -104,48 +105,48 @@ export const ability: AbilityTranslationEntries = { }, clearBody: { name: "恆淨之軀", - description: "不會因爲對手的招式或特性\n而被降低能力。", + description: "不會因對手的招式或特性而\n被降低能力。", }, naturalCure: { - name: "自然回覆", - description: "回到同行隊伍後,異常狀態\n就會被治癒。", + name: "自然回復", + description: "異常狀態會在離場後治癒。", }, lightningRod: { name: "避雷針", description: - "將電屬性的招式吸引到自己\n身上,不會受到傷害,而是\n會提高特攻。", + "將電屬性的招式吸引到自己\n身上,不但不會受到傷害,\n反而會提高特攻。", }, sereneGrace: { name: "天恩", - description: "託天恩的福,招式的追加效\n果容易出現。", + description: "受到上天保佑,容易出現招式的追加效果。", }, - swiftSwim: { name: "悠遊自如", description: "下雨天氣時,速度會提高。" }, - chlorophyll: { name: "葉綠素", description: "晴朗天氣時,速度會提高。" }, + swiftSwim: { name: "悠遊自如", description: "天氣為下雨時,速度會提高。" }, + chlorophyll: { name: "葉綠素", description: "天氣為晴朗時,速度會提高。" }, illuminate: { name: "發光", - description: "通過讓周圍變亮來保持命中\n率不會被降低。", + description: "透過讓周圍變亮,命中率不\n會被降低。", }, trace: { name: "複製", - description: "出場時,複製對手的特性,\n變爲與之相同的特性。", + description: "出場時,複製對手的特性,\n變為與之相同的特性。", }, - hugePower: { name: "大力士", description: "物理攻擊的威力會變爲2倍\n。" }, + hugePower: { name: "大力士", description: "物理攻擊的威力會變為2倍\n。" }, poisonPoint: { name: "毒刺", - description: "有時會讓接觸到自己的對手\n變爲中毒狀態。", + description: "有時會讓接觸到自己的對手\n變為中毒狀態。", }, innerFocus: { name: "精神力", description: - "擁有經過鍛鍊的精神,而不\n會因對手的攻擊而畏縮。對\n威嚇也毫不動搖。", + "靠著經過鍛鍊的精神,不會\n因對手的攻擊而畏縮。面對\n威嚇也不會動搖。", }, magmaArmor: { name: "熔岩鎧甲", - description: "將熾熱的熔岩覆蓋在身上,\n不會變爲冰凍狀態。", + description: "將熾熱的熔岩覆蓋在身上,\n不會陷入冰凍狀態。", }, waterVeil: { name: "水幕", - description: "將水幕裹在身上,不會變爲\n灼傷狀態。", + description: "將水幕裹在身上,不會陷入\n灼傷狀態。", }, magnetPull: { name: "磁力", @@ -153,10 +154,10 @@ export const ability: AbilityTranslationEntries = { }, soundproof: { name: "隔音", - description: "通過屏蔽聲音,不受到聲音\n招式的影響。", + description: "透過遮蔽聲音,不受到聲音\n招式的影響。", }, - rainDish: { name: "雨盤", description: "下雨天氣時,會緩緩回覆\nHP。" }, - sandStream: { name: "揚沙", description: "出場時,會把天氣變爲沙暴。" }, + rainDish: { name: "雨盤", description: "天氣為下雨時,會緩緩回復\nHP。" }, + sandStream: { name: "揚沙", description: "出場時,會把天氣變為沙暴。" }, pressure: { name: "壓迫感", description: "給予對手壓迫感,大量減少\n其使用招式的PP。", @@ -164,70 +165,70 @@ export const ability: AbilityTranslationEntries = { thickFat: { name: "厚脂肪", description: - "因爲被厚厚的脂肪保護着,\n會讓火屬性和冰屬性的招式\n傷害減半。", + "被厚厚的脂肪保護著,能夠\n讓火屬性和冰屬性招式的傷\n害減半。", }, earlyBird: { name: "早起", - description: "即使變爲睡眠狀態,也能以\n2倍的速度提早醒來。", + description: "即使陷入睡眠狀態,也能以\n2倍的速度提早醒來。", }, flameBody: { name: "火焰之軀", - description: "有時會讓接觸到自己的對手\n變爲灼傷狀態。", + description: "有時會讓接觸到自己的對手\n變為灼傷狀態。", }, - runAway: { name: "逃跑", description: "一定能從野生寶可夢那兒逃\n走。" }, + runAway: { name: "逃跑", description: "一定能從野生寶可夢那裡逃\n走。" }, keenEye: { name: "銳利目光", - description: "多虧了銳利的目光,命中率\n不會被降低。", + description: "靠著銳利的目光,命中率不\n會被降低。", }, hyperCutter: { name: "怪力鉗", - description: "因爲擁有以力量自豪的鉗子,\n不會被對手降低攻擊。", + description: "因為擁有以力量自豪的鉗子,\n不會被對手降低攻擊。", }, pickup: { name: "撿拾", description: "有時會撿來對手用過的道具,\n冒險過程中也會撿到。", }, - truant: { name: "懶惰", description: "如果使出招式,下一回合就\n會休息。" }, - hustle: { name: "活力", description: "自己的攻擊變高,但命中率\n會降低。" }, + truant: { name: "懶惰", description: "如果使出招式,下一回合就\n需要休息。" }, + hustle: { name: "活力", description: "自己的攻擊雖會變高,但命\n中率會降低。" }, cuteCharm: { name: "迷人之軀", - description: "有時會讓接觸到自己的對手\n着迷。", + description: "有時會讓接觸到自己的對手陷\n入著迷狀態。", }, plus: { name: "正電", description: - "出場的夥伴之間如果有正電\n或負電特性的寶可夢,自己\n的特攻會提高。", + "場上的夥伴之中,如果有正\n電或負電特性的寶可夢,自\n己的特攻會提高。", }, minus: { name: "負電", description: - "出場的夥伴之間如果有正電\n或負電特性的寶可夢,自己\n的特攻會提高。", + "場上的夥伴之中,如果有正\n電或負電特性的寶可夢,自\n己的特攻會提高。", }, forecast: { name: "陰晴不定", description: - "受天氣的影響,會變爲水屬\n性、火屬性或冰屬性中的某\n一個。", + "在天氣的影響下,會變成水\n屬性、火屬性或冰屬性之中\n的一種。", }, stickyHold: { - name: "黏着", - description: "因爲道具是粘在黏性身體上\n的,所以不會被對手奪走。", + name: "黏著", + description: "道具會黏在具有黏性的身體\n上,不會被對手奪走。", }, shedSkin: { name: "蛻皮", - description: "通過蛻去身上的皮,有時會\n治癒異常狀態。", + description: "透過蛻去身上的皮,有時會\n治癒異常狀態。", }, guts: { name: "毅力", - description: "如果變爲異常狀態,會拿出\n毅力,攻擊會提高。", + description: "陷入異常狀態時,會拿出毅\n力,攻擊會提高。", }, marvelScale: { name: "神奇鱗片", - description: "如果變爲異常狀態,神奇鱗\n片會發生反應,防禦會提高。", + description: "陷入異常狀態時,神奇鱗片\n會發生反應,防禦會提高。", }, liquidOoze: { name: "污泥漿", description: - "吸收了污泥漿的對手會因強\n烈的惡臭而受到傷害,減少\nHP。", + "吸收了污泥漿的對手會因為\n強烈的惡臭而使得HP減少。", }, overgrow: { name: "茂盛", @@ -249,52 +250,52 @@ export const ability: AbilityTranslationEntries = { name: "堅硬腦袋", description: "即使使出會受反作用力傷害\n的招式,HP也不會減少。", }, - drought: { name: "日照", description: "出場時,會將天氣變爲晴朗。" }, + drought: { name: "日照", description: "出場時,會將天氣變為晴朗。" }, arenaTrap: { name: "沙穴", description: "在戰鬥中讓對手無法逃走。" }, vitalSpirit: { name: "幹勁", - description: "通過激發出幹勁,不會變爲\n睡眠狀態。", + description: "透過激發出幹勁,不會變為\n睡眠狀態。", }, whiteSmoke: { name: "白色煙霧", - description: "被白色煙霧保護着,不會被\n對手降低能力。", + description: "被白色煙霧保護著,不會被\n對手降低能力。", }, purePower: { name: "瑜伽之力", - description: "因瑜伽的力量,物理攻擊的\n威力會變爲2倍。", + description: "因瑜伽的力量,物理攻擊的\n威力會變為2倍。", }, shellArmor: { name: "硬殼盔甲", - description: "被堅硬的殼保護着,對手的\n攻擊不會擊中要害。", + description: "被堅硬的殼保護著,對手的\n攻擊不會擊中要害。", }, airLock: { name: "氣閘", description: "所有天氣的影響都會消失。" }, tangledFeet: { name: "蹣跚", - description: "在混亂狀態時,閃避率會提\n高。", + description: "陷入混亂狀態時,閃避率會\n提高。", }, motorDrive: { name: "電氣引擎", description: - "受到電屬性的招式攻擊時,\n不會受到傷害,而是速度會\n提高。", + "受到電屬性的招式攻擊時,\n不但不會受到傷害,反而速\n度會提高。", }, rivalry: { name: "鬥爭心", description: - "面對性別相同的對手,會燃\n起鬥爭心,變得更強。而面\n對性別不同的,則會變弱。", + "面對性別相同的對手,會燃\n起鬥爭心,變得更強。面對\n性別不同的對手時則會變弱。", }, steadfast: { name: "不屈之心", description: "每次畏縮時,不屈之心就會\n燃起,速度也會提高。", }, - snowCloak: { name: "雪隱", description: "下雪天氣時,閃避率會提高。" }, + snowCloak: { name: "雪隱", description: "天氣為下雪時,閃避率會提\n高。" }, gluttony: { - name: "貪喫鬼", + name: "貪吃鬼", description: - "原本HP變得很少時纔會喫\n樹果,在HP還有一半時就\n會把它喫掉。", + "原本HP變得很少時才會吃\n樹果,在HP還有一半時就\n會把它吃掉。", }, angerPoint: { name: "憤怒穴位", - description: "要害被擊中時,會大發雷霆\n,攻擊力變爲最大。", + description: "要害被擊中時會大發雷霆。\n攻擊力會提高到最大。", }, unburden: { name: "輕裝", @@ -302,23 +303,23 @@ export const ability: AbilityTranslationEntries = { }, heatproof: { name: "耐熱", - description: "耐熱的體質會讓火屬性的招\n式傷害減半。", + description: "靠著耐熱的體質,讓火屬性\n的招式傷害減半。", }, - simple: { name: "單純", description: "能力變化會變爲平時的2倍。" }, + simple: { name: "單純", description: "能力變化會變為平時的2倍。" }, drySkin: { name: "乾燥皮膚", description: - "下雨天氣時和受到水屬性的\n招式時,HP會回覆。晴朗\n天氣時和受到火屬性的招式\n時,HP會減少。", + "下雨天氣時和受到水屬性的\n招式時,HP會回復。晴朗\n天氣時和受到火屬性的招式\n時,HP會減少。", }, download: { name: "下載", description: "比較對手的防禦和特防,根\n據較低的那項能力相應地提\n高自己的攻擊或特攻。", }, - ironFist: { name: "鐵拳", description: "使用拳類招式的威力會提高。" }, + ironFist: { name: "鐵拳", description: "使用到拳頭的招式威力會\n提高。" }, poisonHeal: { name: "毒療", - description: "變爲中毒狀態時,HP不會\n減少,反而會增加起來。", + description: "陷入中毒狀態時,HP不會\n減少,反而會漸漸增加。", }, adaptability: { name: "適應力", @@ -326,133 +327,133 @@ export const ability: AbilityTranslationEntries = { }, skillLink: { name: "連續攻擊", - description: "如果使用連續招式,總是能\n使出最高次數。", + description: "使用連續招式時,每回都能\n以最多次數進行攻擊。", }, hydration: { - name: "溼潤之軀", - description: "下雨天氣時,異常狀態會治\n愈。", + name: "濕潤之軀", + description: "天氣為下雨時,會治癒異常\n狀態。", }, solarPower: { name: "太陽之力", - description: "晴朗天氣時,特攻會提高,\n而每回合HP會減少。", + description: "天氣為晴朗時特攻會提高,\n但每回合HP會減少。", }, quickFeet: { name: "飛毛腿", - description: "變爲異常狀態時,速度會提\n高。", + description: "陷入異常狀態時,速度會提\n高。", }, normalize: { name: "一般皮膚", description: - "無論是什麼屬性的招式,全\n部會變爲一般屬性。威力會\n少量提高。", + "無論是什麼屬性的招式,全\n部都會變為一般屬性。威力\n會少量提高。", }, - sniper: { name: "狙擊手", description: "擊中要害時,威力會變得更\n強。" }, + sniper: { name: "狙擊手", description: "擊中要害時,威力會進一步\n提高。" }, magicGuard: { name: "魔法防守", description: "不會受到攻擊以外的傷害。" }, noGuard: { name: "無防守", description: "由於無防守戰術,雙方使出\n的招式都必定會擊中。", }, - stall: { name: "慢出", description: "使出招式的順序必定會變爲\n最後。" }, + stall: { name: "慢出", description: "使出招式的順序必定會變為\n最後。" }, technician: { name: "技術高手", - description: "攻擊時可以將低威力招式的\n威力提高。", + description: "可讓威力低的招式提高威力\n來進行攻擊。", }, leafGuard: { name: "葉子防守", - description: "晴朗天氣時,不會變爲異常\n狀態。", + description: "天氣為晴朗時,不會陷入異\n常狀態。", }, klutz: { name: "笨拙", description: "無法使用持有的道具。" }, moldBreaker: { name: "破格", - description: "可以不受對手特性的干擾,\n向對手使出招式。", + description: "可不受特性影響,向對手使\n出招式。", }, superLuck: { name: "超幸運", - description: "因爲擁有超幸運,攻擊容易\n擊中對手的要害。", + description: "因為非常幸運,容易擊中對\n手的要害。", }, aftermath: { name: "引爆", - description: "變爲瀕死時,會對接觸到自\n己的對手造成傷害。", + description: "瀕死時,會對接觸到自己的\n對手造成傷害。", }, anticipation: { name: "危險預知", - description: "可以察覺到對手擁有的危險\n招式。", + description: "察覺對手持有的危險招式。", }, forewarn: { name: "預知夢", - description: "出場時,只讀取1個對手擁\n有的招式。", + description: "出場時,預見1個對手持有\n的招式。", }, unaware: { name: "純樸", - description: "可以無視對手能力的變化,\n進行攻擊。", + description: "可無視對手能力的變化,進\n行攻擊。", }, tintedLens: { name: "有色眼鏡", - description: "可以將效果不好的招式以通\n常的威力使出。", + description: "可將效果不好的招式以正常\n的威力使出。", }, filter: { name: "過濾", - description: "受到效果絕佳的攻擊時,可\n以減弱其威力。", + description: "受到效果絕佳的攻擊時,可\n減弱其威力。", }, slowStart: { name: "慢啓動", - description: "在5回合內,攻擊和速度減\n半。", + description: "在5回合內,攻擊和速度會\n減半。", }, scrappy: { name: "膽量", description: - "一般屬性和格鬥屬性的招式\n可以擊中幽靈屬性的寶可夢\n。對威嚇也毫不動搖。", + "一般屬性和格鬥屬性的招式\n可擊中幽靈屬性的寶可夢。\n面對威嚇也不會動搖。", }, stormDrain: { name: "引水", description: - "將水屬性的招式引到自己身\n上,不會受到傷害,而是會\n提高特攻。", + "將水屬性的招式引到自己身\n上,不但不會受到傷害,反\n而會提高特攻。", }, iceBody: { name: "冰凍之軀", - description: "下雪天氣時,會緩緩回覆\nHP。", + description: "天氣為下雪時,會漸漸回復\nHP。", }, solidRock: { name: "堅硬岩石", - description: "受到效果絕佳的攻擊時,可\n以減弱其威力。", + description: "受到效果絕佳的攻擊時,可\n減弱其威力。", }, - snowWarning: { name: "降雪", description: "出場時,會將天氣變爲下雪。" }, + snowWarning: { name: "降雪", description: "出場時,會將天氣變為下雪。" }, honeyGather: { name: "採蜜", description: "The Pokémon gathers Honey after a battle. The Honey is then sold for money.", }, frisk: { name: "察覺", - description: "進入戰鬥時,神奇寶貝可以檢查對方神奇寶貝的能力。", + description: "出場時,可以察覺對手的特\n性。", }, reckless: { name: "捨身", - description: "自己會因反作用力受傷的招\n式,其威力會提高。", + description: "會讓自己因反作用力而受傷\n的招式威力會提高。", }, multitype: { name: "多屬性", - description: "自己的屬性會根據持有的石\n板而改變。", + description: "自己的屬性會依持有的石板\n而改變。", }, flowerGift: { name: "花之禮", - description: "晴朗天氣時,自己與同伴的\n攻擊和特防能力會提高。", + description: "天氣為晴朗時,自己和同伴\n的攻擊和特防能力會提高。", }, - badDreams: { name: "夢魘", description: "給予睡眠狀態的對手傷害。" }, + badDreams: { name: "夢魘", description: "給予陷入睡眠狀態的對手傷\n害。" }, pickpocket: { name: "順手牽羊", description: "盜取接觸到自己的對手的道\n具。", }, sheerForce: { name: "強行", - description: "招式的追加效果消失,但因\n此能以更高的威力使出招式\n。", + description: "招式會失去追加效果,但可\n以用更高的威力使出招式。", }, contrary: { name: "唱反調", description: - "能力的變化發生逆轉,原本\n提高時會降低,而原本降低\n時會提高。", + "能力的變化會逆轉,原本提\n高時會降低,原本降低時會\n提高。", }, unnerve: { name: "緊張感", - description: "讓對手緊張,使其無法食用\n樹果。", + description: "讓對手感到緊張,無法吃樹\n果。", }, defiant: { name: "不服輸", @@ -460,35 +461,35 @@ export const ability: AbilityTranslationEntries = { }, defeatist: { name: "軟弱", - description: "HP減半時,會變得軟弱,\n攻擊和特攻會減半。", + description: "HP降到一半以下時,會變\n得軟弱而使得攻擊和特攻減\n半。", }, cursedBody: { name: "詛咒之軀", - description: "受到攻擊時,有時會把對手\n的招式變爲定身法狀態。", + description: "受到攻擊時,有時會把對手\n的招式變為定身法狀態。", }, - healer: { name: "治癒之心", description: "有時會治癒異常狀態的同伴。" }, - friendGuard: { name: "友情防守", description: "可以減少我方的傷害。" }, + healer: { name: "治癒之心", description: "有時會治癒同伴的異常狀態。" }, + friendGuard: { name: "友情防守", description: "可以減少我方受到的傷害。" }, weakArmor: { name: "碎裂鎧甲", - description: "受到物理招式的傷害時,防\n御會降低,速度會大幅提高。", + description: "因物理招式受到傷害時,防\n禦會降低,速度會大幅提高。", }, - heavyMetal: { name: "重金屬", description: "自身的重量會變爲2倍。" }, - lightMetal: { name: "輕金屬", description: "自身的重量會減半。" }, + heavyMetal: { name: "重金屬", description: "自己的重量會變為2倍。" }, + lightMetal: { name: "輕金屬", description: "自己的重量會減半。" }, multiscale: { name: "多重鱗片", description: "HP全滿時,受到的傷害會\n變少。", }, toxicBoost: { name: "中毒激升", - description: "變爲中毒狀態時,物理招式\n的威力會提高。", + description: "陷入中毒狀態時,物理招式\n的威力會提高。", }, flareBoost: { name: "受熱激升", - description: "變爲灼傷狀態時,特殊招式\n的威力會提高。", + description: "陷入灼傷狀態時,特殊招式\n的威力會提高。", }, harvest: { name: "收穫", - description: "可以多次製作出已被使用掉\n的樹果。", + description: "可多次採收已被使用過的樹果。", }, telepathy: { name: "心靈感應", @@ -505,62 +506,62 @@ export const ability: AbilityTranslationEntries = { }, poisonTouch: { name: "毒手", - description: "只通過接觸就有可能讓對手\n變爲中毒狀態。", + description: "有時僅是接觸就能讓對手中\n毒。", }, regenerator: { name: "再生力", description: "退回同行隊伍後,HP會少\n量回復。", }, - bigPecks: { name: "健壯胸肌", description: "不會受到防禦降低的效果。" }, - sandRush: { name: "撥沙", description: "沙暴天氣時,速度會提高。" }, + bigPecks: { name: "健壯胸肌", description: "不會受到降低防禦的效果影\n響。" }, + sandRush: { name: "撥沙", description: "天氣為沙暴時,速度會提高。" }, wonderSkin: { name: "奇蹟皮膚", - description: "成爲不易受到變化招式攻擊\n的身體。", + description: "不易受到變化類招式攻擊的\n身體。", }, analytic: { name: "分析", - description: "如果在最後使出招式,招式\n的威力會提高。", + description: "如果在最後使出招式,招式\n的威力就會變強。", }, illusion: { name: "幻覺", - description: "假扮成同行隊伍中的最後一\n只寶可夢出場,迷惑對手。", + description: "假扮成同行隊伍中的最後一\n隻寶可夢出場,迷惑對手。", }, - imposter: { name: "變身者", description: "變身爲當前面對的寶可夢。" }, + imposter: { name: "變身者", description: "變身為當前面對的寶可夢。" }, infiltrator: { name: "穿透", - description: "可以穿透對手的壁障或替身\n進行攻擊。", + description: "可穿透對手的屏障或替身進\n行攻擊。", }, mummy: { name: "木乃伊", - description: "被對手接觸到後,會將對手\n變爲木乃伊。", + description: "被對手接觸到後,會將對手\n變為木乃伊。", }, moxie: { name: "自信過度", - description: "如果打倒對手,就會充滿自\n信,攻擊會提高。", + description: "如果打倒對手,會充滿自信\n並提高攻擊。", }, justified: { name: "正義之心", - description: "受到惡屬性的招式攻擊時,\n因爲正義感,攻擊會提高。", + description: "受到惡屬性的招式攻擊時,\n因為正義感,攻擊會提高。", }, rattled: { name: "膽怯", description: - "受到惡屬性、幽靈屬性和蟲\n屬性的攻擊或威嚇時,會因\n膽怯而速度提高。", + "受到惡屬性、幽靈屬性和蟲\n屬性的招式攻擊,或受到威\n嚇時,會因膽怯而使得速度\n提高。", }, magicBounce: { name: "魔法鏡", - description: "可以不受到由對手使出的變\n化招式影響,並將其反彈。", + description: "可不受到由對手使出的變化\n類招式所影響,並將其反彈。", }, sapSipper: { name: "食草", description: - "受到草屬性的招式攻擊時,\n不會受到傷害,而是攻擊會\n提高。", + "受到草屬性的招式攻擊時,\n不但不會受到傷害,反而攻\n擊會提高。", }, - prankster: { name: "惡作劇之心", description: "可以率先使出變化招式。" }, + prankster: { name: "惡作劇之心", description: "可以搶先使出變化類招式。" }, sandForce: { name: "沙之力", description: - "沙暴天氣時,岩石屬性、地\n面屬性和鋼屬性的招式威力\n會提高。", + "天氣為沙暴時,岩石屬性、\n地面屬性和鋼屬性招式的威\n力會提高。", }, ironBarbs: { name: "鐵刺", @@ -568,7 +569,7 @@ export const ability: AbilityTranslationEntries = { }, zenMode: { name: "達摩模式", - description: "HP變爲一半以下時,樣子\n會改變。", + description: "HP變為一半以下時,樣子\n會改變。", }, victoryStar: { name: "勝利之星", @@ -584,24 +585,24 @@ export const ability: AbilityTranslationEntries = { }, aromaVeil: { name: "芳香幕", - description: "可以防住向自己和同伴發出\n的心靈攻擊。", + description: "可防住向自己和同伴發出的\n心靈攻擊。", }, flowerVeil: { name: "花幕", - description: "我方的草屬性寶可夢能力不\n會降低,也不會變爲異常狀\n態。", + description: "我方的草屬性寶可夢能力不\n會降低。也不會陷入異常狀\n態。", }, cheekPouch: { name: "頰囊", - description: "無論是哪種樹果,食用後,\nHP都會回覆。", + description: "無論是哪種樹果,吃下去後\nHP都會回復。", }, protean: { name: "變幻自如", description: - "變爲與自己使出的招式相同\n的屬性。每次出場戰鬥僅生\n效一次。", + "每次出場戰鬥時,變為與自\n己使出的招式相同的屬性1\n次。", }, furCoat: { name: "毛皮大衣", - description: "對手給予的物理招式的傷害\n會減半。", + description: "對手的物理招式造成的傷害\n會減半。", }, magician: { name: "魔術師", @@ -609,32 +610,32 @@ export const ability: AbilityTranslationEntries = { }, bulletproof: { name: "防彈", - description: "可以防住對手的球和彈類招\n式。", + description: "可防住對手的球和彈類的招\n式。", }, competitive: { name: "好勝", - description: "如果被對手降低能力,特攻\n會大幅提高。", + description: "被對手降低能力時,特攻會\n大幅提高。", }, strongJaw: { name: "強壯之顎", - description: "因爲顎部強壯,啃咬類招式\n的威力會提高。", + description: "顎部強壯,會提高啃咬類招\n式的威力。", }, refrigerate: { name: "冰凍皮膚", - description: "一般屬性的招式會變爲冰屬\n性。威力會少量提高。", + description: "一般屬性的招式會變為冰屬\n性。威力會少量提高。", }, sweetVeil: { name: "甜幕", - description: "自己和同伴的寶可夢不會變\n爲睡眠狀態。", + description: "自己和我方的寶可夢不會陷\n入睡眠狀態。", }, stanceChange: { name: "戰鬥切換", description: - "如果使出攻擊招式,會變爲\n刀劍形態,如果使出招式“\n王者盾牌”,會變爲盾牌形\n態。", + "若使出攻擊招式,會變為刀\n劍形態,若使出招式「王者\n盾牌」,會變為盾牌形態。", }, galeWings: { name: "疾風之翼", - description: "HP全滿時,飛行屬性的招\n式可以率先使出。", + description: "HP全滿時,可以搶先在對\n手之前使出飛行屬性的招式。", }, megaLauncher: { name: "超級發射器", @@ -648,43 +649,43 @@ export const ability: AbilityTranslationEntries = { toughClaws: { name: "硬爪", description: "接觸到對手的招式威力會提\n高。" }, pixilate: { name: "妖精皮膚", - description: "一般屬性的招式會變爲妖精\n屬性。威力會少量提高。", + description: "一般屬性的招式會變為妖精\n屬性。威力會少量提高。", }, gooey: { name: "黏滑", - description: "對於用攻擊接觸到自己的對\n手,會降低其速度。", + description: "對手用攻擊接觸到自己時,\n降低此對手的速度。", }, aerilate: { name: "飛行皮膚", - description: "一般屬性的招式會變爲飛行\n屬性。威力會少量提高。", + description: "一般屬性的招式會變為飛行\n屬性。威力會少量提高。", }, - parentalBond: { name: "親子愛", description: "親子倆可以合計攻擊2次。" }, + parentalBond: { name: "親子愛", description: "親子倆可合計攻擊2次。" }, darkAura: { name: "暗黑氣場", description: "全體的惡屬性招式變強。" }, fairyAura: { name: "妖精氣場", description: "全體的妖精屬性招式變強。" }, auraBreak: { name: "氣場破壞", - description: "讓氣場的效果發生逆轉,降\n低威力。", + description: "讓氣場的效果逆轉,並降低\n威力。", }, primordialSea: { name: "始源之海", - description: "變爲不會受到火屬性攻擊的\n天氣。", + description: "變為讓火屬性攻擊失效的天\n氣。", }, desolateLand: { name: "終結之地", - description: "變爲不會受到水屬性攻擊的\n天氣。", + description: "變為讓水屬性攻擊失效的天\n氣。", }, deltaStream: { name: "德爾塔氣流", - description: "變爲令飛行屬性的弱點消失\n的天氣。", + description: "變為令飛行屬性的弱點消失\n的天氣。", }, stamina: { name: "持久力", description: "受到攻擊時,防禦會提高。" }, wimpOut: { name: "躍躍欲逃", - description: "HP變爲一半時,會慌慌張\n張逃走,退回同行隊伍中。", + description: "HP變為一半時,會慌慌張\n張逃走,退回同行隊伍中。", }, emergencyExit: { name: "危險迴避", - description: "HP變爲一半時,爲了迴避\n危險,會退回到同行隊伍中。", + description: "HP減到一半時,為了避開\n危險,會退回到同行隊伍中。", }, waterCompaction: { name: "遇水凝固", @@ -696,11 +697,11 @@ export const ability: AbilityTranslationEntries = { }, shieldsDown: { name: "界限盾殼", - description: "HP變爲一半時,殼會壞掉,\n變得有攻擊性。", + description: "HP變為一半時,殼會壞掉,\n變得更有攻擊性。", }, stakeout: { name: "蹲守", - description: "可以對替換出場的對手以2\n倍的傷害進行攻擊。", + description: "可以向替換出場的對手以2\n倍的傷害進行攻擊。", }, waterBubble: { name: "水泡", @@ -709,52 +710,52 @@ export const ability: AbilityTranslationEntries = { steelworker: { name: "鋼能力者", description: "鋼屬性的招式威力會提高。" }, berserk: { name: "怒火沖天", - description: "因對手的攻擊HP變爲一半\n時,特攻會提高。", + description: "HP因對手的攻擊降到一半\n時,特攻會提高。", }, - slushRush: { name: "撥雪", description: "下雪天氣時,速度會提高。" }, + slushRush: { name: "撥雪", description: "天氣為下雪時,速度會提高。" }, longReach: { name: "遠隔", description: "可以不接觸對手就使出所有\n的招式。", }, liquidVoice: { name: "溼潤之聲", - description: "所有的聲音招式都變爲水屬\n性。", + description: "所有的聲音招式都變為水屬\n性。", }, - triage: { name: "先行治療", description: "可以率先使出回覆招式。" }, + triage: { name: "先行治療", description: "可以搶先使出回復招式。" }, galvanize: { name: "電氣皮膚", - description: "一般屬性的招式會變爲電屬\n性。威力會少量提高。", + description: "一般屬性的招式會變為電屬\n性。威力會少量提高。", }, surgeSurfer: { name: "衝浪之尾", - description: "電氣場地時,速度會變爲2\n倍。", + description: "電氣場地時,速度會變為2\n倍。", }, schooling: { name: "魚羣", description: - "HP多的時候會聚起來變強。\nHP剩餘量變少時,羣體\n會分崩離析。", + "HP多的時候會聚起來變強。\nHP剩餘量變少時,群體\n會分崩離析。", }, disguise: { name: "畫皮", - description: "通過畫皮覆蓋住身體,可以\n防住1次攻擊。", + description: "用畫皮覆蓋住身體,可防住\n1次攻擊。", }, battleBond: { name: "牽絆變身", description: - "打倒對手時,與訓練家的牽\n絆會增強,自己的攻擊、特\n攻、速度會提高。", + "打倒對手時,與訓練家的牽\n絆會加深,自己的攻擊、特\n攻和速度會提高。", }, powerConstruct: { - name: "羣聚變形", - description: "HP變爲一半時,細胞們會\n趕來支援,變爲完全體形態。", + name: "群聚變形", + description: "HP變為一半時,細胞們會\n趕來支援,變為完全體形態。", }, corrosion: { name: "腐蝕", - description: "可以使鋼屬性和毒屬性的寶\n可夢也陷入中毒狀態。", + description: "就算對方是鋼屬性或毒屬性\n寶可夢,也可讓對方陷入中\n毒狀態。", }, comatose: { name: "絕對睡眠", description: - "總是半夢半醒的狀態,絕對\n不會醒來。可以就這麼睡着\n進行攻擊。", + "總是半夢半醒的狀態,絕對\n不會醒來。可在睡著的狀況\n下進行攻擊。", }, queenlyMajesty: { name: "女王的威嚴", @@ -766,13 +767,13 @@ export const ability: AbilityTranslationEntries = { }, dancer: { name: "舞者", - description: "有誰使出跳舞招式時,自己\n也能就這麼接着使出跳舞招\n式。", + description: "當有誰使出跳舞招式時,自\n己也能接著使出跳舞招式。", }, battery: { name: "蓄電池", description: "會提高我方的特殊招式的威\n力。" }, fluffy: { name: "毛茸茸", description: - "會將對手所給予的接觸類招\n式的傷害減半,但火屬性招\n式的傷害會變爲2倍。", + "會將對手所給予的接觸類招\n式的傷害減半,但火屬性招\n式的傷害會變為2倍。", }, dazzling: { name: "鮮豔之軀", @@ -780,44 +781,44 @@ export const ability: AbilityTranslationEntries = { }, soulHeart: { name: "魂心", - description: "寶可夢每次變爲瀕死狀態時\n,特攻會提高。", + description: "每當場上有寶可夢陷入瀕死\n狀態時,特攻就會提高。", }, tanglingHair: { name: "捲髮", - description: "對於用攻擊接觸到自己的對\n手,會降低其速度。", + description: "對手用攻擊接觸到自己時,\n降低此對手的速度。", }, receiver: { name: "接球手", - description: "繼承被打倒的同伴的特性,\n變爲相同的特性。", + description: "繼承被打倒的同伴的特性,\n變為相同的特性。", }, powerOfAlchemy: { name: "化學之力", - description: "繼承被打倒的同伴的特性,\n變爲相同的特性。", + description: "繼承被打倒的同伴的特性,\n變為相同的特性。", }, beastBoost: { name: "異獸提升", - description: "打倒對手的時候,自己最高\n的那項能力會提高。", + description: "打倒對手的時候,會提高自\n己最高的那項能力。", }, rksSystem: { name: "AR系統", - description: "根據持有的存儲碟,自己的\n屬性會改變。", + description: "根據持有的記憶碟,自己的\n屬性會改變。", }, electricSurge: { name: "電氣製造者", - description: "出場時,會佈下電氣場地。", + description: "出場時,會布下電氣場地。", }, psychicSurge: { name: "精神製造者", - description: "出場時,會佈下精神場地。", + description: "出場時,會布下精神場地。", }, - mistySurge: { name: "薄霧製造者", description: "出場時,會佈下薄霧場地。" }, + mistySurge: { name: "薄霧製造者", description: "出場時,會布下薄霧場地。" }, grassySurge: { name: "青草製造者", - description: "出場時,會佈下青草場地。", + description: "出場時,會布下青草場地。", }, fullMetalBody: { name: "金屬防護", - description: "不會因爲對手的招式或特性\n而被降低能力。", + description: "不會因對手的招式或特性而\n被降低能力。", }, shadowShield: { name: "幻影防守", @@ -825,33 +826,33 @@ export const ability: AbilityTranslationEntries = { }, prismArmor: { name: "棱鏡裝甲", - description: "受到效果絕佳的攻擊時,可\n以減弱其威力。", + description: "受到效果絕佳的攻擊時,可\n減弱其威力。", }, neuroforce: { name: "腦核之力", - description: "效果絕佳的攻擊,威力會變\n得更強。", + description: "可進一步提升效果絕佳招式\n的威力。", }, intrepidSword: { name: "不撓之劍", - description: "首次出場時,攻擊會提高。", + description: "在戰鬥中首次出場時,攻擊\n會提高。", }, dauntlessShield: { name: "不屈之盾", - description: "首次出場時,防禦會提高。", + description: "在戰鬥中首次出場時,防禦\n會提高。", }, libero: { name: "自由者", description: - "變爲與自己使出的招式相同\n的屬性。每次出場戰鬥僅生\n效一次。", + "每次出場戰鬥時,變為與自\n己使出的招式相同的屬性1\n次。", }, ballFetch: { name: "撿球", - description: "沒有攜帶道具時,會拾取第\n1個投出後捕捉失敗的精靈\n球。", + description: "當寶可夢沒有攜帶道具時,\n會撿回第1個投出後捕捉失\n敗的精靈球。", }, cottonDown: { name: "棉絮", description: - "受到攻擊後撒下棉絮,降低\n除自己以外的所有寶可夢的\n速度。", + "受到攻擊時會撒下棉絮,降\n低除自己以外的所有寶可夢\n的速度。", }, propellerTail: { name: "螺旋尾鰭", @@ -864,7 +865,7 @@ export const ability: AbilityTranslationEntries = { gulpMissile: { name: "一口導彈", description: - "衝浪或潛水時會叼來獵物。\n受到傷害時,會吐出獵物進\n行攻擊。", + "衝浪或潛水時會叼來獵物。\n當受到傷害時,會吐出獵物\n攻擊對手。", }, stalwart: { name: "堅毅", @@ -872,18 +873,18 @@ export const ability: AbilityTranslationEntries = { }, steamEngine: { name: "蒸汽機", - description: "受到水屬性或火屬性的招式\n攻擊時,速度會巨幅提高。", + description: "受到水屬性或火屬性招式攻\n擊時,速度會極大幅提高。", }, punkRock: { name: "龐克搖滾", - description: "聲音招式的威力會提高。受\n到的聲音招式傷害會減半。", + description: "聲音招式的威力會提高。受\n到聲音招式的傷害會減半。", }, - sandSpit: { name: "吐沙", description: "受到攻擊時,會颳起沙暴。" }, + sandSpit: { name: "吐沙", description: "受到攻擊時,會刮起沙暴。" }, iceScales: { name: "冰鱗粉", - description: "由於有冰鱗粉的守護,受到\n的特殊攻擊傷害會減半。", + description: "得到冰鱗粉的守護,受到的\n特殊攻擊傷害會減半。", }, - ripen: { name: "熟成", description: "使樹果成熟,效果變爲2倍。" }, + ripen: { name: "熟成", description: "讓樹果成熟,使效果變為2\n倍。" }, iceFace: { name: "結凍頭", description: @@ -891,11 +892,11 @@ export const ability: AbilityTranslationEntries = { }, powerSpot: { name: "能量點", - description: "只要處在相鄰位置,招式的\n威力就會提高。", + description: "只要站在旁邊,招式的威力\n就會提高。", }, mimicry: { name: "擬態", - description: "寶可夢的屬性會根據場地的\n狀態而變化。", + description: "寶可夢的屬性會根據場地的\n狀態而改變。", }, screenCleaner: { name: "除障", @@ -909,7 +910,7 @@ export const ability: AbilityTranslationEntries = { perishBody: { name: "滅亡之軀", description: - "受到接觸類招式攻擊時,雙\n方都會在3回合後變爲瀕死\n狀態。替換後效果消失。", + "在受到接觸類招式攻擊時,\n3個回合後雙方都會陷入瀕\n死。替換寶可夢後效果就\n會消失。", }, wanderingSpirit: { name: "遊魂", @@ -917,30 +918,30 @@ export const ability: AbilityTranslationEntries = { }, gorillaTactics: { name: "一猩一意", - description: "雖然攻擊會提高,但是隻能\n使出一開始所選的招式。", + description: "攻擊雖然會提高,但只能使\n出最初選擇的招式。", }, neutralizingGas: { name: "化學變化氣體", description: - "特性爲化學變化氣體的寶可\n夢在場時,場上所有寶可夢\n的特性效果都會消失或者無\n法生效。", + "當場上有特性是化學變化氣\n體的寶可夢時,所有寶可夢\n的特性效果都會消失或無\n法發動。", }, pastelVeil: { name: "粉彩護幕", - description: "自己和同伴都不會陷入中毒\n的異常狀態。", + description: "自己和我方同伴都不會陷入\n中毒的異常狀態。", }, hungerSwitch: { name: "飽了又餓", - description: "每回合結束時會在滿腹花紋\n與空腹花紋之間交替改變樣\n子。", + description: "在每個回合結束時,會在滿\n腹花紋和空腹花紋之間交替\n改變樣子。", }, quickDraw: { name: "速擊", description: "有時能比對手先一步行動。" }, unseenFist: { name: "無形拳", description: - "如果使出的是接觸到對手的\n招式,就可以無視守護效果\n進行攻擊。", + "只要是接觸到對手的招式,\n就可以無視對手的防守效果\n進行攻擊。", }, curiousMedicine: { name: "怪藥", - description: "出場時會從貝殼撒藥,將我\n方的能力變化復原。", + description: "出場時,會從貝殼撒藥,將\n我方的能力變化復原。", }, transistor: { name: "電晶體", description: "電屬性的招式威力會提高。" }, dragonsMaw: { name: "龍顎", description: "龍屬性的招式威力會提高。" }, @@ -954,15 +955,15 @@ export const ability: AbilityTranslationEntries = { }, asOneGlastrier: { name: "人馬一體", - description: "兼備蕾冠王的緊張感和靈幽\n馬的漆黑嘶鳴這兩種特性。", + description: "兼備蕾冠王的緊張感和雪暴\n馬的蒼白嘶鳴這2種特性。", }, asOneSpectrier: { name: "人馬一體", - description: "兼備蕾冠王的緊張感和靈幽\n馬的漆黑嘶鳴這兩種特性。", + description: "兼備蕾冠王的緊張感和靈幽\n馬的漆黑嘶鳴這2種特性。", }, lingeringAroma: { name: "甩不掉的氣味", - description: "被對手接觸到後,甩不掉的\n氣味會沾上對手。", + description: "被對手接觸到時,甩不掉的\n氣味會沾染給對手。", }, seedSower: { name: "掉出種子", @@ -971,123 +972,122 @@ export const ability: AbilityTranslationEntries = { thermalExchange: { name: "熱交換", description: - "受到火屬性的招式攻擊時,\n攻擊會提高,且不會陷入灼\n傷狀態。", + "受到火屬性的招式攻擊時,\n攻擊會提高,不會陷入灼傷\n狀態。", }, angerShell: { name: "憤怒甲殼", description: - "因被對手攻擊而HP變爲一\n半時,會因憤怒降低防禦和\n特防。但攻擊、特攻、速度\n會提高。", + "HP因對手的攻擊降到一半\n時,會因憤怒而降低防禦和\n特防,但攻擊、特攻和速度\n會提高。", }, purifyingSalt: { name: "潔淨之鹽", description: - "因潔淨的鹽而不會陷入異常\n狀態。會讓幽靈屬性的招式\n傷害減半。", + "因潔淨的鹽而不會陷入異常\n狀態。能夠讓幽靈屬性招式\n的傷害減半。", }, wellBakedBody: { name: "焦香之軀", description: - "受到火屬性的招式攻擊時,\n不會受到傷害,而是會大幅\n提高防禦。", + "受到火屬性的招式攻擊時,\n不但不會受到傷害,反而防\n禦會大幅提高", }, windRider: { name: "乘風", description: - "吹起了順風或受到風的招式\n攻擊時,不會受到傷害,而\n是會提高攻擊。", + "吹起順風或受到風的招式攻\n擊時,不但不會受到傷害,\n反而攻擊會提高。", }, guardDog: { name: "看門犬", description: - "受到威嚇時,攻擊會提高。\n讓替換寶可夢的招式和道具\n無效。", + "受到威嚇時,攻擊會提高。\n會讓替換寶可夢的招式和道\n具失效。", }, rockyPayload: { name: "搬巖", description: "岩石屬性的招式威力會提高。" }, windPower: { name: "風力發電", - description: "受到風的招式攻擊時,會變\n爲充電狀態。", + description: "受到風的招式攻擊時,會變\n成充電狀態。", }, zeroToHero: { name: "全能變身", - description: "回到同行隊伍後,會變爲全\n能形態。", + description: "離場後會變為全能形態。", }, commander: { name: "發號施令", description: - "出場時,若我方當中有喫吼\n霸,就會進入其口中,並從\n其口中發出指令。", + "出場時,若我方有吃吼霸,\n便會進入吃吼霸的口中,從\n那裡發號施令。", }, electromorphosis: { name: "電力轉換", - description: "受到傷害時,會變爲充電狀\n態。", + description: "受到傷害時,會變成充電狀\n態。", }, protosynthesis: { name: "古代活性", - description: "攜帶着驅勁能量或天氣爲晴\n朗時,數值最高的能力會提\n高。", + description: "攜帶著驅勁能量或天氣為晴\n朗時,數值最高的能力會提\n高。", }, quarkDrive: { name: "夸克充能", description: - "攜帶着驅勁能量或在電氣場\n地上時,數值最高的能力會\n提高。", + "攜帶著驅勁能量或在電氣場\n地上時,數值最高的能力會\n提高。", }, goodAsGold: { name: "黃金之軀", - description: "不會氧化的堅固黃金身軀不\n會受到對手的變化招式的影\n響。", + description: "既不氧化又堅韌的黃金之軀\n不會受到對手的變化類招式\n攻擊。", }, vesselOfRuin: { name: "災禍之鼎", - description: "以能呼喚災厄的鼎的力量降\n低除自己以外的寶可夢的特\n攻。", + description: "在喚來災厄之鼎的力量下,\n除自己以外的特攻會變弱。", }, swordOfRuin: { name: "災禍之劍", - description: "以能呼喚災厄的劍的力量降\n低除自己以外的寶可夢的防\n御。", + description: "在喚來災厄之劍的力量下,\n除自己以外的防禦會變弱。", }, tabletsOfRuin: { name: "災禍之簡", - description: "以能呼喚災厄的簡的力量降\n低除自己以外的寶可夢的攻\n擊。", + description: "在喚來災厄之木簡的力量下\n,除自己以外的攻擊會變弱。", }, beadsOfRuin: { name: "災禍之玉", - description: - "以能呼喚災厄的勾玉的力量\n降低除自己以外的寶可夢的\n特防。", + description: "在喚來災厄之木簡的力量下\n,除自己以外的特防會變弱。", }, orichalcumPulse: { name: "緋紅脈動", description: - "出場時,會將天氣變爲晴朗\n。日照強烈時,會通過古代\n的脈動升高攻擊。", + "出場時,會將天氣變為晴朗\n。日照很強時,會因為古代\n的脈動而使攻擊升高。", }, hadronEngine: { name: "強子引擎", description: - "出場時,會佈下電氣場地。\n處於電氣場地時,會通過未\n來的機關升高特攻。", + "出場時,會布下電氣場地。\n在電氣場地時,會因為未來\n的機關而使特攻升高。", }, opportunist: { name: "跟風", - description: "對手的能力提高時,自己也\n會趁機同樣地提高能力。", + description: "對手的能力提高時,自己也\n會跟著提高能力。", }, cudChew: { name: "反芻", - description: "喫了樹果後,會在下一回合\n結束時從胃反芻出來再喫1\n次。", + description: "食用樹果後,會在下一回合\n結束時從胃裡取出,以1次\n為限再次食用。", }, - sharpness: { name: "鋒銳", description: "提高切割對手的招式的威力。" }, + sharpness: { name: "鋒銳", description: "切斬對手的招式威力會提高。" }, supremeOverlord: { name: "大將", description: - "出場時,攻擊和特攻會按照\n目前被打倒的同伴數量逐漸\n提升,被打倒越多,提升越\n多。", + "出場時,先前每有1隻同伴\n被打倒,攻擊和特攻就會提\n高少許。", }, - costar: { name: "同臺共演", description: "出場時,複製同伴的能力變\n化。" }, + costar: { name: "同台共演", description: "出場時,會複製同伴的能力\n變化。" }, toxicDebris: { name: "毒滿地", - description: "受到物理招式的傷害時,會\n在對手腳下散佈毒菱。", + description: "因物理招式受到傷害時,會\n在對手腳下散布毒菱。", }, armorTail: { name: "尾甲", - description: "包裹頭部的神祕尾巴使對手\n無法對我方使出先制招式。", + description: "包覆著頭部的神秘尾巴使對\n手無法對我方使出先制招式。", }, earthEater: { name: "食土", description: - "受到地面屬性的招式攻擊時\n,不會受到傷害,而是會得\n到回覆。", + "受到地面屬性的招式攻擊時\n,不會受到傷害,而是會回\n復。", }, myceliumMight: { name: "菌絲之力", description: - "使出變化招式時,雖然行動\n必定會變慢,但能不受對手\n的特性妨礙。", + "使出變化類招式時,行動一\n定會變緩慢,但不會受到對\n手特性的干擾。", }, mindsEye: { name: "心眼", @@ -1097,50 +1097,50 @@ export const ability: AbilityTranslationEntries = { supersweetSyrup: { name: "甘露之蜜", description: - "首次出場時,會散發出甜膩\n的蜜的香味來降低對手的閃\n避率。", + "在對戰中首次出場時,會四\n處散播甜膩的蜜香,降低對\n手的閃避率。", }, hospitality: { name: "款待", - description: "出場時款待同伴,回覆其少\n量HP。", + description: "出場時款待同伴,使其回復\n少量HP。", }, toxicChain: { name: "毒鎖鏈", description: - "憑藉含有毒素的鎖鏈的力量,\n有時能讓被招式擊中的對\n手陷入劇毒狀態。", + "靠著含有毒素的鎖鏈的力量\n,有時會讓被招式擊中的對\n手陷入劇毒狀態。", }, embodyAspectTeal: { name: "面影輝映", - description: "將回憶映於心中,讓水井面\n具發出光輝,提高自己的特\n防。", + description: "將回憶映於心中,使碧草面\n具發出光輝,提高自己的速\n度。", }, embodyAspectWellspring: { name: "面影輝映", - description: "將回憶映於心中,讓碧草面\n具發出光輝,提高自己的速\n度。", + description: "將回憶映於心中,使水井面\n具發出光輝,提高自己的特\n防。", }, embodyAspectHearthflame: { name: "面影輝映", - description: "將回憶映於心中,讓火竈面\n具發出光輝,提高自己的攻\n擊。", + description: "將回憶映於心中,使火灶面\n具發出光輝,提高自己的攻\n擊。", }, embodyAspectCornerstone: { name: "面影輝映", - description: "將回憶映於心中,讓礎石面\n具發出光輝,提高自己的防\n御。", + description: "將回憶映於心中,使礎石面\n具發出光輝,提高自己的防\n御。", }, teraShift: { name: "太晶變形", - description: "出場時,會吸收周圍的能量\n,變爲太晶形態。", + description: "出場時,會吸收周圍的能量\n,變為太晶形態。", }, teraShell: { name: "太晶甲殼", description: - "甲殼蘊藏着全部屬性的力量\n,會將自己HP全滿時受到\n的傷害全都變爲效果不好。", + "蘊藏著所有屬性力量的甲殼\n會將自身HP全滿時受到的\n傷害全都變為效果不好。", }, teraformZero: { name: "歸零化境", description: - "太樂巴戈斯變爲星晶形態時\n,蘊藏在它身上的力量會將\n天氣和場地的影響全部歸零。", + "太樂巴戈斯變為星晶形態時\n,蘊藏其身的力量會將天氣\n和場地的影響全部歸零。", }, poisonPuppeteer: { name: "毒傀儡", description: - "因桃歹郎的招式而陷入中毒\n狀態的對手同時也會陷入混\n亂狀態。", + "因為桃歹郎的招式而陷入中\n毒狀態的對手同時也會陷入\n混亂狀態。", }, } as const; From cb27fc2b06676454ea86b77608e79fa6096d5ccb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Ricardo=20Fleury=20Oliveira?= Date: Thu, 6 Jun 2024 12:19:38 -0300 Subject: [PATCH 082/129] [Localization] ptBR removing capital letters in battle.ts (#1873) --- src/locales/pt_BR/battle.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/locales/pt_BR/battle.ts b/src/locales/pt_BR/battle.ts index 11238d1902c..47a74fe17b1 100644 --- a/src/locales/pt_BR/battle.ts +++ b/src/locales/pt_BR/battle.ts @@ -56,8 +56,8 @@ export const battle: SimpleTranslationEntries = { "skipItemQuestion": "Tem certeza de que não quer escolher um item?", "eggHatching": "Opa?", "ivScannerUseQuestion": "Quer usar o Scanner de IVs em {{pokemonName}}?", - "wildPokemonWithAffix": "{{pokemonName}} Selvagem", - "foePokemonWithAffix": "{{pokemonName}} Adversário", + "wildPokemonWithAffix": "{{pokemonName}} selvagem", + "foePokemonWithAffix": "{{pokemonName}} adversário", "useMove": "{{pokemonNameWithAffix}} usou {{moveName}}!", "drainMessage": "{{pokemonName}} teve sua\nenergia drenada!", "regainHealth": "{{pokemonName}} recuperou\npontos de saúde!" From 3fea384dc1e6742478fd5e1b13526eb86c93bd23 Mon Sep 17 00:00:00 2001 From: SeafoamQueen <167576411+SeafoamQueen@users.noreply.github.com> Date: Thu, 6 Jun 2024 11:26:04 -0400 Subject: [PATCH 083/129] [Feature] Added Battle Style setting (#1872) * Added Battle Style setting with Shift and Set options * Changed 'Shift' option to 'Switch' to match the Gen 8 setting. --- src/battle-scene.ts | 6 ++++++ src/phases.ts | 5 +++++ src/system/settings/settings.ts | 11 +++++++++++ 3 files changed, 22 insertions(+) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 38882cf3e68..a873fab80b2 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -145,6 +145,12 @@ export default class BattleScene extends SceneBase { public fusionPaletteSwaps: boolean = true; public enableTouchControls: boolean = false; public enableVibration: boolean = false; + /** + * Determines the selected battle style. + * - 0 = 'Shift' + * - 1 = 'Set' - The option to switch the active pokemon at the start of a battle will not display. + */ + public battleStyle: integer = 0; public disableMenu: boolean = false; diff --git a/src/phases.ts b/src/phases.ts index 45acc79ca01..c43ab6e1c7f 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -1667,6 +1667,11 @@ export class CheckSwitchPhase extends BattlePhase { const pokemon = this.scene.getPlayerField()[this.fieldIndex]; + if (this.scene.battleStyle === 1) { + super.end(); + return; + } + if (this.scene.field.getAll().indexOf(pokemon) === -1) { this.scene.unshiftPhase(new SummonMissingPhase(this.scene, this.fieldIndex)); super.end(); diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index 3cd578dbaf4..75fc8185c89 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -42,6 +42,7 @@ export const SettingKeys = { EXP_Gains_Speed: "EXP_GAINS_SPEED", EXP_Party_Display: "EXP_PARTY_DISPLAY", Skip_Seen_Dialogues: "SKIP_SEEN_DIALOGUES", + Battle_Style: "BATTLE_STYLE", Enable_Retries: "ENABLE_RETRIES", Tutorials: "TUTORIALS", Touch_Controls: "TOUCH_CONTROLS", @@ -107,6 +108,13 @@ export const Setting: Array = [ default: 0, type: SettingType.GENERAL }, + { + key: SettingKeys.Battle_Style, + label: "Battle Style", + options: ["Switch", "Set"], + default: 0, + type: SettingType.GENERAL + }, { key: SettingKeys.Enable_Retries, label: "Enable Retries", @@ -348,6 +356,9 @@ export function setSetting(scene: BattleScene, setting: string, value: integer): case SettingKeys.Skip_Seen_Dialogues: scene.skipSeenDialogues = Setting[index].options[value] === "On"; break; + case SettingKeys.Battle_Style: + scene.battleStyle = value; + break; case SettingKeys.Candy_Upgrade_Notification: if (scene.candyUpgradeNotification === value) { break; From ba66f2c91646700e52e5593ad6a780d779b70ddf Mon Sep 17 00:00:00 2001 From: MrWaterT <87186129+MrWaterT@users.noreply.github.com> Date: Fri, 7 Jun 2024 00:31:31 +0900 Subject: [PATCH 084/129] [Bug] Remove text on summary bg (#1709) --- public/images/ui/summary_bg.png | Bin 2880 -> 2126 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/public/images/ui/summary_bg.png b/public/images/ui/summary_bg.png index 1fa1d95fcc3ea6c3c6c09a65682e2d5b2de54ef8..b77cdadd2aca9054577345428d89cd1db1c9cc0f 100644 GIT binary patch literal 2126 zcmZWrdstH07C*=d4=QxhFEccS#zKwEX-Kn@mo;IQ^x0$VDb6&ebIB~yv?2+)IK7n8 zNlo)nPJLsRjE_{T38HVT)KO_nys6UJuf5LR=i<(= ztpq!g9Rxvy5box12*R*{T!rAkv;Y0XBJje;bN2}$i1^$(Fh8v#xc zjaN;t!Z*HrV_)vC21C!;--InD?%rdKg~BYK@}9E|Q>GrZTJ3=^;+#!;Izxso_>9Dg zSq~3| zH8dO-7W9XBFz0g2h<$XPeqy4$E(0sjcN%S9579dUFC_>Q3sxGA4sH0EKArEcbW4xN z^Lx%Un}nG4q^pnSqDAW&=Upon{2|jPX;65q4LYzKF5V+7op)&W@1zryLd40&Nz9)ojz zo#vE9+b)w)DV~>tec!w z9~%AQW0GhVP7 z=&GW)$Yp1~g*V*A$WG<1Jbw7BfCyw#w$QQ2#p^;uTan z5i6#QOm1XL$z@xyZeA3G_ZkZR!BF!I&rZt6t&iuRd##G8DI0rsL>RVAib}TtvLpT~ znMmTt!vX?l!v31<=% zy(e?_$BIj`q~rupt`9XBe<``T4a)rj{^cU@D8T7Z$)6nT5pN2YKv&7fKqBlW=rXFXaDFSP+N zI)WmquxMIqSjS5I76|jZ_9{k9$kt?S!3}|vsTL7#ify~+ff_$fx zECRr#mDVze+8}%gqMdiJQq!&@$Xli4L%=}`9mTViw=v-`g}VggrK`R-I+YwFk+=NH zllz0k#fmdd1a3b-XI=7yV^eW1*KxH(z~ikgw;k-x(?+m6K!wlN2tu((P$~Iss};0{ z0(WNNUKAm0|6+`+1oM%DRQM9)Y)461FxDXiD@aA(dy~kiE8*lTRsh{f$_pP7c@HX~ z-Ld*7su^r|E%eJJk)&xqqPlQU}<8rI2o_*)k0AOPJ9lX6_F{DXewJFOY+I&1{te9MsmIg_Qz%uSEw*2+6 ze*CFruD+Z}tZZR6kIiC2iBNS5@-xa z>1CbgC?d_3{|HQTO-)A#!l7{#9%k?E?)b$L^fGGfnXLpz*nbyEx^EtkkztBEMR)7p z$Nc_LMJS1(;ArM3M60GScI+P;80y3zCAtUbZt~9-bRnzf1Ja+QrlvCAOh0@1e#@Ej z=Wjer`8)6`)ADAbPQ0#ZF0U)PkUug>T)42qPL&l-%Aj&kN$zYwY@t5d26tX33^Ft0_@;{JixNQIc literal 2880 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 From 8d6a0bb0a1f1d03a0ebc266ba330b0e49f60b5b5 Mon Sep 17 00:00:00 2001 From: Adrian T <68144167+torranx@users.noreply.github.com> Date: Thu, 6 Jun 2024 23:49:50 +0800 Subject: [PATCH 085/129] [Ability] Implement Ice Face (#1755) * implement ice face ability * remove showing ability bar * fixes * add documentations * move code out of phases.ts * add hardcoded eiscue check, localization * add KO locale * remove unnecessary constructor * use && instead of || to specify ice form on eiscue --- .../cry/{875-noice.m4a => 875-no-ice.m4a} | Bin src/battle-scene.ts | 7 +++ src/data/ability.ts | 52 ++++++++++++++++- src/data/battler-tags.ts | 55 ++++++++++++++++++ src/data/enums/battler-tag-type.ts | 3 +- src/data/pokemon-forms.ts | 4 ++ src/locales/de/ability-trigger.ts | 1 + src/locales/en/ability-trigger.ts | 3 +- src/locales/es/ability-trigger.ts | 1 + src/locales/fr/ability-trigger.ts | 3 +- src/locales/it/ability-trigger.ts | 1 + src/locales/ko/ability-trigger.ts | 1 + src/locales/pt_BR/ability-trigger.ts | 3 +- src/locales/zh_CN/ability-trigger.ts | 3 +- src/locales/zh_TW/ability-trigger.ts | 3 +- 15 files changed, 132 insertions(+), 8 deletions(-) rename public/audio/cry/{875-noice.m4a => 875-no-ice.m4a} (100%) diff --git a/public/audio/cry/875-noice.m4a b/public/audio/cry/875-no-ice.m4a similarity index 100% rename from public/audio/cry/875-noice.m4a rename to public/audio/cry/875-no-ice.m4a diff --git a/src/battle-scene.ts b/src/battle-scene.ts index a873fab80b2..638314b3444 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -59,6 +59,7 @@ import {InputsController} from "./inputs-controller"; import {UiInputs} from "./ui-inputs"; import { MoneyFormat } from "./enums/money-format"; import { NewArenaEvent } from "./battle-scene-events"; +import { Abilities } from "./data/enums/abilities"; import ArenaFlyout from "./ui/arena-flyout"; import { EaseType } from "./ui/enums/ease-type"; @@ -1048,6 +1049,12 @@ export default class BattleScene extends SceneBase { if (resetArenaState) { this.arena.removeAllTags(); playerField.forEach((_, p) => this.unshiftPhase(new ReturnPhase(this, p))); + + for (const pokemon of this.getParty()) { + if (pokemon.hasAbility(Abilities.ICE_FACE)) { + pokemon.formIndex = 0; + } + } this.unshiftPhase(new ShowTrainerPhase(this)); } for (const pokemon of this.getParty()) { diff --git a/src/data/ability.ts b/src/data/ability.ts index 4bb4f36350e..db44835a86a 100755 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -3344,6 +3344,48 @@ export class PostSummonStatChangeOnArenaAbAttr extends PostSummonStatChangeAbAtt } } +/** + * Applies immunity to physical moves. + * This is used in Ice Face ability. + */ +export class IceFaceMoveImmunityAbAttr extends MoveImmunityAbAttr { + /** + * Applies the Ice Face pre-defense ability to the Pokémon. + * Removes BattlerTagType.ICE_FACE hit by physical attack and is in Ice Face form. + * + * @param {Pokemon} pokemon - The Pokémon with the Ice Face ability. + * @param {boolean} passive - Whether the ability is passive. + * @param {Pokemon} attacker - The attacking Pokémon. + * @param {PokemonMove} move - The move being used. + * @param {Utils.BooleanHolder} cancelled - A holder for whether the move was cancelled. + * @param {any[]} args - Additional arguments. + * @returns {boolean} - Whether the immunity was applied. + */ + applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { + const isImmune = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args); + + if (isImmune) { + const simulated = args.length > 1 && args[1]; + if (!simulated) { + pokemon.removeTag(BattlerTagType.ICE_FACE); + } + } + + return isImmune; + } + + /** + * Gets the message triggered when the Pokémon avoids damage using the Ice Face ability. + * @param {Pokemon} pokemon - The Pokémon with the Ice Face ability. + * @param {string} abilityName - The name of the ability. + * @param {...any} args - Additional arguments. + * @returns {string} - The trigger message. + */ + getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + return i18next.t("abilityTriggers:iceFaceAvoidedDamage", { pokemonName: pokemon.name, abilityName: abilityName }); + } +} + function applyAbAttrsInternal(attrType: { new(...args: any[]): TAttr }, pokemon: Pokemon, applyFunc: AbAttrApplyFunc, args: any[], isAsync: boolean = false, showAbilityInstant: boolean = false, quiet: boolean = false, passive: boolean = false): Promise { return new Promise(resolve => { @@ -4330,8 +4372,14 @@ export function initAbilities() { .attr(UnsuppressableAbilityAbAttr) .attr(NoTransformAbilityAbAttr) .attr(NoFusionAbilityAbAttr) - .ignorable() - .unimplemented(), + // Add BattlerTagType.ICE_FACE if the pokemon is in ice face form + .conditionalAttr(pokemon => pokemon.formIndex === 0, PostSummonAddBattlerTagAbAttr, BattlerTagType.ICE_FACE, 0, false) + // When summoned with active HAIL or SNOW, add BattlerTagType.ICE_FACE + .conditionalAttr(getWeatherCondition(WeatherType.HAIL, WeatherType.SNOW), PostSummonAddBattlerTagAbAttr, BattlerTagType.ICE_FACE, 0) + // When weather changes to HAIL or SNOW while pokemon is fielded, add BattlerTagType.ICE_FACE + .attr(PostWeatherChangeAddBattlerTagAttr, BattlerTagType.ICE_FACE, 0, WeatherType.HAIL, WeatherType.SNOW) + .attr(IceFaceMoveImmunityAbAttr, (target, user, move) => move.getMove().category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE)) + .ignorable(), new Ability(Abilities.POWER_SPOT, 8) .unimplemented(), new Ability(Abilities.MIMICRY, 8) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 50d4b62decb..bfdeb2189dc 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -15,6 +15,8 @@ import { TerrainType } from "./terrain"; import { WeatherType } from "./weather"; import { BattleStat } from "./battle-stat"; import { allAbilities } from "./ability"; +import { SpeciesFormChangeManualTrigger } from "./pokemon-forms"; +import { Species } from "./enums/species"; export enum BattlerTagLapseType { FAINT, @@ -1354,6 +1356,57 @@ export class CursedTag extends BattlerTag { } } +/** + * Provides the Ice Face ability's effects. + */ +export class IceFaceTag extends BattlerTag { + constructor(sourceMove: Moves) { + super(BattlerTagType.ICE_FACE, BattlerTagLapseType.CUSTOM, 1, sourceMove); + } + + /** + * Determines if the Ice Face tag can be added to the Pokémon. + * @param {Pokemon} pokemon - The Pokémon to which the tag might be added. + * @returns {boolean} - True if the tag can be added, false otherwise. + */ + canAdd(pokemon: Pokemon): boolean { + const weatherType = pokemon.scene.arena.weather?.weatherType; + const isWeatherSnowOrHail = weatherType === WeatherType.HAIL || weatherType === WeatherType.SNOW; + const isFormIceFace = pokemon.formIndex === 0; + + + // Hard code Eiscue for now, this is to prevent the game from crashing if fused pokemon has Ice Face + if ((pokemon.species.speciesId === Species.EISCUE && isFormIceFace) || isWeatherSnowOrHail) { + return true; + } + return false; + } + + /** + * Applies the Ice Face tag to the Pokémon. + * Triggers a form change to Ice Face if the Pokémon is not in its Ice Face form. + * @param {Pokemon} pokemon - The Pokémon to which the tag is added. + */ + onAdd(pokemon: Pokemon): void { + super.onAdd(pokemon); + + if (pokemon.formIndex !== 0) { + pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger); + } + } + + /** + * Removes the Ice Face tag from the Pokémon. + * Triggers a form change to Noice when the tag is removed. + * @param {Pokemon} pokemon - The Pokémon from which the tag is removed. + */ + onRemove(pokemon: Pokemon): void { + super.onRemove(pokemon); + + pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger); + } +} + export function getBattlerTag(tagType: BattlerTagType, turnCount: integer, sourceMove: Moves, sourceId: integer): BattlerTag { switch (tagType) { case BattlerTagType.RECHARGING: @@ -1467,6 +1520,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: integer, sourc return new MinimizeTag(); case BattlerTagType.DESTINY_BOND: return new DestinyBondTag(sourceMove, sourceId); + case BattlerTagType.ICE_FACE: + return new IceFaceTag(sourceMove); case BattlerTagType.NONE: default: return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); diff --git a/src/data/enums/battler-tag-type.ts b/src/data/enums/battler-tag-type.ts index 6d36cdafab5..98f01c9c375 100644 --- a/src/data/enums/battler-tag-type.ts +++ b/src/data/enums/battler-tag-type.ts @@ -57,5 +57,6 @@ export enum BattlerTagType { GROUNDED = "GROUNDED", MAGNET_RISEN = "MAGNET_RISEN", MINIMIZED = "MINIMIZED", - DESTINY_BOND = "DESTINY_BOND" + DESTINY_BOND = "DESTINY_BOND", + ICE_FACE = "ICE_FACE" } diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index d87e4d90299..da6bac42ac7 100644 --- a/src/data/pokemon-forms.ts +++ b/src/data/pokemon-forms.ts @@ -730,6 +730,10 @@ export const pokemonFormChanges: PokemonFormChanges = { [Species.GALAR_DARMANITAN]: [ new SpeciesFormChange(Species.GALAR_DARMANITAN, "", "zen", new SpeciesFormChangeManualTrigger(), true), new SpeciesFormChange(Species.GALAR_DARMANITAN, "zen", "", new SpeciesFormChangeManualTrigger(), true) + ], + [Species.EISCUE]: [ + new SpeciesFormChange(Species.EISCUE, "", "no-ice", new SpeciesFormChangeManualTrigger(), true), + new SpeciesFormChange(Species.EISCUE, "no-ice", "", new SpeciesFormChangeManualTrigger(), true), ] }; diff --git a/src/locales/de/ability-trigger.ts b/src/locales/de/ability-trigger.ts index 07853233a01..d3fbdfc1b77 100644 --- a/src/locales/de/ability-trigger.ts +++ b/src/locales/de/ability-trigger.ts @@ -4,4 +4,5 @@ export const abilityTriggers: SimpleTranslationEntries = { "blockRecoilDamage" : "{{pokemonName}} wurde durch {{abilityName}}\nvor Rückstoß geschützt!", "badDreams": "{{pokemonName}} ist in einem Alptraum gefangen!", "windPowerCharged": "Being hit by {{moveName}} charged {{pokemonName}} with power!", + "iceFaceAvoidedDamage": "{{pokemonName}} avoided\ndamage with {{abilityName}}!" } as const; diff --git a/src/locales/en/ability-trigger.ts b/src/locales/en/ability-trigger.ts index b6e4c7c67fd..a99053785ab 100644 --- a/src/locales/en/ability-trigger.ts +++ b/src/locales/en/ability-trigger.ts @@ -5,5 +5,6 @@ export const abilityTriggers: SimpleTranslationEntries = { "badDreams": "{{pokemonName}} is tormented!", "windPowerCharged": "Being hit by {{moveName}} charged {{pokemonName}} with power!", "perishBody": "{{pokemonName}}'s {{abilityName}}\nwill faint both pokemon in 3 turns!", - "poisonHeal": "{{pokemonName}}'s {{abilityName}}\nrestored its HP a little!" + "poisonHeal": "{{pokemonName}}'s {{abilityName}}\nrestored its HP a little!", + "iceFaceAvoidedDamage": "{{pokemonName}} avoided\ndamage with {{abilityName}}!" } as const; diff --git a/src/locales/es/ability-trigger.ts b/src/locales/es/ability-trigger.ts index 594a67628e8..3ede2cd9cf3 100644 --- a/src/locales/es/ability-trigger.ts +++ b/src/locales/es/ability-trigger.ts @@ -4,4 +4,5 @@ export const abilityTriggers: SimpleTranslationEntries = { "blockRecoilDamage" : "¡{{abilityName}} de {{pokemonName}}\nlo protegió del daño de retroceso!", "badDreams": "¡{{pokemonName}} está atormentado!", "windPowerCharged": "¡{{pokemonName}} se ha cargado de electricidad gracias a {{moveName}}!", + "iceFaceAvoidedDamage": "{{pokemonName}} avoided\ndamage with {{abilityName}}!" } as const; diff --git a/src/locales/fr/ability-trigger.ts b/src/locales/fr/ability-trigger.ts index eb450117238..f3311118591 100644 --- a/src/locales/fr/ability-trigger.ts +++ b/src/locales/fr/ability-trigger.ts @@ -3,5 +3,6 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const abilityTriggers: SimpleTranslationEntries = { "blockRecoilDamage" : "{{abilityName}}\nde {{pokemonName}} le protège du contrecoup !", "badDreams": "{{pokemonName}} a le sommeil agité !", - "windPowerCharged": "{{pokemonName}} a été touché par la capacité {{moveName}} et se charge en électricité !" + "windPowerCharged": "{{pokemonName}} a été touché par la capacité {{moveName}} et se charge en électricité !", + "iceFaceAvoidedDamage": "{{pokemonName}} avoided\ndamage with {{abilityName}}!" } as const; diff --git a/src/locales/it/ability-trigger.ts b/src/locales/it/ability-trigger.ts index 61053896c49..8aa81f6090d 100644 --- a/src/locales/it/ability-trigger.ts +++ b/src/locales/it/ability-trigger.ts @@ -4,4 +4,5 @@ export const abilityTriggers: SimpleTranslationEntries = { "blockRecoilDamage" : "{{abilityName}} di {{pokemonName}}\nl'ha protetto dal contraccolpo!", "badDreams": "{{pokemonName}} è tormentato!", "windPowerCharged": "Being hit by {{moveName}} charged {{pokemonName}} with power!", + "iceFaceAvoidedDamage": "{{pokemonName}} avoided\ndamage with {{abilityName}}!" } as const; diff --git a/src/locales/ko/ability-trigger.ts b/src/locales/ko/ability-trigger.ts index 7fc98edce76..8fed1208fff 100644 --- a/src/locales/ko/ability-trigger.ts +++ b/src/locales/ko/ability-trigger.ts @@ -3,4 +3,5 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const abilityTriggers: SimpleTranslationEntries = { "blockRecoilDamage" : "{{pokemonName}}[[는]] {{abilityName}} 때문에\n반동 데미지를 받지 않는다!", "badDreams": "{{pokemonName}}[[는]]\n나이트메어 때문에 시달리고 있다!", + "iceFaceAvoidedDamage": "{{pokemonName}} avoided\ndamage with {{abilityName}}!" } as const; diff --git a/src/locales/pt_BR/ability-trigger.ts b/src/locales/pt_BR/ability-trigger.ts index 8c57fb5b98c..19b043094a5 100644 --- a/src/locales/pt_BR/ability-trigger.ts +++ b/src/locales/pt_BR/ability-trigger.ts @@ -3,5 +3,6 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const abilityTriggers: SimpleTranslationEntries = { "blockRecoilDamage" : "{{abilityName}} de {{pokemonName}}\nprotegeu-o do dano de recuo!", "badDreams": "{{pokemonName}} está tendo pesadelos!", - "windPowerCharged": "Ser atingido por {{moveName}} carregou {{pokemonName}} com poder!" + "windPowerCharged": "Ser atingido por {{moveName}} carregou {{pokemonName}} com poder!", + "iceFaceAvoidedDamage": "{{pokemonName}} avoided\ndamage with {{abilityName}}!" } as const; diff --git a/src/locales/zh_CN/ability-trigger.ts b/src/locales/zh_CN/ability-trigger.ts index 643952ad352..07fedd8ac3e 100644 --- a/src/locales/zh_CN/ability-trigger.ts +++ b/src/locales/zh_CN/ability-trigger.ts @@ -3,5 +3,6 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const abilityTriggers: SimpleTranslationEntries = { "blockRecoilDamage" : "{{pokemonName}} 的 {{abilityName}}\n抵消了反作用力!", "badDreams": "{{pokemonName}} 被折磨着!", - "windPowerCharged": "受 {{moveName}} 的影响, {{pokemonName}} 提升了能力!" + "windPowerCharged": "受 {{moveName}} 的影响, {{pokemonName}} 提升了能力!", + "iceFaceAvoidedDamage": "{{pokemonName}} avoided\ndamage with {{abilityName}}!" } as const; diff --git a/src/locales/zh_TW/ability-trigger.ts b/src/locales/zh_TW/ability-trigger.ts index 1d9a3446785..1281a8720c1 100644 --- a/src/locales/zh_TW/ability-trigger.ts +++ b/src/locales/zh_TW/ability-trigger.ts @@ -3,5 +3,6 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const abilityTriggers: SimpleTranslationEntries = { "blockRecoilDamage" : "{{pokemonName}} 的 {{abilityName}}\n抵消了反作用力!", "badDreams": "{{pokemonName}} 被折磨着!", - "windPowerCharged": "Being hit by {{moveName}} charged {{pokemonName}} with power!" + "windPowerCharged": "Being hit by {{moveName}} charged {{pokemonName}} with power!", + "iceFaceAvoidedDamage": "{{pokemonName}} avoided\ndamage with {{abilityName}}!" } as const; From 223d8a731df8b80686a8dfd323ab8481c5e524c6 Mon Sep 17 00:00:00 2001 From: Dmitriy K Date: Thu, 6 Jun 2024 12:04:16 -0400 Subject: [PATCH 086/129] [Feature] Implement Full Heal/Lum/Full Restore to heal Confusion #1658 (#1876) * Added Confusion to be healed with Full Heals and Full Restores * Semi-Colon oversight * Changed resetStatus to have a condition whether to include confusion or not, defaults to false so you manually have to add * Fixed spacing and semicolon * Refactored the Lum Berry case * Fix berry conflicts * Update {} * Fix PP Conflict * Build fix? * Fix Modifier * Build Fix * Fix * Fix StatuHeal from eslint * Fix revive (will show testing through everything again) * Update documentation --------- Co-authored-by: Ethan Co-authored-by: Ethan <71776311+EvasiveAce@users.noreply.github.com> --- src/data/berry.ts | 7 ++----- src/field/pokemon.ts | 12 +++++++++--- src/modifier/modifier-type.ts | 5 +++-- src/modifier/modifier.ts | 7 ++----- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/data/berry.ts b/src/data/berry.ts index f5bcd5e4be8..38f8f924c8a 100644 --- a/src/data/berry.ts +++ b/src/data/berry.ts @@ -81,12 +81,9 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { } if (pokemon.status) { pokemon.scene.queueMessage(getPokemonMessage(pokemon, getStatusEffectHealText(pokemon.status.effect))); - pokemon.resetStatus(); - pokemon.updateInfo(); - } - if (pokemon.getTag(BattlerTagType.CONFUSED)) { - pokemon.lapseTag(BattlerTagType.CONFUSED); } + pokemon.resetStatus(true, true); + pokemon.updateInfo(); }; case BerryType.LIECHI: case BerryType.GANLON: diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 86909f056aa..285583bd05a 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -2438,10 +2438,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * Resets the status of a pokemon - * @param revive whether revive should be cured, defaults to true + * Resets the status of a pokemon. + * @param revive Whether revive should be cured; defaults to true. + * @param confusion Whether resetStatus should include confusion or not; defaults to false. */ - resetStatus(revive: boolean = true): void { + resetStatus(revive: boolean = true, confusion: boolean = false): void { const lastStatus = this.status?.effect; if (!revive && lastStatus === StatusEffect.FAINT) { return; @@ -2453,6 +2454,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.lapseTag(BattlerTagType.NIGHTMARE); } } + if (confusion) { + if (this.getTag(BattlerTagType.CONFUSED)) { + this.lapseTag(BattlerTagType.CONFUSED); + } + } } primeSummonData(summonDataPrimer: PokemonSummonData): void { diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 3d03f1710f7..46019281d4b 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -23,6 +23,7 @@ import { ModifierTier } from "./modifier-tier"; import { Nature, getNatureName, getNatureStatMultiplier } from "#app/data/nature"; import i18next from "#app/plugins/i18n"; import { getModifierTierTextTint } from "#app/ui/text"; +import { BattlerTagType } from "#app/data/enums/battler-tag-type.js"; import * as Overrides from "../overrides"; const outputModifierData = false; @@ -233,7 +234,7 @@ export class PokemonHpRestoreModifierType extends PokemonModifierType { constructor(localeKey: string, iconImage: string, restorePoints: integer, restorePercent: integer, healStatus: boolean = false, newModifierFunc?: NewModifierFunc, selectFilter?: PokemonSelectFilter, group?: string) { super(localeKey, iconImage, newModifierFunc || ((_type, args) => new Modifiers.PokemonHpRestoreModifier(this, (args[0] as PlayerPokemon).id, this.restorePoints, this.restorePercent, this.healStatus, false)), selectFilter || ((pokemon: PlayerPokemon) => { - if (!pokemon.hp || (pokemon.hp >= pokemon.getMaxHp() && (!this.healStatus || !pokemon.status))) { + if (!pokemon.hp || (pokemon.hp >= pokemon.getMaxHp() && (!this.healStatus || (!pokemon.status && !pokemon.getTag(BattlerTagType.CONFUSED))))) { return PartyUiHandler.NoEffectMessage; } return null; @@ -283,7 +284,7 @@ export class PokemonStatusHealModifierType extends PokemonModifierType { constructor(localeKey: string, iconImage: string) { super(localeKey, iconImage, ((_type, args) => new Modifiers.PokemonStatusHealModifier(this, (args[0] as PlayerPokemon).id)), ((pokemon: PlayerPokemon) => { - if (!pokemon.hp || !pokemon.status) { + if (!pokemon.hp || (!pokemon.status && !pokemon.getTag(BattlerTagType.CONFUSED))) { return PartyUiHandler.NoEffectMessage; } return null; diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 26d68d74e87..b602dfbdc9e 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1130,13 +1130,11 @@ export class PokemonHpRestoreModifier extends ConsumablePokemonModifier { restorePoints = Math.floor(restorePoints * (args[1] as number)); } if (this.fainted || this.healStatus) { - pokemon.resetStatus(); + pokemon.resetStatus(true, true); } pokemon.hp = Math.min(pokemon.hp + Math.max(Math.ceil(Math.max(Math.floor((this.restorePercent * 0.01) * pokemon.getMaxHp()), restorePoints)), 1), pokemon.getMaxHp()); - return true; } - return false; } } @@ -1148,8 +1146,7 @@ export class PokemonStatusHealModifier extends ConsumablePokemonModifier { apply(args: any[]): boolean { const pokemon = args[0] as Pokemon; - pokemon.resetStatus(); - + pokemon.resetStatus(true, true); return true; } } From a221a46220569db82a8aeee9080876ae0942d4a2 Mon Sep 17 00:00:00 2001 From: HighMans <42877729+HighMans@users.noreply.github.com> Date: Thu, 6 Jun 2024 12:10:38 -0400 Subject: [PATCH 087/129] Attempt to sort items on summary & battle screen for user's party. (#1188) * Sort items in summary screen by type then name. * Use modifierSortFunc from modifier.ts * Implement proper sort function which also applies to the battle scene. * Implement proper sort function which also applies to the battle scene. * Run linter. * Fix type assertions. --- src/modifier/modifier.ts | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index b602dfbdc9e..271cf38cb5c 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -29,10 +29,25 @@ export type ModifierPredicate = (modifier: Modifier) => boolean; const iconOverflowIndex = 24; export const modifierSortFunc = (a: Modifier, b: Modifier) => { + const itemNameMatch = a.type.name.localeCompare(b.type.name); + const typeNameMatch = a.constructor.name.localeCompare(b.constructor.name); const aId = a instanceof PokemonHeldItemModifier ? a.pokemonId : 4294967295; const bId = b instanceof PokemonHeldItemModifier ? b.pokemonId : 4294967295; - return aId < bId ? 1 : aId > bId ? -1 : 0; + //First sort by pokemonID + if (aId < bId) { + return 1; + } else if (aId>bId) { + return -1; + } else if (aId === bId) { + //Then sort by item type + if (typeNameMatch === 0) { + return itemNameMatch; + //Finally sort by item name + } else { + return typeNameMatch; + } + } }; export class ModifierBar extends Phaser.GameObjects.Container { @@ -50,18 +65,19 @@ export class ModifierBar extends Phaser.GameObjects.Container { this.removeAll(true); const visibleIconModifiers = modifiers.filter(m => m.isIconVisible(this.scene as BattleScene)); - - visibleIconModifiers.sort(modifierSortFunc); + const nonPokemonSpecificModifiers = visibleIconModifiers.filter(m => !(m as PokemonHeldItemModifier).pokemonId).sort(modifierSortFunc); + const pokemonSpecificModifiers = visibleIconModifiers.filter(m => (m as PokemonHeldItemModifier).pokemonId).sort(modifierSortFunc); + const sortedVisibleIconModifiers = nonPokemonSpecificModifiers.concat(pokemonSpecificModifiers); const thisArg = this; - visibleIconModifiers.forEach((modifier: PersistentModifier, i: integer) => { + sortedVisibleIconModifiers.forEach((modifier: PersistentModifier, i: integer) => { const icon = modifier.getIcon(this.scene as BattleScene); if (i >= iconOverflowIndex) { icon.setVisible(false); } this.add(icon); - this.setModifierIconPosition(icon, visibleIconModifiers.length); + this.setModifierIconPosition(icon, sortedVisibleIconModifiers.length); icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 24), Phaser.Geom.Rectangle.Contains); icon.on("pointerover", () => { (this.scene as BattleScene).ui.showTooltip(modifier.type.name, modifier.type.getDescription(this.scene as BattleScene)); From 90aa9b42099a770fdbfca2dbfb94cc571b5032b7 Mon Sep 17 00:00:00 2001 From: Lee ByungHoon Date: Fri, 7 Jun 2024 01:22:31 +0900 Subject: [PATCH 088/129] [Localization] #1761 Korean trainer dialogue (#1853) * [Localization] #1761 Translation to Korean about some trainers ### trainer list - ace_trainer - parasol_lady - twins - cyclist - black_belt * fixed the grammar of some sentences. and changed Korean about ace trainer. * Modify translation dialogue with parasol lday and cyclist --- src/locales/ko/dialogue.ts | 66 +++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/locales/ko/dialogue.ts b/src/locales/ko/dialogue.ts index 83e49e33923..795bc82ae7c 100644 --- a/src/locales/ko/dialogue.ts +++ b/src/locales/ko/dialogue.ts @@ -144,69 +144,69 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "ace_trainer": { "encounter": { - 1: "You seem quite confident.", - 2: "Your Pokémon… Show them to me…", - 3: "Because I'm an Ace Trainer, people think I'm strong.", - 4: "Are you aware of what it takes to be an Ace Trainer?" + 1: "너는 꽤 자신만만해 보이는구나.", + 2: "너의 포켓몬들… 나에게 보여줘…", + 3: "내가 엘리트 트레이너라서, 사람들은 내가 강하다고 생각해.", + 4: "엘리트 트레이너가 되려면 무엇이 필요한지 알고 있니?" }, "victory": { - 1: "Yes… You have good Pokémon…", - 2: "What?! But I'm a battling genius!", - 3: "Of course, you are the main character!", - 4: "OK! OK! You could be an Ace Trainer!" + 1: "그러네… 넌 좋은 포켓몬을 가졌구나…", + 2: "뭐? 말도 안 돼, 난 배틀 천재라구!", + 3: "그래도 역시, 네가 주인공이구나!", + 4: "좋아! 좋아! 너도 엘리트 트레이너가 될 수 있겠어!" }, "defeat": { - 1: "I am devoting my body and soul to Pokémon battles!", - 2: "All within my expectations… Nothing to be surprised about…", - 3: "I thought I'd grow up to be a frail person who looked like they would break if you squeezed them too hard.", - 4: "Of course I'm strong and don't lose. It's important that I win gracefully." + 1: "난 내 몸과 마음을 포켓몬 배틀에 바치고 있어!", + 2: "모두 예상된 일이었어… 이건 놀랄 일이 아니야…", + 3: "난 내가 강하게 압박하면 무너질 연약한 사람이라고 생각했어.", + 4: "난 강하고, 지지 않아. 그저 멋지게 이길 수 없었기 때문이야." } }, "parasol_lady": { "encounter": { - 1: "Time to grace the battlefield with elegance and poise!", + 1: "우아함과 침착함으로 승부하겠습니다!", }, "victory": { - 1: "My elegance remains unbroken!", + 1: "그래도 제 우아함은 무너지지 않아요!", } }, "twins": { "encounter": { - 1: "Get ready, because when we team up, it's double the trouble!", - 2: "Two hearts, one strategy – let's see if you can keep up with our twin power!", - 3: "Hope you're ready for double trouble, because we're about to bring the heat!" + 1: "각오해, 우리가 한 팀이 되면 두 배로 강해진다구!", + 2: "마음은 두 개지만 우리는 하나 – 네가 쌍둥이의 파워를 따라잡을 수 있는지 보겠어!", + 3: "두 배로 각오하는 게 좋을 거야. 우리가 곧 화나게 할 거거든!" }, "victory": { - 1: "We may have lost this round, but our bond remains unbreakable!", - 2: "Our twin spirit won't be dimmed for long.", - 3: "We'll come back stronger as a dynamic duo!" + 1: "비록 우리는 졌지만, 우리의 관계는 깨지지 않아!", + 2: "그래도 우리의 영혼은 오랫동안 흐려지지 않을 거야.", + 3: "더 강력한 듀오로 강해져서 돌아오겠어!" }, "defeat": { - 1: "Twin power reigns supreme!", - 2: "Two hearts, one triumph!", - 3: "Double the smiles, double the victory dance!" + 1: "우리 쌍둥이의 파워는 최고야!", + 2: "마음은 두 개지만 승리는 하나!", + 3: "미소도 두 배, 승리의 댄스도 두 배!" } }, "cyclist": { "encounter": { - 1: "Get ready to eat my dust!", - 2: "Gear up, challenger! I'm about to leave you in the dust!", - 3: "Pedal to the metal, let's see if you can keep pace!" + 1: "내가 만든 먼지나 마실 준비하시지!", + 2: "준비하라구! 난 널 먼지 속에 놓고 올 거니까!", + 3: "전력을 다해야 할 거야, 네가 날 따라올 수 있는지 지켜보겠어!" }, "victory": { - 1: "Spokes may be still, but determination pedals on.", - 2: "Outpaced!", - 3: "The road to victory has many twists and turns yet to explore." + 1: "바퀴가 움직이지 않더라도, 나의 페달은 그렇지 않아.", + 2: "이런, 따라잡혔어!", + 3: "승리로 가는 길에는 아직 만나지 못한 우여곡절이 많이 있구나." }, }, "black_belt": { "encounter": { - 1: "I praise your courage in challenging me! For I am the one with the strongest kick!", - 2: "Oh, I see. Would you like to be cut to pieces? Or do you prefer the role of punching bag?" + 1: "네 용기가 대단하구나! 난 최고의 발차기 실력을 갖추고 있다구!", + 2: "오, 알겠어. 산산조각이 나고 싶구나? 아니면 샌드백이 되고 싶은 거야?" }, "victory": { - 1: "Oh. The Pokémon did the fighting. My strong kick didn't help a bit.", - 2: "Hmmm… If I was going to lose anyway, I was hoping to get totally messed up in the process." + 1: "이런, 포켓몬이 배틀을 하는 동안 내 발차기는 전혀 도움이 되지 않았어.", + 2: "음… 어차피 내가 질거라면, 완전 엉망진창이 되길 바랬는데…" }, }, "battle_girl": { From 14b5d141c12cbbe91cd3ba5a5ba872dc1fd4a288 Mon Sep 17 00:00:00 2001 From: Noor Q <65061866+Mehauk@users.noreply.github.com> Date: Thu, 6 Jun 2024 10:50:02 -0600 Subject: [PATCH 089/129] [QoL] Added an indicator when money changes. (#1772) * added an animation and color shift when adding/removing money * eslint fix * The animation is now vertically centered, larger and faster. --- src/battle-scene.ts | 36 +++++++++++++++++++++++++++++------- src/phases.ts | 2 ++ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 638314b3444..031106d016f 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -13,7 +13,7 @@ import { Biome } from "./data/enums/biome"; import { Arena, ArenaBase } from "./field/arena"; import { GameData } from "./system/game-data"; import { PlayerGender } from "./data/enums/player-gender"; -import { TextStyle, addTextObject } from "./ui/text"; +import { TextStyle, addTextObject, getTextColor } from "./ui/text"; import { Moves } from "./data/enums/moves"; import { allMoves } from "./data/move"; import { ModifierPoolType, getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getPartyLuckValue } from "./modifier/modifier-type"; @@ -409,28 +409,28 @@ export default class BattleScene extends SceneBase { this.biomeWaveText = addTextObject(this, (this.game.canvas.width / 6) - 2, 0, startingWave.toString(), TextStyle.BATTLE_INFO); this.biomeWaveText.setName("text-biome-wave"); - this.biomeWaveText.setOrigin(1, 0); + this.biomeWaveText.setOrigin(1, 0.5); this.fieldUI.add(this.biomeWaveText); this.moneyText = addTextObject(this, (this.game.canvas.width / 6) - 2, 0, "", TextStyle.MONEY); this.moneyText.setName("text-money"); - this.moneyText.setOrigin(1, 0); + this.moneyText.setOrigin(1, 0.5); this.fieldUI.add(this.moneyText); this.scoreText = addTextObject(this, (this.game.canvas.width / 6) - 2, 0, "", TextStyle.PARTY, { fontSize: "54px" }); this.scoreText.setName("text-score"); - this.scoreText.setOrigin(1, 0); + this.scoreText.setOrigin(1, 0.5); this.fieldUI.add(this.scoreText); this.luckText = addTextObject(this, (this.game.canvas.width / 6) - 2, 0, "", TextStyle.PARTY, { fontSize: "54px" }); this.luckText.setName("text-luck"); - this.luckText.setOrigin(1, 0); + this.luckText.setOrigin(1, 0.5); this.luckText.setVisible(false); this.fieldUI.add(this.luckText); this.luckLabelText = addTextObject(this, (this.game.canvas.width / 6) - 2, 0, "Luck:", TextStyle.PARTY, { fontSize: "54px" }); this.luckLabelText.setName("text-luck-label"); - this.luckLabelText.setOrigin(1, 0); + this.luckLabelText.setOrigin(1, 0.5); this.luckLabelText.setVisible(false); this.fieldUI.add(this.luckLabelText); @@ -1378,6 +1378,24 @@ export default class BattleScene extends SceneBase { } } + animateMoneyChanged(positiveChange: boolean): void { + let deltaScale = this.moneyText.scale * 0.14; + if (positiveChange) { + this.moneyText.setShadowColor("#008000"); + } else { + this.moneyText.setShadowColor("#FF0000"); + deltaScale = -deltaScale; + } + this.tweens.add({ + targets: this.moneyText, + duration: Utils.fixedInt(250), + scale: this.moneyText.scale + deltaScale, + loop: 0, + yoyo: true, + onComplete: (_) => this.moneyText.setShadowColor(getTextColor(TextStyle.MONEY, true)), + }); + } + updateScoreText(): void { this.scoreText.setText(`Score: ${this.score.toString()}`); this.scoreText.setVisible(this.gameMode.isDaily); @@ -1421,7 +1439,10 @@ export default class BattleScene extends SceneBase { updateUIPositions(): void { const enemyModifierCount = this.enemyModifiers.filter(m => m.isIconVisible(this)).length; - this.biomeWaveText.setY(-(this.game.canvas.height / 6) + (enemyModifierCount ? enemyModifierCount <= 12 ? 15 : 24 : 0)); + const biomeWaveTextHeight = this.biomeWaveText.getBottomLeft().y - this.biomeWaveText.getTopLeft().y; + this.biomeWaveText.setY( + -(this.game.canvas.height / 6) + (enemyModifierCount ? enemyModifierCount <= 12 ? 15 : 24 : 0) + (biomeWaveTextHeight / 2) + ); this.moneyText.setY(this.biomeWaveText.y + 10); this.scoreText.setY(this.moneyText.y + 10); [ this.luckLabelText, this.luckText ].map(l => l.setY((this.scoreText.visible ? this.scoreText : this.moneyText).y + 10)); @@ -1823,6 +1844,7 @@ export default class BattleScene extends SceneBase { addMoney(amount: integer): void { this.money = Math.min(this.money + amount, Number.MAX_SAFE_INTEGER); this.updateMoneyText(); + this.animateMoneyChanged(true); this.validateAchvs(MoneyAchv); } diff --git a/src/phases.ts b/src/phases.ts index c43ab6e1c7f..bb875fe2470 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -4921,6 +4921,7 @@ export class SelectModifierPhase extends BattlePhase { this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); this.scene.money -= rerollCost; this.scene.updateMoneyText(); + this.scene.animateMoneyChanged(false); this.scene.playSound("buy"); } } else if (cursor === 1) { @@ -4966,6 +4967,7 @@ export class SelectModifierPhase extends BattlePhase { if (success) { this.scene.money -= cost; this.scene.updateMoneyText(); + this.scene.animateMoneyChanged(false); this.scene.playSound("buy"); (this.scene.ui.getHandler() as ModifierSelectUiHandler).updateCostText(); } else { From f3de114d2bfdef27eba9cc0a1edfdcbfc48ce29b Mon Sep 17 00:00:00 2001 From: MadridPawmot <42167718+Alekemon@users.noreply.github.com> Date: Thu, 6 Jun 2024 18:55:32 +0200 Subject: [PATCH 090/129] [enhancement] Added Sailor trainer class (#1411) * Add files via upload * Update trainer-type.ts * Update biomes.ts * Update trainer-config.ts * Update trainer-names.ts * Update trainer-names.ts * Update trainers.ts * Update trainers.ts * Added German translation Thanks to CodeTappert * Update trainers.ts * Update trainers.ts * Update trainers.ts * Update trainers.ts * Update trainers.ts * Update trainers.ts * Update trainers.ts to resolve conflicts * Fixed syntax error * Update biomes.ts * Re-added possible biomes for the Sailor trainer class * Added data for dialogue lines for the sailor class * Added dialogue from FRLG * Added locale * Added locale * Added locale * Update trainers.ts * Added locale * Added locale * Added Korean translation * Added locale * Added Portuguese translation * Added locale * Added locale * Added Mandarin Chinese translation * Added Cantonese Chinese translation * Added images * Recentered sprite * Added Spanish translation for the dialogue * Delete duplicate * Delete duplicate * Updated dialogue with requested changes * Update Spanish translation to reflect the changes * Update dialogue.ts * Update dialogue.ts * Update dialogue.ts * Update dialogue.ts * Update dialogue.ts * Update dialogue.ts * Update dialogue.ts --- public/images/trainer/sailor.json | 41 ++++++++++++++++++++++++++++++ public/images/trainer/sailor.png | Bin 0 -> 2778 bytes src/data/biomes.ts | 9 +++++-- src/data/dialogue.ts | 14 ++++++++++ src/data/enums/trainer-type.ts | 1 + src/data/trainer-config.ts | 1 + src/data/trainer-names.ts | 2 ++ src/locales/de/dialogue.ts | 12 +++++++++ src/locales/de/trainers.ts | 1 + src/locales/en/dialogue.ts | 12 +++++++++ src/locales/en/trainers.ts | 1 + src/locales/es/dialogue.ts | 12 +++++++++ src/locales/es/trainers.ts | 1 + src/locales/fr/dialogue.ts | 12 +++++++++ src/locales/fr/trainers.ts | 1 + src/locales/it/dialogue.ts | 12 +++++++++ src/locales/it/trainers.ts | 1 + src/locales/ko/dialogue.ts | 12 +++++++++ src/locales/ko/trainers.ts | 1 + src/locales/pt_BR/dialogue.ts | 12 +++++++++ src/locales/pt_BR/trainers.ts | 1 + src/locales/zh_CN/dialogue.ts | 12 +++++++++ src/locales/zh_CN/trainers.ts | 1 + src/locales/zh_TW/dialogue.ts | 12 +++++++++ src/locales/zh_TW/trainers.ts | 1 + 25 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 public/images/trainer/sailor.json create mode 100644 public/images/trainer/sailor.png diff --git a/public/images/trainer/sailor.json b/public/images/trainer/sailor.json new file mode 100644 index 00000000000..6a9331dbaf6 --- /dev/null +++ b/public/images/trainer/sailor.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "sailor.png", + "format": "RGBA8888", + "size": { + "w": 72, + "h": 72 + }, + "scale": 1, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 72, + "h": 72 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 73, + "h": 73 + }, + "frame": { + "x": 0, + "y": 0, + "w": 39, + "h": 72 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:f692676a166fc1915532cd94d5799af4:fb833f76fb6797474657726bb59a7eee:aeb55e30992938f494b6cd2420158dda$" + } +} diff --git a/public/images/trainer/sailor.png b/public/images/trainer/sailor.png new file mode 100644 index 0000000000000000000000000000000000000000..ec61152bce3593009032e1d9986ddeeea57b6825 GIT binary patch literal 2778 zcmV<03MKW4P)Px zrtF*f3aqSLR4uCIa(4DJ9F%9yR{s0s5T5_%Y1Z*kGlys>`)=Dh%f%Qjlr73e6(Qo% znpIb)r!VQ86+{KFxjt^Al!!-|XXo}UrJ#h$W0LmkY$w(s5^n%?$hEQAS@C#$iM1Qk z=ZR@zNLq??#I?lA+vFKQ^^f8)xtQzF)m68E-0Sup0QW3qw_1FEHWuTdTE zuWsmdDVMslbWUMr_WWnKI{gYby$g^mk7{dAK{DiWt?>*Nb z4jRSgO3z(b7f?dQMZQsOZ;W97aOQNt}+(FUpiB9PDs^l2tsT1L> zF24bzrHe4y+=VR5;M?LOSl!s92F%V+2;8(hgzDD{W`_lZZUj{Q_$^%7*>lFBP+Um6 zJ`7yA2OX`k0ebPUfYD!8o8<*V+C4ge*};TLLO={M`_t84BiS&M*`T;m94;2l%z#ia z2m+7M@+!F!&cW(}>quQ+Zzn<+urXghK;gEfDa@ZbeyL$82O;SJ+L#TBJPeWCj{*Jp zDst00DQtc?1ml{FX;j!fiQ0B~}6 zP`eriZgq|WHa7$VH@e-lJQ!4Wz%eWvF(DM~byHb^oPHtlP~QoCD%<{}8Ek(zqRTv4 zj97`pTeIMl$@8Eh-^4JC!?(;^;RuERPR@S;9RBeW4^$W*%B?J}wO}g;6dxgTl0p;j za1MjV1IftL%{-h6{5fq3xx z3curdJop-ZM=*F`US6|(G5tV#<-E-8U5Zw2yRrSTAI$g}y1%Be* z4nyKHQ0{s0Io%xG+tmX#253BZ@<($UW$m-mCk+(bj^%?pLn>BxZ|)S#WVlbXVFLk* z>{7zji-&g`RU?@DxSy=tX7Ap0s|qksx3`^vZaK@}oz7FRBq&q4@#gYVy}j@`U`t z49eFdzzure!SMzm;iEEQi2-=8fBv_mhtV4Z2m>~QS-UWU;|M@v?;a2m9?dgF6~Oqt zMc%V_cb#t{(d4ovx=}o-_I9tyn-WcW0`%q#%2%Tr)My);XN9!=d*e|v(Jknr_+S<0 zQ^7pgX|0*V)DaWPgN->oeOcLpE~^JCDPLcP{K5;W58dqE(SdaWBbmwX9LLfD#h5rO zjTaEdA2P}4^U;`(bCiqO z3P4DFFd#Y1yYyo_U+)TJFdox$j={LtX^*e_`>thGCCaJGjYkKvuLIu-9s&VQNVXhp)v@9Qmk%vVkl zZ%*ocXzvK#v!^&)jDm(#k*f40KsGiQKf13X#H)`h$2}F4 zYIS6LC_D$adlh7>r#vblU$?xLX(YC(^7j{mB38~tv0vr+S~T+%6`7F!7=XHxa=tic zRZw{hYx!D_J&QgQ#pr(PoiG%nY4XgkUb-esX6;TD1t!>wQqYj_od`&w5P{jQqV!;m zQ0=dZcWW48aH^F00SeTT5{I~`_r{lSIuX+zQTx15b>}|v#z6?G=3-@em#nmO1f52 z1yCNBkgw%UW9hf?cND0gyBeTyOsWA{9(jo{!j{BbaQ5B1_XBEmtqt{ZGN{EHB`H%u zXn<8kLV|t;El@g+ZG^Cs(S$(dpB+JB$`) zRZ)B?MyGD@HI=|?IUiJ+C)$owzv)?UyoZe)#Cnw9Ji~O`}ysM|i zo`G>bwPDu1A^l}IYu`0+WP1@5LmgzdOD?H0a%d#AZS{X8=yP|!64joPc}D~{^;70Q gsSTY+`u_v(#sQf=B|lIA0000 s.isOfType(Type.DARK)), + [TrainerType.SAILOR]: new TrainerConfig(++t).setMoneyMultiplier(1.4).setEncounterBgm(TrainerType.BACKPACKER).setSpeciesFilter(s => s.isOfType(Type.WATER) || s.isOfType(Type.FIGHTING)), [TrainerType.SCIENTIST]: new TrainerConfig(++t).setHasGenders("Scientist Female").setHasDouble("Scientists").setMoneyMultiplier(1.7).setEncounterBgm(TrainerType.SCIENTIST) .setSpeciesPools({ [TrainerPoolTier.COMMON]: [Species.MAGNEMITE, Species.GRIMER, Species.DROWZEE, Species.VOLTORB, Species.KOFFING], diff --git a/src/data/trainer-names.ts b/src/data/trainer-names.ts index 0aa5bb594d4..2ad2060f233 100644 --- a/src/data/trainer-names.ts +++ b/src/data/trainer-names.ts @@ -60,6 +60,7 @@ const trainerNameConfigs: TrainerNameConfigs = { [TrainerType.RICH]: new TrainerNameConfig(TrainerType.RICH, "Gentleman").hasGenderVariant("Madame"), [TrainerType.RICH_KID]: new TrainerNameConfig(TrainerType.RICH_KID, "Rich_Boy").hasGenderVariant("Lady"), [TrainerType.ROUGHNECK]: new TrainerNameConfig(TrainerType.ROUGHNECK), + [TrainerType.SAILOR]: new TrainerNameConfig(TrainerType.SAILOR), [TrainerType.SCIENTIST]: new TrainerNameConfig(TrainerType.SCIENTIST), [TrainerType.SMASHER]: new TrainerNameConfig(TrainerType.SMASHER), [TrainerType.SNOW_WORKER]: new TrainerNameConfig(TrainerType.SNOW_WORKER, "Worker"), @@ -111,6 +112,7 @@ export const trainerNamePools = { [TrainerType.RICH]: [["Alfred","Edward","Gregory","Preston","Thomas","Tucker","Walter","Clifford","Everett","Micah","Nate","Pierre","Terrance","Arthur","Brooks","Emanuel","Lamar","Jeremy","Leonardo","Milton","Frederic","Renaud","Robert","Yan","Daniel","Sheldon","Stonewall","Gerald","Ronald","Smith","Stanley","Reginald","Orson","Wilco","Caden","Glenn"],["Rebecca","Reina","Cassandra","Emilia","Grace","Marian","Elizabeth","Kathleen","Sayuri","Caroline","Judy"]], [TrainerType.RICH_KID]: [["Garret","Winston","Dawson","Enrique","Jason","Roman","Trey","Liam","Anthony","Brad","Cody","Manuel","Martin","Pierce","Rolan","Keenan","Filbert","Antoin","Cyus","Diek","Dugo","Flitz","Jurek","Lond","Perd","Quint","Basto","Benit","Brot","Denc","Guyit","Marcon","Perc","Puros","Roex","Sainz","Symin","Tark","Venak"],["Anette","Brianna","Cindy","Colleen","Daphne","Elizabeth","Naomi","Sarah","Charlotte","Gillian","Jacki","Lady","Melissa","Celeste","Colette","Elizandra","Isabel","Lynette","Magnolia","Sophie","Lina","Dulcie","Auro","Brin","Caril","Eloos","Gwin","Illa","Kowly","Rima","Ristin","Vesey","Brena","Deasy","Denslon","Kylet","Nemi","Rene","Sanol","Stouner","Sturk","Talmen","Zoila"]], [TrainerType.ROUGHNECK]: ["Camron","Corey","Gabriel","Isaiah","Jamal","Koji","Luke","Paxton","Raul","Zeek","Kirby","Chance","Dave","Fletcher","Johnny","Reese","Joey","Ricky","Silvester","Martin"], + [TrainerType.SAILOR]: ["Alberto","Bost","Brennan","Brenden","Claude","Cory","Damian","Dirk","Duncan","Dwayne","Dylan","Eddie","Edmond","Elijah","Ernest","Eugene","Garrett","Golos","Gratin","Grestly","Harry","Hols","Hudson","Huey","Jebol","Jeff","Leonald","Luther","Kelvin","Kenneth","Kent","Knook","Marc","Mifis","Monar","Morkor","Ordes","Oxlin","Parker","Paul","Philip","Roberto","Samson","Skyler","Stanly","Tebu","Terrell","Trevor","Yasu","Zachariah"], [TrainerType.SCIENTIST]: [["Jed","Marc","Mitch","Rich","Ross","Beau","Braydon","Connor","Ed","Ivan","Jerry","Jose","Joshua","Parker","Rodney","Taylor","Ted","Travis","Zackery","Darrius","Emilio","Fredrick","Shaun","Stefano","Travon","Daniel","Garett","Gregg","Linden","Lowell","Trenton","Dudley","Luke","Markus","Nathan","Orville","Randall","Ron","Ronald","Simon","Steve","William","Franklin","Clarke","Jacques","Terrance","Ernst","Justus","Ikaika","Jayson","Kyle","Reid","Tyrone","Adam","Albert","Alphonse","Cory","Donnie","Elton","Francis","Gordon","Herbert","Humphrey","Jordan","Julian","Keaton","Levi","Melvin","Murray","West","Craig","Coren","Dubik","Kotan","Lethco","Mante","Mort","Myron","Odlow","Ribek","Roeck","Vogi","Vonder","Zogo","Doimo","Doton","Durel","Hildon","Kukla","Messa","Nanot","Platen","Raburn","Reman","Acrod","Coffy","Elrok","Foss","Hardig","Hombol","Hospel","Kaller","Klots","Krilok","Limar","Loket","Mesak","Morbit","Newin","Orill","Tabor","Tekot"],["Blythe","Chan","Kathrine","Marie","Maria","Naoko","Samantha","Satomi","Shannon","Athena","Caroline","Lumi","Lumina","Marissa","Sonia"]], [TrainerType.SMASHER]: ["Aspen","Elena","Mari","Amy","Lizzy"], [TrainerType.SNOW_WORKER]: [["Braden","Brendon","Colin","Conrad","Dillan","Gary","Gerardo","Holden","Jackson","Mason","Quentin","Willy","Noel","Arnold","Brady","Brand","Cairn","Cliff","Don","Eddie","Felix","Filipe","Glenn","Gus","Heath","Matthew","Patton","Rich","Rob","Ryan","Scott","Shelby","Sterling","Tyler","Victor","Zack","Friedrich","Herman","Isaac","Leo","Maynard","Mitchell","Morgann","Nathan","Niel","Pasqual","Paul","Tavarius","Tibor","Dimitri","Narek","Yusif","Frank","Jeff","Vaclav","Ovid","Francis","Keith","Russel","Sangon","Toway","Bomber","Chean","Demit","Hubor","Kebile","Laber","Ordo","Retay","Ronix","Wagel","Dobit","Kaster","Lobel","Releo","Saken","Rustix"],["Georgia","Sandra","Yvonne"]], diff --git a/src/locales/de/dialogue.ts b/src/locales/de/dialogue.ts index c69fb1aacd0..d9ad65ee8d4 100644 --- a/src/locales/de/dialogue.ts +++ b/src/locales/de/dialogue.ts @@ -379,6 +379,18 @@ export const PGMdialogue: DialogueTranslationEntries = { 3: "Ow! I scorched the tip of my nose!" }, }, + "sailor": { + "encounter": { + 1: "Matey, you're walking the plank if you lose!", + 2: "Come on then! My sailor's pride is at stake!", + 3: "Ahoy there! Are you seasick?" + }, + "victory": { + 1: "Argh! Beaten by a kid!", + 2: "Your spirit sank me!", + 3: "I think it's me that's seasick..." + }, + }, "brock": { "encounter": { 1: "Meine Expertise in Bezug auf Gesteins-Pokémon wird dich besiegen! Komm schon!", diff --git a/src/locales/de/trainers.ts b/src/locales/de/trainers.ts index 6ac1abbcabf..b7d7ec01617 100644 --- a/src/locales/de/trainers.ts +++ b/src/locales/de/trainers.ts @@ -95,6 +95,7 @@ export const trainerClasses: SimpleTranslationEntries = { "rich_kid_female": "Rich Kid", "rich_kids": "Schnösel", "roughneck": "Raufbold", + "sailor": "Matrose", "scientist": "Forscher", "scientist_female": "Forscherin", "scientists": "Forscher", diff --git a/src/locales/en/dialogue.ts b/src/locales/en/dialogue.ts index c5b0d72d3d7..601e6363f3a 100644 --- a/src/locales/en/dialogue.ts +++ b/src/locales/en/dialogue.ts @@ -371,6 +371,18 @@ export const PGMdialogue: DialogueTranslationEntries = { 3: "Ow! I scorched the tip of my nose!" }, }, + "sailor": { + "encounter": { + 1: "Matey, you're walking the plank if you lose!", + 2: "Come on then! My sailor's pride is at stake!", + 3: "Ahoy there! Are you seasick?" + }, + "victory": { + 1: "Argh! Beaten by a kid!", + 2: "Your spirit sank me!", + 3: "I think it's me that's seasick..." + }, + }, "brock": { "encounter": { 1: "My expertise on Rock-type Pokémon will take you down! Come on!", diff --git a/src/locales/en/trainers.ts b/src/locales/en/trainers.ts index cd6f78ccc13..701980f8d37 100644 --- a/src/locales/en/trainers.ts +++ b/src/locales/en/trainers.ts @@ -95,6 +95,7 @@ export const trainerClasses: SimpleTranslationEntries = { "rich_kid_female": "Rich Kid", "rich_kids": "Rich Kids", "roughneck": "Roughneck", + "sailor": "Sailor", "scientist": "Scientist", "scientist_female": "Scientist", "scientists": "Scientists", diff --git a/src/locales/es/dialogue.ts b/src/locales/es/dialogue.ts index faabab3077d..938ef331088 100644 --- a/src/locales/es/dialogue.ts +++ b/src/locales/es/dialogue.ts @@ -371,6 +371,18 @@ export const PGMdialogue: DialogueTranslationEntries = { 3: "Ow! I scorched the tip of my nose!" }, }, + "sailor": { + "encounter": { + 1: "¡Amigo, te haré caminar por la borda si pierdes!", + 2: "¡Adelante! ¡Mi orgullo como marinero está en auge!", + 3: "¡Ah del barco! ¿Estás mareado?" + }, + "victory": { + 1: "¡Argh! ¡Derrotado por un niño!", + 2: "¡Tu espíritu me ha hundido!", + 3: "Creo que soy yo quien está mareado..." + }, + }, "brock": { "encounter": { 1: "My expertise on Rock-type Pokémon will take you down! Come on!", diff --git a/src/locales/es/trainers.ts b/src/locales/es/trainers.ts index 3e2b2b1a788..6d776f44c9c 100644 --- a/src/locales/es/trainers.ts +++ b/src/locales/es/trainers.ts @@ -94,6 +94,7 @@ export const trainerClasses: SimpleTranslationEntries = { "rich_kid_female": "Niña Bien", "rich_kids": "Niños Bien", "roughneck": "Calvo", + "sailor": "Marinero", "scientist": "Científico", "scientist_female": "Científica", "scientists": "Científicos", diff --git a/src/locales/fr/dialogue.ts b/src/locales/fr/dialogue.ts index befc9d96caa..950703bfded 100644 --- a/src/locales/fr/dialogue.ts +++ b/src/locales/fr/dialogue.ts @@ -371,6 +371,18 @@ export const PGMdialogue: DialogueTranslationEntries = { 3: "Ow! I scorched the tip of my nose!" }, }, + "sailor": { + "encounter": { + 1: "Matey, you're walking the plank if you lose!", + 2: "Come on then! My sailor's pride is at stake!", + 3: "Ahoy there! Are you seasick?" + }, + "victory": { + 1: "Argh! Beaten by a kid!", + 2: "Your spirit sank me!", + 3: "I think it's me that's seasick..." + }, + }, "brock": { "encounter": { 1: "My expertise on Rock-type Pokémon will take you down! Come on!", diff --git a/src/locales/fr/trainers.ts b/src/locales/fr/trainers.ts index 6ed221a7b86..a44c03e1b68 100644 --- a/src/locales/fr/trainers.ts +++ b/src/locales/fr/trainers.ts @@ -95,6 +95,7 @@ export const trainerClasses: SimpleTranslationEntries = { "rich_kid_female": "Mademoiselle", "rich_kids": "Richards", "roughneck": "Loubard", + "sailor": "Marin", "scientist": "Scientifique", "scientist_female": "Scientifique", "scientists": "Scientifiques", diff --git a/src/locales/it/dialogue.ts b/src/locales/it/dialogue.ts index faabab3077d..26602079f65 100644 --- a/src/locales/it/dialogue.ts +++ b/src/locales/it/dialogue.ts @@ -371,6 +371,18 @@ export const PGMdialogue: DialogueTranslationEntries = { 3: "Ow! I scorched the tip of my nose!" }, }, + "sailor": { + "encounter": { + 1: "Matey, you're walking the plank if you lose!", + 2: "Come on then! My sailor's pride is at stake!", + 3: "Ahoy there! Are you seasick?" + }, + "victory": { + 1: "Argh! Beaten by a kid!", + 2: "Your spirit sank me!", + 3: "I think it's me that's seasick..." + }, + }, "brock": { "encounter": { 1: "My expertise on Rock-type Pokémon will take you down! Come on!", diff --git a/src/locales/it/trainers.ts b/src/locales/it/trainers.ts index 9c3f025541e..9c0a644c1c6 100644 --- a/src/locales/it/trainers.ts +++ b/src/locales/it/trainers.ts @@ -95,6 +95,7 @@ export const trainerClasses: SimpleTranslationEntries = { "rich_kid_female": "Rich Kid", "rich_kids": "Rich Kids", "roughneck": "Roughneck", + "sailor": "Sailor", "scientist": "Scientist", "scientist_female": "Scientist", "scientists": "Scientists", diff --git a/src/locales/ko/dialogue.ts b/src/locales/ko/dialogue.ts index 795bc82ae7c..7867ae7b021 100644 --- a/src/locales/ko/dialogue.ts +++ b/src/locales/ko/dialogue.ts @@ -371,6 +371,18 @@ export const PGMdialogue: DialogueTranslationEntries = { 3: "Ow! I scorched the tip of my nose!" }, }, + "sailor": { + "encounter": { + 1: "Matey, you're walking the plank if you lose!", + 2: "Come on then! My sailor's pride is at stake!", + 3: "Ahoy there! Are you seasick?" + }, + "victory": { + 1: "Argh! Beaten by a kid!", + 2: "Your spirit sank me!", + 3: "I think it's me that's seasick..." + }, + }, "brock": { "encounter": { 1: "내 전문인 바위 타입 포켓몬으로 널 쓰러뜨려줄게! 덤벼!", diff --git a/src/locales/ko/trainers.ts b/src/locales/ko/trainers.ts index 62e8de560d2..0bc99640333 100644 --- a/src/locales/ko/trainers.ts +++ b/src/locales/ko/trainers.ts @@ -101,6 +101,7 @@ export const trainerClasses: SimpleTranslationEntries = { "smasher": "테니스선수", "snow_worker": "작업원", "snow_worker_female": "작업원", + "sailor": "선원", "striker": "축구선수", "school_kid": "학원끝난 아이", "school_kid_female": "학원끝난 아이", diff --git a/src/locales/pt_BR/dialogue.ts b/src/locales/pt_BR/dialogue.ts index cf5ff6ccf4f..1258e41cb54 100644 --- a/src/locales/pt_BR/dialogue.ts +++ b/src/locales/pt_BR/dialogue.ts @@ -371,6 +371,18 @@ export const PGMdialogue: DialogueTranslationEntries = { 3: "Ai! Queimei minha língua!" }, }, + "sailor": { + "encounter": { + 1: "Matey, you're walking the plank if you lose!", + 2: "Come on then! My sailor's pride is at stake!", + 3: "Ahoy there! Are you seasick?" + }, + "victory": { + 1: "Argh! Beaten by a kid!", + 2: "Your spirit sank me!", + 3: "I think it's me that's seasick..." + }, + }, "brock": { "encounter": { 1: "Minha especialidade em Pokémon do tipo Pedra vai te derrubar! Vamos lá!", diff --git a/src/locales/pt_BR/trainers.ts b/src/locales/pt_BR/trainers.ts index 5d624c60ad5..f93875dcbfc 100644 --- a/src/locales/pt_BR/trainers.ts +++ b/src/locales/pt_BR/trainers.ts @@ -95,6 +95,7 @@ export const trainerClasses: SimpleTranslationEntries = { "rich_kid_female": "Garota Rica", "rich_kids": "Garotos Ricos", "roughneck": "Arruaceiro", + "sailor": "Marinheiro", "scientist": "Cientista", "scientist_female": "Cientista", "scientists": "Cientistas", diff --git a/src/locales/zh_CN/dialogue.ts b/src/locales/zh_CN/dialogue.ts index faabab3077d..26602079f65 100644 --- a/src/locales/zh_CN/dialogue.ts +++ b/src/locales/zh_CN/dialogue.ts @@ -371,6 +371,18 @@ export const PGMdialogue: DialogueTranslationEntries = { 3: "Ow! I scorched the tip of my nose!" }, }, + "sailor": { + "encounter": { + 1: "Matey, you're walking the plank if you lose!", + 2: "Come on then! My sailor's pride is at stake!", + 3: "Ahoy there! Are you seasick?" + }, + "victory": { + 1: "Argh! Beaten by a kid!", + 2: "Your spirit sank me!", + 3: "I think it's me that's seasick..." + }, + }, "brock": { "encounter": { 1: "My expertise on Rock-type Pokémon will take you down! Come on!", diff --git a/src/locales/zh_CN/trainers.ts b/src/locales/zh_CN/trainers.ts index ae0680a3c75..28b0760cc9b 100644 --- a/src/locales/zh_CN/trainers.ts +++ b/src/locales/zh_CN/trainers.ts @@ -95,6 +95,7 @@ export const trainerClasses: SimpleTranslationEntries = { "rich_kid_female": "Rich Kid", "rich_kids": "富二代组合", "roughneck": "光头男", + "sailor": "水手", "scientist": "研究员", "scientist_female": "研究员", "scientists": "研究员组合", diff --git a/src/locales/zh_TW/dialogue.ts b/src/locales/zh_TW/dialogue.ts index 7cb29ec8191..5e79471f771 100644 --- a/src/locales/zh_TW/dialogue.ts +++ b/src/locales/zh_TW/dialogue.ts @@ -371,6 +371,18 @@ export const PGMdialogue: DialogueTranslationEntries = { 3: "Ow! I scorched the tip of my nose!" }, }, + "sailor": { + "encounter": { + 1: "Matey, you're walking the plank if you lose!", + 2: "Come on then! My sailor's pride is at stake!", + 3: "Ahoy there! Are you seasick?" + }, + "victory": { + 1: "Argh! Beaten by a kid!", + 2: "Your spirit sank me!", + 3: "I think it's me that's seasick..." + }, + }, "brock": { "encounter": { 1: "My expertise on Rock-type Pokémon will take you down! Come on!", diff --git a/src/locales/zh_TW/trainers.ts b/src/locales/zh_TW/trainers.ts index f20ee156c3a..07b2949bdd8 100644 --- a/src/locales/zh_TW/trainers.ts +++ b/src/locales/zh_TW/trainers.ts @@ -95,6 +95,7 @@ export const trainerClasses: SimpleTranslationEntries = { "rich_kid_female": "富家孩子", "rich_kids": "富二代組合", "roughneck": "光頭男", + "sailor": "水手", "scientist": "研究員", "scientist_female": "研究員", "scientists": "研究員組合", From 17edaeb708b889d544eb5a86fb08d50b50234de0 Mon Sep 17 00:00:00 2001 From: HighMans <42877729+HighMans@users.noreply.github.com> Date: Thu, 6 Jun 2024 13:13:14 -0400 Subject: [PATCH 091/129] [QoL] Sort items on summary screen (#1880) --- src/ui/summary-ui-handler.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index 5a1f883d94f..fb788f2c9c0 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -22,6 +22,8 @@ import { Variant, getVariantTint } from "#app/data/variant"; import {Button} from "../enums/buttons"; import { Ability } from "../data/ability.js"; import i18next from "i18next"; +import {modifierSortFunc} from "../modifier/modifier"; + enum Page { PROFILE, @@ -828,8 +830,9 @@ export default class SummaryUiHandler extends UiHandler { statsContainer.add(statValue); }); - const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier - && (m as PokemonHeldItemModifier).pokemonId === this.pokemon.id, true) as PokemonHeldItemModifier[]; + const itemModifiers = (this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier + && (m as PokemonHeldItemModifier).pokemonId === this.pokemon.id, true) as PokemonHeldItemModifier[]) + .sort(modifierSortFunc); itemModifiers.forEach((item, i) => { const icon = item.getIcon(this.scene, true); From 1276006de36565b8ff32a3c4d6a7bdb20058b7a3 Mon Sep 17 00:00:00 2001 From: Matthew Olker Date: Thu, 6 Jun 2024 13:30:43 -0400 Subject: [PATCH 092/129] Money shouldn't cover full screen even if its funny --- src/battle-scene.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 031106d016f..e5c75e26ebb 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1379,16 +1379,14 @@ export default class BattleScene extends SceneBase { } animateMoneyChanged(positiveChange: boolean): void { - let deltaScale = this.moneyText.scale * 0.14; - if (positiveChange) { - this.moneyText.setShadowColor("#008000"); - } else { - this.moneyText.setShadowColor("#FF0000"); - deltaScale = -deltaScale; + if (this.tweens.getTweensOf(this.moneyText).length > 0) { + return; } + const deltaScale = this.moneyText.scale * 0.14 * (positiveChange ? 1 : -1); + this.moneyText.setShadowColor(positiveChange ? "#008000" : "#FF0000"); this.tweens.add({ targets: this.moneyText, - duration: Utils.fixedInt(250), + duration: 250, scale: this.moneyText.scale + deltaScale, loop: 0, yoyo: true, From 609efb0cf91b29c9cdb53db852b4f67b7cd734be Mon Sep 17 00:00:00 2001 From: Tempoanon <163687446+Tempo-anon@users.noreply.github.com> Date: Thu, 6 Jun 2024 13:58:02 -0400 Subject: [PATCH 093/129] Fix typo in healing charm for Chinese localization (#1874) --- src/locales/zh_CN/modifier-type.ts | 2 +- src/locales/zh_TW/modifier-type.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/locales/zh_CN/modifier-type.ts b/src/locales/zh_CN/modifier-type.ts index 456cc7bb8fb..f07a464f539 100644 --- a/src/locales/zh_CN/modifier-type.ts +++ b/src/locales/zh_CN/modifier-type.ts @@ -199,7 +199,7 @@ export const modifierType: ModifierTypeTranslationEntries = { "MULTI_LENS": { name: "多重镜" }, - "HEALING_CHARM": { name: "治愈护符", description: "HP回复量增加10% (含复活)" }, + "HEALING_CHARM": { name: "治愈护符", description: "HP回复量增加10% (不含复活)" }, "CANDY_JAR": { name: "糖果罐", description: "神奇糖果提供的升级额外增加1级" }, "BERRY_POUCH": { name: "树果袋", description: "使用树果时有30%的几率不会消耗树果" }, diff --git a/src/locales/zh_TW/modifier-type.ts b/src/locales/zh_TW/modifier-type.ts index a6e73ebfc28..730f269bb21 100644 --- a/src/locales/zh_TW/modifier-type.ts +++ b/src/locales/zh_TW/modifier-type.ts @@ -212,7 +212,7 @@ export const modifierType: ModifierTypeTranslationEntries = { MULTI_LENS: { name: "多重鏡" }, HEALING_CHARM: { name: "治癒護符", - description: "HP恢復量增加10% (含復活)", + description: "HP恢復量增加10% (不含復活)", }, CANDY_JAR: { name: "糖果罐", description: "神奇糖果提供的升級提升1級" }, BERRY_POUCH: { From ac8ae6c72415120e636e0432b5625789c498eb4e Mon Sep 17 00:00:00 2001 From: Tempoanon <163687446+Tempo-anon@users.noreply.github.com> Date: Thu, 6 Jun 2024 13:59:22 -0400 Subject: [PATCH 094/129] Thunderclap should not let the AI read your inputs (#1884) --- src/field/pokemon.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 285583bd05a..a973297567a 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -3396,7 +3396,7 @@ export class EnemyPokemon extends Pokemon { const target = this.scene.getField()[mt]; let targetScore = move.getUserBenefitScore(this, target, move) + move.getTargetBenefitScore(this, target, move) * (mt < BattlerIndex.ENEMY === this.isPlayer() ? 1 : -1); - if ((move.name.endsWith(" (N)") || !move.applyConditions(this, target, move)) && ![Moves.SUCKER_PUNCH, Moves.UPPER_HAND].includes(move.id)) { + 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) { const effectiveness = target.getAttackMoveEffectiveness(this, pokemonMove); From 1312d4f825d9fb5775062e72780adb4b397b2859 Mon Sep 17 00:00:00 2001 From: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> Date: Thu, 6 Jun 2024 21:01:21 +0200 Subject: [PATCH 095/129] [Qol] The title will now be displayed in the voucher overview (and flyout) (#1886) * The title will now be displayed in the voucher overview * Update src/system/voucher.ts * Intend --- src/system/voucher.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/system/voucher.ts b/src/system/voucher.ts index 9381dd650f8..c11410400b5 100644 --- a/src/system/voucher.ts +++ b/src/system/voucher.ts @@ -108,12 +108,13 @@ const voucherAchvs: Achv[] = [ achvs.CLASSIC_VICTORY ]; : VoucherType.PREMIUM; const key = TrainerType[trainerType]; const trainerName = trainerConfigs[trainerType].name; + const trainer = trainerConfigs[trainerType]; + const title = trainer.title ? ` (${trainer.title})` : ""; vouchers[key] = new Voucher( voucherType, - i18next.t("voucher:defeatTrainer", { trainerName }) + `${i18next.t("voucher:defeatTrainer", { trainerName })} ${title}`, ); } - const voucherKeys = Object.keys(vouchers); for (const k of voucherKeys) { vouchers[k].id = k; From 4fcc3ef9ff891b4fee312c6035cad8745074e9f3 Mon Sep 17 00:00:00 2001 From: Cycrum <48132050+Cycrum@users.noreply.github.com> Date: Thu, 6 Jun 2024 16:19:41 -0400 Subject: [PATCH 096/129] [Feature] Update Gardevoir, Glalie, and Meloetta genders (#1864) Updated Pokemon genders to canonical rates --- src/data/pokemon-species.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 562a79ea333..ae9e7fef547 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -1286,7 +1286,7 @@ export function initSpecies() { new PokemonSpecies(Species.PELIPPER, 3, false, false, false, "Water Bird Pokémon", Type.WATER, Type.FLYING, 1.2, 28, Abilities.KEEN_EYE, Abilities.DRIZZLE, Abilities.RAIN_DISH, 440, 60, 50, 100, 95, 70, 65, 45, 50, 154, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.RALTS, 3, false, false, false, "Feeling Pokémon", Type.PSYCHIC, Type.FAIRY, 0.4, 6.6, Abilities.SYNCHRONIZE, Abilities.TRACE, Abilities.TELEPATHY, 198, 28, 25, 25, 45, 35, 40, 235, 35, 40, GrowthRate.SLOW, 50, false), new PokemonSpecies(Species.KIRLIA, 3, false, false, false, "Emotion Pokémon", Type.PSYCHIC, Type.FAIRY, 0.8, 20.2, Abilities.SYNCHRONIZE, Abilities.TRACE, Abilities.TELEPATHY, 278, 38, 35, 35, 65, 55, 50, 120, 35, 97, GrowthRate.SLOW, 50, false), - new PokemonSpecies(Species.GARDEVOIR, 3, false, false, false, "Embrace Pokémon", Type.PSYCHIC, Type.FAIRY, 1.6, 48.4, Abilities.SYNCHRONIZE, Abilities.TRACE, Abilities.TELEPATHY, 518, 68, 65, 65, 125, 115, 80, 45, 35, 259, GrowthRate.SLOW, 0, false, true, + new PokemonSpecies(Species.GARDEVOIR, 3, false, false, false, "Embrace Pokémon", Type.PSYCHIC, Type.FAIRY, 1.6, 48.4, Abilities.SYNCHRONIZE, Abilities.TRACE, Abilities.TELEPATHY, 518, 68, 65, 65, 125, 115, 80, 45, 35, 259, GrowthRate.SLOW, 50, false, true, new PokemonForm("Normal", "", Type.PSYCHIC, Type.FAIRY, 1.6, 48.4, Abilities.SYNCHRONIZE, Abilities.TRACE, Abilities.TELEPATHY, 518, 68, 65, 65, 125, 115, 80, 45, 35, 259, false, null, true), new PokemonForm("Mega", SpeciesFormKey.MEGA, Type.PSYCHIC, Type.FAIRY, 1.6, 48.4, Abilities.PIXILATE, Abilities.PIXILATE, Abilities.PIXILATE, 618, 68, 85, 65, 165, 135, 100, 45, 35, 259), ), @@ -1404,7 +1404,7 @@ export function initSpecies() { ), new PokemonSpecies(Species.WYNAUT, 3, false, false, false, "Bright Pokémon", Type.PSYCHIC, null, 0.6, 14, Abilities.SHADOW_TAG, Abilities.NONE, Abilities.TELEPATHY, 260, 95, 23, 48, 23, 48, 23, 125, 50, 52, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.SNORUNT, 3, false, false, false, "Snow Hat Pokémon", Type.ICE, null, 0.7, 16.8, Abilities.INNER_FOCUS, Abilities.ICE_BODY, Abilities.MOODY, 300, 50, 50, 50, 50, 50, 50, 190, 50, 60, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(Species.GLALIE, 3, false, false, false, "Face Pokémon", Type.ICE, null, 1.5, 256.5, Abilities.INNER_FOCUS, Abilities.ICE_BODY, Abilities.MOODY, 480, 80, 80, 80, 80, 80, 80, 75, 50, 168, GrowthRate.MEDIUM_FAST, 100, false, true, + new PokemonSpecies(Species.GLALIE, 3, false, false, false, "Face Pokémon", Type.ICE, null, 1.5, 256.5, Abilities.INNER_FOCUS, Abilities.ICE_BODY, Abilities.MOODY, 480, 80, 80, 80, 80, 80, 80, 75, 50, 168, GrowthRate.MEDIUM_FAST, 50, false, true, new PokemonForm("Normal", "", Type.ICE, null, 1.5, 256.5, Abilities.INNER_FOCUS, Abilities.ICE_BODY, Abilities.MOODY, 480, 80, 80, 80, 80, 80, 80, 75, 50, 168, false, null, true), new PokemonForm("Mega", SpeciesFormKey.MEGA, Type.ICE, null, 2.1, 350.2, Abilities.REFRIGERATE, Abilities.REFRIGERATE, Abilities.REFRIGERATE, 580, 80, 120, 80, 120, 80, 100, 75, 50, 168), ), @@ -1829,7 +1829,7 @@ export function initSpecies() { new PokemonForm("Ordinary Form", "ordinary", Type.WATER, Type.FIGHTING, 1.4, 48.5, Abilities.JUSTIFIED, Abilities.NONE, Abilities.NONE, 580, 91, 72, 90, 129, 90, 108, 3, 35, 290, false, null, true), new PokemonForm("Resolute", "resolute", Type.WATER, Type.FIGHTING, 1.4, 48.5, Abilities.JUSTIFIED, Abilities.NONE, Abilities.NONE, 580, 91, 72, 90, 129, 90, 108, 3, 35, 290), ), - new PokemonSpecies(Species.MELOETTA, 5, false, false, true, "Melody Pokémon", Type.NORMAL, Type.PSYCHIC, 0.6, 6.5, Abilities.SERENE_GRACE, Abilities.NONE, Abilities.NONE, 600, 100, 77, 77, 128, 128, 90, 3, 100, 270, GrowthRate.SLOW, 0, false, true, + new PokemonSpecies(Species.MELOETTA, 5, false, false, true, "Melody Pokémon", Type.NORMAL, Type.PSYCHIC, 0.6, 6.5, Abilities.SERENE_GRACE, Abilities.NONE, Abilities.NONE, 600, 100, 77, 77, 128, 128, 90, 3, 100, 270, GrowthRate.SLOW, null, false, true, new PokemonForm("Aria Forme", "aria", Type.NORMAL, Type.PSYCHIC, 0.6, 6.5, Abilities.SERENE_GRACE, Abilities.NONE, Abilities.NONE, 600, 100, 77, 77, 128, 128, 90, 3, 100, 270, false, null, true), new PokemonForm("Pirouette Forme", "pirouette", Type.NORMAL, Type.FIGHTING, 0.6, 6.5, Abilities.SERENE_GRACE, Abilities.NONE, Abilities.NONE, 600, 100, 128, 90, 77, 77, 128, 3, 100, 270), ), From a18d796e2e2b88f297259ede61a9d1633520b31e Mon Sep 17 00:00:00 2001 From: returntoice Date: Fri, 7 Jun 2024 05:43:25 +0900 Subject: [PATCH 097/129] [Localization] Fix Korean multi lens description (#1891) --- src/locales/ko/modifier-type.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locales/ko/modifier-type.ts b/src/locales/ko/modifier-type.ts index fce9d25b629..fcfb1cc4283 100644 --- a/src/locales/ko/modifier-type.ts +++ b/src/locales/ko/modifier-type.ts @@ -93,7 +93,7 @@ export const modifierType: ModifierTypeTranslationEntries = { description: "기술의 명중률이 {{accuracyAmount}} 증가 (최대 100)", }, "PokemonMultiHitModifierType": { - description: "공격이 가진 갯수에 따라 60/75/82.5%의 위력으로 한번 더 명중", + description: "지닌 개수(최대 3개)마다 추가 공격을 하는 대신, 공격력이 60%(1개)/75%(2개)/82.5%(3개)만큼 감소합니다.", }, "TmModifierType": { name: "No.{{moveId}} {{moveName}}", From 9c1a13eb54ebf14103a200023909fe35baeec99b Mon Sep 17 00:00:00 2001 From: Greenlamp2 <44787002+Greenlamp2@users.noreply.github.com> Date: Thu, 6 Jun 2024 22:55:01 +0200 Subject: [PATCH 098/129] Fix - double inputs (#1842) * fix an issue where the input repeated itself too fast * remove remnant code --- src/inputs-controller.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/inputs-controller.ts b/src/inputs-controller.ts index aa0f781cfef..ac5d339c048 100644 --- a/src/inputs-controller.ts +++ b/src/inputs-controller.ts @@ -48,7 +48,7 @@ export interface InterfaceConfig { custom?: MappingLayout; } -const repeatInputDelayMillis = 250; +const repeatInputDelayMillis = 500; // Phaser.Input.Gamepad.GamepadPlugin#refreshPads declare module "phaser" { @@ -88,7 +88,6 @@ declare module "phaser" { * providing a unified interface for all input-related interactions. */ export class InputsController { - private buttonKeys: Phaser.Input.Keyboard.Key[][]; private gamepads: Array = new Array(); private scene: BattleScene; public events: Phaser.Events.EventEmitter; @@ -123,7 +122,6 @@ export class InputsController { constructor(scene: BattleScene) { this.scene = scene; this.time = this.scene.time; - this.buttonKeys = []; this.selectedDevice = { [Device.GAMEPAD]: null, [Device.KEYBOARD]: "default" From 209a69d098375dcb1f5f5be4be1d674e3b3d585f Mon Sep 17 00:00:00 2001 From: Raidette <73134872+Raidette@users.noreply.github.com> Date: Thu, 6 Jun 2024 23:22:04 +0200 Subject: [PATCH 099/129] [Move] Rototiller implementation (#1885) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * rototiller implementation * attack now fails if no eligible grass type pokémon is fielded --- src/data/move.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/data/move.ts b/src/data/move.ts index 1e2c5e7485b..937af6de354 100755 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -6683,7 +6683,11 @@ export function initMoves() { .condition((user, target, move) => user.battleData.berriesEaten.length > 0), new StatusMove(Moves.ROTOTILLER, Type.GROUND, -1, 10, 100, 0, 6) .target(MoveTarget.ALL) - .unimplemented(), + .condition((user,target,move) => { + // If any fielded pokémon is grass-type and grounded. + return [...user.scene.getEnemyParty(),...user.scene.getParty()].some((poke) => poke.isOfType(Type.GRASS) && poke.isGrounded()); + }) + .attr(StatChangeAttr, [BattleStat.ATK, BattleStat.SPATK], 1, false, (user, target, move) => target.isOfType(Type.GRASS) && target.isGrounded()), new StatusMove(Moves.STICKY_WEB, Type.BUG, -1, 20, -1, 0, 6) .attr(AddArenaTrapTagAttr, ArenaTagType.STICKY_WEB) .target(MoveTarget.ENEMY_SIDE), From 5568def1a451f8e07af765f36ed8123f4e2a782d Mon Sep 17 00:00:00 2001 From: prime <10091050+prime-dialga@users.noreply.github.com> Date: Fri, 7 Jun 2024 02:20:13 +0200 Subject: [PATCH 100/129] Show C-Button on mobile during modifier selection (#1893) --- index.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.css b/index.css index c0791259002..b71c4c29bea 100644 --- a/index.css +++ b/index.css @@ -152,7 +152,7 @@ body { display: none; } -#touchControls:not([data-ui-mode='COMMAND']):not([data-ui-mode='FIGHT']):not([data-ui-mode='BALL']):not([data-ui-mode='TARGET_SELECT']) #apad .apadBattle { +#touchControls:not([data-ui-mode='COMMAND']):not([data-ui-mode='FIGHT']):not([data-ui-mode='BALL']):not([data-ui-mode='TARGET_SELECT']):not([data-ui-mode='MODIFIER_SELECT']) #apad .apadBattle { display: none; } From 4cce8a1bfae75dc9198145cb8336aac8a6de5822 Mon Sep 17 00:00:00 2001 From: Benjamin Odom Date: Thu, 6 Jun 2024 19:57:12 -0500 Subject: [PATCH 101/129] Update index.css (#1898) --- index.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.css b/index.css index b71c4c29bea..1b2f0211692 100644 --- a/index.css +++ b/index.css @@ -146,8 +146,8 @@ body { margin-left: 10%; } -#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_DISPLAY']):not([data-ui-mode='SETTINGS_AUDIO']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadRectBtnContainer > .apadSqBtn, -#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_DISPLAY']):not([data-ui-mode='SETTINGS_AUDIO']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadSqBtnContainer +#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_DISPLAY']):not([data-ui-mode='SETTINGS_AUDIO']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadRectBtnContainer > .apadSqBtn:not(.apadBattle), +#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_DISPLAY']):not([data-ui-mode='SETTINGS_AUDIO']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadSqBtnContainer > .apadSqBtn:not(.apadBattle) { display: none; } From a815b73d9644ec2eb486f8d0986531715167ca9c Mon Sep 17 00:00:00 2001 From: DustinLin <39450497+DustinLin@users.noreply.github.com> Date: Thu, 6 Jun 2024 18:11:14 -0700 Subject: [PATCH 102/129] [Documentation] documentation of move.ts (#1828) * starting documentation of move.ts * more docs * fixing comments * updating comments --- src/data/move.ts | 211 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 207 insertions(+), 4 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index 937af6de354..d0e8aff7a46 100755 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -42,7 +42,7 @@ export enum MoveTarget { /** {@link https://bulbapedia.bulbagarden.net/wiki/Category:Moves_that_target_all_adjacent_Pok%C3%A9mon Moves that target all adjacent Pokemon} */ ALL_NEAR_OTHERS, NEAR_ENEMY, - /** {@link https://bulbapedia.bulbagarden.net/wiki/Category:Moves_that_target_all_adjacent_foes Moves that taret all adjacent foes} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Category:Moves_that_target_all_adjacent_foes Moves that target all adjacent foes} */ ALL_NEAR_ENEMIES, RANDOM_NEAR_ENEMY, ALL_ENEMIES, @@ -166,10 +166,22 @@ export default class Move implements Localizable { return this.attrs.some((attr) => attr instanceof attrType); } + /** + * Takes as input a boolean function and returns the first MoveAttr in attrs that matches true + * @param attrPredicate + * @returns the first {@linkcode MoveAttr} element in attrs that makes the input function return true + */ findAttr(attrPredicate: (attr: MoveAttr) => boolean): MoveAttr { return this.attrs.find(attrPredicate); } + /** + * Adds a new MoveAttr to the move (appends to the attr array) + * if the MoveAttr also comes with a condition, also adds that to the conditions array: {@linkcode MoveCondition} + * @param AttrType {@linkcode MoveAttr} the constructor of a MoveAttr class + * @param args the args needed to instantiate a the given class + * @returns the called object {@linkcode Move} + */ attr MoveAttr>(AttrType: T, ...args: ConstructorParameters): this { const attr = new AttrType(...args); this.attrs.push(attr); @@ -184,9 +196,16 @@ export default class Move implements Localizable { return this; } - addAttr(attr: MoveAttr): this { - this.attrs.push(attr); - let attrCondition = attr.getCondition(); + /** + * Adds a new MoveAttr to the move (appends to the attr array) + * if the MoveAttr also comes with a condition, also adds that to the conditions array: {@linkcode MoveCondition} + * Almost identical to {@link attr}, except you are passing in a MoveAttr object, instead of a constructor and it's arguments + * @param attrAdd {@linkcode MoveAttr} the attribute to add + * @returns the called object {@linkcode Move} + */ + addAttr(attrAdd: MoveAttr): this { + this.attrs.push(attrAdd); + let attrCondition = attrAdd.getCondition(); if (attrCondition) { if (typeof attrCondition === "function") { attrCondition = new MoveCondition(attrCondition); @@ -197,15 +216,30 @@ export default class Move implements Localizable { return this; } + /** + * Sets the move target of this move + * @param moveTarget {@linkcode MoveTarget} the move target to set + * @returns the called object {@linkcode Move} + */ target(moveTarget: MoveTarget): this { this.moveTarget = moveTarget; return this; } + /** + * Getter function that returns if this Move has a MoveFlag + * @param flag {@linkcode MoveFlags} to check + * @returns boolean + */ hasFlag(flag: MoveFlags): boolean { + // internally it is taking the bitwise AND (MoveFlags are represented as bit-shifts) and returning False if result is 0 and true otherwise return !!(this.flags & flag); } + /** + * Getter function that returns if the move hits multiple targets + * @returns boolean + */ isMultiTarget(): boolean { switch (this.moveTarget) { case MoveTarget.ALL_OTHERS: @@ -222,6 +256,11 @@ export default class Move implements Localizable { return false; } + /** + * Getter function that returns if the move targets itself or an ally + * @returns boolean + */ + isAllyTarget(): boolean { switch (this.moveTarget) { case MoveTarget.USER: @@ -235,6 +274,12 @@ export default class Move implements Localizable { return false; } + /** + * Checks if the move is immune to certain types + * currently only look at case of Grass types and powder moves + * @param type {@linkcode Type} enum + * @returns boolean + */ isTypeImmune(type: Type): boolean { switch (type) { case Type.GRASS: @@ -246,6 +291,11 @@ export default class Move implements Localizable { return false; } + /** + * Adds a move condition to the move + * @param condition {@linkcode MoveCondition} or {@linkcode MoveConditionFunc}, appends to conditions array a new MoveCondition object + * @returns the called object {@linkcode Move} + */ condition(condition: MoveCondition | MoveConditionFunc): this { if (typeof condition === "function") { condition = new MoveCondition(condition as MoveConditionFunc); @@ -255,17 +305,31 @@ export default class Move implements Localizable { return this; } + /** + * Marks the move as "partial": appends texts to the move name + * @returns the called object {@linkcode Move} + */ partial(): this { this.nameAppend += " (P)"; return this; } + /** + * Marks the move as "unimplemented": appends texts to the move name + * @returns the called object {@linkcode Move} + */ unimplemented(): this { this.nameAppend += " (N)"; return this; } + /** + * Sets the flags of the move + * @param flag {@linkcode MoveFlags} + * @param on a boolean, if True, then "ORs" the flag onto existing ones, if False then "XORs" the flag onto existing ones + */ private setFlag(flag: MoveFlags, on: boolean): void { + // bitwise OR and bitwise XOR respectively if (on) { this.flags |= flag; } else { @@ -273,51 +337,110 @@ export default class Move implements Localizable { } } + /** + * Sets the {@linkcode MoveFlags.MAKES_CONTACT} flag for the calling Move + * @param makesContact The value (boolean) to set the flag to + * @returns The {@linkcode Move} that called this function + */ makesContact(makesContact?: boolean): this { this.setFlag(MoveFlags.MAKES_CONTACT, makesContact); return this; } + /** + * Sets the {@linkcode MoveFlags.IGNORE_PROTECT} flag for the calling Move + * @param ignoresProtect The value (boolean) to set the flag to + * example: @see {@linkcode Moves.CURSE} + * @returns The {@linkcode Move} that called this function + */ ignoresProtect(ignoresProtect?: boolean): this { this.setFlag(MoveFlags.IGNORE_PROTECT, ignoresProtect); return this; } + /** + * Sets the {@linkcode MoveFlags.IGNORE_VIRTUAL} flag for the calling Move + * @param ignoresVirtual The value (boolean) to set the flag to + * example: @see {@linkcode Moves.NATURE_POWER} + * @returns The {@linkcode Move} that called this function + */ ignoresVirtual(ignoresVirtual?: boolean): this { this.setFlag(MoveFlags.IGNORE_VIRTUAL, ignoresVirtual); return this; } + /** + * Sets the {@linkcode MoveFlags.SOUND_BASED} flag for the calling Move + * @param soundBased The value (boolean) to set the flag to + * example: @see {@linkcode Moves.UPROAR} + * @returns The {@linkcode Move} that called this function + */ soundBased(soundBased?: boolean): this { this.setFlag(MoveFlags.SOUND_BASED, soundBased); return this; } + /** + * Sets the {@linkcode MoveFlags.HIDE_USER} flag for the calling Move + * @param hidesUser The value (boolean) to set the flag to + * example: @see {@linkcode Moves.TELEPORT} + * @returns The {@linkcode Move} that called this function + */ hidesUser(hidesUser?: boolean): this { this.setFlag(MoveFlags.HIDE_USER, hidesUser); return this; } + /** + * Sets the {@linkcode MoveFlags.HIDE_TARGET} flag for the calling Move + * @param hidesTarget The value (boolean) to set the flag to + * example: @see {@linkcode Moves.WHIRLWIND} + * @returns The {@linkcode Move} that called this function + */ hidesTarget(hidesTarget?: boolean): this { this.setFlag(MoveFlags.HIDE_TARGET, hidesTarget); return this; } + /** + * Sets the {@linkcode MoveFlags.BITING_MOVE} flag for the calling Move + * @param bitingMove The value (boolean) to set the flag to + * example: @see {@linkcode Moves.BITE} + * @returns The {@linkcode Move} that called this function + */ bitingMove(bitingMove?: boolean): this { this.setFlag(MoveFlags.BITING_MOVE, bitingMove); return this; } + /** + * Sets the {@linkcode MoveFlags.PULSE_MOVE} flag for the calling Move + * @param pulseMove The value (boolean) to set the flag to + * example: @see {@linkcode Moves.WATER_PULSE} + * @returns The {@linkcode Move} that called this function + */ pulseMove(pulseMove?: boolean): this { this.setFlag(MoveFlags.PULSE_MOVE, pulseMove); return this; } + /** + * Sets the {@linkcode MoveFlags.PUNCHING_MOVE} flag for the calling Move + * @param punchingMove The value (boolean) to set the flag to + * example: @see {@linkcode Moves.DRAIN_PUNCH} + * @returns The {@linkcode Move} that called this function + */ punchingMove(punchingMove?: boolean): this { this.setFlag(MoveFlags.PUNCHING_MOVE, punchingMove); return this; } + /** + * Sets the {@linkcode MoveFlags.SLICING_MOVE} flag for the calling Move + * @param slicingMove The value (boolean) to set the flag to + * example: @see {@linkcode Moves.X_SCISSOR} + * @returns The {@linkcode Move} that called this function + */ slicingMove(slicingMove?: boolean): this { this.setFlag(MoveFlags.SLICING_MOVE, slicingMove); return this; @@ -334,42 +457,92 @@ export default class Move implements Localizable { return this; } + /** + * Sets the {@linkcode MoveFlags.BALLBOMB_MOVE} flag for the calling Move + * @param ballBombMove The value (boolean) to set the flag to + * example: @see {@linkcode Moves.ELECTRO_BALL} + * @returns The {@linkcode Move} that called this function + */ ballBombMove(ballBombMove?: boolean): this { this.setFlag(MoveFlags.BALLBOMB_MOVE, ballBombMove); return this; } + /** + * Sets the {@linkcode MoveFlags.POWDER_MOVE} flag for the calling Move + * @param powderMove The value (boolean) to set the flag to + * example: @see {@linkcode Moves.STUN_SPORE} + * @returns The {@linkcode Move} that called this function + */ powderMove(powderMove?: boolean): this { this.setFlag(MoveFlags.POWDER_MOVE, powderMove); return this; } + /** + * Sets the {@linkcode MoveFlags.DANCE_MOVE} flag for the calling Move + * @param danceMove The value (boolean) to set the flag to + * example: @see {@linkcode Moves.PETAL_DANCE} + * @returns The {@linkcode Move} that called this function + */ danceMove(danceMove?: boolean): this { this.setFlag(MoveFlags.DANCE_MOVE, danceMove); return this; } + /** + * Sets the {@linkcode MoveFlags.WIND_MOVE} flag for the calling Move + * @param windMove The value (boolean) to set the flag to + * example: @see {@linkcode Moves.HURRICANE} + * @returns The {@linkcode Move} that called this function + */ windMove(windMove?: boolean): this { this.setFlag(MoveFlags.WIND_MOVE, windMove); return this; } + /** + * Sets the {@linkcode MoveFlags.TRIAGE_MOVE} flag for the calling Move + * @param triageMove The value (boolean) to set the flag to + * example: @see {@linkcode Moves.ABSORB} + * @returns The {@linkcode Move} that called this function + */ triageMove(triageMove?: boolean): this { this.setFlag(MoveFlags.TRIAGE_MOVE, triageMove); return this; } + /** + * Sets the {@linkcode MoveFlags.IGNORE_ABILITIES} flag for the calling Move + * @param ignoresAbilities sThe value (boolean) to set the flag to + * example: @see {@linkcode Moves.SUNSTEEL_STRIKE} + * @returns The {@linkcode Move} that called this function + */ ignoresAbilities(ignoresAbilities?: boolean): this { this.setFlag(MoveFlags.IGNORE_ABILITIES, ignoresAbilities); return this; } + /** + * Sets the {@linkcode MoveFlags.CHECK_ALL_HITS} flag for the calling Move + * @param checkAllHits The value (boolean) to set the flag to + * example: @see {@linkcode Moves.TRIPLE_AXEL} + * @returns The {@linkcode Move} that called this function + */ checkAllHits(checkAllHits?: boolean): this { this.setFlag(MoveFlags.CHECK_ALL_HITS, checkAllHits); return this; } + /** + * Checks if the move flag applies to the pokemon(s) using/receiving the move + * @param flag {@linkcode MoveFlags} MoveFlag to check on user and/or target + * @param user {@linkcode Pokemon} the Pokemon using the move + * @param target {@linkcode Pokemon} the Pokemon receiving the move + * @returns boolean + */ checkFlag(flag: MoveFlags, user: Pokemon, target: Pokemon): boolean { + // special cases below, eg: if the move flag is MAKES_CONTACT, and the user pokemon has an ability that ignores contact (like "Long Reach"), then overrides and move does not make contact switch (flag) { case MoveFlags.MAKES_CONTACT: if (user.hasAbilityWithAttr(IgnoreContactAbAttr)) { @@ -389,6 +562,13 @@ export default class Move implements Localizable { return !!(this.flags & flag); } + /** + * Applies each {@linkcode MoveCondition} of this move to the params + * @param user {@linkcode Pokemon} to apply conditions to + * @param target {@linkcode Pokemon} to apply conditions to + * @param move {@linkcode Move} to apply conditions to + * @returns boolean: false if any of the apply()'s return false, else true + */ applyConditions(user: Pokemon, target: Pokemon, move: Move): boolean { for (const condition of this.conditions) { if (!condition.apply(user, target, move)) { @@ -399,6 +579,14 @@ export default class Move implements Localizable { return true; } + /** + * Sees if, given the target pokemon, a move fails on it (by looking at each {@linkcode MoveAttr} of this move + * @param user {@linkcode Pokemon} using the move + * @param target {@linkcode Pokemon} receiving the move + * @param move {@linkcode Move} using the move + * @param cancelled {@linkcode Utils.BooleanHolder} to hold boolean value + * @returns string of the failed text, or null + */ getFailedText(user: Pokemon, target: Pokemon, move: Move, cancelled: Utils.BooleanHolder): string | null { for (const attr of this.attrs) { const failedText = attr.getFailedText(user, target, move, cancelled); @@ -409,6 +597,13 @@ export default class Move implements Localizable { return null; } + /** + * Calculates the userBenefitScore across all the attributes and conditions + * @param user {@linkcode Pokemon} using the move + * @param target {@linkcode Pokemon} receiving the move + * @param move {@linkcode Move} using the move + * @returns integer representing the total benefitScore + */ getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { let score = 0; @@ -423,10 +618,18 @@ export default class Move implements Localizable { return score; } + /** + * Calculates the targetBenefitScore across all the attributes + * @param user {@linkcode Pokemon} using the move + * @param target {@linkcode Pokemon} receiving the move + * @param move {@linkcode Move} using the move + * @returns integer representing the total benefitScore + */ getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { let score = 0; for (const attr of this.attrs) { + // conditionals to check if the move is self targeting (if so then you are applying the move to yourself, not the target) score += attr.getTargetBenefitScore(user, !attr.selfTarget ? target : user, move) * (target !== user && attr.selfTarget ? -1 : 1); } From 901392152319a14489ba5924761147b705f6c41b Mon Sep 17 00:00:00 2001 From: Xavion3 Date: Fri, 7 Jun 2024 13:01:13 +1000 Subject: [PATCH 103/129] [Bug] Fix speed tie code (#1895) * Fix speed tie code * Fix off by one error * Shuffle before sorting to make code cleaner --- src/phases.ts | 5 +++-- src/utils.ts | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/phases.ts b/src/phases.ts index bb875fe2470..3b4cb021ccf 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -633,11 +633,12 @@ export abstract class FieldPhase extends BattlePhase { const playerField = this.scene.getPlayerField().filter(p => p.isActive()) as Pokemon[]; const enemyField = this.scene.getEnemyField().filter(p => p.isActive()) as Pokemon[]; - let orderedTargets: Pokemon[] = playerField.concat(enemyField).sort((a: Pokemon, b: Pokemon) => { + // We shuffle the list before sorting so speed ties produce random results + let orderedTargets: Pokemon[] = Utils.randSeedShuffle(playerField.concat(enemyField)).sort((a: Pokemon, b: Pokemon) => { const aSpeed = a?.getBattleStat(Stat.SPD) || 0; const bSpeed = b?.getBattleStat(Stat.SPD) || 0; - return aSpeed < bSpeed ? 1 : aSpeed > bSpeed ? -1 : !this.scene.randBattleSeedInt(2) ? -1 : 1; + return bSpeed - aSpeed; }); const speedReversed = new Utils.BooleanHolder(false); diff --git a/src/utils.ts b/src/utils.ts index 68f6e323af1..42736add1fa 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -127,6 +127,23 @@ export function randSeedEasedWeightedItem(items: T[], easingFunction: string return items[Math.floor(easedValue * items.length)]; } +/** + * Shuffle a list using the seeded rng. Utilises the Fisher-Yates algorithm. + * @param {Array} items An array of items. + * @returns {Array} A new shuffled array of items. + */ +export function randSeedShuffle(items: T[]): T[] { + if (items.length <= 1) { + return items; + } + const newArray = items.slice(0); + for (let i = items.length - 1; i > 0; i--) { + const j = Phaser.Math.RND.integerInRange(0, i); + [newArray[i], newArray[j]] = [newArray[j], newArray[i]]; + } + return newArray; +} + export function getFrameMs(frameCount: integer): integer { return Math.floor((1 / 60) * 1000 * frameCount); } From a852106a9d2aa8b693c46b669acdd5deec57d212 Mon Sep 17 00:00:00 2001 From: MrWaterT <87186129+MrWaterT@users.noreply.github.com> Date: Fri, 7 Jun 2024 13:19:45 +0900 Subject: [PATCH 104/129] [Localization] Correct and apply updates to Korean (#1863) * [Localization] Apply Korean to update ability-trigeer.ts windPowerCharged: Wind Power/Wind Rider #1566 Perish Body #1554 Poison Heal #1245 menu.ts Loading screen disclaimer 7c9e5e9 modifier-type.ts TM description with overlay key notice #1585 * Correct wrong text and align text width change RV remove space from FGEN * Edit wrong space * Use special color(official) rather than unofficial shiny * Translate iceFaceAvoidedDamage --- src/locales/ko/ability-trigger.ts | 5 ++++- src/locales/ko/menu.ts | 4 ++-- src/locales/ko/modifier-type.ts | 2 +- src/locales/ko/starter-select-ui-handler.ts | 4 ++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/locales/ko/ability-trigger.ts b/src/locales/ko/ability-trigger.ts index 8fed1208fff..3ce78178081 100644 --- a/src/locales/ko/ability-trigger.ts +++ b/src/locales/ko/ability-trigger.ts @@ -3,5 +3,8 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const abilityTriggers: SimpleTranslationEntries = { "blockRecoilDamage" : "{{pokemonName}}[[는]] {{abilityName}} 때문에\n반동 데미지를 받지 않는다!", "badDreams": "{{pokemonName}}[[는]]\n나이트메어 때문에 시달리고 있다!", - "iceFaceAvoidedDamage": "{{pokemonName}} avoided\ndamage with {{abilityName}}!" + "windPowerCharged": "{{pokemonName}}[[는]]\n{{moveName}}에 맞아 충전되었다!", + "perishBody": "{{pokemonName}}의 {{abilityName}} 때문에\n양쪽 포켓몬 모두는 3턴 후에 쓰러져 버린다!", + "poisonHeal": "{{pokemonName}}[[는]] {{abilityName}}[[로]]인해\n조금 회복했다.", + "iceFaceAvoidedDamage": "{{pokemonName}}[[는]] {{abilityName}} 때문에\n데미지를 받지 않는다!", } as const; diff --git a/src/locales/ko/menu.ts b/src/locales/ko/menu.ts index b91d674521e..3bd52540f94 100644 --- a/src/locales/ko/menu.ts +++ b/src/locales/ko/menu.ts @@ -49,6 +49,6 @@ export const menu: SimpleTranslationEntries = { "empty":"빈 슬롯", "yes":"예", "no":"아니오", - "disclaimer": "DISCLAIMER", - "disclaimerDescription": "This game is an unfinished product; it might have playability issues (including the potential loss of save data),\n change without notice, and may or may not be updated further or completed." + "disclaimer": "면책 조항", + "disclaimerDescription": "이 게임은 완전히 개발되지 않았습니다- (세이브 데이터 소실을 포함) 플레이에 지장을 주는 문제가 생길 수 있으며,\n공지 없이 업데이트가 진행 혹은 중지될 수 있습니다.", } as const; diff --git a/src/locales/ko/modifier-type.ts b/src/locales/ko/modifier-type.ts index fcfb1cc4283..34d57474e56 100644 --- a/src/locales/ko/modifier-type.ts +++ b/src/locales/ko/modifier-type.ts @@ -101,7 +101,7 @@ export const modifierType: ModifierTypeTranslationEntries = { }, "TmModifierTypeWithInfo": { name: "No.{{moveId}} {{moveName}}", - description: "포켓몬에게 {{moveName}}를(을) 가르침\n(Hold C or Shift for more info)", + description: "포켓몬에게 {{moveName}}를(을) 가르침\n(C 또는 Shift를 꾹 눌러 정보 확인)", }, "EvolutionItemModifierType": { description: "어느 특정 포켓몬을 진화", diff --git a/src/locales/ko/starter-select-ui-handler.ts b/src/locales/ko/starter-select-ui-handler.ts index bb2b4369c92..9a27824e541 100644 --- a/src/locales/ko/starter-select-ui-handler.ts +++ b/src/locales/ko/starter-select-ui-handler.ts @@ -30,12 +30,12 @@ export const starterSelectUiHandler: SimpleTranslationEntries = { "selectMoveSwapWith": "교체될 기술을 선택해주세요. 대상:", "unlockPassive": "패시브 해금", "reduceCost": "코스트 줄이기", - "cycleShiny": "R: 색상 전환", + "cycleShiny": "R: 특별한 색", "cycleForm": "F: 폼 체인지", "cycleGender": "G: 암수 전환", "cycleAbility": "E: 특성 전환", "cycleNature": "N: 성격 전환", - "cycleVariant": "V: 형태 전환", + "cycleVariant": "V: 색상 전환", "enablePassive": "패시브 활성화", "disablePassive": "패시브 비활성화", "locked": "잠김", From cc2e31dbe00380e4cc36d8366fef6ec36c991cb7 Mon Sep 17 00:00:00 2001 From: Cycrum <48132050+Cycrum@users.noreply.github.com> Date: Fri, 7 Jun 2024 00:45:36 -0400 Subject: [PATCH 105/129] [Localization] Corrected capitalization on Quick Claw activation text (#1894) * Update pokemon-species.ts Updated Pokemon genders to canonical rates * Corrected capitalization of Quick Claw activation --- 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 271cf38cb5c..832c16ee4a9 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -797,7 +797,7 @@ export class BypassSpeedChanceModifier extends PokemonHeldItemModifier { const hasQuickClaw = this.type instanceof ModifierTypes.PokemonHeldItemModifierType && this.type.id === "QUICK_CLAW"; if (isCommandFight && hasQuickClaw) { - pokemon.scene.queueMessage(getPokemonMessage(pokemon, " used its quick claw to move faster!")); + pokemon.scene.queueMessage(getPokemonMessage(pokemon, " used its Quick Claw to move faster!")); } return true; } From 132d01776e6e98fa7cc9d59d14e5a9c40e297c8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Ricardo=20Fleury=20Oliveira?= Date: Fri, 7 Jun 2024 08:57:10 -0300 Subject: [PATCH 106/129] [ptBR Localization] Updated dialogue.ts, Tera Shards and trainer.ts (#1881) * updated sailor dialogue * tera fix * trainers * ice face --- src/locales/pt_BR/ability-trigger.ts | 2 +- src/locales/pt_BR/dialogue.ts | 26 +++++++++++++------------- src/locales/pt_BR/modifier-type.ts | 4 ++-- src/locales/pt_BR/trainers.ts | 8 ++++---- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/locales/pt_BR/ability-trigger.ts b/src/locales/pt_BR/ability-trigger.ts index 19b043094a5..6aee4d542f4 100644 --- a/src/locales/pt_BR/ability-trigger.ts +++ b/src/locales/pt_BR/ability-trigger.ts @@ -4,5 +4,5 @@ export const abilityTriggers: SimpleTranslationEntries = { "blockRecoilDamage" : "{{abilityName}} de {{pokemonName}}\nprotegeu-o do dano de recuo!", "badDreams": "{{pokemonName}} está tendo pesadelos!", "windPowerCharged": "Ser atingido por {{moveName}} carregou {{pokemonName}} com poder!", - "iceFaceAvoidedDamage": "{{pokemonName}} avoided\ndamage with {{abilityName}}!" + "iceFaceAvoidedDamage": "{{pokemonName}} evitou\ndanos com sua {{abilityName}}!" } as const; diff --git a/src/locales/pt_BR/dialogue.ts b/src/locales/pt_BR/dialogue.ts index 1258e41cb54..dbae36075ec 100644 --- a/src/locales/pt_BR/dialogue.ts +++ b/src/locales/pt_BR/dialogue.ts @@ -373,14 +373,14 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "sailor": { "encounter": { - 1: "Matey, you're walking the plank if you lose!", - 2: "Come on then! My sailor's pride is at stake!", - 3: "Ahoy there! Are you seasick?" + 1: "Mano, você vai andar na prancha se perder!", + 2: "Vem com tudo! Sou um marinheiro com orgulho!", + 3: "Ahoy marujo! Tá enjoado, é?!" }, "victory": { - 1: "Argh! Beaten by a kid!", - 2: "Your spirit sank me!", - 3: "I think it's me that's seasick..." + 1: "Argh! Perdi pra uma criança!", + 2: "Sua vontade de ganhar me afogou!", + 3: "Estou achando que quem tá enjoado sou eu..." }, }, "brock": { @@ -765,14 +765,14 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "shauntal": { "encounter": { - 1: "Com licença. Você é um desafiante, certo?\nSou a usuária de Pokémon do tipo Fantasma da Elite Four, Shauntal, e serei sua oponente.", + 1: "Com licença. Você é um desafiante, certo?\nSou a usuária de Pokémon do tipo Fantasma da Elite dos Quatro, Shauntal, e serei sua oponente.", 2: "Adoro escrever sobre Treinadores que vêm aqui e os Pokémon que treinam.\nPosso usar você e seus Pokémon como tema?", 3: "Cada pessoa que trabalha com Pokémon tem uma história para contar.\nQue história está prestes a ser contada?" }, "victory": { 1: "Uau. Estou sem palavras!", 2: "D-desculpe! Primeiro, preciso me desculpar com meus Pokémon...\n\nLamento muito que você tenha tido uma experiência ruim por minha causa!", - 3: "Mesmo com isso, ainda sou uma das Elite Four!" + 3: "Mesmo com isso, ainda sou uma da Elite dos Quatro!" }, "defeat": { 1: "Eheh.", @@ -921,13 +921,13 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "lacey": { "encounter": { - 1: "Vou enfrentar você com meu time usual como membro da Elite Four." + 1: "Vou enfrentar você com meu time usual como membro da Elite dos Quatro." }, "victory": { 1: "Foi uma excelente batalha. Estou ansiosa para o próximo desafio." }, "defeat": { - 1: "Fufufu... Nada mal.\nDesafiantes que derrotam a Elite Four são dignos de notar." + 1: "Fufufu... Nada mal.\nDesafiantes que derrotam a Elite dos Quatro são dignos de notar." } }, "drayton": { @@ -1007,7 +1007,7 @@ export const PGMdialogue: DialogueTranslationEntries = { 1: "Batalhar é um assunto profundo e complexo..." }, "defeat": { - 1: "Vencer um membro da Elite Four não é fácil." + 1: "Vencer um membro da Elite dos Quatro não é fácil." } }, "cress": { @@ -1392,7 +1392,7 @@ export const PGMdialogue: DialogueTranslationEntries = { "larry_elite": { "encounter": { 1: `Olá… Sou eu, Larry. - $Eu também sou membro da Elite Four, sim… Infelizmente para mim.`, + $Eu também sou membro da Elite dos Quatro, sim… Infelizmente para mim.`, }, "victory": { 1: "Bem, isso tirou o vento debaixo das nossas asas…" @@ -1476,7 +1476,7 @@ export const PGMdialogue: DialogueTranslationEntries = { 1: "Então, aqui está você… Por que não vemos para quem os ventos favorecem hoje, você… ou eu?" }, "victory": { - 1: "É frustrante para mim como membro da Elite Four, mas parece que sua força é real." + 1: "É frustrante para mim como membro da Elite dos Quatro, mas parece que sua força é real." }, "defeat": { 1: "Essa foi uma jogada de mestre!" diff --git a/src/locales/pt_BR/modifier-type.ts b/src/locales/pt_BR/modifier-type.ts index adae36adc26..373938b9266 100644 --- a/src/locales/pt_BR/modifier-type.ts +++ b/src/locales/pt_BR/modifier-type.ts @@ -113,8 +113,8 @@ export const modifierType: ModifierTypeTranslationEntries = { description: "Combina dois Pokémon (transfere Habilidade, divide os atributos base e tipos, compartilha os movimentos)", }, "TerastallizeModifierType": { - name: "{{teraType}} Fragmento Tera", - description: "{{teraType}} Terastalize um Pokémon por até 10 batalhas", + name: "Fragmento Tera {{teraType}}", + description: "Terastalize um Pokémon para o tipo {{teraType}} por 10 ondas", }, "ContactHeldItemTransferChanceModifierType": { description: "Quando atacar, tem {{chancePercent}}% de chance de roubar um item do oponente", diff --git a/src/locales/pt_BR/trainers.ts b/src/locales/pt_BR/trainers.ts index f93875dcbfc..4942fa7999f 100644 --- a/src/locales/pt_BR/trainers.ts +++ b/src/locales/pt_BR/trainers.ts @@ -6,10 +6,10 @@ export const titles: SimpleTranslationEntries = { "elite_four_female": "Elite dos Quatro", "gym_leader": "Líder de Ginásio", "gym_leader_female": "Líder de Ginásio", - "gym_leader_double": "Gym Leader Duo", + "gym_leader_double": "Líderes de Ginásio", "champion": "Campeão", - "champion_female": "Champion", - "champion_double": "Champion Duo", + "champion_female": "Campeã", + "champion_double": "Dupla Campeã", "rival": "Rival", "professor": "Professor", "frontier_brain": "Cérebro da Fronteira", @@ -30,7 +30,7 @@ export const trainerClasses: SimpleTranslationEntries = { "baker": "Padeira", "battle_girl": "Lutadora", "beauty": "Modelo", - "beginners": "Beginners", + "beginners": "Iniciantes", "biker": "Motoqueiro", "black_belt": "Faixa Preta", "breeder": "Criador", From ffdbdc1139e8f5f3c795282b46a828d024bfe54d Mon Sep 17 00:00:00 2001 From: DustinLin <39450497+DustinLin@users.noreply.github.com> Date: Fri, 7 Jun 2024 05:38:22 -0700 Subject: [PATCH 107/129] fix sleep talk targeting (#1813) --- src/data/move.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/data/move.ts b/src/data/move.ts index d0e8aff7a46..6b189d0d3e3 100755 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -5913,6 +5913,7 @@ export function initMoves() { .attr(BypassSleepAttr) .attr(RandomMovesetMoveAttr) .condition(userSleptOrComatoseCondition) + .target(MoveTarget.ALL_ENEMIES) .ignoresVirtual(), new StatusMove(Moves.HEAL_BELL, Type.NORMAL, -1, 5, -1, 0, 2) .attr(PartyStatusCureAttr, "A bell chimed!", Abilities.SOUNDPROOF) From 50bf717c5c00ed09992ac650ad16b913c5527560 Mon Sep 17 00:00:00 2001 From: Sangmin Lee <66083363+GINK-SS@users.noreply.github.com> Date: Fri, 7 Jun 2024 22:57:14 +0900 Subject: [PATCH 108/129] [Localization] #1761 Korean trainer dialogue (roark, gardenia, maylene, fantina) (#1905) * [Localization] #1761 Korean trainer dialogue (roark, gardenia) * [Localization] #1761 Korean trainer dialogue (maylene, fantina) * Modified Roark's dialogue more natural Co-authored-by: returntoice --------- Co-authored-by: returntoice --- src/locales/ko/dialogue.ts | 46 +++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/locales/ko/dialogue.ts b/src/locales/ko/dialogue.ts index 7867ae7b021..27e43fe17a3 100644 --- a/src/locales/ko/dialogue.ts +++ b/src/locales/ko/dialogue.ts @@ -854,22 +854,22 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "roark": { "encounter": { - 1: "I need to see your potential as a Trainer. And, I'll need to see the toughness of the Pokémon that battle with you!", - 2: "Here goes! These are my rocking Pokémon, my pride and joy!", - 3: "Rock-type Pokémon are simply the best!", - 4: "I need to see your potential as a Trainer. And, I'll need to see the toughness of the Pokémon that battle with you!" + 1: "트레이너로서 너의 실력이 어느 정도인지 그리고 함께 싸울 포켓몬이 얼마나 강한지 확인해보겠어!", + 2: "자 간다! 내 자랑이자 기쁨인 바위타입 포켓몬들이야!", + 3: "바위타입 포켓몬이야말로 최고지!", + 4: "트레이너로서 너의 실력이 어느 정도인지 그리고 함께 싸울 포켓몬이 얼마나 강한지 확인해보겠어!" }, "victory": { - 1: "W-what? That can't be! My buffed-up Pokémon!", - 2: "…We lost control there. Next time I'd like to challenge you to a Fossil-digging race underground.", - 3: "With skill like yours, it's natural for you to win.", - 4: "Wh-what?! It can't be! Even that wasn't enough?", - 5: "I blew it." + 1: "이-이럴수가! 열심히 단련시킨 포켓몬들이!", + 2: "…져버린 건가. 다음엔 지하통로에서 화석캐기 승부를 하고 싶구나.", + 3: "네 실력이라면 승리는 당연한 결과겠지.", + 4: "뭐-뭐야?! 이럴 수가! 이것도 부족했단 말인가?", + 5: "내가 실수했군." }, "defeat": { - 1: "See? I'm proud of my rocking battle style!", - 2: "Thanks! The battle gave me confidence that I may be able to beat my dad!", - 3: "I feel like I just smashed through a really stubborn boulder!" + 1: "봤지? 이게 바로 내가 자랑하는 바위 스타일 전투야!", + 2: "고마워! 이번 승부로 아버지를 이길 수 있겠다는 자신감이 생겼어!", + 3: "정말 단단한 바위를 뚫고 나온 기분인데!" } }, "morty": { @@ -1001,13 +1001,13 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "gardenia": { "encounter": { - 1: "You have a winning aura about you. So, anyway, this will be fun. Let's have our battle!" + 1: "너에게선 승리의 기운이 느껴져. 어쨌든 재밌을 것 같아. 포켓몬 승부하자!" }, "victory": { - 1: "Amazing! You're very good, aren't you?" + 1: "대단해! 너 정말 강하구나!" }, "defeat": { - 1: "Yes! My Pokémon and I are perfectly good!" + 1: "그렇지! 나와 포켓몬은 완벽해!" } }, "aaron": { @@ -1707,26 +1707,26 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "maylene": { "encounter": { - 1: `I've come to challenge you now, and I won't hold anything back. - $Please prepare yourself for battle!`, + 1: `지금 당신에게 도전하러 왔어요. + $전력을 다할 테니 각오하세요!`, }, "victory": { - 1: "I admit defeat…" + 1: "나의 패배입니다…" }, "defeat": { - 1: "That was awesome." + 1: "멋진 승부였습니다." } }, "fantina": { "encounter": { - 1: `You shall challenge me, yes? But I shall win. - $That is what the Gym Leader of Hearthome does, non?`, + 1: `당신도 도전해 보세요. 전 당신을 이기겠어요. + $그것이 체육관 관장!`, }, "victory": { - 1: "You are so fantastically strong. I know why I have lost." + 1: "당신 최고로 강해요. 나 진 것 알아요." }, "defeat": { - 1: "I am so, so, very happy!" + 1: "너무, 너무 행복해요!" } }, "byron": { From 4745b591c2629b2e3d24d9dc8ccbaa52fd5c1b5b Mon Sep 17 00:00:00 2001 From: MadridPawmot <42167718+Alekemon@users.noreply.github.com> Date: Fri, 7 Jun 2024 16:02:24 +0200 Subject: [PATCH 109/129] [Localization] Spanish dialogues (#1892) * Update dialogue.ts * Fixed typo --- src/locales/es/dialogue.ts | 136 ++++++++++++++++++------------------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/src/locales/es/dialogue.ts b/src/locales/es/dialogue.ts index 938ef331088..b27f5a7570c 100644 --- a/src/locales/es/dialogue.ts +++ b/src/locales/es/dialogue.ts @@ -4,104 +4,104 @@ import {DialogueTranslationEntries, SimpleTranslationEntries} from "#app/plugins export const PGMdialogue: DialogueTranslationEntries = { "youngster": { "encounter": { - 1: "Hey, wanna battle?", - 2: "Are you a new trainer too?", - 3: "Hey, I haven't seen you before. Let's battle!", - 4: "I just lost, so I'm trying to find more Pokémon.\nWait! You look weak! Come on, let's battle!", - 5: "Have we met or not? I don't really remember. Well, I guess it's nice to meet you anyway!", - 6: "All right! Let's go!", - 7: "All right! Here I come! I'll show you my power!", - 8: "Haw haw haw... I'll show you how hawesome my Pokémon are!", - 9: "No need to waste time saying hello. Bring it on whenever you're ready!", - 10: "Don't let your guard down, or you may be crying when a kid beats you.", - 11: "I've raised my Pokémon with great care. You're not allowed to hurt them!", - 12: "Glad you made it! It won't be an easy job from here.", - 13: "The battles continue forever! Welcome to the world with no end!" + 1: "Hey, ¿quieres luchar?", + 2: "¿También eres un entrenador novato?", + 3: "No te había visto antes. ¡Vamos a luchar!", + 4: "Perdí y estoy intentando buscar nuevos Pokémon.\n¡Espera, pareces débil! ¡Vamos, a luchar!", + 5: "¿Nos conocimos o no? Ni me acuerdo. ¡Supongo que es un gusto conocerte!", + 6: "¡Venga, vamos!", + 7: "¡Allá voy! ¡Te enseñaré mi poder!", + 8: "¡Ja ja ja! ¡Te enseñaré lo jimpresionante que es mi equipo!", + 9: "No pierdas tiempo saludando. ¡Adelante, cuando estés listo!", + 10: "No bajes la guardia o llorarás porque un niño te ganó.", + 11: "He cuidado a mis Pokémon con cariño. ¡Prohibido hacerles daño!", + 12: "¡Encantado de que lo lograses! A partir de aquí no será fácil.", + 13: "¡Las batallas continúan para siempre! ¡Bienvenido al mundo sin fin!" }, "victory": { - 1: "Wow! You're strong!", - 2: "I didn't stand a chance, huh?", - 3: "I'll find you again when I'm older and beat you!", - 4: "Ugh. I don't have any more Pokémon.", - 5: "No way… NO WAY! How could I lose again…", - 6: "No! I lost!", - 7: "Whoa! You are incredible! I'm amazed and surprised!", - 8: "Could it be… How… My Pokémon and I are the strongest, though…", - 9: "I won't lose next time! Let's battle again sometime!", - 10: "Sheesh! Can't you see that I'm just a kid! It wasn't fair of you to go all out like that!", - 11: "Your Pokémon are more amazing! Trade with me!", - 12: "I got a little carried away earlier, but what job was I talking about?", - 13: "Ahaha! There it is! That's right! You're already right at home in this world!" + 1: "¡Guau! ¡Eres fuerte!", + 2: "¿No tuve oportunidad, eh?", + 3: "¡Cuando sea mayor te encontraré y te ganaré!", + 4: "Ay. No tengo más Pokémon.", + 5: "Imposible… ¡IMPOSIBLE! Cómo pude perder de nuevo…", + 6: "¡No! ¡Perdí!", + 7: "¡Guau! ¡Eres increíble! ¡Estoy alucinado y sorprendido!", + 8: "Puede ser… Cómo… Aunque mis Pokémon y yo somos los más fuertes…", + 9: "¡No perderé a la próxima! ¡Luchemos otra vez pronto!", + 10: "¡Oye! ¡No ves que solo soy un niño! ¡No es justo que vayas así conmigo!", + 11: "¡Tus Pokémon molan más! ¡Intercámbiamelos!", + 12: "Me perdí antes pero, ¿de qué tarea estaba hablando antes?", + 13: "¡Jajaja! ¡Esa es! ¡Correcto! ¡Ya te sientes como en casa en este mundo!" } }, "lass": { "encounter": { - 1: "Let's have a battle, shall we?", - 2: "You look like a new trainer. Let's have a battle!", - 3: "I don't recognize you. How about a battle?", - 4: "Let's have a fun Pokémon battle!", - 5: "I'll show you the ropes of how to really use Pokémon!", - 6: "A serious battle starts from a serious beginning! Are you sure you're ready?", - 7: "You're only young once. And you only get one shot at a given battle. Soon, you'll be nothing but a memory.", - 8: "You'd better go easy on me, OK? Though I'll be seriously fighting!", - 9: "School is boring. I've got nothing to do. Yawn. I'm only battling to kill the time." + 1: "¿Luchemos, podría ser?", + 2: "Pareces novato. ¡Luchemos!", + 3: "No te reconozco. ¿Un combate?", + 4: "¡Tengamos un combate Pokémon divertido!", + 5: "¡Te enseñaré lo básico de cómo entrenar Pokémon!", + 6: "¡Un combate serio empieza por un comienzo serio! ¿Seguro que estás listo?", + 7: "Solo se es joven una vez. Y solo tienes una oportunidad en una batalla. Pronto, solo serás un recuerdo.", + 8: "Asegúrate de ir fácil conmigo, ¿vale? ¡Pero seré seria luchando!", + 9: "El colegio es aburrido. No hay nada que hacer. Uaa. Solo lucho para pasar el tiempo." }, "victory": { - 1: "That was impressive! I've got a lot to learn.", - 2: "I didn't think you'd beat me that bad…", - 3: "I hope we get to have a rematch some day.", - 4: "That was pretty amazingly fun! You've totally exhausted me…", - 5: "You actually taught me a lesson! You're pretty amazing!", - 6: "Seriously, I lost. That is, like, seriously depressing, but you were seriously cool.", - 7: "I don't need memories like this. Deleting memory…", - 8: "Hey! I told you to go easy on me! Still, you're pretty cool when you're serious.", - 9: "I'm actually getting tired of battling… There's gotta be something new to do…" + 1: "¡Impresionante! Tengo mucho que aprender.", + 2: "No pensé que me vencerías así…", + 3: "Espero la revancha algún día.", + 4: "¡Fue increíblemente divertido! Me dejaste cansada…", + 5: "¡Me enseñaste una lección! ¡Eres increíble!", + 6: "En serio, he perdido. O sea, es seriamente decepcionante, pero tú eres realmente guay.", + 7: "No necesito estos recuerdos. Borrando recuerdos…", + 8: "¡Te dije que fueses fácil conmigo! Aun así, me gusta cuando eres serio.", + 9: "Me canso de luchar… Habrá algo nuevo que hacer…" } }, "breeder": { "encounter": { - 1: "Obedient Pokémon, selfish Pokémon… Pokémon have unique characteristics.", - 2: "Even though my upbringing and behavior are poor, I've raised my Pokémon well.", - 3: "Hmm, do you discipline your Pokémon? Pampering them too much is no good.", + 1: "Pokémon obedientes, Pokémon egoístas… Los Pokémon tienen características únicas.", + 2: "Aunque tengo descendencia y comportamiento pobre, he cuidado muy bien a mis Pokémon.", + 3: "¿Tienes disciplina con tus Pokémon? Malcriarlos mucho no es bueno.", }, "victory": { - 1: "It is important to nurture and train each Pokémon's characteristics.", - 2: "Unlike my diabolical self, these are some good Pokémon.", - 3: "Too much praise can spoil both Pokémon and people.", + 1: "Es importante cuidar y mimar todas las características de los Pokémon.", + 2: "No como mi diabólica personalidad, mis Pokémon son muy buenos.", + 3: "Malcriar puede arruinar a los Pokémon y a los humanos.", }, "defeat": { - 1: "You should not get angry at your Pokémon, even if you lose a battle.", - 2: "Right? Pretty good Pokémon, huh? I'm suited to raising things.", - 3: "No matter how much you love your Pokémon, you still have to discipline them when they misbehave." + 1: "No deberías enfadarte con tu Pokémon, aún tras un combate perdido.", + 2: "¿Eh? ¿Buen Pokémon? Me acostumbro a cuidarlos.", + 3: "No importa el cariño que le tengas a tus Pokémon, tienes que ser serio si se portan mal." } }, "breeder_female": { "encounter": { - 1: "Pokémon never betray you. They return all the love you give them.", - 2: "Shall I give you a tip for training good Pokémon?", - 3: "I have raised these very special Pokémon using a special method." + 1: "Los Pokémon nunca te traicionan, te devuelven el amor que les diste.", + 2: "¿Puedo darte un consejo para entrenar bien tus Pokémon?", + 3: "Cuidé estos Pokémon con un método especial." }, "victory": { - 1: "Ugh… It wasn't supposed to be like this. Did I administer the wrong blend?", - 2: "How could that happen to my Pokémon… What are you feeding your Pokémon?", - 3: "If I lose, that tells you I was just killing time. It doesn't damage my ego at all." + 1: "Ugh… No se supone que acabaría así. ¿Les cuidé de forma errónea?", + 2: "Cómo pudo pasarle esto a mi Pokémon... ¿Qué le das a tu Pokémon?", + 3: "Si pierdo, solo fue un pasatiempo. Mi ego se ve intacto." }, "defeat": { - 1: "This proves my Pokémon have accepted my love.", - 2: "The real trick behind training good Pokémon is catching good Pokémon.", - 3: "Pokémon will be strong or weak depending on how you raise them." + 1: "Esto demuestra que los Pokémon aceptaron mi amor.", + 2: "El truco detrás de atrapar buenos Pokémon es cuidar buenos Pokémon.", + 3: "Los Pokémon serán fuertes o débiles según los cuides." } }, "fisherman": { "encounter": { - 1: "Aack! You made me lose a bite!\nWhat are you going to do about it?", - 2: "Go away! You're scaring the Pokémon!", - 3: "Let's see if you can reel in a victory!", + 1: "¡Uy uy uy! ¡Hiciste que huyera un pez!\n¿Qué harás al respecto?", + 2: "¡Vete! ¡Espantas los Pokémon!", + 3: "¡Veamos si pescas una victoria!", }, "victory": { - 1: "Just forget about it.", - 2: "Next time, I'll be reelin' in the triumph!", - 3: "Guess I underestimated the currents this time.", + 1: "Olvídalo.", + 2: "A la siguiente, ¡pescaré mi victoria!", + 3: "Infravaloré la corriente esta vez.", }, }, "fisherman_female": { From f17a4ff3f2c33562e51a0c9c8d2c641386cfed0b Mon Sep 17 00:00:00 2001 From: MutenYoshii Date: Fri, 7 Jun 2024 10:27:11 -0400 Subject: [PATCH 110/129] [Move] Implemented Court Change (#1799) * [Move] Implemented Court Change * Returned overides to normal * Added recommended changes * Removed an unnecessary if statement for swaparenatagsattr * Move the swaptags array to the call as well as changes to the quiet boolean --------- Co-authored-by: Juan --- src/data/arena-tag.ts | 69 ++++++++++++++++++++++++++++--------------- src/data/move.ts | 48 +++++++++++++++++++++++++++--- src/field/arena.ts | 8 ++--- 3 files changed, 94 insertions(+), 31 deletions(-) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 37de1af2efa..a0c36649e84 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -29,6 +29,7 @@ export abstract class ArenaTag { public sourceId: integer; public side: ArenaTagSide; + constructor(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId?: integer, side: ArenaTagSide = ArenaTagSide.BOTH) { this.tagType = tagType; this.turnCount = turnCount; @@ -41,10 +42,12 @@ export abstract class ArenaTag { return true; } - onAdd(arena: Arena): void { } + onAdd(arena: Arena, quiet: boolean = false): void { } - onRemove(arena: Arena): void { - arena.scene.queueMessage(`${this.getMoveName()}\'s effect wore off${this.side === ArenaTagSide.PLAYER ? "\non your side" : this.side === ArenaTagSide.ENEMY ? "\non the foe's side" : ""}.`); + onRemove(arena: Arena, quiet: boolean = false): void { + if (!quiet) { + arena.scene.queueMessage(`${this.getMoveName()}\'s effect wore off${this.side === ArenaTagSide.PLAYER ? "\non your side" : this.side === ArenaTagSide.ENEMY ? "\non the foe's side" : ""}.`); + } } onOverlap(arena: Arena): void { } @@ -65,11 +68,13 @@ export class MistTag extends ArenaTag { super(ArenaTagType.MIST, turnCount, Moves.MIST, sourceId, side); } - onAdd(arena: Arena): void { + onAdd(arena: Arena, quiet: boolean = false): void { super.onAdd(arena); const source = arena.scene.getPokemonById(this.sourceId); - arena.scene.queueMessage(getPokemonMessage(source, "'s team became\nshrouded in mist!")); + if (!quiet) { + arena.scene.queueMessage(getPokemonMessage(source, "'s team became\nshrouded in mist!")); + } } apply(arena: Arena, args: any[]): boolean { @@ -113,8 +118,10 @@ class ReflectTag extends WeakenMoveScreenTag { return false; } - onAdd(arena: Arena): void { - arena.scene.queueMessage(`Reflect reduced the damage of physical moves${this.side === ArenaTagSide.PLAYER ? "\non your side" : this.side === ArenaTagSide.ENEMY ? "\non the foe's side" : ""}.`); + onAdd(arena: Arena, quiet: boolean = false): void { + if (!quiet) { + arena.scene.queueMessage(`Reflect reduced the damage of physical moves${this.side === ArenaTagSide.PLAYER ? "\non your side" : this.side === ArenaTagSide.ENEMY ? "\non the foe's side" : ""}.`); + } } } @@ -135,8 +142,10 @@ class LightScreenTag extends WeakenMoveScreenTag { return false; } - onAdd(arena: Arena): void { - arena.scene.queueMessage(`Light Screen reduced the damage of special moves${this.side === ArenaTagSide.PLAYER ? "\non your side" : this.side === ArenaTagSide.ENEMY ? "\non the foe's side" : ""}.`); + onAdd(arena: Arena, quiet: boolean = false): void { + if (!quiet) { + arena.scene.queueMessage(`Light Screen reduced the damage of special moves${this.side === ArenaTagSide.PLAYER ? "\non your side" : this.side === ArenaTagSide.ENEMY ? "\non the foe's side" : ""}.`); + } } } @@ -145,8 +154,10 @@ class AuroraVeilTag extends WeakenMoveScreenTag { super(ArenaTagType.AURORA_VEIL, turnCount, Moves.AURORA_VEIL, sourceId, side); } - onAdd(arena: Arena): void { - arena.scene.queueMessage(`Aurora Veil reduced the damage of moves${this.side === ArenaTagSide.PLAYER ? "\non your side" : this.side === ArenaTagSide.ENEMY ? "\non the foe's side" : ""}.`); + onAdd(arena: Arena, quiet: boolean = false): void { + if (!quiet) { + arena.scene.queueMessage(`Aurora Veil reduced the damage of moves${this.side === ArenaTagSide.PLAYER ? "\non your side" : this.side === ArenaTagSide.ENEMY ? "\non the foe's side" : ""}.`); + } } } @@ -386,11 +397,13 @@ class SpikesTag extends ArenaTrapTag { super(ArenaTagType.SPIKES, Moves.SPIKES, sourceId, side, 3); } - onAdd(arena: Arena): void { + onAdd(arena: Arena, quiet: boolean = false): void { super.onAdd(arena); const source = arena.scene.getPokemonById(this.sourceId); - arena.scene.queueMessage(`${this.getMoveName()} were scattered\nall around ${source.getOpponentDescriptor()}'s feet!`); + if (!quiet) { + arena.scene.queueMessage(`${this.getMoveName()} were scattered\nall around ${source.getOpponentDescriptor()}'s feet!`); + } } activateTrap(pokemon: Pokemon): boolean { @@ -423,11 +436,13 @@ class ToxicSpikesTag extends ArenaTrapTag { this.neutralized = false; } - onAdd(arena: Arena): void { + onAdd(arena: Arena, quiet: boolean = false): void { super.onAdd(arena); const source = arena.scene.getPokemonById(this.sourceId); - arena.scene.queueMessage(`${this.getMoveName()} were scattered\nall around ${source.getOpponentDescriptor()}'s feet!`); + if (!quiet) { + arena.scene.queueMessage(`${this.getMoveName()} were scattered\nall around ${source.getOpponentDescriptor()}'s feet!`); + } } onRemove(arena: Arena): void { @@ -493,11 +508,13 @@ class StealthRockTag extends ArenaTrapTag { super(ArenaTagType.STEALTH_ROCK, Moves.STEALTH_ROCK, sourceId, side, 1); } - onAdd(arena: Arena): void { + onAdd(arena: Arena, quiet: boolean = false): void { super.onAdd(arena); const source = arena.scene.getPokemonById(this.sourceId); - arena.scene.queueMessage(`Pointed stones float in the air\naround ${source.getOpponentDescriptor()}!`); + if (!quiet) { + arena.scene.queueMessage(`Pointed stones float in the air\naround ${source.getOpponentDescriptor()}!`); + } } getDamageHpRatio(pokemon: Pokemon): number { @@ -562,13 +579,15 @@ class StickyWebTag extends ArenaTrapTag { super(ArenaTagType.STICKY_WEB, Moves.STICKY_WEB, sourceId, side, 1); } - onAdd(arena: Arena): void { + onAdd(arena: Arena, quiet: boolean = false): void { super.onAdd(arena); // does not seem to be used anywhere // eslint-disable-next-line @typescript-eslint/no-unused-vars const source = arena.scene.getPokemonById(this.sourceId); - arena.scene.queueMessage(`A ${this.getMoveName()} has been laid out on the ground around the opposing team!`); + if (!quiet) { + arena.scene.queueMessage(`A ${this.getMoveName()} has been laid out on the ground around the opposing team!`); + } } activateTrap(pokemon: Pokemon): boolean { @@ -626,8 +645,10 @@ class TailwindTag extends ArenaTag { super(ArenaTagType.TAILWIND, turnCount, Moves.TAILWIND, sourceId, side); } - onAdd(arena: Arena): void { - arena.scene.queueMessage(`The Tailwind blew from behind${this.side === ArenaTagSide.PLAYER ? "\nyour" : this.side === ArenaTagSide.ENEMY ? "\nthe opposing" : ""} team!`); + onAdd(arena: Arena, quiet: boolean = false): void { + if (!quiet) { + arena.scene.queueMessage(`The Tailwind blew from behind${this.side === ArenaTagSide.PLAYER ? "\nyour" : this.side === ArenaTagSide.ENEMY ? "\nthe opposing" : ""} team!`); + } const source = arena.scene.getPokemonById(this.sourceId); const party = source.isPlayer() ? source.scene.getPlayerField() : source.scene.getEnemyField(); @@ -646,8 +667,10 @@ class TailwindTag extends ArenaTag { } } - onRemove(arena: Arena): void { - arena.scene.queueMessage(`${this.side === ArenaTagSide.PLAYER ? "Your" : this.side === ArenaTagSide.ENEMY ? "The opposing" : ""} team's Tailwind petered out!`); + onRemove(arena: Arena, quiet: boolean = false): void { + if (!quiet) { + arena.scene.queueMessage(`${this.side === ArenaTagSide.PLAYER ? "Your" : this.side === ArenaTagSide.ENEMY ? "The opposing" : ""} team's Tailwind petered out!`); + } } } diff --git a/src/data/move.ts b/src/data/move.ts index 6b189d0d3e3..9bdfc90903f 100755 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -2195,7 +2195,7 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr { (args[0] as Utils.BooleanHolder).value = true; user.scene.queueMessage(getPokemonMessage(user, ` ${this.chargeText.replace("{TARGET}", target.name)}`)); user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER }); - user.scene.arena.addTag(this.tagType, 3, move.id, user.id, ArenaTagSide.BOTH, target.getBattlerIndex()); + user.scene.arena.addTag(this.tagType, 3, move.id, user.id, ArenaTagSide.BOTH, false, target.getBattlerIndex()); resolve(true); }); @@ -4224,6 +4224,48 @@ export class RemoveScreensAttr extends MoveEffectAttr { } } + +/*Swaps arena effects between the player and enemy side + * @extends MoveEffectAttr + * @see {@linkcode apply} +*/ +export class SwapArenaTagsAttr extends MoveEffectAttr { + public SwapTags: ArenaTagType[]; + + + constructor(SwapTags: ArenaTagType[]) { + super(true, MoveEffectTrigger.POST_APPLY); + this.SwapTags = SwapTags; + } + + apply(user:Pokemon, target:Pokemon, move:Move, args: any[]): boolean { + if (!super.apply(user, target, move, args)) { + return false; + } + + const tagPlayerTemp = user.scene.arena.findTagsOnSide((t => this.SwapTags.includes(t.tagType)), ArenaTagSide.PLAYER); + const tagEnemyTemp = user.scene.arena.findTagsOnSide((t => this.SwapTags.includes(t.tagType)), ArenaTagSide.ENEMY); + + + if (tagPlayerTemp) { + for (const swapTagsType of tagPlayerTemp) { + user.scene.arena.removeTagOnSide(swapTagsType.tagType, ArenaTagSide.PLAYER, true); + user.scene.arena.addTag(swapTagsType.tagType, swapTagsType.turnCount, swapTagsType.sourceMove, swapTagsType.sourceId, ArenaTagSide.ENEMY, true); + } + } + if (tagEnemyTemp) { + for (const swapTagsType of tagEnemyTemp) { + user.scene.arena.removeTagOnSide(swapTagsType.tagType, ArenaTagSide.ENEMY, true); + user.scene.arena.addTag(swapTagsType.tagType, swapTagsType.turnCount, swapTagsType.sourceMove, swapTagsType.sourceId, ArenaTagSide.PLAYER, true); + } + } + + + user.scene.queueMessage( `${user.name} swapped the battle effects affecting each side of the field!`); + return true; + } +} + /** * Attribute used for Revival Blessing. * @extends MoveEffectAttr @@ -7461,9 +7503,7 @@ export function initMoves() { .attr(FirstAttackDoublePowerAttr) .bitingMove(), new StatusMove(Moves.COURT_CHANGE, Type.NORMAL, 100, 10, -1, 0, 8) - .target(MoveTarget.BOTH_SIDES) - .unimplemented(), - /* Unused */ + .attr(SwapArenaTagsAttr, [ArenaTagType.AURORA_VEIL, ArenaTagType.LIGHT_SCREEN, ArenaTagType.MIST, ArenaTagType.REFLECT, ArenaTagType.SPIKES, ArenaTagType.STEALTH_ROCK, ArenaTagType.STICKY_WEB, ArenaTagType.TAILWIND, ArenaTagType.TOXIC_SPIKES]), new AttackMove(Moves.MAX_FLARE, Type.FIRE, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) .unimplemented() diff --git a/src/field/arena.ts b/src/field/arena.ts index 6999eb39785..d34cd2c9bea 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -545,7 +545,7 @@ export class Arena { this.applyTagsForSide(tagType, ArenaTagSide.BOTH, ...args); } - addTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, side: ArenaTagSide = ArenaTagSide.BOTH, targetIndex?: BattlerIndex): boolean { + addTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, side: ArenaTagSide = ArenaTagSide.BOTH, quiet: boolean = false, targetIndex?: BattlerIndex): boolean { const existingTag = this.getTagOnSide(tagType, side); if (existingTag) { existingTag.onOverlap(this); @@ -554,7 +554,7 @@ export class Arena { const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId, targetIndex, side); this.tags.push(newTag); - newTag.onAdd(this); + newTag.onAdd(this, quiet); this.eventTarget.dispatchEvent(new TagAddedEvent(newTag.tagType, newTag.side, newTag.turnCount)); @@ -600,10 +600,10 @@ export class Arena { return !!tag; } - removeTagOnSide(tagType: ArenaTagType, side: ArenaTagSide): boolean { + removeTagOnSide(tagType: ArenaTagType, side: ArenaTagSide, quiet: boolean = false): boolean { const tag = this.getTagOnSide(tagType, side); if (tag) { - tag.onRemove(this); + tag.onRemove(this, quiet); this.tags.splice(this.tags.indexOf(tag), 1); this.eventTarget.dispatchEvent(new TagRemovedEvent(tag.tagType, tag.side, tag.turnCount)); From 20a3a4f60fe58a5fe929a51dff76a5db64080492 Mon Sep 17 00:00:00 2001 From: Matthew Olker Date: Fri, 7 Jun 2024 10:27:29 -0400 Subject: [PATCH 111/129] added a few more gameobject names for debug --- src/battle-scene.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index e5c75e26ebb..54b378b8081 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -320,6 +320,7 @@ export default class BattleScene extends SceneBase { const field = this.add.container(0, 0); field.setScale(6); + field.setName("container-field"); this.field = field; @@ -455,9 +456,13 @@ export default class BattleScene extends SceneBase { const loadPokemonAssets = []; this.arenaPlayer = new ArenaBase(this, true); + this.arenaPlayer.setName("container-arena-player"); this.arenaPlayerTransition = new ArenaBase(this, true); + this.arenaPlayerTransition.setName("container-arena-player-transition"); this.arenaEnemy = new ArenaBase(this, false); + this.arenaEnemy.setName("container-arena-enemy"); this.arenaNextEnemy = new ArenaBase(this, false); + this.arenaNextEnemy.setName("container-arena-next-enemy"); this.arenaBgTransition.setVisible(false); this.arenaPlayerTransition.setVisible(false); @@ -472,6 +477,7 @@ export default class BattleScene extends SceneBase { const trainer = this.addFieldSprite(0, 0, `trainer_${this.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`); trainer.setOrigin(0.5, 1); + trainer.setName("sprite-trainer"); field.add(trainer); From 19712e0d430f84e7217d10c6b78405facab90a87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=AA=E9=9C=81?= Date: Sat, 8 Jun 2024 00:27:42 +0800 Subject: [PATCH 112/129] fix language auto detect (#1784) --- src/locales/zh_CN/tutorial.ts | 2 +- src/plugins/i18n.ts | 6 +++--- src/system/settings/settings.ts | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/locales/zh_CN/tutorial.ts b/src/locales/zh_CN/tutorial.ts index fc737798489..8b0e5aad8fd 100644 --- a/src/locales/zh_CN/tutorial.ts +++ b/src/locales/zh_CN/tutorial.ts @@ -1,7 +1,7 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const tutorial: SimpleTranslationEntries = { - "intro": `欢迎来到PokéRogue!这是一款以战斗为核心的融合了roguelite元素的宝可梦同人游戏。 + "intro": `欢迎来到PokéRogue!这是一款以战斗为核心的\n融合了roguelite元素的宝可梦同人游戏。 $本游戏未进行商业化,我们没有\nPokémon或Pokémon使用的版 $权资产的所有权。 $游戏仍在开发中,但已可完整游玩。如需报\n告错误,请通过 Discord 社区。 diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts index 3f6469904d4..7d485d3f046 100644 --- a/src/plugins/i18n.ts +++ b/src/plugins/i18n.ts @@ -174,13 +174,13 @@ export function initI18n(): void { de: { ...deConfig }, - pt_BR: { + "pt-BR": { ...ptBrConfig }, - zh_CN: { + "zh-CN": { ...zhCnConfig }, - zh_TW: { + "zh-TW": { ...zhTwConfig }, ko: { diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index 75fc8185c89..f79393dac5c 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -480,15 +480,15 @@ export function setSetting(scene: BattleScene, setting: string, value: integer): }, { label: "Português (BR)", - handler: () => changeLocaleHandler("pt_BR") + handler: () => changeLocaleHandler("pt-BR") }, { label: "简体中文", - handler: () => changeLocaleHandler("zh_CN") + handler: () => changeLocaleHandler("zh-CN") }, { label: "繁體中文", - handler: () => changeLocaleHandler("zh_TW") + handler: () => changeLocaleHandler("zh-TW") }, { label: "한국어", From bb1dde5b0c68164f370a52e59bfc21274f9872a3 Mon Sep 17 00:00:00 2001 From: hayuna Date: Fri, 7 Jun 2024 19:14:52 +0200 Subject: [PATCH 113/129] [Refactor] Move enum ExpNotification into separated file (#1909) * Move enum ExpNotification into separated file * Update phases.ts * Change type of this.scene.expParty into enum --- src/battle-scene.ts | 3 ++- src/enums/exp-notification.ts | 11 +++++++++++ src/phases.ts | 15 ++++++++------- 3 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 src/enums/exp-notification.ts diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 54b378b8081..cce26a5adf6 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -62,6 +62,7 @@ import { NewArenaEvent } from "./battle-scene-events"; import { Abilities } from "./data/enums/abilities"; import ArenaFlyout from "./ui/arena-flyout"; import { EaseType } from "./ui/enums/ease-type"; +import { ExpNotification } from "./enums/exp-notification"; export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1"; @@ -141,7 +142,7 @@ export default class BattleScene extends SceneBase { * Modes `1` and `2` are still compatible with stats display, level up, new move, etc. * @default 0 - Uses the default normal experience gain display. */ - public expParty: integer = 0; + public expParty: ExpNotification = 0; public hpBarSpeed: integer = 0; public fusionPaletteSwaps: boolean = true; public enableTouchControls: boolean = false; diff --git a/src/enums/exp-notification.ts b/src/enums/exp-notification.ts new file mode 100644 index 00000000000..b7f50814d3a --- /dev/null +++ b/src/enums/exp-notification.ts @@ -0,0 +1,11 @@ +/** + * Determines exp notification style. + * - Default - the normal exp gain display, nothing changed + * - Only level up - we display the level up in the small frame instead of a message + * - Skip - no level up frame nor message +*/ +export enum ExpNotification { + DEFAULT, + ONLY_LEVEL_UP, + SKIP +} diff --git a/src/phases.ts b/src/phases.ts index 3b4cb021ccf..0bd4cb9469d 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -63,6 +63,7 @@ import * as Overrides from "./overrides"; import { TextStyle, addTextObject } from "./ui/text"; import { Type } from "./data/type"; import { BerryUsedEvent, EncounterPhaseEvent, MoveUsedEvent, TurnEndEvent, TurnInitEvent } from "./battle-scene-events"; +import { ExpNotification } from "./enums/exp-notification"; export class LoginPhase extends Phase { @@ -4289,20 +4290,20 @@ export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase { this.scene.unshiftPhase(new HidePartyExpBarPhase(this.scene)); pokemon.updateInfo(); - if (this.scene.expParty === 2) { // 2 - Skip - no level up frame nor message + if (this.scene.expParty === ExpNotification.SKIP) { this.end(); - } else if (this.scene.expParty === 1) { // 1 - Only level up - we display the level up in the small frame instead of a message + } else if (this.scene.expParty === ExpNotification.ONLY_LEVEL_UP) { if (newLevel > lastLevel) { // this means if we level up // instead of displaying the exp gain in the small frame, we display the new level // we use the same method for mode 0 & 1, by giving a parameter saying to display the exp or the level - this.scene.partyExpBar.showPokemonExp(pokemon, exp.value, this.scene.expParty === 1, newLevel).then(() => { + this.scene.partyExpBar.showPokemonExp(pokemon, exp.value, this.scene.expParty === ExpNotification.ONLY_LEVEL_UP, newLevel).then(() => { setTimeout(() => this.end(), 800 / Math.pow(2, this.scene.expGainsSpeed)); }); } else { this.end(); } } else if (this.scene.expGainsSpeed < 3) { - this.scene.partyExpBar.showPokemonExp(pokemon, exp.value, this.scene.expParty === 1, newLevel).then(() => { + this.scene.partyExpBar.showPokemonExp(pokemon, exp.value, false, newLevel).then(() => { setTimeout(() => this.end(), 500 / Math.pow(2, this.scene.expGainsSpeed)); }); } else { @@ -4349,12 +4350,12 @@ export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { const prevStats = pokemon.stats.slice(0); pokemon.calculateStats(); pokemon.updateInfo(); - if (this.scene.expParty === 0) { // 0 - default - the normal exp gain display, nothing changed + if (this.scene.expParty === ExpNotification.DEFAULT) { this.scene.playSound("level_up_fanfare"); this.scene.ui.showText(i18next.t("battle:levelUp", { pokemonName: this.getPokemon().name, level: this.level }), null, () => this.scene.ui.getMessageHandler().promptLevelUpStats(this.partyMemberIndex, prevStats, false).then(() => this.end()), null, true); - } else if (this.scene.expParty === 2) { // 2 - Skip - no level up frame nor message + } else if (this.scene.expParty === ExpNotification.SKIP) { this.end(); - } else { // 1 - Only level up - we display the level up in the small frame instead of a message + } else { // we still want to display the stats if activated this.scene.ui.getMessageHandler().promptLevelUpStats(this.partyMemberIndex, prevStats, false).then(() => this.end()); } From fb21caa769423b5171127759e6972fc59bb9849e Mon Sep 17 00:00:00 2001 From: Adam Clemons Date: Fri, 7 Jun 2024 12:17:22 -0600 Subject: [PATCH 114/129] [Bug] Fix ability Download switch in (1669) (#1679) * Update ability Dowbload * Remove whitespace * Update ability.ts --- src/data/ability.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index db44835a86a..4b0b380a42f 100755 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -1693,15 +1693,13 @@ export class DownloadAbAttr extends PostSummonAbAttr { this.enemySpDef = 0; this.enemyCountTally = 0; - if (pokemon.getOpponents()[0].summonData !== undefined) { - for (const opponent of pokemon.getOpponents()) { - this.enemyCountTally++; - this.enemyDef += opponent.getBattleStat(Stat.DEF); - this.enemySpDef += opponent.getBattleStat(Stat.SPDEF); - } - this.enemyDef = Math.round(this.enemyDef / this.enemyCountTally); - this.enemySpDef = Math.round(this.enemySpDef / this.enemyCountTally); + for (const opponent of pokemon.getOpponents()) { + this.enemyCountTally++; + this.enemyDef += opponent.getBattleStat(Stat.DEF); + this.enemySpDef += opponent.getBattleStat(Stat.SPDEF); } + this.enemyDef = Math.round(this.enemyDef / this.enemyCountTally); + this.enemySpDef = Math.round(this.enemySpDef / this.enemyCountTally); if (this.enemyDef < this.enemySpDef) { this.stats = [BattleStat.ATK]; From a224f5e822098a8e32e747c51cad3eb63e3c286e Mon Sep 17 00:00:00 2001 From: Madmadness65 Date: Fri, 7 Jun 2024 13:58:48 -0500 Subject: [PATCH 115/129] Add Partner Pikachu & Eevee party icons This also necessitated giving them unique front & back sprites too, even if they are currently just duplicates of the normal forms. This change will help players identify when they are using/have encountered a Partner Pikachu or Eevee. --- public/images/pokemon/133-partner.json | 2834 +++++ public/images/pokemon/133-partner.png | Bin 0 -> 6532 bytes public/images/pokemon/25-partner.json | 2456 +++++ public/images/pokemon/25-partner.png | Bin 0 -> 7629 bytes public/images/pokemon/back/133-partner.json | 2834 +++++ public/images/pokemon/back/133-partner.png | Bin 0 -> 5980 bytes public/images/pokemon/back/25-partner.json | 2456 +++++ public/images/pokemon/back/25-partner.png | Bin 0 -> 7035 bytes .../pokemon/back/female/25-partner.json | 2456 +++++ .../images/pokemon/back/female/25-partner.png | Bin 0 -> 7097 bytes .../pokemon/back/shiny/133-partner.json | 2834 +++++ .../images/pokemon/back/shiny/133-partner.png | Bin 0 -> 5980 bytes .../images/pokemon/back/shiny/25-partner.json | 2456 +++++ .../images/pokemon/back/shiny/25-partner.png | Bin 0 -> 7036 bytes .../pokemon/back/shiny/female/25-partner.json | 2456 +++++ .../pokemon/back/shiny/female/25-partner.png | Bin 0 -> 7102 bytes public/images/pokemon/female/25-partner.json | 2456 +++++ public/images/pokemon/female/25-partner.png | Bin 0 -> 7413 bytes public/images/pokemon/icons/1/133-partner.png | Bin 0 -> 394 bytes .../images/pokemon/icons/1/133s-partner.png | Bin 0 -> 419 bytes public/images/pokemon/icons/1/25-partner.png | Bin 0 -> 397 bytes public/images/pokemon/icons/1/25s-partner.png | Bin 0 -> 397 bytes public/images/pokemon/shiny/133-partner.json | 2834 +++++ public/images/pokemon/shiny/133-partner.png | Bin 0 -> 6532 bytes public/images/pokemon/shiny/25-partner.json | 2456 +++++ public/images/pokemon/shiny/25-partner.png | Bin 0 -> 7629 bytes .../pokemon/shiny/female/25-partner.json | 2456 +++++ .../pokemon/shiny/female/25-partner.png | Bin 0 -> 7414 bytes public/images/pokemon_icons_1.json | 9212 +++++++++-------- public/images/pokemon_icons_1.png | Bin 72497 -> 72491 bytes src/data/pokemon-species.ts | 4 +- 31 files changed, 35634 insertions(+), 4566 deletions(-) create mode 100644 public/images/pokemon/133-partner.json create mode 100644 public/images/pokemon/133-partner.png create mode 100644 public/images/pokemon/25-partner.json create mode 100644 public/images/pokemon/25-partner.png create mode 100644 public/images/pokemon/back/133-partner.json create mode 100644 public/images/pokemon/back/133-partner.png create mode 100644 public/images/pokemon/back/25-partner.json create mode 100644 public/images/pokemon/back/25-partner.png create mode 100644 public/images/pokemon/back/female/25-partner.json create mode 100644 public/images/pokemon/back/female/25-partner.png create mode 100644 public/images/pokemon/back/shiny/133-partner.json create mode 100644 public/images/pokemon/back/shiny/133-partner.png create mode 100644 public/images/pokemon/back/shiny/25-partner.json create mode 100644 public/images/pokemon/back/shiny/25-partner.png create mode 100644 public/images/pokemon/back/shiny/female/25-partner.json create mode 100644 public/images/pokemon/back/shiny/female/25-partner.png create mode 100644 public/images/pokemon/female/25-partner.json create mode 100644 public/images/pokemon/female/25-partner.png create mode 100644 public/images/pokemon/icons/1/133-partner.png create mode 100644 public/images/pokemon/icons/1/133s-partner.png create mode 100644 public/images/pokemon/icons/1/25-partner.png create mode 100644 public/images/pokemon/icons/1/25s-partner.png create mode 100644 public/images/pokemon/shiny/133-partner.json create mode 100644 public/images/pokemon/shiny/133-partner.png create mode 100644 public/images/pokemon/shiny/25-partner.json create mode 100644 public/images/pokemon/shiny/25-partner.png create mode 100644 public/images/pokemon/shiny/female/25-partner.json create mode 100644 public/images/pokemon/shiny/female/25-partner.png diff --git a/public/images/pokemon/133-partner.json b/public/images/pokemon/133-partner.json new file mode 100644 index 00000000000..f53a88c52a5 --- /dev/null +++ b/public/images/pokemon/133-partner.json @@ -0,0 +1,2834 @@ +{ + "textures": [ + { + "image": "133-partner.png", + "format": "RGBA8888", + "size": { + "w": 245, + "h": 245 + }, + "scale": 1, + "frames": [ + { + "filename": "0106.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 3, + "w": 35, + "h": 44 + }, + "frame": { + "x": 0, + "y": 0, + "w": 35, + "h": 44 + } + }, + { + "filename": "0107.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 3, + "w": 35, + "h": 44 + }, + "frame": { + "x": 0, + "y": 0, + "w": 35, + "h": 44 + } + }, + { + "filename": "0108.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 3, + "w": 35, + "h": 44 + }, + "frame": { + "x": 35, + "y": 0, + "w": 35, + "h": 44 + } + }, + { + "filename": "0109.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 3, + "w": 35, + "h": 44 + }, + "frame": { + "x": 35, + "y": 0, + "w": 35, + "h": 44 + } + }, + { + "filename": "0111.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 3, + "w": 35, + "h": 44 + }, + "frame": { + "x": 70, + "y": 0, + "w": 35, + "h": 44 + } + }, + { + "filename": "0112.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 3, + "w": 35, + "h": 44 + }, + "frame": { + "x": 70, + "y": 0, + "w": 35, + "h": 44 + } + }, + { + "filename": "0113.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 3, + "w": 35, + "h": 44 + }, + "frame": { + "x": 105, + "y": 0, + "w": 35, + "h": 44 + } + }, + { + "filename": "0114.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 3, + "w": 35, + "h": 44 + }, + "frame": { + "x": 105, + "y": 0, + "w": 35, + "h": 44 + } + }, + { + "filename": "0008.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 2, + "w": 44, + "h": 45 + }, + "frame": { + "x": 140, + "y": 0, + "w": 44, + "h": 45 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 2, + "w": 44, + "h": 45 + }, + "frame": { + "x": 140, + "y": 0, + "w": 44, + "h": 45 + } + }, + { + "filename": "0041.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 2, + "w": 44, + "h": 45 + }, + "frame": { + "x": 140, + "y": 0, + "w": 44, + "h": 45 + } + }, + { + "filename": "0042.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 2, + "w": 44, + "h": 45 + }, + "frame": { + "x": 140, + "y": 0, + "w": 44, + "h": 45 + } + }, + { + "filename": "0075.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 2, + "w": 44, + "h": 45 + }, + "frame": { + "x": 140, + "y": 0, + "w": 44, + "h": 45 + } + }, + { + "filename": "0010.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 45, + "h": 45 + }, + "frame": { + "x": 184, + "y": 0, + "w": 45, + "h": 45 + } + }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 45, + "h": 45 + }, + "frame": { + "x": 184, + "y": 0, + "w": 45, + "h": 45 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 45, + "h": 45 + }, + "frame": { + "x": 184, + "y": 0, + "w": 45, + "h": 45 + } + }, + { + "filename": "0043.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 45, + "h": 45 + }, + "frame": { + "x": 184, + "y": 0, + "w": 45, + "h": 45 + } + }, + { + "filename": "0044.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 45, + "h": 45 + }, + "frame": { + "x": 184, + "y": 0, + "w": 45, + "h": 45 + } + }, + { + "filename": "0060.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 45, + "h": 45 + }, + "frame": { + "x": 184, + "y": 0, + "w": 45, + "h": 45 + } + }, + { + "filename": "0076.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 45, + "h": 45 + }, + "frame": { + "x": 184, + "y": 0, + "w": 45, + "h": 45 + } + }, + { + "filename": "0077.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 45, + "h": 45 + }, + "frame": { + "x": 184, + "y": 0, + "w": 45, + "h": 45 + } + }, + { + "filename": "0093.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 45, + "h": 45 + }, + "frame": { + "x": 184, + "y": 0, + "w": 45, + "h": 45 + } + }, + { + "filename": "0094.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 45, + "h": 45 + }, + "frame": { + "x": 184, + "y": 0, + "w": 45, + "h": 45 + } + }, + { + "filename": "0105.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 2, + "w": 36, + "h": 45 + }, + "frame": { + "x": 0, + "y": 44, + "w": 36, + "h": 45 + } + }, + { + "filename": "0110.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 34, + "h": 45 + }, + "frame": { + "x": 36, + "y": 44, + "w": 34, + "h": 45 + } + }, + { + "filename": "0126.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 2, + "w": 44, + "h": 45 + }, + "frame": { + "x": 70, + "y": 44, + "w": 44, + "h": 45 + } + }, + { + "filename": "0127.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 2, + "w": 44, + "h": 45 + }, + "frame": { + "x": 70, + "y": 44, + "w": 44, + "h": 45 + } + }, + { + "filename": "0005.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 42, + "h": 46 + }, + "frame": { + "x": 114, + "y": 45, + "w": 42, + "h": 46 + } + }, + { + "filename": "0038.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 42, + "h": 46 + }, + "frame": { + "x": 114, + "y": 45, + "w": 42, + "h": 46 + } + }, + { + "filename": "0039.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 42, + "h": 46 + }, + "frame": { + "x": 114, + "y": 45, + "w": 42, + "h": 46 + } + }, + { + "filename": "0071.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 42, + "h": 46 + }, + "frame": { + "x": 114, + "y": 45, + "w": 42, + "h": 46 + } + }, + { + "filename": "0072.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 42, + "h": 46 + }, + "frame": { + "x": 114, + "y": 45, + "w": 42, + "h": 46 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 43, + "h": 46 + }, + "frame": { + "x": 156, + "y": 45, + "w": 43, + "h": 46 + } + }, + { + "filename": "0012.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 43, + "h": 46 + }, + "frame": { + "x": 156, + "y": 45, + "w": 43, + "h": 46 + } + }, + { + "filename": "0028.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 43, + "h": 46 + }, + "frame": { + "x": 156, + "y": 45, + "w": 43, + "h": 46 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 43, + "h": 46 + }, + "frame": { + "x": 156, + "y": 45, + "w": 43, + "h": 46 + } + }, + { + "filename": "0045.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 43, + "h": 46 + }, + "frame": { + "x": 156, + "y": 45, + "w": 43, + "h": 46 + } + }, + { + "filename": "0061.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 43, + "h": 46 + }, + "frame": { + "x": 156, + "y": 45, + "w": 43, + "h": 46 + } + }, + { + "filename": "0062.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 43, + "h": 46 + }, + "frame": { + "x": 156, + "y": 45, + "w": 43, + "h": 46 + } + }, + { + "filename": "0078.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 43, + "h": 46 + }, + "frame": { + "x": 156, + "y": 45, + "w": 43, + "h": 46 + } + }, + { + "filename": "0079.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 43, + "h": 46 + }, + "frame": { + "x": 156, + "y": 45, + "w": 43, + "h": 46 + } + }, + { + "filename": "0095.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 43, + "h": 46 + }, + "frame": { + "x": 156, + "y": 45, + "w": 43, + "h": 46 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 44, + "h": 46 + }, + "frame": { + "x": 199, + "y": 45, + "w": 44, + "h": 46 + } + }, + { + "filename": "0058.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 44, + "h": 46 + }, + "frame": { + "x": 199, + "y": 45, + "w": 44, + "h": 46 + } + }, + { + "filename": "0059.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 44, + "h": 46 + }, + "frame": { + "x": 199, + "y": 45, + "w": 44, + "h": 46 + } + }, + { + "filename": "0091.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 44, + "h": 46 + }, + "frame": { + "x": 199, + "y": 45, + "w": 44, + "h": 46 + } + }, + { + "filename": "0092.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 44, + "h": 46 + }, + "frame": { + "x": 199, + "y": 45, + "w": 44, + "h": 46 + } + }, + { + "filename": "0115.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 36, + "h": 46 + }, + "frame": { + "x": 0, + "y": 89, + "w": 36, + "h": 46 + } + }, + { + "filename": "0125.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 44, + "h": 46 + }, + "frame": { + "x": 36, + "y": 89, + "w": 44, + "h": 46 + } + }, + { + "filename": "0128.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 44, + "h": 46 + }, + "frame": { + "x": 80, + "y": 91, + "w": 44, + "h": 46 + } + }, + { + "filename": "0129.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 44, + "h": 46 + }, + "frame": { + "x": 80, + "y": 91, + "w": 44, + "h": 46 + } + }, + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0002.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0016.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0018.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0034.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0050.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0051.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0052.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0066.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0067.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0068.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0069.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0083.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0084.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0085.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0100.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0101.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0102.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0118.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0119.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0003.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 163, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0004.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 163, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0020.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 163, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0036.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 163, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0037.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 163, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0053.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 163, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0054.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 163, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0070.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 163, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0086.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 163, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0087.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 163, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0015.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 204, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 204, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0032.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 204, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0048.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 204, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0049.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 204, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0065.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 204, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0081.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 204, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0082.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 204, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0098.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 204, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0099.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 204, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0006.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 0, + "y": 135, + "w": 42, + "h": 47 + } + }, + { + "filename": "0007.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 0, + "y": 135, + "w": 42, + "h": 47 + } + }, + { + "filename": "0023.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 0, + "y": 135, + "w": 42, + "h": 47 + } + }, + { + "filename": "0024.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 0, + "y": 135, + "w": 42, + "h": 47 + } + }, + { + "filename": "0040.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 0, + "y": 135, + "w": 42, + "h": 47 + } + }, + { + "filename": "0056.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 0, + "y": 135, + "w": 42, + "h": 47 + } + }, + { + "filename": "0057.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 0, + "y": 135, + "w": 42, + "h": 47 + } + }, + { + "filename": "0073.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 0, + "y": 135, + "w": 42, + "h": 47 + } + }, + { + "filename": "0074.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 0, + "y": 135, + "w": 42, + "h": 47 + } + }, + { + "filename": "0090.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 0, + "y": 135, + "w": 42, + "h": 47 + } + }, + { + "filename": "0103.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 38, + "h": 47 + }, + "frame": { + "x": 42, + "y": 135, + "w": 38, + "h": 47 + } + }, + { + "filename": "0104.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 38, + "h": 47 + }, + "frame": { + "x": 42, + "y": 135, + "w": 38, + "h": 47 + } + }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 80, + "y": 137, + "w": 42, + "h": 47 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 80, + "y": 137, + "w": 42, + "h": 47 + } + }, + { + "filename": "0030.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 80, + "y": 137, + "w": 42, + "h": 47 + } + }, + { + "filename": "0046.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 80, + "y": 137, + "w": 42, + "h": 47 + } + }, + { + "filename": "0047.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 80, + "y": 137, + "w": 42, + "h": 47 + } + }, + { + "filename": "0063.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 80, + "y": 137, + "w": 42, + "h": 47 + } + }, + { + "filename": "0064.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 80, + "y": 137, + "w": 42, + "h": 47 + } + }, + { + "filename": "0080.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 80, + "y": 137, + "w": 42, + "h": 47 + } + }, + { + "filename": "0096.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 80, + "y": 137, + "w": 42, + "h": 47 + } + }, + { + "filename": "0097.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 80, + "y": 137, + "w": 42, + "h": 47 + } + }, + { + "filename": "0021.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 122, + "y": 138, + "w": 42, + "h": 47 + } + }, + { + "filename": "0022.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 122, + "y": 138, + "w": 42, + "h": 47 + } + }, + { + "filename": "0055.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 122, + "y": 138, + "w": 42, + "h": 47 + } + }, + { + "filename": "0088.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 122, + "y": 138, + "w": 42, + "h": 47 + } + }, + { + "filename": "0089.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 122, + "y": 138, + "w": 42, + "h": 47 + } + }, + { + "filename": "0116.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 38, + "h": 47 + }, + "frame": { + "x": 164, + "y": 138, + "w": 38, + "h": 47 + } + }, + { + "filename": "0117.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 38, + "h": 47 + }, + "frame": { + "x": 164, + "y": 138, + "w": 38, + "h": 47 + } + }, + { + "filename": "0120.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 202, + "y": 138, + "w": 41, + "h": 47 + } + }, + { + "filename": "0121.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 0, + "y": 182, + "w": 42, + "h": 47 + } + }, + { + "filename": "0122.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 0, + "y": 182, + "w": 42, + "h": 47 + } + }, + { + "filename": "0123.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 44, + "h": 47 + }, + "frame": { + "x": 42, + "y": 184, + "w": 44, + "h": 47 + } + }, + { + "filename": "0124.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 44, + "h": 47 + }, + "frame": { + "x": 42, + "y": 184, + "w": 44, + "h": 47 + } + }, + { + "filename": "0130.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 44, + "h": 47 + }, + "frame": { + "x": 86, + "y": 185, + "w": 44, + "h": 47 + } + }, + { + "filename": "0131.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 130, + "y": 185, + "w": 42, + "h": 47 + } + }, + { + "filename": "0132.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 130, + "y": 185, + "w": 42, + "h": 47 + } + }, + { + "filename": "0133.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 172, + "y": 185, + "w": 41, + "h": 47 + } + }, + { + "filename": "0134.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 172, + "y": 185, + "w": 41, + "h": 47 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:a12f810e0c533df6c81afe00a33144eb:fd50f6a2654aabba8f54e3bb2ba922b1:4e938f9dc8bce1cd7822677c800d242b$" + } +} diff --git a/public/images/pokemon/133-partner.png b/public/images/pokemon/133-partner.png new file mode 100644 index 0000000000000000000000000000000000000000..b8b1c2a40709f0978199e006ef43d5c0f9bdac98 GIT binary patch literal 6532 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 qgrhje{ukDt^=JKAf7YM%XZ)5erK4e> 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 YA_jl;HW|J@{QH$vRnk(dm$!=eKizk9ivR!s literal 0 HcmV?d00001 diff --git a/public/images/pokemon/back/133-partner.json b/public/images/pokemon/back/133-partner.json new file mode 100644 index 00000000000..e5fe317d53d --- /dev/null +++ b/public/images/pokemon/back/133-partner.json @@ -0,0 +1,2834 @@ +{ + "textures": [ + { + "image": "133-partner.png", + "format": "RGBA8888", + "size": { + "w": 245, + "h": 245 + }, + "scale": 1, + "frames": [ + { + "filename": "0110.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 13, + "y": 4, + "w": 36, + "h": 45 + }, + "frame": { + "x": 0, + "y": 0, + "w": 36, + "h": 45 + } + }, + { + "filename": "0008.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 45, + "h": 47 + }, + "frame": { + "x": 36, + "y": 0, + "w": 45, + "h": 47 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 45, + "h": 47 + }, + "frame": { + "x": 36, + "y": 0, + "w": 45, + "h": 47 + } + }, + { + "filename": "0041.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 45, + "h": 47 + }, + "frame": { + "x": 36, + "y": 0, + "w": 45, + "h": 47 + } + }, + { + "filename": "0042.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 45, + "h": 47 + }, + "frame": { + "x": 36, + "y": 0, + "w": 45, + "h": 47 + } + }, + { + "filename": "0075.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 45, + "h": 47 + }, + "frame": { + "x": 36, + "y": 0, + "w": 45, + "h": 47 + } + }, + { + "filename": "0108.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 12, + "y": 2, + "w": 36, + "h": 47 + }, + "frame": { + "x": 81, + "y": 0, + "w": 36, + "h": 47 + } + }, + { + "filename": "0109.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 12, + "y": 2, + "w": 36, + "h": 47 + }, + "frame": { + "x": 81, + "y": 0, + "w": 36, + "h": 47 + } + }, + { + "filename": "0111.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 12, + "y": 2, + "w": 36, + "h": 47 + }, + "frame": { + "x": 117, + "y": 0, + "w": 36, + "h": 47 + } + }, + { + "filename": "0112.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 12, + "y": 2, + "w": 36, + "h": 47 + }, + "frame": { + "x": 117, + "y": 0, + "w": 36, + "h": 47 + } + }, + { + "filename": "0125.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 44, + "h": 47 + }, + "frame": { + "x": 153, + "y": 0, + "w": 44, + "h": 47 + } + }, + { + "filename": "0126.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 45, + "h": 47 + }, + "frame": { + "x": 197, + "y": 0, + "w": 45, + "h": 47 + } + }, + { + "filename": "0127.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 45, + "h": 47 + }, + "frame": { + "x": 197, + "y": 0, + "w": 45, + "h": 47 + } + }, + { + "filename": "0106.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 10, + "y": 1, + "w": 36, + "h": 48 + }, + "frame": { + "x": 0, + "y": 45, + "w": 36, + "h": 48 + } + }, + { + "filename": "0107.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 10, + "y": 1, + "w": 36, + "h": 48 + }, + "frame": { + "x": 0, + "y": 45, + "w": 36, + "h": 48 + } + }, + { + "filename": "0128.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 44, + "h": 47 + }, + "frame": { + "x": 36, + "y": 47, + "w": 44, + "h": 47 + } + }, + { + "filename": "0129.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 44, + "h": 47 + }, + "frame": { + "x": 36, + "y": 47, + "w": 44, + "h": 47 + } + }, + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0002.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0018.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0051.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0052.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0068.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0069.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0085.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0101.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0102.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0118.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0119.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0010.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 46, + "h": 48 + }, + "frame": { + "x": 118, + "y": 47, + "w": 46, + "h": 48 + } + }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 46, + "h": 48 + }, + "frame": { + "x": 118, + "y": 47, + "w": 46, + "h": 48 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 46, + "h": 48 + }, + "frame": { + "x": 118, + "y": 47, + "w": 46, + "h": 48 + } + }, + { + "filename": "0043.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 46, + "h": 48 + }, + "frame": { + "x": 118, + "y": 47, + "w": 46, + "h": 48 + } + }, + { + "filename": "0044.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 46, + "h": 48 + }, + "frame": { + "x": 118, + "y": 47, + "w": 46, + "h": 48 + } + }, + { + "filename": "0060.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 46, + "h": 48 + }, + "frame": { + "x": 118, + "y": 47, + "w": 46, + "h": 48 + } + }, + { + "filename": "0076.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 46, + "h": 48 + }, + "frame": { + "x": 118, + "y": 47, + "w": 46, + "h": 48 + } + }, + { + "filename": "0077.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 46, + "h": 48 + }, + "frame": { + "x": 118, + "y": 47, + "w": 46, + "h": 48 + } + }, + { + "filename": "0093.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 46, + "h": 48 + }, + "frame": { + "x": 118, + "y": 47, + "w": 46, + "h": 48 + } + }, + { + "filename": "0094.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 46, + "h": 48 + }, + "frame": { + "x": 118, + "y": 47, + "w": 46, + "h": 48 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 164, + "y": 47, + "w": 45, + "h": 48 + } + }, + { + "filename": "0012.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 164, + "y": 47, + "w": 45, + "h": 48 + } + }, + { + "filename": "0028.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 164, + "y": 47, + "w": 45, + "h": 48 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 164, + "y": 47, + "w": 45, + "h": 48 + } + }, + { + "filename": "0045.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 164, + "y": 47, + "w": 45, + "h": 48 + } + }, + { + "filename": "0061.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 164, + "y": 47, + "w": 45, + "h": 48 + } + }, + { + "filename": "0062.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 164, + "y": 47, + "w": 45, + "h": 48 + } + }, + { + "filename": "0078.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 164, + "y": 47, + "w": 45, + "h": 48 + } + }, + { + "filename": "0079.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 164, + "y": 47, + "w": 45, + "h": 48 + } + }, + { + "filename": "0095.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 164, + "y": 47, + "w": 45, + "h": 48 + } + }, + { + "filename": "0113.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 10, + "y": 1, + "w": 36, + "h": 48 + }, + "frame": { + "x": 209, + "y": 47, + "w": 36, + "h": 48 + } + }, + { + "filename": "0114.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 10, + "y": 1, + "w": 36, + "h": 48 + }, + "frame": { + "x": 209, + "y": 47, + "w": 36, + "h": 48 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 0, + "y": 94, + "w": 45, + "h": 48 + } + }, + { + "filename": "0058.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 0, + "y": 94, + "w": 45, + "h": 48 + } + }, + { + "filename": "0059.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 0, + "y": 94, + "w": 45, + "h": 48 + } + }, + { + "filename": "0091.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 0, + "y": 94, + "w": 45, + "h": 48 + } + }, + { + "filename": "0092.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 0, + "y": 94, + "w": 45, + "h": 48 + } + }, + { + "filename": "0105.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 37, + "h": 48 + }, + "frame": { + "x": 45, + "y": 95, + "w": 37, + "h": 48 + } + }, + { + "filename": "0123.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 43, + "h": 48 + }, + "frame": { + "x": 82, + "y": 95, + "w": 43, + "h": 48 + } + }, + { + "filename": "0124.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 43, + "h": 48 + }, + "frame": { + "x": 82, + "y": 95, + "w": 43, + "h": 48 + } + }, + { + "filename": "0130.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 43, + "h": 48 + }, + "frame": { + "x": 125, + "y": 95, + "w": 43, + "h": 48 + } + }, + { + "filename": "0003.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 40, + "h": 49 + }, + "frame": { + "x": 168, + "y": 95, + "w": 40, + "h": 49 + } + }, + { + "filename": "0004.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 40, + "h": 49 + }, + "frame": { + "x": 168, + "y": 95, + "w": 40, + "h": 49 + } + }, + { + "filename": "0020.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 40, + "h": 49 + }, + "frame": { + "x": 168, + "y": 95, + "w": 40, + "h": 49 + } + }, + { + "filename": "0036.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 40, + "h": 49 + }, + "frame": { + "x": 168, + "y": 95, + "w": 40, + "h": 49 + } + }, + { + "filename": "0037.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 40, + "h": 49 + }, + "frame": { + "x": 168, + "y": 95, + "w": 40, + "h": 49 + } + }, + { + "filename": "0053.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 40, + "h": 49 + }, + "frame": { + "x": 168, + "y": 95, + "w": 40, + "h": 49 + } + }, + { + "filename": "0054.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 40, + "h": 49 + }, + "frame": { + "x": 168, + "y": 95, + "w": 40, + "h": 49 + } + }, + { + "filename": "0070.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 40, + "h": 49 + }, + "frame": { + "x": 168, + "y": 95, + "w": 40, + "h": 49 + } + }, + { + "filename": "0086.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 40, + "h": 49 + }, + "frame": { + "x": 168, + "y": 95, + "w": 40, + "h": 49 + } + }, + { + "filename": "0087.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 40, + "h": 49 + }, + "frame": { + "x": 168, + "y": 95, + "w": 40, + "h": 49 + } + }, + { + "filename": "0115.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 9, + "y": 0, + "w": 37, + "h": 49 + }, + "frame": { + "x": 208, + "y": 95, + "w": 37, + "h": 49 + } + }, + { + "filename": "0005.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 49 + }, + "frame": { + "x": 0, + "y": 142, + "w": 42, + "h": 49 + } + }, + { + "filename": "0038.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 49 + }, + "frame": { + "x": 0, + "y": 142, + "w": 42, + "h": 49 + } + }, + { + "filename": "0039.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 49 + }, + "frame": { + "x": 0, + "y": 142, + "w": 42, + "h": 49 + } + }, + { + "filename": "0071.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 49 + }, + "frame": { + "x": 0, + "y": 142, + "w": 42, + "h": 49 + } + }, + { + "filename": "0072.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 49 + }, + "frame": { + "x": 0, + "y": 142, + "w": 42, + "h": 49 + } + }, + { + "filename": "0006.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 44, + "h": 49 + }, + "frame": { + "x": 42, + "y": 143, + "w": 44, + "h": 49 + } + }, + { + "filename": "0007.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 44, + "h": 49 + }, + "frame": { + "x": 42, + "y": 143, + "w": 44, + "h": 49 + } + }, + { + "filename": "0023.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 44, + "h": 49 + }, + "frame": { + "x": 42, + "y": 143, + "w": 44, + "h": 49 + } + }, + { + "filename": "0024.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 44, + "h": 49 + }, + "frame": { + "x": 42, + "y": 143, + "w": 44, + "h": 49 + } + }, + { + "filename": "0040.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 44, + "h": 49 + }, + "frame": { + "x": 42, + "y": 143, + "w": 44, + "h": 49 + } + }, + { + "filename": "0056.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 44, + "h": 49 + }, + "frame": { + "x": 42, + "y": 143, + "w": 44, + "h": 49 + } + }, + { + "filename": "0057.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 44, + "h": 49 + }, + "frame": { + "x": 42, + "y": 143, + "w": 44, + "h": 49 + } + }, + { + "filename": "0073.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 44, + "h": 49 + }, + "frame": { + "x": 42, + "y": 143, + "w": 44, + "h": 49 + } + }, + { + "filename": "0074.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 44, + "h": 49 + }, + "frame": { + "x": 42, + "y": 143, + "w": 44, + "h": 49 + } + }, + { + "filename": "0090.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 44, + "h": 49 + }, + "frame": { + "x": 42, + "y": 143, + "w": 44, + "h": 49 + } + }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 43, + "h": 49 + }, + "frame": { + "x": 86, + "y": 143, + "w": 43, + "h": 49 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 43, + "h": 49 + }, + "frame": { + "x": 86, + "y": 143, + "w": 43, + "h": 49 + } + }, + { + "filename": "0030.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 43, + "h": 49 + }, + "frame": { + "x": 86, + "y": 143, + "w": 43, + "h": 49 + } + }, + { + "filename": "0046.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 43, + "h": 49 + }, + "frame": { + "x": 86, + "y": 143, + "w": 43, + "h": 49 + } + }, + { + "filename": "0047.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 43, + "h": 49 + }, + "frame": { + "x": 86, + "y": 143, + "w": 43, + "h": 49 + } + }, + { + "filename": "0063.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 43, + "h": 49 + }, + "frame": { + "x": 86, + "y": 143, + "w": 43, + "h": 49 + } + }, + { + "filename": "0064.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 43, + "h": 49 + }, + "frame": { + "x": 86, + "y": 143, + "w": 43, + "h": 49 + } + }, + { + "filename": "0080.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 43, + "h": 49 + }, + "frame": { + "x": 86, + "y": 143, + "w": 43, + "h": 49 + } + }, + { + "filename": "0096.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 43, + "h": 49 + }, + "frame": { + "x": 86, + "y": 143, + "w": 43, + "h": 49 + } + }, + { + "filename": "0097.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 43, + "h": 49 + }, + "frame": { + "x": 86, + "y": 143, + "w": 43, + "h": 49 + } + }, + { + "filename": "0016.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 49 + }, + "frame": { + "x": 129, + "y": 143, + "w": 38, + "h": 49 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 49 + }, + "frame": { + "x": 129, + "y": 143, + "w": 38, + "h": 49 + } + }, + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 49 + }, + "frame": { + "x": 129, + "y": 143, + "w": 38, + "h": 49 + } + }, + { + "filename": "0034.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 49 + }, + "frame": { + "x": 129, + "y": 143, + "w": 38, + "h": 49 + } + }, + { + "filename": "0050.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 49 + }, + "frame": { + "x": 129, + "y": 143, + "w": 38, + "h": 49 + } + }, + { + "filename": "0066.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 49 + }, + "frame": { + "x": 129, + "y": 143, + "w": 38, + "h": 49 + } + }, + { + "filename": "0067.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 49 + }, + "frame": { + "x": 129, + "y": 143, + "w": 38, + "h": 49 + } + }, + { + "filename": "0083.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 49 + }, + "frame": { + "x": 129, + "y": 143, + "w": 38, + "h": 49 + } + }, + { + "filename": "0084.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 49 + }, + "frame": { + "x": 129, + "y": 143, + "w": 38, + "h": 49 + } + }, + { + "filename": "0100.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 49 + }, + "frame": { + "x": 129, + "y": 143, + "w": 38, + "h": 49 + } + }, + { + "filename": "0015.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 167, + "y": 144, + "w": 41, + "h": 49 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 167, + "y": 144, + "w": 41, + "h": 49 + } + }, + { + "filename": "0032.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 167, + "y": 144, + "w": 41, + "h": 49 + } + }, + { + "filename": "0048.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 167, + "y": 144, + "w": 41, + "h": 49 + } + }, + { + "filename": "0049.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 167, + "y": 144, + "w": 41, + "h": 49 + } + }, + { + "filename": "0065.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 167, + "y": 144, + "w": 41, + "h": 49 + } + }, + { + "filename": "0081.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 167, + "y": 144, + "w": 41, + "h": 49 + } + }, + { + "filename": "0082.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 167, + "y": 144, + "w": 41, + "h": 49 + } + }, + { + "filename": "0098.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 167, + "y": 144, + "w": 41, + "h": 49 + } + }, + { + "filename": "0099.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 167, + "y": 144, + "w": 41, + "h": 49 + } + }, + { + "filename": "0116.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 8, + "y": 0, + "w": 37, + "h": 49 + }, + "frame": { + "x": 208, + "y": 144, + "w": 37, + "h": 49 + } + }, + { + "filename": "0117.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 8, + "y": 0, + "w": 37, + "h": 49 + }, + "frame": { + "x": 208, + "y": 144, + "w": 37, + "h": 49 + } + }, + { + "filename": "0021.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 49 + }, + "frame": { + "x": 0, + "y": 191, + "w": 42, + "h": 49 + } + }, + { + "filename": "0022.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 49 + }, + "frame": { + "x": 0, + "y": 191, + "w": 42, + "h": 49 + } + }, + { + "filename": "0055.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 49 + }, + "frame": { + "x": 0, + "y": 191, + "w": 42, + "h": 49 + } + }, + { + "filename": "0088.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 49 + }, + "frame": { + "x": 0, + "y": 191, + "w": 42, + "h": 49 + } + }, + { + "filename": "0089.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 49 + }, + "frame": { + "x": 0, + "y": 191, + "w": 42, + "h": 49 + } + }, + { + "filename": "0103.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 8, + "y": 0, + "w": 38, + "h": 49 + }, + "frame": { + "x": 42, + "y": 192, + "w": 38, + "h": 49 + } + }, + { + "filename": "0104.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 8, + "y": 0, + "w": 38, + "h": 49 + }, + "frame": { + "x": 42, + "y": 192, + "w": 38, + "h": 49 + } + }, + { + "filename": "0120.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 40, + "h": 49 + }, + "frame": { + "x": 80, + "y": 192, + "w": 40, + "h": 49 + } + }, + { + "filename": "0121.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 120, + "y": 192, + "w": 41, + "h": 49 + } + }, + { + "filename": "0122.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 120, + "y": 192, + "w": 41, + "h": 49 + } + }, + { + "filename": "0131.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 161, + "y": 193, + "w": 41, + "h": 49 + } + }, + { + "filename": "0132.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 161, + "y": 193, + "w": 41, + "h": 49 + } + }, + { + "filename": "0133.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 39, + "h": 49 + }, + "frame": { + "x": 202, + "y": 193, + "w": 39, + "h": 49 + } + }, + { + "filename": "0134.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 39, + "h": 49 + }, + "frame": { + "x": 202, + "y": 193, + "w": 39, + "h": 49 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:17629ce8c8a380688fdc9acf34c5cbbb:944bb4be78739c5b736be4525f997374:4e938f9dc8bce1cd7822677c800d242b$" + } +} diff --git a/public/images/pokemon/back/133-partner.png b/public/images/pokemon/back/133-partner.png new file mode 100644 index 0000000000000000000000000000000000000000..6cc7446032f8fa9c54d345bd01396e580f9e94c7 GIT binary patch literal 5980 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 KMNUMnLSTZTz4&zi literal 0 HcmV?d00001 diff --git a/public/images/pokemon/back/25-partner.json b/public/images/pokemon/back/25-partner.json new file mode 100644 index 00000000000..3776dc1bbca --- /dev/null +++ b/public/images/pokemon/back/25-partner.json @@ -0,0 +1,2456 @@ +{ + "textures": [ + { + "image": "25-partner.png", + "format": "RGBA8888", + "size": { + "w": 302, + "h": 302 + }, + "scale": 1, + "frames": [ + { + "filename": "0107.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 41, + "h": 47 + } + }, + { + "filename": "0111.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 41, + "h": 47 + } + }, + { + "filename": "0108.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 0, + "y": 47, + "w": 39, + "h": 47 + } + }, + { + "filename": "0110.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 0, + "y": 47, + "w": 39, + "h": 47 + } + }, + { + "filename": "0112.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 0, + "y": 47, + "w": 39, + "h": 47 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 41, + "y": 0, + "w": 37, + "h": 47 + } + }, + { + "filename": "0020.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 41, + "y": 0, + "w": 37, + "h": 47 + } + }, + { + "filename": "0075.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 0, + "y": 94, + "w": 37, + "h": 47 + } + }, + { + "filename": "0076.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 0, + "y": 94, + "w": 37, + "h": 47 + } + }, + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0002.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0055.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0056.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0091.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0092.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0105.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0106.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0109.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0113.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0114.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0115.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0116.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0003.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 78, + "y": 0, + "w": 37, + "h": 46 + } + }, + { + "filename": "0004.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 78, + "y": 0, + "w": 37, + "h": 46 + } + }, + { + "filename": "0015.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 0, + "y": 141, + "w": 37, + "h": 46 + } + }, + { + "filename": "0016.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 0, + "y": 141, + "w": 37, + "h": 46 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 37, + "y": 94, + "w": 36, + "h": 47 + } + }, + { + "filename": "0018.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 37, + "y": 94, + "w": 36, + "h": 47 + } + }, + { + "filename": "0021.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 115, + "y": 0, + "w": 37, + "h": 46 + } + }, + { + "filename": "0022.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 115, + "y": 0, + "w": 37, + "h": 46 + } + }, + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 0, + "y": 187, + "w": 37, + "h": 46 + } + }, + { + "filename": "0034.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 0, + "y": 187, + "w": 37, + "h": 46 + } + }, + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 37, + "y": 141, + "w": 36, + "h": 47 + } + }, + { + "filename": "0036.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 37, + "y": 141, + "w": 36, + "h": 47 + } + }, + { + "filename": "0037.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 0, + "y": 233, + "w": 36, + "h": 47 + } + }, + { + "filename": "0038.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 0, + "y": 233, + "w": 36, + "h": 47 + } + }, + { + "filename": "0039.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 152, + "y": 0, + "w": 37, + "h": 46 + } + }, + { + "filename": "0040.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 152, + "y": 0, + "w": 37, + "h": 46 + } + }, + { + "filename": "0057.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 189, + "y": 0, + "w": 37, + "h": 46 + } + }, + { + "filename": "0058.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 189, + "y": 0, + "w": 37, + "h": 46 + } + }, + { + "filename": "0069.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 226, + "y": 0, + "w": 37, + "h": 46 + } + }, + { + "filename": "0070.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 226, + "y": 0, + "w": 37, + "h": 46 + } + }, + { + "filename": "0005.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 263, + "y": 0, + "w": 39, + "h": 43 + } + }, + { + "filename": "0006.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 263, + "y": 0, + "w": 39, + "h": 43 + } + }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 263, + "y": 43, + "w": 39, + "h": 43 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 263, + "y": 43, + "w": 39, + "h": 43 + } + }, + { + "filename": "0051.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 36, + "h": 46 + }, + "frame": { + "x": 37, + "y": 188, + "w": 36, + "h": 46 + } + }, + { + "filename": "0052.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 36, + "h": 46 + }, + "frame": { + "x": 37, + "y": 188, + "w": 36, + "h": 46 + } + }, + { + "filename": "0087.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 36, + "y": 234, + "w": 37, + "h": 46 + } + }, + { + "filename": "0088.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 36, + "y": 234, + "w": 37, + "h": 46 + } + }, + { + "filename": "0053.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 75, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0054.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 75, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0071.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 73, + "y": 94, + "w": 36, + "h": 47 + } + }, + { + "filename": "0072.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 73, + "y": 94, + "w": 36, + "h": 47 + } + }, + { + "filename": "0073.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 73, + "y": 141, + "w": 36, + "h": 47 + } + }, + { + "filename": "0074.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 73, + "y": 141, + "w": 36, + "h": 47 + } + }, + { + "filename": "0089.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 73, + "y": 188, + "w": 36, + "h": 47 + } + }, + { + "filename": "0090.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 73, + "y": 188, + "w": 36, + "h": 47 + } + }, + { + "filename": "0077.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 2, + "w": 38, + "h": 45 + }, + "frame": { + "x": 111, + "y": 46, + "w": 38, + "h": 45 + } + }, + { + "filename": "0078.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 2, + "w": 38, + "h": 45 + }, + "frame": { + "x": 111, + "y": 46, + "w": 38, + "h": 45 + } + }, + { + "filename": "0059.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 149, + "y": 46, + "w": 39, + "h": 43 + } + }, + { + "filename": "0060.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 149, + "y": 46, + "w": 39, + "h": 43 + } + }, + { + "filename": "0067.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 188, + "y": 46, + "w": 39, + "h": 43 + } + }, + { + "filename": "0068.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 188, + "y": 46, + "w": 39, + "h": 43 + } + }, + { + "filename": "0093.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 227, + "y": 46, + "w": 36, + "h": 47 + } + }, + { + "filename": "0094.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 227, + "y": 46, + "w": 36, + "h": 47 + } + }, + { + "filename": "0079.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 263, + "y": 86, + "w": 39, + "h": 43 + } + }, + { + "filename": "0080.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 263, + "y": 86, + "w": 39, + "h": 43 + } + }, + { + "filename": "0095.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 36, + "h": 46 + }, + "frame": { + "x": 73, + "y": 235, + "w": 36, + "h": 46 + } + }, + { + "filename": "0096.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 36, + "h": 46 + }, + "frame": { + "x": 73, + "y": 235, + "w": 36, + "h": 46 + } + }, + { + "filename": "0101.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 109, + "y": 94, + "w": 36, + "h": 47 + } + }, + { + "filename": "0102.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 109, + "y": 94, + "w": 36, + "h": 47 + } + }, + { + "filename": "0103.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 109, + "y": 141, + "w": 36, + "h": 47 + } + }, + { + "filename": "0104.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 109, + "y": 141, + "w": 36, + "h": 47 + } + }, + { + "filename": "0099.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 36, + "h": 46 + }, + "frame": { + "x": 109, + "y": 188, + "w": 36, + "h": 46 + } + }, + { + "filename": "0100.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 36, + "h": 46 + }, + "frame": { + "x": 109, + "y": 188, + "w": 36, + "h": 46 + } + }, + { + "filename": "0097.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 2, + "w": 36, + "h": 45 + }, + "frame": { + "x": 109, + "y": 234, + "w": 36, + "h": 45 + } + }, + { + "filename": "0098.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 2, + "w": 36, + "h": 45 + }, + "frame": { + "x": 109, + "y": 234, + "w": 36, + "h": 45 + } + }, + { + "filename": "0085.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 3, + "w": 37, + "h": 44 + }, + "frame": { + "x": 145, + "y": 91, + "w": 37, + "h": 44 + } + }, + { + "filename": "0086.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 3, + "w": 37, + "h": 44 + }, + "frame": { + "x": 145, + "y": 91, + "w": 37, + "h": 44 + } + }, + { + "filename": "0023.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 4, + "w": 38, + "h": 43 + }, + "frame": { + "x": 145, + "y": 135, + "w": 38, + "h": 43 + } + }, + { + "filename": "0024.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 4, + "w": 38, + "h": 43 + }, + "frame": { + "x": 145, + "y": 135, + "w": 38, + "h": 43 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 4, + "w": 38, + "h": 43 + }, + "frame": { + "x": 145, + "y": 178, + "w": 38, + "h": 43 + } + }, + { + "filename": "0032.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 4, + "w": 38, + "h": 43 + }, + "frame": { + "x": 145, + "y": 178, + "w": 38, + "h": 43 + } + }, + { + "filename": "0083.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 4, + "w": 38, + "h": 43 + }, + "frame": { + "x": 145, + "y": 221, + "w": 38, + "h": 43 + } + }, + { + "filename": "0084.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 4, + "w": 38, + "h": 43 + }, + "frame": { + "x": 145, + "y": 221, + "w": 38, + "h": 43 + } + }, + { + "filename": "0007.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 7, + "w": 40, + "h": 40 + }, + "frame": { + "x": 182, + "y": 89, + "w": 40, + "h": 40 + } + }, + { + "filename": "0008.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 7, + "w": 40, + "h": 40 + }, + "frame": { + "x": 182, + "y": 89, + "w": 40, + "h": 40 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 12, + "w": 41, + "h": 35 + }, + "frame": { + "x": 222, + "y": 93, + "w": 41, + "h": 35 + } + }, + { + "filename": "0010.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 12, + "w": 41, + "h": 35 + }, + "frame": { + "x": 222, + "y": 93, + "w": 41, + "h": 35 + } + }, + { + "filename": "0041.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 4, + "w": 37, + "h": 43 + }, + "frame": { + "x": 183, + "y": 129, + "w": 37, + "h": 43 + } + }, + { + "filename": "0042.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 4, + "w": 37, + "h": 43 + }, + "frame": { + "x": 183, + "y": 129, + "w": 37, + "h": 43 + } + }, + { + "filename": "0049.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 4, + "w": 37, + "h": 43 + }, + "frame": { + "x": 183, + "y": 172, + "w": 37, + "h": 43 + } + }, + { + "filename": "0050.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 4, + "w": 37, + "h": 43 + }, + "frame": { + "x": 183, + "y": 172, + "w": 37, + "h": 43 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 7, + "w": 40, + "h": 40 + }, + "frame": { + "x": 183, + "y": 215, + "w": 40, + "h": 40 + } + }, + { + "filename": "0012.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 7, + "w": 40, + "h": 40 + }, + "frame": { + "x": 183, + "y": 215, + "w": 40, + "h": 40 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 12, + "w": 40, + "h": 35 + }, + "frame": { + "x": 145, + "y": 264, + "w": 40, + "h": 35 + } + }, + { + "filename": "0028.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 12, + "w": 40, + "h": 35 + }, + "frame": { + "x": 145, + "y": 264, + "w": 40, + "h": 35 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 7, + "w": 39, + "h": 40 + }, + "frame": { + "x": 185, + "y": 255, + "w": 39, + "h": 40 + } + }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 7, + "w": 39, + "h": 40 + }, + "frame": { + "x": 185, + "y": 255, + "w": 39, + "h": 40 + } + }, + { + "filename": "0063.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 13, + "w": 41, + "h": 34 + }, + "frame": { + "x": 222, + "y": 128, + "w": 41, + "h": 34 + } + }, + { + "filename": "0064.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 13, + "w": 41, + "h": 34 + }, + "frame": { + "x": 222, + "y": 128, + "w": 41, + "h": 34 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 7, + "w": 39, + "h": 40 + }, + "frame": { + "x": 263, + "y": 129, + "w": 39, + "h": 40 + } + }, + { + "filename": "0030.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 7, + "w": 39, + "h": 40 + }, + "frame": { + "x": 263, + "y": 129, + "w": 39, + "h": 40 + } + }, + { + "filename": "0061.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 7, + "w": 40, + "h": 40 + }, + "frame": { + "x": 220, + "y": 162, + "w": 40, + "h": 40 + } + }, + { + "filename": "0062.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 7, + "w": 40, + "h": 40 + }, + "frame": { + "x": 220, + "y": 162, + "w": 40, + "h": 40 + } + }, + { + "filename": "0065.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 7, + "w": 40, + "h": 40 + }, + "frame": { + "x": 260, + "y": 169, + "w": 40, + "h": 40 + } + }, + { + "filename": "0066.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 7, + "w": 40, + "h": 40 + }, + "frame": { + "x": 260, + "y": 169, + "w": 40, + "h": 40 + } + }, + { + "filename": "0043.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 7, + "w": 38, + "h": 40 + }, + "frame": { + "x": 223, + "y": 209, + "w": 38, + "h": 40 + } + }, + { + "filename": "0044.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 7, + "w": 38, + "h": 40 + }, + "frame": { + "x": 223, + "y": 209, + "w": 38, + "h": 40 + } + }, + { + "filename": "0047.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 7, + "w": 38, + "h": 40 + }, + "frame": { + "x": 261, + "y": 209, + "w": 38, + "h": 40 + } + }, + { + "filename": "0048.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 7, + "w": 38, + "h": 40 + }, + "frame": { + "x": 261, + "y": 209, + "w": 38, + "h": 40 + } + }, + { + "filename": "0081.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 8, + "w": 39, + "h": 39 + }, + "frame": { + "x": 224, + "y": 249, + "w": 39, + "h": 39 + } + }, + { + "filename": "0082.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 8, + "w": 39, + "h": 39 + }, + "frame": { + "x": 224, + "y": 249, + "w": 39, + "h": 39 + } + }, + { + "filename": "0045.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 12, + "w": 39, + "h": 35 + }, + "frame": { + "x": 263, + "y": 249, + "w": 39, + "h": 35 + } + }, + { + "filename": "0046.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 12, + "w": 39, + "h": 35 + }, + "frame": { + "x": 263, + "y": 249, + "w": 39, + "h": 35 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:cfdc5980d26f5e9ff2936babc0854215:6c08d027c2bbef936a8cbfa5e456edbf:d7d9e845c71962a58076b5efe962284e$" + } +} diff --git a/public/images/pokemon/back/25-partner.png b/public/images/pokemon/back/25-partner.png new file mode 100644 index 0000000000000000000000000000000000000000..8085a963e28bca24a51b1378ff62fb0fe113b748 GIT binary patch literal 7035 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 AjsO4v literal 0 HcmV?d00001 diff --git a/public/images/pokemon/back/shiny/133-partner.json b/public/images/pokemon/back/shiny/133-partner.json new file mode 100644 index 00000000000..dc90bd7b683 --- /dev/null +++ b/public/images/pokemon/back/shiny/133-partner.json @@ -0,0 +1,2834 @@ +{ + "textures": [ + { + "image": "133-partner.png", + "format": "RGBA8888", + "size": { + "w": 245, + "h": 245 + }, + "scale": 1, + "frames": [ + { + "filename": "0110.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 13, + "y": 4, + "w": 36, + "h": 45 + }, + "frame": { + "x": 0, + "y": 0, + "w": 36, + "h": 45 + } + }, + { + "filename": "0008.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 45, + "h": 47 + }, + "frame": { + "x": 36, + "y": 0, + "w": 45, + "h": 47 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 45, + "h": 47 + }, + "frame": { + "x": 36, + "y": 0, + "w": 45, + "h": 47 + } + }, + { + "filename": "0041.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 45, + "h": 47 + }, + "frame": { + "x": 36, + "y": 0, + "w": 45, + "h": 47 + } + }, + { + "filename": "0042.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 45, + "h": 47 + }, + "frame": { + "x": 36, + "y": 0, + "w": 45, + "h": 47 + } + }, + { + "filename": "0075.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 45, + "h": 47 + }, + "frame": { + "x": 36, + "y": 0, + "w": 45, + "h": 47 + } + }, + { + "filename": "0108.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 12, + "y": 2, + "w": 36, + "h": 47 + }, + "frame": { + "x": 81, + "y": 0, + "w": 36, + "h": 47 + } + }, + { + "filename": "0109.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 12, + "y": 2, + "w": 36, + "h": 47 + }, + "frame": { + "x": 81, + "y": 0, + "w": 36, + "h": 47 + } + }, + { + "filename": "0111.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 12, + "y": 2, + "w": 36, + "h": 47 + }, + "frame": { + "x": 117, + "y": 0, + "w": 36, + "h": 47 + } + }, + { + "filename": "0112.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 12, + "y": 2, + "w": 36, + "h": 47 + }, + "frame": { + "x": 117, + "y": 0, + "w": 36, + "h": 47 + } + }, + { + "filename": "0125.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 44, + "h": 47 + }, + "frame": { + "x": 153, + "y": 0, + "w": 44, + "h": 47 + } + }, + { + "filename": "0126.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 45, + "h": 47 + }, + "frame": { + "x": 197, + "y": 0, + "w": 45, + "h": 47 + } + }, + { + "filename": "0127.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 45, + "h": 47 + }, + "frame": { + "x": 197, + "y": 0, + "w": 45, + "h": 47 + } + }, + { + "filename": "0106.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 10, + "y": 1, + "w": 36, + "h": 48 + }, + "frame": { + "x": 0, + "y": 45, + "w": 36, + "h": 48 + } + }, + { + "filename": "0107.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 10, + "y": 1, + "w": 36, + "h": 48 + }, + "frame": { + "x": 0, + "y": 45, + "w": 36, + "h": 48 + } + }, + { + "filename": "0128.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 44, + "h": 47 + }, + "frame": { + "x": 36, + "y": 47, + "w": 44, + "h": 47 + } + }, + { + "filename": "0129.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 44, + "h": 47 + }, + "frame": { + "x": 36, + "y": 47, + "w": 44, + "h": 47 + } + }, + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0002.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0018.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0051.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0052.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0068.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0069.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0085.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0101.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0102.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0118.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0119.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 48 + }, + "frame": { + "x": 80, + "y": 47, + "w": 38, + "h": 48 + } + }, + { + "filename": "0010.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 46, + "h": 48 + }, + "frame": { + "x": 118, + "y": 47, + "w": 46, + "h": 48 + } + }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 46, + "h": 48 + }, + "frame": { + "x": 118, + "y": 47, + "w": 46, + "h": 48 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 46, + "h": 48 + }, + "frame": { + "x": 118, + "y": 47, + "w": 46, + "h": 48 + } + }, + { + "filename": "0043.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 46, + "h": 48 + }, + "frame": { + "x": 118, + "y": 47, + "w": 46, + "h": 48 + } + }, + { + "filename": "0044.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 46, + "h": 48 + }, + "frame": { + "x": 118, + "y": 47, + "w": 46, + "h": 48 + } + }, + { + "filename": "0060.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 46, + "h": 48 + }, + "frame": { + "x": 118, + "y": 47, + "w": 46, + "h": 48 + } + }, + { + "filename": "0076.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 46, + "h": 48 + }, + "frame": { + "x": 118, + "y": 47, + "w": 46, + "h": 48 + } + }, + { + "filename": "0077.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 46, + "h": 48 + }, + "frame": { + "x": 118, + "y": 47, + "w": 46, + "h": 48 + } + }, + { + "filename": "0093.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 46, + "h": 48 + }, + "frame": { + "x": 118, + "y": 47, + "w": 46, + "h": 48 + } + }, + { + "filename": "0094.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 46, + "h": 48 + }, + "frame": { + "x": 118, + "y": 47, + "w": 46, + "h": 48 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 164, + "y": 47, + "w": 45, + "h": 48 + } + }, + { + "filename": "0012.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 164, + "y": 47, + "w": 45, + "h": 48 + } + }, + { + "filename": "0028.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 164, + "y": 47, + "w": 45, + "h": 48 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 164, + "y": 47, + "w": 45, + "h": 48 + } + }, + { + "filename": "0045.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 164, + "y": 47, + "w": 45, + "h": 48 + } + }, + { + "filename": "0061.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 164, + "y": 47, + "w": 45, + "h": 48 + } + }, + { + "filename": "0062.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 164, + "y": 47, + "w": 45, + "h": 48 + } + }, + { + "filename": "0078.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 164, + "y": 47, + "w": 45, + "h": 48 + } + }, + { + "filename": "0079.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 164, + "y": 47, + "w": 45, + "h": 48 + } + }, + { + "filename": "0095.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 164, + "y": 47, + "w": 45, + "h": 48 + } + }, + { + "filename": "0113.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 10, + "y": 1, + "w": 36, + "h": 48 + }, + "frame": { + "x": 209, + "y": 47, + "w": 36, + "h": 48 + } + }, + { + "filename": "0114.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 10, + "y": 1, + "w": 36, + "h": 48 + }, + "frame": { + "x": 209, + "y": 47, + "w": 36, + "h": 48 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 0, + "y": 94, + "w": 45, + "h": 48 + } + }, + { + "filename": "0058.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 0, + "y": 94, + "w": 45, + "h": 48 + } + }, + { + "filename": "0059.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 0, + "y": 94, + "w": 45, + "h": 48 + } + }, + { + "filename": "0091.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 0, + "y": 94, + "w": 45, + "h": 48 + } + }, + { + "filename": "0092.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 45, + "h": 48 + }, + "frame": { + "x": 0, + "y": 94, + "w": 45, + "h": 48 + } + }, + { + "filename": "0105.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 37, + "h": 48 + }, + "frame": { + "x": 45, + "y": 95, + "w": 37, + "h": 48 + } + }, + { + "filename": "0123.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 43, + "h": 48 + }, + "frame": { + "x": 82, + "y": 95, + "w": 43, + "h": 48 + } + }, + { + "filename": "0124.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 43, + "h": 48 + }, + "frame": { + "x": 82, + "y": 95, + "w": 43, + "h": 48 + } + }, + { + "filename": "0130.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 43, + "h": 48 + }, + "frame": { + "x": 125, + "y": 95, + "w": 43, + "h": 48 + } + }, + { + "filename": "0003.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 40, + "h": 49 + }, + "frame": { + "x": 168, + "y": 95, + "w": 40, + "h": 49 + } + }, + { + "filename": "0004.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 40, + "h": 49 + }, + "frame": { + "x": 168, + "y": 95, + "w": 40, + "h": 49 + } + }, + { + "filename": "0020.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 40, + "h": 49 + }, + "frame": { + "x": 168, + "y": 95, + "w": 40, + "h": 49 + } + }, + { + "filename": "0036.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 40, + "h": 49 + }, + "frame": { + "x": 168, + "y": 95, + "w": 40, + "h": 49 + } + }, + { + "filename": "0037.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 40, + "h": 49 + }, + "frame": { + "x": 168, + "y": 95, + "w": 40, + "h": 49 + } + }, + { + "filename": "0053.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 40, + "h": 49 + }, + "frame": { + "x": 168, + "y": 95, + "w": 40, + "h": 49 + } + }, + { + "filename": "0054.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 40, + "h": 49 + }, + "frame": { + "x": 168, + "y": 95, + "w": 40, + "h": 49 + } + }, + { + "filename": "0070.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 40, + "h": 49 + }, + "frame": { + "x": 168, + "y": 95, + "w": 40, + "h": 49 + } + }, + { + "filename": "0086.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 40, + "h": 49 + }, + "frame": { + "x": 168, + "y": 95, + "w": 40, + "h": 49 + } + }, + { + "filename": "0087.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 40, + "h": 49 + }, + "frame": { + "x": 168, + "y": 95, + "w": 40, + "h": 49 + } + }, + { + "filename": "0115.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 9, + "y": 0, + "w": 37, + "h": 49 + }, + "frame": { + "x": 208, + "y": 95, + "w": 37, + "h": 49 + } + }, + { + "filename": "0005.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 49 + }, + "frame": { + "x": 0, + "y": 142, + "w": 42, + "h": 49 + } + }, + { + "filename": "0038.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 49 + }, + "frame": { + "x": 0, + "y": 142, + "w": 42, + "h": 49 + } + }, + { + "filename": "0039.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 49 + }, + "frame": { + "x": 0, + "y": 142, + "w": 42, + "h": 49 + } + }, + { + "filename": "0071.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 49 + }, + "frame": { + "x": 0, + "y": 142, + "w": 42, + "h": 49 + } + }, + { + "filename": "0072.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 49 + }, + "frame": { + "x": 0, + "y": 142, + "w": 42, + "h": 49 + } + }, + { + "filename": "0006.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 44, + "h": 49 + }, + "frame": { + "x": 42, + "y": 143, + "w": 44, + "h": 49 + } + }, + { + "filename": "0007.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 44, + "h": 49 + }, + "frame": { + "x": 42, + "y": 143, + "w": 44, + "h": 49 + } + }, + { + "filename": "0023.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 44, + "h": 49 + }, + "frame": { + "x": 42, + "y": 143, + "w": 44, + "h": 49 + } + }, + { + "filename": "0024.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 44, + "h": 49 + }, + "frame": { + "x": 42, + "y": 143, + "w": 44, + "h": 49 + } + }, + { + "filename": "0040.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 44, + "h": 49 + }, + "frame": { + "x": 42, + "y": 143, + "w": 44, + "h": 49 + } + }, + { + "filename": "0056.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 44, + "h": 49 + }, + "frame": { + "x": 42, + "y": 143, + "w": 44, + "h": 49 + } + }, + { + "filename": "0057.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 44, + "h": 49 + }, + "frame": { + "x": 42, + "y": 143, + "w": 44, + "h": 49 + } + }, + { + "filename": "0073.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 44, + "h": 49 + }, + "frame": { + "x": 42, + "y": 143, + "w": 44, + "h": 49 + } + }, + { + "filename": "0074.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 44, + "h": 49 + }, + "frame": { + "x": 42, + "y": 143, + "w": 44, + "h": 49 + } + }, + { + "filename": "0090.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 44, + "h": 49 + }, + "frame": { + "x": 42, + "y": 143, + "w": 44, + "h": 49 + } + }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 43, + "h": 49 + }, + "frame": { + "x": 86, + "y": 143, + "w": 43, + "h": 49 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 43, + "h": 49 + }, + "frame": { + "x": 86, + "y": 143, + "w": 43, + "h": 49 + } + }, + { + "filename": "0030.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 43, + "h": 49 + }, + "frame": { + "x": 86, + "y": 143, + "w": 43, + "h": 49 + } + }, + { + "filename": "0046.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 43, + "h": 49 + }, + "frame": { + "x": 86, + "y": 143, + "w": 43, + "h": 49 + } + }, + { + "filename": "0047.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 43, + "h": 49 + }, + "frame": { + "x": 86, + "y": 143, + "w": 43, + "h": 49 + } + }, + { + "filename": "0063.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 43, + "h": 49 + }, + "frame": { + "x": 86, + "y": 143, + "w": 43, + "h": 49 + } + }, + { + "filename": "0064.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 43, + "h": 49 + }, + "frame": { + "x": 86, + "y": 143, + "w": 43, + "h": 49 + } + }, + { + "filename": "0080.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 43, + "h": 49 + }, + "frame": { + "x": 86, + "y": 143, + "w": 43, + "h": 49 + } + }, + { + "filename": "0096.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 43, + "h": 49 + }, + "frame": { + "x": 86, + "y": 143, + "w": 43, + "h": 49 + } + }, + { + "filename": "0097.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 43, + "h": 49 + }, + "frame": { + "x": 86, + "y": 143, + "w": 43, + "h": 49 + } + }, + { + "filename": "0016.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 49 + }, + "frame": { + "x": 129, + "y": 143, + "w": 38, + "h": 49 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 49 + }, + "frame": { + "x": 129, + "y": 143, + "w": 38, + "h": 49 + } + }, + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 49 + }, + "frame": { + "x": 129, + "y": 143, + "w": 38, + "h": 49 + } + }, + { + "filename": "0034.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 49 + }, + "frame": { + "x": 129, + "y": 143, + "w": 38, + "h": 49 + } + }, + { + "filename": "0050.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 49 + }, + "frame": { + "x": 129, + "y": 143, + "w": 38, + "h": 49 + } + }, + { + "filename": "0066.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 49 + }, + "frame": { + "x": 129, + "y": 143, + "w": 38, + "h": 49 + } + }, + { + "filename": "0067.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 49 + }, + "frame": { + "x": 129, + "y": 143, + "w": 38, + "h": 49 + } + }, + { + "filename": "0083.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 49 + }, + "frame": { + "x": 129, + "y": 143, + "w": 38, + "h": 49 + } + }, + { + "filename": "0084.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 49 + }, + "frame": { + "x": 129, + "y": 143, + "w": 38, + "h": 49 + } + }, + { + "filename": "0100.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 38, + "h": 49 + }, + "frame": { + "x": 129, + "y": 143, + "w": 38, + "h": 49 + } + }, + { + "filename": "0015.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 167, + "y": 144, + "w": 41, + "h": 49 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 167, + "y": 144, + "w": 41, + "h": 49 + } + }, + { + "filename": "0032.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 167, + "y": 144, + "w": 41, + "h": 49 + } + }, + { + "filename": "0048.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 167, + "y": 144, + "w": 41, + "h": 49 + } + }, + { + "filename": "0049.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 167, + "y": 144, + "w": 41, + "h": 49 + } + }, + { + "filename": "0065.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 167, + "y": 144, + "w": 41, + "h": 49 + } + }, + { + "filename": "0081.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 167, + "y": 144, + "w": 41, + "h": 49 + } + }, + { + "filename": "0082.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 167, + "y": 144, + "w": 41, + "h": 49 + } + }, + { + "filename": "0098.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 167, + "y": 144, + "w": 41, + "h": 49 + } + }, + { + "filename": "0099.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 167, + "y": 144, + "w": 41, + "h": 49 + } + }, + { + "filename": "0116.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 8, + "y": 0, + "w": 37, + "h": 49 + }, + "frame": { + "x": 208, + "y": 144, + "w": 37, + "h": 49 + } + }, + { + "filename": "0117.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 8, + "y": 0, + "w": 37, + "h": 49 + }, + "frame": { + "x": 208, + "y": 144, + "w": 37, + "h": 49 + } + }, + { + "filename": "0021.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 49 + }, + "frame": { + "x": 0, + "y": 191, + "w": 42, + "h": 49 + } + }, + { + "filename": "0022.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 49 + }, + "frame": { + "x": 0, + "y": 191, + "w": 42, + "h": 49 + } + }, + { + "filename": "0055.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 49 + }, + "frame": { + "x": 0, + "y": 191, + "w": 42, + "h": 49 + } + }, + { + "filename": "0088.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 49 + }, + "frame": { + "x": 0, + "y": 191, + "w": 42, + "h": 49 + } + }, + { + "filename": "0089.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 49 + }, + "frame": { + "x": 0, + "y": 191, + "w": 42, + "h": 49 + } + }, + { + "filename": "0103.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 8, + "y": 0, + "w": 38, + "h": 49 + }, + "frame": { + "x": 42, + "y": 192, + "w": 38, + "h": 49 + } + }, + { + "filename": "0104.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 8, + "y": 0, + "w": 38, + "h": 49 + }, + "frame": { + "x": 42, + "y": 192, + "w": 38, + "h": 49 + } + }, + { + "filename": "0120.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 40, + "h": 49 + }, + "frame": { + "x": 80, + "y": 192, + "w": 40, + "h": 49 + } + }, + { + "filename": "0121.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 120, + "y": 192, + "w": 41, + "h": 49 + } + }, + { + "filename": "0122.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 120, + "y": 192, + "w": 41, + "h": 49 + } + }, + { + "filename": "0131.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 161, + "y": 193, + "w": 41, + "h": 49 + } + }, + { + "filename": "0132.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 49 + }, + "frame": { + "x": 161, + "y": 193, + "w": 41, + "h": 49 + } + }, + { + "filename": "0133.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 39, + "h": 49 + }, + "frame": { + "x": 202, + "y": 193, + "w": 39, + "h": 49 + } + }, + { + "filename": "0134.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 50 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 39, + "h": 49 + }, + "frame": { + "x": 202, + "y": 193, + "w": 39, + "h": 49 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:5e94f60a2f7a7dc3f5d699bd3107f486:44eae6b3793ef296999ef086d542c6a4:4e938f9dc8bce1cd7822677c800d242b$" + } +} diff --git a/public/images/pokemon/back/shiny/133-partner.png b/public/images/pokemon/back/shiny/133-partner.png new file mode 100644 index 0000000000000000000000000000000000000000..a1f95b24496ec20acd5456ef6d26debf77450473 GIT binary patch literal 5980 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 KMNUMnLSTYpq5C!f literal 0 HcmV?d00001 diff --git a/public/images/pokemon/back/shiny/25-partner.json b/public/images/pokemon/back/shiny/25-partner.json new file mode 100644 index 00000000000..d9f755885d2 --- /dev/null +++ b/public/images/pokemon/back/shiny/25-partner.json @@ -0,0 +1,2456 @@ +{ + "textures": [ + { + "image": "25-partner.png", + "format": "RGBA8888", + "size": { + "w": 302, + "h": 302 + }, + "scale": 1, + "frames": [ + { + "filename": "0107.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 41, + "h": 47 + } + }, + { + "filename": "0111.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 41, + "h": 47 + } + }, + { + "filename": "0108.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 0, + "y": 47, + "w": 39, + "h": 47 + } + }, + { + "filename": "0110.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 0, + "y": 47, + "w": 39, + "h": 47 + } + }, + { + "filename": "0112.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 0, + "y": 47, + "w": 39, + "h": 47 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 41, + "y": 0, + "w": 37, + "h": 47 + } + }, + { + "filename": "0020.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 41, + "y": 0, + "w": 37, + "h": 47 + } + }, + { + "filename": "0075.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 0, + "y": 94, + "w": 37, + "h": 47 + } + }, + { + "filename": "0076.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 0, + "y": 94, + "w": 37, + "h": 47 + } + }, + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0002.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0055.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0056.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0091.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0092.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0105.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0106.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0109.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0113.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0114.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0115.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0116.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 39, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0003.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 78, + "y": 0, + "w": 37, + "h": 46 + } + }, + { + "filename": "0004.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 78, + "y": 0, + "w": 37, + "h": 46 + } + }, + { + "filename": "0015.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 0, + "y": 141, + "w": 37, + "h": 46 + } + }, + { + "filename": "0016.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 0, + "y": 141, + "w": 37, + "h": 46 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 37, + "y": 94, + "w": 36, + "h": 47 + } + }, + { + "filename": "0018.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 37, + "y": 94, + "w": 36, + "h": 47 + } + }, + { + "filename": "0021.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 115, + "y": 0, + "w": 37, + "h": 46 + } + }, + { + "filename": "0022.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 115, + "y": 0, + "w": 37, + "h": 46 + } + }, + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 0, + "y": 187, + "w": 37, + "h": 46 + } + }, + { + "filename": "0034.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 0, + "y": 187, + "w": 37, + "h": 46 + } + }, + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 37, + "y": 141, + "w": 36, + "h": 47 + } + }, + { + "filename": "0036.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 37, + "y": 141, + "w": 36, + "h": 47 + } + }, + { + "filename": "0037.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 0, + "y": 233, + "w": 36, + "h": 47 + } + }, + { + "filename": "0038.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 0, + "y": 233, + "w": 36, + "h": 47 + } + }, + { + "filename": "0039.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 152, + "y": 0, + "w": 37, + "h": 46 + } + }, + { + "filename": "0040.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 152, + "y": 0, + "w": 37, + "h": 46 + } + }, + { + "filename": "0057.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 189, + "y": 0, + "w": 37, + "h": 46 + } + }, + { + "filename": "0058.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 189, + "y": 0, + "w": 37, + "h": 46 + } + }, + { + "filename": "0069.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 226, + "y": 0, + "w": 37, + "h": 46 + } + }, + { + "filename": "0070.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 226, + "y": 0, + "w": 37, + "h": 46 + } + }, + { + "filename": "0005.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 263, + "y": 0, + "w": 39, + "h": 43 + } + }, + { + "filename": "0006.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 263, + "y": 0, + "w": 39, + "h": 43 + } + }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 263, + "y": 43, + "w": 39, + "h": 43 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 263, + "y": 43, + "w": 39, + "h": 43 + } + }, + { + "filename": "0051.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 36, + "h": 46 + }, + "frame": { + "x": 37, + "y": 188, + "w": 36, + "h": 46 + } + }, + { + "filename": "0052.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 36, + "h": 46 + }, + "frame": { + "x": 37, + "y": 188, + "w": 36, + "h": 46 + } + }, + { + "filename": "0087.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 36, + "y": 234, + "w": 37, + "h": 46 + } + }, + { + "filename": "0088.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 36, + "y": 234, + "w": 37, + "h": 46 + } + }, + { + "filename": "0053.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 75, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0054.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 75, + "y": 47, + "w": 36, + "h": 47 + } + }, + { + "filename": "0071.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 73, + "y": 94, + "w": 36, + "h": 47 + } + }, + { + "filename": "0072.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 73, + "y": 94, + "w": 36, + "h": 47 + } + }, + { + "filename": "0073.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 73, + "y": 141, + "w": 36, + "h": 47 + } + }, + { + "filename": "0074.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 73, + "y": 141, + "w": 36, + "h": 47 + } + }, + { + "filename": "0089.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 73, + "y": 188, + "w": 36, + "h": 47 + } + }, + { + "filename": "0090.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 73, + "y": 188, + "w": 36, + "h": 47 + } + }, + { + "filename": "0077.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 2, + "w": 38, + "h": 45 + }, + "frame": { + "x": 111, + "y": 46, + "w": 38, + "h": 45 + } + }, + { + "filename": "0078.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 2, + "w": 38, + "h": 45 + }, + "frame": { + "x": 111, + "y": 46, + "w": 38, + "h": 45 + } + }, + { + "filename": "0059.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 149, + "y": 46, + "w": 39, + "h": 43 + } + }, + { + "filename": "0060.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 149, + "y": 46, + "w": 39, + "h": 43 + } + }, + { + "filename": "0067.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 188, + "y": 46, + "w": 39, + "h": 43 + } + }, + { + "filename": "0068.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 188, + "y": 46, + "w": 39, + "h": 43 + } + }, + { + "filename": "0093.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 227, + "y": 46, + "w": 36, + "h": 47 + } + }, + { + "filename": "0094.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 227, + "y": 46, + "w": 36, + "h": 47 + } + }, + { + "filename": "0079.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 263, + "y": 86, + "w": 39, + "h": 43 + } + }, + { + "filename": "0080.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 263, + "y": 86, + "w": 39, + "h": 43 + } + }, + { + "filename": "0095.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 36, + "h": 46 + }, + "frame": { + "x": 73, + "y": 235, + "w": 36, + "h": 46 + } + }, + { + "filename": "0096.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 36, + "h": 46 + }, + "frame": { + "x": 73, + "y": 235, + "w": 36, + "h": 46 + } + }, + { + "filename": "0101.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 109, + "y": 94, + "w": 36, + "h": 47 + } + }, + { + "filename": "0102.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 109, + "y": 94, + "w": 36, + "h": 47 + } + }, + { + "filename": "0103.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 109, + "y": 141, + "w": 36, + "h": 47 + } + }, + { + "filename": "0104.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 36, + "h": 47 + }, + "frame": { + "x": 109, + "y": 141, + "w": 36, + "h": 47 + } + }, + { + "filename": "0099.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 36, + "h": 46 + }, + "frame": { + "x": 109, + "y": 188, + "w": 36, + "h": 46 + } + }, + { + "filename": "0100.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 36, + "h": 46 + }, + "frame": { + "x": 109, + "y": 188, + "w": 36, + "h": 46 + } + }, + { + "filename": "0097.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 2, + "w": 36, + "h": 45 + }, + "frame": { + "x": 109, + "y": 234, + "w": 36, + "h": 45 + } + }, + { + "filename": "0098.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 2, + "w": 36, + "h": 45 + }, + "frame": { + "x": 109, + "y": 234, + "w": 36, + "h": 45 + } + }, + { + "filename": "0085.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 3, + "w": 37, + "h": 44 + }, + "frame": { + "x": 145, + "y": 91, + "w": 37, + "h": 44 + } + }, + { + "filename": "0086.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 3, + "w": 37, + "h": 44 + }, + "frame": { + "x": 145, + "y": 91, + "w": 37, + "h": 44 + } + }, + { + "filename": "0023.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 4, + "w": 38, + "h": 43 + }, + "frame": { + "x": 145, + "y": 135, + "w": 38, + "h": 43 + } + }, + { + "filename": "0024.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 4, + "w": 38, + "h": 43 + }, + "frame": { + "x": 145, + "y": 135, + "w": 38, + "h": 43 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 4, + "w": 38, + "h": 43 + }, + "frame": { + "x": 145, + "y": 178, + "w": 38, + "h": 43 + } + }, + { + "filename": "0032.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 4, + "w": 38, + "h": 43 + }, + "frame": { + "x": 145, + "y": 178, + "w": 38, + "h": 43 + } + }, + { + "filename": "0083.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 4, + "w": 38, + "h": 43 + }, + "frame": { + "x": 145, + "y": 221, + "w": 38, + "h": 43 + } + }, + { + "filename": "0084.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 4, + "w": 38, + "h": 43 + }, + "frame": { + "x": 145, + "y": 221, + "w": 38, + "h": 43 + } + }, + { + "filename": "0007.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 7, + "w": 40, + "h": 40 + }, + "frame": { + "x": 182, + "y": 89, + "w": 40, + "h": 40 + } + }, + { + "filename": "0008.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 7, + "w": 40, + "h": 40 + }, + "frame": { + "x": 182, + "y": 89, + "w": 40, + "h": 40 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 12, + "w": 41, + "h": 35 + }, + "frame": { + "x": 222, + "y": 93, + "w": 41, + "h": 35 + } + }, + { + "filename": "0010.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 12, + "w": 41, + "h": 35 + }, + "frame": { + "x": 222, + "y": 93, + "w": 41, + "h": 35 + } + }, + { + "filename": "0041.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 4, + "w": 37, + "h": 43 + }, + "frame": { + "x": 183, + "y": 129, + "w": 37, + "h": 43 + } + }, + { + "filename": "0042.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 4, + "w": 37, + "h": 43 + }, + "frame": { + "x": 183, + "y": 129, + "w": 37, + "h": 43 + } + }, + { + "filename": "0049.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 4, + "w": 37, + "h": 43 + }, + "frame": { + "x": 183, + "y": 172, + "w": 37, + "h": 43 + } + }, + { + "filename": "0050.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 4, + "w": 37, + "h": 43 + }, + "frame": { + "x": 183, + "y": 172, + "w": 37, + "h": 43 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 7, + "w": 40, + "h": 40 + }, + "frame": { + "x": 183, + "y": 215, + "w": 40, + "h": 40 + } + }, + { + "filename": "0012.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 7, + "w": 40, + "h": 40 + }, + "frame": { + "x": 183, + "y": 215, + "w": 40, + "h": 40 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 12, + "w": 40, + "h": 35 + }, + "frame": { + "x": 145, + "y": 264, + "w": 40, + "h": 35 + } + }, + { + "filename": "0028.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 12, + "w": 40, + "h": 35 + }, + "frame": { + "x": 145, + "y": 264, + "w": 40, + "h": 35 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 7, + "w": 39, + "h": 40 + }, + "frame": { + "x": 185, + "y": 255, + "w": 39, + "h": 40 + } + }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 7, + "w": 39, + "h": 40 + }, + "frame": { + "x": 185, + "y": 255, + "w": 39, + "h": 40 + } + }, + { + "filename": "0063.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 13, + "w": 41, + "h": 34 + }, + "frame": { + "x": 222, + "y": 128, + "w": 41, + "h": 34 + } + }, + { + "filename": "0064.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 13, + "w": 41, + "h": 34 + }, + "frame": { + "x": 222, + "y": 128, + "w": 41, + "h": 34 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 7, + "w": 39, + "h": 40 + }, + "frame": { + "x": 263, + "y": 129, + "w": 39, + "h": 40 + } + }, + { + "filename": "0030.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 7, + "w": 39, + "h": 40 + }, + "frame": { + "x": 263, + "y": 129, + "w": 39, + "h": 40 + } + }, + { + "filename": "0061.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 7, + "w": 40, + "h": 40 + }, + "frame": { + "x": 220, + "y": 162, + "w": 40, + "h": 40 + } + }, + { + "filename": "0062.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 7, + "w": 40, + "h": 40 + }, + "frame": { + "x": 220, + "y": 162, + "w": 40, + "h": 40 + } + }, + { + "filename": "0065.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 7, + "w": 40, + "h": 40 + }, + "frame": { + "x": 260, + "y": 169, + "w": 40, + "h": 40 + } + }, + { + "filename": "0066.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 7, + "w": 40, + "h": 40 + }, + "frame": { + "x": 260, + "y": 169, + "w": 40, + "h": 40 + } + }, + { + "filename": "0043.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 7, + "w": 38, + "h": 40 + }, + "frame": { + "x": 223, + "y": 209, + "w": 38, + "h": 40 + } + }, + { + "filename": "0044.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 7, + "w": 38, + "h": 40 + }, + "frame": { + "x": 223, + "y": 209, + "w": 38, + "h": 40 + } + }, + { + "filename": "0047.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 7, + "w": 38, + "h": 40 + }, + "frame": { + "x": 261, + "y": 209, + "w": 38, + "h": 40 + } + }, + { + "filename": "0048.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 7, + "w": 38, + "h": 40 + }, + "frame": { + "x": 261, + "y": 209, + "w": 38, + "h": 40 + } + }, + { + "filename": "0081.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 8, + "w": 39, + "h": 39 + }, + "frame": { + "x": 224, + "y": 249, + "w": 39, + "h": 39 + } + }, + { + "filename": "0082.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 8, + "w": 39, + "h": 39 + }, + "frame": { + "x": 224, + "y": 249, + "w": 39, + "h": 39 + } + }, + { + "filename": "0045.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 12, + "w": 39, + "h": 35 + }, + "frame": { + "x": 263, + "y": 249, + "w": 39, + "h": 35 + } + }, + { + "filename": "0046.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 12, + "w": 39, + "h": 35 + }, + "frame": { + "x": 263, + "y": 249, + "w": 39, + "h": 35 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:11d6f26f5786e76420a96085163ce189:617f0fbd4eb98368acc7dc3c1484aeb6:d7d9e845c71962a58076b5efe962284e$" + } +} diff --git a/public/images/pokemon/back/shiny/25-partner.png b/public/images/pokemon/back/shiny/25-partner.png new file mode 100644 index 0000000000000000000000000000000000000000..f4fb2eab219f32e6d4dff1bb5be9f03dd202e650 GIT binary patch literal 7036 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 literal 0 HcmV?d00001 diff --git a/public/images/pokemon/back/shiny/female/25-partner.json b/public/images/pokemon/back/shiny/female/25-partner.json new file mode 100644 index 00000000000..6d76db3b6df --- /dev/null +++ b/public/images/pokemon/back/shiny/female/25-partner.json @@ -0,0 +1,2456 @@ +{ + "textures": [ + { + "image": "25-partner.png", + "format": "RGBA8888", + "size": { + "w": 309, + "h": 309 + }, + "scale": 1, + "frames": [ + { + "filename": "0107.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 41, + "h": 47 + } + }, + { + "filename": "0111.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 41, + "h": 47 + } + }, + { + "filename": "0108.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 0, + "w": 40, + "h": 47 + }, + "frame": { + "x": 0, + "y": 47, + "w": 40, + "h": 47 + } + }, + { + "filename": "0110.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 0, + "w": 40, + "h": 47 + }, + "frame": { + "x": 0, + "y": 47, + "w": 40, + "h": 47 + } + }, + { + "filename": "0112.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 0, + "w": 40, + "h": 47 + }, + "frame": { + "x": 0, + "y": 47, + "w": 40, + "h": 47 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 38, + "h": 47 + }, + "frame": { + "x": 41, + "y": 0, + "w": 38, + "h": 47 + } + }, + { + "filename": "0020.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 38, + "h": 47 + }, + "frame": { + "x": 41, + "y": 0, + "w": 38, + "h": 47 + } + }, + { + "filename": "0075.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 38, + "h": 47 + }, + "frame": { + "x": 0, + "y": 94, + "w": 38, + "h": 47 + } + }, + { + "filename": "0076.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 38, + "h": 47 + }, + "frame": { + "x": 0, + "y": 94, + "w": 38, + "h": 47 + } + }, + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 40, + "y": 47, + "w": 37, + "h": 47 + } + }, + { + "filename": "0002.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 40, + "y": 47, + "w": 37, + "h": 47 + } + }, + { + "filename": "0055.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 40, + "y": 47, + "w": 37, + "h": 47 + } + }, + { + "filename": "0056.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 40, + "y": 47, + "w": 37, + "h": 47 + } + }, + { + "filename": "0091.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 40, + "y": 47, + "w": 37, + "h": 47 + } + }, + { + "filename": "0092.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 40, + "y": 47, + "w": 37, + "h": 47 + } + }, + { + "filename": "0105.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 40, + "y": 47, + "w": 37, + "h": 47 + } + }, + { + "filename": "0106.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 40, + "y": 47, + "w": 37, + "h": 47 + } + }, + { + "filename": "0109.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 40, + "y": 47, + "w": 37, + "h": 47 + } + }, + { + "filename": "0113.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 40, + "y": 47, + "w": 37, + "h": 47 + } + }, + { + "filename": "0114.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 40, + "y": 47, + "w": 37, + "h": 47 + } + }, + { + "filename": "0115.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 40, + "y": 47, + "w": 37, + "h": 47 + } + }, + { + "filename": "0116.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 40, + "y": 47, + "w": 37, + "h": 47 + } + }, + { + "filename": "0003.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 38, + "h": 46 + }, + "frame": { + "x": 79, + "y": 0, + "w": 38, + "h": 46 + } + }, + { + "filename": "0004.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 38, + "h": 46 + }, + "frame": { + "x": 79, + "y": 0, + "w": 38, + "h": 46 + } + }, + { + "filename": "0015.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 38, + "h": 46 + }, + "frame": { + "x": 0, + "y": 141, + "w": 38, + "h": 46 + } + }, + { + "filename": "0016.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 38, + "h": 46 + }, + "frame": { + "x": 0, + "y": 141, + "w": 38, + "h": 46 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 38, + "y": 94, + "w": 37, + "h": 47 + } + }, + { + "filename": "0018.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 38, + "y": 94, + "w": 37, + "h": 47 + } + }, + { + "filename": "0021.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 38, + "h": 46 + }, + "frame": { + "x": 117, + "y": 0, + "w": 38, + "h": 46 + } + }, + { + "filename": "0022.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 38, + "h": 46 + }, + "frame": { + "x": 117, + "y": 0, + "w": 38, + "h": 46 + } + }, + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 38, + "h": 46 + }, + "frame": { + "x": 0, + "y": 187, + "w": 38, + "h": 46 + } + }, + { + "filename": "0034.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 38, + "h": 46 + }, + "frame": { + "x": 0, + "y": 187, + "w": 38, + "h": 46 + } + }, + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 38, + "y": 141, + "w": 37, + "h": 47 + } + }, + { + "filename": "0036.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 38, + "y": 141, + "w": 37, + "h": 47 + } + }, + { + "filename": "0037.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 0, + "y": 233, + "w": 37, + "h": 47 + } + }, + { + "filename": "0038.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 0, + "y": 233, + "w": 37, + "h": 47 + } + }, + { + "filename": "0039.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 38, + "h": 46 + }, + "frame": { + "x": 155, + "y": 0, + "w": 38, + "h": 46 + } + }, + { + "filename": "0040.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 38, + "h": 46 + }, + "frame": { + "x": 155, + "y": 0, + "w": 38, + "h": 46 + } + }, + { + "filename": "0057.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 38, + "h": 46 + }, + "frame": { + "x": 193, + "y": 0, + "w": 38, + "h": 46 + } + }, + { + "filename": "0058.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 38, + "h": 46 + }, + "frame": { + "x": 193, + "y": 0, + "w": 38, + "h": 46 + } + }, + { + "filename": "0069.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 38, + "h": 46 + }, + "frame": { + "x": 231, + "y": 0, + "w": 38, + "h": 46 + } + }, + { + "filename": "0070.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 38, + "h": 46 + }, + "frame": { + "x": 231, + "y": 0, + "w": 38, + "h": 46 + } + }, + { + "filename": "0005.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 4, + "w": 40, + "h": 43 + }, + "frame": { + "x": 269, + "y": 0, + "w": 40, + "h": 43 + } + }, + { + "filename": "0006.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 4, + "w": 40, + "h": 43 + }, + "frame": { + "x": 269, + "y": 0, + "w": 40, + "h": 43 + } + }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 4, + "w": 40, + "h": 43 + }, + "frame": { + "x": 269, + "y": 43, + "w": 40, + "h": 43 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 4, + "w": 40, + "h": 43 + }, + "frame": { + "x": 269, + "y": 43, + "w": 40, + "h": 43 + } + }, + { + "filename": "0051.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 38, + "y": 188, + "w": 37, + "h": 46 + } + }, + { + "filename": "0052.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 38, + "y": 188, + "w": 37, + "h": 46 + } + }, + { + "filename": "0087.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 38, + "h": 46 + }, + "frame": { + "x": 37, + "y": 234, + "w": 38, + "h": 46 + } + }, + { + "filename": "0088.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 38, + "h": 46 + }, + "frame": { + "x": 37, + "y": 234, + "w": 38, + "h": 46 + } + }, + { + "filename": "0053.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 75, + "y": 94, + "w": 37, + "h": 47 + } + }, + { + "filename": "0054.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 75, + "y": 94, + "w": 37, + "h": 47 + } + }, + { + "filename": "0071.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 75, + "y": 141, + "w": 37, + "h": 47 + } + }, + { + "filename": "0072.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 75, + "y": 141, + "w": 37, + "h": 47 + } + }, + { + "filename": "0073.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 77, + "y": 47, + "w": 37, + "h": 47 + } + }, + { + "filename": "0074.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 77, + "y": 47, + "w": 37, + "h": 47 + } + }, + { + "filename": "0089.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 75, + "y": 188, + "w": 37, + "h": 47 + } + }, + { + "filename": "0090.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 75, + "y": 188, + "w": 37, + "h": 47 + } + }, + { + "filename": "0077.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 39, + "h": 45 + }, + "frame": { + "x": 114, + "y": 46, + "w": 39, + "h": 45 + } + }, + { + "filename": "0078.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 39, + "h": 45 + }, + "frame": { + "x": 114, + "y": 46, + "w": 39, + "h": 45 + } + }, + { + "filename": "0059.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 4, + "w": 40, + "h": 43 + }, + "frame": { + "x": 153, + "y": 46, + "w": 40, + "h": 43 + } + }, + { + "filename": "0060.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 4, + "w": 40, + "h": 43 + }, + "frame": { + "x": 153, + "y": 46, + "w": 40, + "h": 43 + } + }, + { + "filename": "0067.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 4, + "w": 40, + "h": 43 + }, + "frame": { + "x": 193, + "y": 46, + "w": 40, + "h": 43 + } + }, + { + "filename": "0068.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 4, + "w": 40, + "h": 43 + }, + "frame": { + "x": 193, + "y": 46, + "w": 40, + "h": 43 + } + }, + { + "filename": "0093.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 75, + "y": 235, + "w": 37, + "h": 47 + } + }, + { + "filename": "0094.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 75, + "y": 235, + "w": 37, + "h": 47 + } + }, + { + "filename": "0101.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 112, + "y": 94, + "w": 37, + "h": 47 + } + }, + { + "filename": "0102.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 112, + "y": 94, + "w": 37, + "h": 47 + } + }, + { + "filename": "0103.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 112, + "y": 141, + "w": 37, + "h": 47 + } + }, + { + "filename": "0104.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 37, + "h": 47 + }, + "frame": { + "x": 112, + "y": 141, + "w": 37, + "h": 47 + } + }, + { + "filename": "0095.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 112, + "y": 188, + "w": 37, + "h": 46 + } + }, + { + "filename": "0096.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 112, + "y": 188, + "w": 37, + "h": 46 + } + }, + { + "filename": "0099.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 112, + "y": 234, + "w": 37, + "h": 46 + } + }, + { + "filename": "0100.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 1, + "w": 37, + "h": 46 + }, + "frame": { + "x": 112, + "y": 234, + "w": 37, + "h": 46 + } + }, + { + "filename": "0097.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 37, + "h": 45 + }, + "frame": { + "x": 149, + "y": 91, + "w": 37, + "h": 45 + } + }, + { + "filename": "0098.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 37, + "h": 45 + }, + "frame": { + "x": 149, + "y": 91, + "w": 37, + "h": 45 + } + }, + { + "filename": "0079.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 4, + "w": 40, + "h": 43 + }, + "frame": { + "x": 186, + "y": 89, + "w": 40, + "h": 43 + } + }, + { + "filename": "0080.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 4, + "w": 40, + "h": 43 + }, + "frame": { + "x": 186, + "y": 89, + "w": 40, + "h": 43 + } + }, + { + "filename": "0085.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 38, + "h": 44 + }, + "frame": { + "x": 149, + "y": 136, + "w": 38, + "h": 44 + } + }, + { + "filename": "0086.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 38, + "h": 44 + }, + "frame": { + "x": 149, + "y": 136, + "w": 38, + "h": 44 + } + }, + { + "filename": "0023.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 149, + "y": 180, + "w": 39, + "h": 43 + } + }, + { + "filename": "0024.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 149, + "y": 180, + "w": 39, + "h": 43 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 149, + "y": 223, + "w": 39, + "h": 43 + } + }, + { + "filename": "0032.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 149, + "y": 223, + "w": 39, + "h": 43 + } + }, + { + "filename": "0083.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 149, + "y": 266, + "w": 39, + "h": 43 + } + }, + { + "filename": "0084.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 4, + "w": 39, + "h": 43 + }, + "frame": { + "x": 149, + "y": 266, + "w": 39, + "h": 43 + } + }, + { + "filename": "0041.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 4, + "w": 38, + "h": 43 + }, + "frame": { + "x": 187, + "y": 132, + "w": 38, + "h": 43 + } + }, + { + "filename": "0042.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 4, + "w": 38, + "h": 43 + }, + "frame": { + "x": 187, + "y": 132, + "w": 38, + "h": 43 + } + }, + { + "filename": "0049.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 4, + "w": 38, + "h": 43 + }, + "frame": { + "x": 188, + "y": 175, + "w": 38, + "h": 43 + } + }, + { + "filename": "0050.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 3, + "y": 4, + "w": 38, + "h": 43 + }, + "frame": { + "x": 188, + "y": 175, + "w": 38, + "h": 43 + } + }, + { + "filename": "0007.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 41, + "h": 40 + }, + "frame": { + "x": 188, + "y": 218, + "w": 41, + "h": 40 + } + }, + { + "filename": "0008.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 41, + "h": 40 + }, + "frame": { + "x": 188, + "y": 218, + "w": 41, + "h": 40 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 41, + "h": 40 + }, + "frame": { + "x": 188, + "y": 258, + "w": 41, + "h": 40 + } + }, + { + "filename": "0012.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 41, + "h": 40 + }, + "frame": { + "x": 188, + "y": 258, + "w": 41, + "h": 40 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 7, + "w": 40, + "h": 40 + }, + "frame": { + "x": 226, + "y": 89, + "w": 40, + "h": 40 + } + }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 7, + "w": 40, + "h": 40 + }, + "frame": { + "x": 226, + "y": 89, + "w": 40, + "h": 40 + } + }, + { + "filename": "0061.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 41, + "h": 40 + }, + "frame": { + "x": 266, + "y": 86, + "w": 41, + "h": 40 + } + }, + { + "filename": "0062.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 41, + "h": 40 + }, + "frame": { + "x": 266, + "y": 86, + "w": 41, + "h": 40 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 12, + "w": 41, + "h": 35 + }, + "frame": { + "x": 266, + "y": 126, + "w": 41, + "h": 35 + } + }, + { + "filename": "0010.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 12, + "w": 41, + "h": 35 + }, + "frame": { + "x": 266, + "y": 126, + "w": 41, + "h": 35 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 7, + "w": 40, + "h": 40 + }, + "frame": { + "x": 226, + "y": 129, + "w": 40, + "h": 40 + } + }, + { + "filename": "0030.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 7, + "w": 40, + "h": 40 + }, + "frame": { + "x": 226, + "y": 129, + "w": 40, + "h": 40 + } + }, + { + "filename": "0065.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 41, + "h": 40 + }, + "frame": { + "x": 226, + "y": 169, + "w": 41, + "h": 40 + } + }, + { + "filename": "0066.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 41, + "h": 40 + }, + "frame": { + "x": 226, + "y": 169, + "w": 41, + "h": 40 + } + }, + { + "filename": "0043.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 7, + "w": 39, + "h": 40 + }, + "frame": { + "x": 267, + "y": 161, + "w": 39, + "h": 40 + } + }, + { + "filename": "0044.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 7, + "w": 39, + "h": 40 + }, + "frame": { + "x": 267, + "y": 161, + "w": 39, + "h": 40 + } + }, + { + "filename": "0047.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 7, + "w": 39, + "h": 40 + }, + "frame": { + "x": 229, + "y": 209, + "w": 39, + "h": 40 + } + }, + { + "filename": "0048.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 7, + "w": 39, + "h": 40 + }, + "frame": { + "x": 229, + "y": 209, + "w": 39, + "h": 40 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 12, + "w": 41, + "h": 35 + }, + "frame": { + "x": 268, + "y": 201, + "w": 41, + "h": 35 + } + }, + { + "filename": "0028.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 12, + "w": 41, + "h": 35 + }, + "frame": { + "x": 268, + "y": 201, + "w": 41, + "h": 35 + } + }, + { + "filename": "0063.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 13, + "w": 41, + "h": 34 + }, + "frame": { + "x": 268, + "y": 236, + "w": 41, + "h": 34 + } + }, + { + "filename": "0064.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 13, + "w": 41, + "h": 34 + }, + "frame": { + "x": 268, + "y": 236, + "w": 41, + "h": 34 + } + }, + { + "filename": "0081.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 8, + "w": 40, + "h": 39 + }, + "frame": { + "x": 229, + "y": 270, + "w": 40, + "h": 39 + } + }, + { + "filename": "0082.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 8, + "w": 40, + "h": 39 + }, + "frame": { + "x": 229, + "y": 270, + "w": 40, + "h": 39 + } + }, + { + "filename": "0045.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 12, + "w": 40, + "h": 35 + }, + "frame": { + "x": 269, + "y": 270, + "w": 40, + "h": 35 + } + }, + { + "filename": "0046.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 41, + "h": 47 + }, + "spriteSourceSize": { + "x": 1, + "y": 12, + "w": 40, + "h": 35 + }, + "frame": { + "x": 269, + "y": 270, + "w": 40, + "h": 35 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:c2491c1baeaa6ad79265c83724d14b58:4d021c74e090acb4be617ac0f9f4914b:d7d9e845c71962a58076b5efe962284e$" + } +} diff --git a/public/images/pokemon/back/shiny/female/25-partner.png b/public/images/pokemon/back/shiny/female/25-partner.png new file mode 100644 index 0000000000000000000000000000000000000000..efc1fc1c0e396dd6f53c590d98afe0e8a29ba9ed GIT binary patch literal 7102 zcmX|G2Q(aE*H(fA(R+`&MD*T5u)5f_5iQYsFA+6Ji0F1L!s=@+K@cU;OIGhSh#-0m zQ5O;a%J+T$oHJ+6JonybZh7XN_ne6}GSs?D%0h~Vhj&*;TLX-ThcA6|-Xg>y{^G8( zxQl?1K136D#T|lzf?%+@9~GXTUsRnqUY8*Ay4|DoD70QauyS58oeb zXpE=2KI4do$EvBLp#}+j-j$=~wyK@pgUWm)_MK7Fp`v3;uV!M`;c;%!>9U>-FVV2V z=@?^36BYbOiqpo%rgFHVfa`mPIPXIJ;W&pc`PSw@`+U91F!SA``8W5 z9%Ib?m)lp%(S>ijT#B@>16kQlQxalZKfr~m6TzCV52uQ#i^474;R!U~3%J$JgJGY% zugRZkTPfF@#_laudZXyB57aNDQK_ln%%A^E$W?`d_?X@IK3f?pRGra$-x(`-+;(O- zT{P26HvrJ~iHV9_0Cc%z7gLtme4E-mx|T_*V0Qy6Nfg7M)7yQeC0F31@OLbfa@B5Q zze}8 z7Ur{U9Z#1UPK~8dncsiP{U}rUCL2vRRfL%%gNhjy&Ai`fv=`0x3vt)+BNedm_7Fz- z_7Xo2BX+DGrK!ZW1*28!IgEuby1OIca-*JE)w9tItRz;GJ;MiiZ=QN>y*!!u70$uv ziLlLULSs$IJu$?UMO|eXaQKdQR(Ui0+G{)ShuJr}*b|u2(*R~$GOz^lX`v3AbLGg< zlhW1maP(UC+Q-hC7v3g89|s?0fnbp-Vh1WChoN=mYr}shdLX;5or1rdQa-LVw7y<_ zl(l}|Vtn{VsQlXx7s#_AOi#c9TWENg$jBP1nVa3Hv)x2cBuzrb%Z2*8Ljgnelyc76 zw=}b>C6vz`sL^_FUZAE2VVjQ)C=$gOt zDzceRZ(~FGX{kb&RNMK)X#!j63F|A=nMWf@d;LD`UKq2F1iTLwerEfJwW*>CNj{Bq2L@l|>EX+RV zF&tH8ZInt7*lU6%0+xkUjzaa1S+k=oA|Job4 z#^<%&FS)z!xA#2m`Px%?+Z~RUD!hV-+&8hBnhu2|fwW*u~$DR=ZsW=d;b!PqXe3;LCq{H$kN6#LJ>&nne z5h(Ks+na0yY&ebUv}`g(k}*sz;rPl&`^5+1A2`+Xs|F*&iYo9<^bD>0=}fs=6HI41-*uj z0QznMwN63M{Db#sS;8LI_Jp|VmwZm%b~iCBLx@eOB=mw|e^yk5W`v#ybm82k^Zx0) z!BI&Eo#QIVg*wTCCbr^zVWg61y&=J3@Tih&t%>7m`EY{0mp1XwDES7-3YPLTW+Jv( z36GcVn27mIle^Qy3G*`w_ggr%D5Lfz102=R@giB@3dLojna_JFyVoOooxpkuKls?C zDh-fO<|}Z2#>vd#^`V|m4}HPJRS?OL+k~?{a0Ot5Y}!=11-lj>qvp%DSEkGQR@~8> zaOiM*XrW2)58>ZGJrVdLu}TO-BI&LCph;_cpo6i9y*!Dw;ExS`htmrqO6h{)w!0&nZ(EQu2~fB~2XGSDvRgG;1oxm9nAevm9*g z6a`UxQ#uGRj8_c>tVG~fbL%{Ru^jB}}`iaU|2x2KDHVEE{dWfiyCmP+>F`6_jm z?1VH66nMLcN~Nh|1-^NY(g!c~3_guv9Vr@T!r&AiP{j|;;(k#`H&YsXeqf;{V`X*6^ zj|*|U7-AZW0Fj9cGY8Gh$-H&Yj++gs->LCcL-Bc9`R(jn-J_DRUZF?4U%S~V58S8> zO(}mR6mKzb$^^BG?OUV#mxGl?v{fir`=#26A!QNnNC@B;7ku^_8M6_rA!BlLqdR=6<%k6tCru;IcJ(P2v zV>+@U6;!v28tvpkvA5CSJ&KjZ$rR}r>p9*iT02KmvD4P+qLO#ng#NN zp)kAeZxCih(SQaMKza=FwY-N^iyQmtg6X>t`r0=r<5gjW^vKKDwTdRToQz-W>yhBf zPlnBrGN%CzI?%b20SXOfqF-5_eGFqGbGUY`R=(vQXB(_Uz|4-97od1f-@rn1pmE2p zjG>M2`Ip?n_rLfB`_kK84Rp3h&-AG)o^qX!VWJHYqSx_0(r z!LqW{+3QaPI3X_*B^<8ZJrc)_=AKPw0Up7W*g@ku4zg!Y0;v~OqWG)XH5P_vHN@53 zRpI)63PdVk8`3imGX)XQf3vZqnz_v>3PD|q@tW2LNI*(QEd+sJ-m zJ1W@Co2;WIIe49Cq6I(CgRbkP*%?;0dzznl#ovl!wLVP{sK5{6#>x*e8#IcXAhP+zVV-MXV=4H8^nTL~3Al18u`NWTT{)%TIf5kIW2jq;vsm**#cR)wSWk1pxhBmoWQTbY-P!%vcd*9uAI)N5xNaBaUI zf`?5DUf22>@z3sFzoJpb2T2TVX#{+|BII~oF&GP94CcD&Os{8dWK7{I!s^;&vhQ09 zHQz+G;oyU1Dt5}8;6kX`<(e1x^X{9QS=)LR^l0~}2Y`0Zmwv9DntsT=qDDzhm0o(i z89PX6lR^XF8PyF08*5n2cQC^%#m;4bfiefoHT8bxkAmV0OWfz)tXT^Ravvf{L1E z=L}QAXSkHv8CYCA&!wD?*`cDfmRU96zy&gMr3pD2)n!CTma=rL$23~qoi^5^bgah{ zsAJ0X<!rAPuFbyPmv|6(@E3AXd}`2uQ_U5+pb@B3F#>|X~Q92{C1BGSZ~r@EorJv z0Nyos*B^LLtIJv9t(t*-vYzDQ{;>_yo-J+_65XQyDB?^Q0TFOl`5 z%~CxRJ!9$iR-V6+L`dFVwm^Xq{7hWjKbyd44>GOdUMpre#u~0=4e#jl)3O>dVh?v+mR0ENW&9xh#CS3!%s`s!7?;oB}NY7KYi=lR5E zRg%Np1@%ZJTOQ~cal~Oei*~$Dz_nf(IqQW!z{ztxnN;f8PqpK6l0sVb9r#B+lt7)7 zQeKsmin+FwFqW2zsnuTmSk8a0{3_4~Ztbls$}Cf^zN--9B5lQ4y1g;_4q3I{r zhdEWrV^_B?=_*+bV-I(zkrl&^;|I|C(1i zC@G7LY7sX>+VK6Ju|D+|m3ZdR;BCV` zmr&Ekp(bbbyDML&!#i)rBzh6B?oNv;p3d*u&fQP6xxVA(d&}*QB`>o>svU!2w)khB{bQOL_7gaPeE;OPyAG%cZWlr8Efv|crd(L|=lj-QacpPk3 zbgfa@cjf3WC!v!_*vt!MkGsi1gBk`Omx5vpD6lKH7c(_DQOF~D^fV(4IlrYf1ccDLe6T+F|kv43<%Ig6CTDV+rx!OvUT6= z*q46-caCE`|DpqDOE~6El`7-L%v-UzKnhbEAPignOR&m+IzTMzGxWbv3FI`KQ_Ah& zytB0q=h(#0P>$O0-%y;`Qf~BQQ;MmN4bsud-|fV|8^O!OCzlVy0+OiHT{U4;^DlA; zvlRCDaCJav^4Kz}Ac8Pww$X!$RD1Ypgnp>6rHq)fCXKJ^!#!ygdNBe$4utA% zP+mR~NGq4lKj@oqsjOJOHg`;YAO8844z5|M{H@c1GM5l9YiB`NIXIed`G1uDad(6( zWW{PNxjsSUxE?D1he6;ywW+zA^%4>HIrwLU61n<6UM=_1_HlDULJ=zHOgp|06Y)(^ z#n%O0d@^5Dd7s{wQ=(PHIchr=W$WtT>G?#yJJgHZcfQHp9@wmH6pa#w=|d=bCGr0s zibMfm*+OhoP6WMxKK#Cf+W1>eQK(>O6jS2=pg?Fog`OGj)(CzdU`xXh6MyT8X+O74 zI7M$4iq=L@l!iRoSFnHUv-L|qRS}UKf7AZ>TZvszMLL}e-y0wR9~XV^1NZXE^0-b6 z>Rntl-dGl?K; zK~)hO!9?p12;2txLjtLA#T&~`@HAxB`i$nLSSwy8PBLK!1L^y>WM(?V_t;s(GpN(o zfiWns@zv+pgK|%bUhQ7|^QQ7O<)sV})8Hr5kg#U#TkduinOwvEeTi0*1bS1U41@Qn zcof*Xn!c*Ig}|k40L!6jYTo9~JqejrMEkV&LQL-1d$a%7F!#lMdD@O3RK2gOv zTm(^aOh|LVM<08GRYBZ@ zr|b}uwr+1;!Od~I20T6K6ulJ(+O_A<4Jcu~m~J8=jG20M+C&uQpdEHES1SqU~>e%KAfZ)f`-7Va(N_gkme14vN52;#3^a2?vU2 z1<&46T$*Lo$K`#S3Mm!0)!nFAz!~zU50_I%4bEf8^fT`<5`Hrpg0PZA+be{@?JEHE zhS13ccbhmENyE%1&lQwD4S0*097vCP1;44ep;Q;iL}RQ>s(Vv3jE(AU!=fhXC7$yd zX!PZ_EJ*N%;u9}gD$eI9h|a&T&CQ4#IVqoiAlw4d=l&@>kp2DAM4jHw}RX zz{x=HvaK;o7X~(4k=C@LLjOX~s=ii1>&A>;eMmaZuT)k6Dktu>-23Qj;OT?>I+*c* zubu-4)IoTx7DAkq27sgVWDOCsjP~JAF~q}-l6I7ItX*+4H1`|U+<8dM+s$4&?nc@o-&@Hx zl}up>CmM*Iexy~C#3kAWIcs06`o=TTI?@j}DjEmpdlL)E4ZAf+r}9h6lQl~ILv%$# zD%MRB3b#8b?2LmLSi9WyJTo0RM_o6np-$BxVW?BdYWnY98Yi`v*5)^le&iYEEEZ~f z$=Wh8GeekG-^O$QE_gbyW4PqDL(Fpt&Fa}Uh7{00-V`0eblW>S0f oG}KM>W0L&b&}p%M8&0mDl?n$Oy;k15`3k@f5U1A&pOF8}}l literal 0 HcmV?d00001 diff --git a/public/images/pokemon/female/25-partner.json b/public/images/pokemon/female/25-partner.json new file mode 100644 index 00000000000..c662ec8a3c6 --- /dev/null +++ b/public/images/pokemon/female/25-partner.json @@ -0,0 +1,2456 @@ +{ + "textures": [ + { + "image": "25-partner.png", + "format": "RGBA8888", + "size": { + "w": 330, + "h": 330 + }, + "scale": 1, + "frames": [ + { + "filename": "0007.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 3, + "w": 50, + "h": 43 + }, + "frame": { + "x": 0, + "y": 0, + "w": 50, + "h": 43 + } + }, + { + "filename": "0008.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 3, + "w": 50, + "h": 43 + }, + "frame": { + "x": 0, + "y": 0, + "w": 50, + "h": 43 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 51, + "h": 43 + }, + "frame": { + "x": 50, + "y": 0, + "w": 51, + "h": 43 + } + }, + { + "filename": "0012.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 51, + "h": 43 + }, + "frame": { + "x": 50, + "y": 0, + "w": 51, + "h": 43 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 3, + "w": 49, + "h": 43 + }, + "frame": { + "x": 101, + "y": 0, + "w": 49, + "h": 43 + } + }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 3, + "w": 49, + "h": 43 + }, + "frame": { + "x": 101, + "y": 0, + "w": 49, + "h": 43 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 50, + "h": 43 + }, + "frame": { + "x": 150, + "y": 0, + "w": 50, + "h": 43 + } + }, + { + "filename": "0030.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 50, + "h": 43 + }, + "frame": { + "x": 150, + "y": 0, + "w": 50, + "h": 43 + } + }, + { + "filename": "0043.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 49, + "h": 43 + }, + "frame": { + "x": 200, + "y": 0, + "w": 49, + "h": 43 + } + }, + { + "filename": "0044.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 49, + "h": 43 + }, + "frame": { + "x": 200, + "y": 0, + "w": 49, + "h": 43 + } + }, + { + "filename": "0047.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 49, + "h": 43 + }, + "frame": { + "x": 249, + "y": 0, + "w": 49, + "h": 43 + } + }, + { + "filename": "0048.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 49, + "h": 43 + }, + "frame": { + "x": 249, + "y": 0, + "w": 49, + "h": 43 + } + }, + { + "filename": "0061.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 3, + "w": 50, + "h": 43 + }, + "frame": { + "x": 0, + "y": 43, + "w": 50, + "h": 43 + } + }, + { + "filename": "0062.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 3, + "w": 50, + "h": 43 + }, + "frame": { + "x": 0, + "y": 43, + "w": 50, + "h": 43 + } + }, + { + "filename": "0065.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 51, + "h": 43 + }, + "frame": { + "x": 50, + "y": 43, + "w": 51, + "h": 43 + } + }, + { + "filename": "0066.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 51, + "h": 43 + }, + "frame": { + "x": 50, + "y": 43, + "w": 51, + "h": 43 + } + }, + { + "filename": "0079.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 3, + "w": 48, + "h": 43 + }, + "frame": { + "x": 101, + "y": 43, + "w": 48, + "h": 43 + } + }, + { + "filename": "0080.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 3, + "w": 48, + "h": 43 + }, + "frame": { + "x": 101, + "y": 43, + "w": 48, + "h": 43 + } + }, + { + "filename": "0083.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 49, + "h": 43 + }, + "frame": { + "x": 149, + "y": 43, + "w": 49, + "h": 43 + } + }, + { + "filename": "0084.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 49, + "h": 43 + }, + "frame": { + "x": 149, + "y": 43, + "w": 49, + "h": 43 + } + }, + { + "filename": "0005.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 48, + "h": 44 + }, + "frame": { + "x": 198, + "y": 43, + "w": 48, + "h": 44 + } + }, + { + "filename": "0006.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 48, + "h": 44 + }, + "frame": { + "x": 198, + "y": 43, + "w": 48, + "h": 44 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 50, + "h": 44 + }, + "frame": { + "x": 246, + "y": 43, + "w": 50, + "h": 44 + } + }, + { + "filename": "0010.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 50, + "h": 44 + }, + "frame": { + "x": 246, + "y": 43, + "w": 50, + "h": 44 + } + }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 48, + "h": 44 + }, + "frame": { + "x": 0, + "y": 86, + "w": 48, + "h": 44 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 48, + "h": 44 + }, + "frame": { + "x": 0, + "y": 86, + "w": 48, + "h": 44 + } + }, + { + "filename": "0023.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 47, + "h": 44 + }, + "frame": { + "x": 48, + "y": 86, + "w": 47, + "h": 44 + } + }, + { + "filename": "0024.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 47, + "h": 44 + }, + "frame": { + "x": 48, + "y": 86, + "w": 47, + "h": 44 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 50, + "h": 44 + }, + "frame": { + "x": 95, + "y": 86, + "w": 50, + "h": 44 + } + }, + { + "filename": "0028.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 50, + "h": 44 + }, + "frame": { + "x": 95, + "y": 86, + "w": 50, + "h": 44 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 47, + "h": 44 + }, + "frame": { + "x": 145, + "y": 86, + "w": 47, + "h": 44 + } + }, + { + "filename": "0032.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 47, + "h": 44 + }, + "frame": { + "x": 145, + "y": 86, + "w": 47, + "h": 44 + } + }, + { + "filename": "0041.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 46, + "h": 44 + }, + "frame": { + "x": 192, + "y": 87, + "w": 46, + "h": 44 + } + }, + { + "filename": "0042.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 46, + "h": 44 + }, + "frame": { + "x": 192, + "y": 87, + "w": 46, + "h": 44 + } + }, + { + "filename": "0045.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 50, + "h": 44 + }, + "frame": { + "x": 238, + "y": 87, + "w": 50, + "h": 44 + } + }, + { + "filename": "0046.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 50, + "h": 44 + }, + "frame": { + "x": 238, + "y": 87, + "w": 50, + "h": 44 + } + }, + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 288, + "y": 87, + "w": 42, + "h": 46 + } + }, + { + "filename": "0002.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 288, + "y": 87, + "w": 42, + "h": 46 + } + }, + { + "filename": "0049.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 46, + "h": 44 + }, + "frame": { + "x": 0, + "y": 130, + "w": 46, + "h": 44 + } + }, + { + "filename": "0050.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 46, + "h": 44 + }, + "frame": { + "x": 0, + "y": 130, + "w": 46, + "h": 44 + } + }, + { + "filename": "0059.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 48, + "h": 44 + }, + "frame": { + "x": 46, + "y": 130, + "w": 48, + "h": 44 + } + }, + { + "filename": "0060.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 48, + "h": 44 + }, + "frame": { + "x": 46, + "y": 130, + "w": 48, + "h": 44 + } + }, + { + "filename": "0067.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 48, + "h": 44 + }, + "frame": { + "x": 46, + "y": 130, + "w": 48, + "h": 44 + } + }, + { + "filename": "0068.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 48, + "h": 44 + }, + "frame": { + "x": 46, + "y": 130, + "w": 48, + "h": 44 + } + }, + { + "filename": "0063.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 50, + "h": 44 + }, + "frame": { + "x": 94, + "y": 130, + "w": 50, + "h": 44 + } + }, + { + "filename": "0064.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 50, + "h": 44 + }, + "frame": { + "x": 94, + "y": 130, + "w": 50, + "h": 44 + } + }, + { + "filename": "0077.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 47, + "h": 44 + }, + "frame": { + "x": 144, + "y": 130, + "w": 47, + "h": 44 + } + }, + { + "filename": "0078.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 47, + "h": 44 + }, + "frame": { + "x": 144, + "y": 130, + "w": 47, + "h": 44 + } + }, + { + "filename": "0081.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 49, + "h": 44 + }, + "frame": { + "x": 191, + "y": 131, + "w": 49, + "h": 44 + } + }, + { + "filename": "0082.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 49, + "h": 44 + }, + "frame": { + "x": 191, + "y": 131, + "w": 49, + "h": 44 + } + }, + { + "filename": "0085.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 46, + "h": 44 + }, + "frame": { + "x": 240, + "y": 131, + "w": 46, + "h": 44 + } + }, + { + "filename": "0086.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 46, + "h": 44 + }, + "frame": { + "x": 240, + "y": 131, + "w": 46, + "h": 44 + } + }, + { + "filename": "0003.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 286, + "y": 133, + "w": 44, + "h": 46 + } + }, + { + "filename": "0004.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 286, + "y": 133, + "w": 44, + "h": 46 + } + }, + { + "filename": "0015.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 44, + "h": 46 + } + }, + { + "filename": "0016.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 44, + "h": 46 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 44, + "y": 174, + "w": 42, + "h": 46 + } + }, + { + "filename": "0018.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 44, + "y": 174, + "w": 42, + "h": 46 + } + }, + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 44, + "y": 174, + "w": 42, + "h": 46 + } + }, + { + "filename": "0036.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 44, + "y": 174, + "w": 42, + "h": 46 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 43, + "h": 46 + }, + "frame": { + "x": 86, + "y": 174, + "w": 43, + "h": 46 + } + }, + { + "filename": "0020.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 43, + "h": 46 + }, + "frame": { + "x": 86, + "y": 174, + "w": 43, + "h": 46 + } + }, + { + "filename": "0021.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 129, + "y": 174, + "w": 44, + "h": 46 + } + }, + { + "filename": "0022.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 129, + "y": 174, + "w": 44, + "h": 46 + } + }, + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 173, + "y": 175, + "w": 44, + "h": 46 + } + }, + { + "filename": "0034.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 173, + "y": 175, + "w": 44, + "h": 46 + } + }, + { + "filename": "0037.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 43, + "h": 46 + }, + "frame": { + "x": 217, + "y": 175, + "w": 43, + "h": 46 + } + }, + { + "filename": "0038.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 43, + "h": 46 + }, + "frame": { + "x": 217, + "y": 175, + "w": 43, + "h": 46 + } + }, + { + "filename": "0039.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 260, + "y": 179, + "w": 44, + "h": 46 + } + }, + { + "filename": "0040.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 260, + "y": 179, + "w": 44, + "h": 46 + } + }, + { + "filename": "0051.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 43, + "h": 46 + }, + "frame": { + "x": 0, + "y": 220, + "w": 43, + "h": 46 + } + }, + { + "filename": "0052.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 43, + "h": 46 + }, + "frame": { + "x": 0, + "y": 220, + "w": 43, + "h": 46 + } + }, + { + "filename": "0053.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 43, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0054.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 43, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0055.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0056.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0091.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0092.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0103.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0104.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0105.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0106.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0109.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0113.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0114.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0057.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 127, + "y": 220, + "w": 44, + "h": 46 + } + }, + { + "filename": "0058.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 127, + "y": 220, + "w": 44, + "h": 46 + } + }, + { + "filename": "0069.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 127, + "y": 220, + "w": 44, + "h": 46 + } + }, + { + "filename": "0070.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 127, + "y": 220, + "w": 44, + "h": 46 + } + }, + { + "filename": "0071.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 171, + "y": 221, + "w": 42, + "h": 46 + } + }, + { + "filename": "0072.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 171, + "y": 221, + "w": 42, + "h": 46 + } + }, + { + "filename": "0089.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 171, + "y": 221, + "w": 42, + "h": 46 + } + }, + { + "filename": "0090.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 171, + "y": 221, + "w": 42, + "h": 46 + } + }, + { + "filename": "0073.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 43, + "h": 46 + }, + "frame": { + "x": 213, + "y": 221, + "w": 43, + "h": 46 + } + }, + { + "filename": "0074.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 43, + "h": 46 + }, + "frame": { + "x": 213, + "y": 221, + "w": 43, + "h": 46 + } + }, + { + "filename": "0075.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 256, + "y": 225, + "w": 44, + "h": 46 + } + }, + { + "filename": "0076.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 256, + "y": 225, + "w": 44, + "h": 46 + } + }, + { + "filename": "0087.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 43, + "h": 46 + }, + "frame": { + "x": 0, + "y": 266, + "w": 43, + "h": 46 + } + }, + { + "filename": "0088.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 43, + "h": 46 + }, + "frame": { + "x": 0, + "y": 266, + "w": 43, + "h": 46 + } + }, + { + "filename": "0093.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 43, + "y": 266, + "w": 42, + "h": 46 + } + }, + { + "filename": "0094.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 43, + "y": 266, + "w": 42, + "h": 46 + } + }, + { + "filename": "0101.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 43, + "y": 266, + "w": 42, + "h": 46 + } + }, + { + "filename": "0102.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 43, + "y": 266, + "w": 42, + "h": 46 + } + }, + { + "filename": "0095.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 266, + "w": 42, + "h": 46 + } + }, + { + "filename": "0096.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 266, + "w": 42, + "h": 46 + } + }, + { + "filename": "0099.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 266, + "w": 42, + "h": 46 + } + }, + { + "filename": "0100.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 266, + "w": 42, + "h": 46 + } + }, + { + "filename": "0097.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 43, + "h": 46 + }, + "frame": { + "x": 127, + "y": 266, + "w": 43, + "h": 46 + } + }, + { + "filename": "0098.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 43, + "h": 46 + }, + "frame": { + "x": 127, + "y": 266, + "w": 43, + "h": 46 + } + }, + { + "filename": "0107.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 47, + "h": 46 + }, + "frame": { + "x": 170, + "y": 267, + "w": 47, + "h": 46 + } + }, + { + "filename": "0111.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 47, + "h": 46 + }, + "frame": { + "x": 170, + "y": 267, + "w": 47, + "h": 46 + } + }, + { + "filename": "0108.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 46, + "h": 46 + }, + "frame": { + "x": 217, + "y": 271, + "w": 46, + "h": 46 + } + }, + { + "filename": "0110.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 46, + "h": 46 + }, + "frame": { + "x": 217, + "y": 271, + "w": 46, + "h": 46 + } + }, + { + "filename": "0112.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 46, + "h": 46 + }, + "frame": { + "x": 217, + "y": 271, + "w": 46, + "h": 46 + } + }, + { + "filename": "0115.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 263, + "y": 271, + "w": 42, + "h": 46 + } + }, + { + "filename": "0116.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 263, + "y": 271, + "w": 42, + "h": 46 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:3c3f1d7c0f960de57a2195aba3e404a1:d500cdc0e036449780ce01accec9c0d5:d7d9e845c71962a58076b5efe962284e$" + } +} diff --git a/public/images/pokemon/female/25-partner.png b/public/images/pokemon/female/25-partner.png new file mode 100644 index 0000000000000000000000000000000000000000..e500db2a3bed79907db93400456cc8842f0b23c1 GIT binary patch literal 7413 zcmXY02Q*yo)5dDiYY0&zM2Q+D>Z%b$ufY;!C2EwlI;)FFBrG<%sJpBnh$Tp(i{5)( zeGx6XZ}a9c13;_U25$GP(%PA&wKBGzMfb8gN)2K~NK0o;YoR0n zoen4LxAbF@!WZ%W!Y6P;)FVDE?eDw!E;1hCo{ZC}U1)ACuV&SCaNXgnwX1~q&n1V~ zg?yB(YaVRmHWk8_fe&Aj^wjO1B?=g`d?``6m{3Uure{^uaDgRbJ~#UuVl$1D${DsshDef;Iu-b>Qp!*by$}u**z3hp(Iu z1h`bfQemzY15(5rZD5!bX11ON%&)3sL#ud|-u=2hY?Yd1LxXQ{9`fBkaDh1BIL79$ z%QA;1#T`rVVNhJ_ActJJV){VXiRsyzp66xfy$~sy!!<(Bb+wSwy>ucIO<3zKn;sZ~ zRbEVm30~4;adS@=-1176xBbUL?`Wc!IkM?9aGzLIGj9kIA(ne9C1FA(`vy|X`Q5eB z-Q;eF@OMn3HI z9zeZ1@0$q%Tx(%!c3Tu1x{u97N0G7H>3?HD{gM|@=d%lyp3x$-aXdOB)BEhS#QQ8g z*VHwo(G%da2}}=WrQ|kyK{R%by$TGDwBYFoax6pr?nUP|m-=m0KN544d_-8|Cn2{) zW$G_fG%ynpdiL$eLa^;?$f%dNA$xCNx7qyX+J^ke(^^#Kc^39TV)wcS5VVOL|0~#flum@{>XY+=`_<` zxll#xx-;WbLHjq|<0iJ-j8>SiGuZGR`l`w7bbCm!W{~yUEo^N zWnOBa;}?DP7;u*BR3neIsxkoOBR$|n_1kL~=6 zXxTCE$FD)kn6P*Ys{zgIY+it|LCiOt?adTN9^+7}2HEMs$g3uu{AS*gB4J38)>z?U zYbu_!wDY|yW@+!CDVxDhwwwcfM+(pRh>Q13{$dR(OXYO9doG4nuOcK%M+H*4 zN8kecYh$s5S9Bp~eGfhJ#-2o8tn%3=FxkdRDrMW;?{8f_$y=v6HI|0%V;}oHcfmCm zU2d35@g4F@iuJ0>91!cv(C~de#o9QhybzUgI&T#d#r*oq39)OU**H?7`xtjp&%JW!c zSP^{N=W9BF9jAjJ;3407`*g?7x3dM;syN{Of!z5Lyy${*AKP@jz@V%TZ?Dwn-ig_7 z541hN>QJ}T>2!o;&512Jg2?=01?nW1h};*X@KUGQi3rCPVHOUtFHxy}+~xPmxu9f0 z<)59}$zWkt_C0fSa*C2a$7a@Rao6 zu5>!DS2r3@a24ni%~LQuZX)5u%W&g5uZ3UJEK3UI&eA-f&P9U^Y5`HhMzWvl>yXlq ze|59xIo(vE2RgTxFaDeW$AY;-^2|~x5HDNSaS(CfsSbY#)j={x6MD>T8A1@)e|2=C zk(03BjpYvsGJ-EcTFsb$sHHr;+?mf+m{$hfr&l(88f`$DF@b$1w2#r^dVZ0w0L1Zd zi$7F%$k;zVe}4aG31AW8xGxWX6UebAi@7?9XocNS#@@=caiRI?LiBrEC;H5i>v(2gm4>7(=Kq_^G&V^ZPDxZ7+d1A`ZzE$ND)Lic zBG9+QvXTL|7G3P&F9evO;_G!af?lZdXeQO~S$bj1j30EJ6dFhEBZQ>WGLfs?A=|KO z;tX{m-@M-htfTin30W79*8@E+Xqc7wPgRvN6I*oh?}reOrxejefu5W`;MQ$0;3!j` zsP#+OHT{6pdB)COFm85sq+hS#b%R@m3zGXf&Ue!(M~teON+kuTp6*1*FX=-FKznqyjMz7_U(_c7rEB+@2D;%a`jC=+ z-$2r4IRW#=tNgmtx*{S_QKU^NM2tlJRL)-iHS%^HY)fQ)d1yLZ$embdqT!A#XsV$U zdo6%Cw|Ag#){_xCJJw@%6veipUY)Jm3z=G>o`2!`qVv64V;>v8DOcCi20Yh!;RNg#a@q=R)D0X9cALC;J#>9#MN&d%}=mSjMk;b=vj8Gdjj4x=3Ap6@f&aA z)r2;<&QF=;fLi3imD$h5N+IyQJdo^CudtEaEju$NX7XHxtvN=LGy9i9uitKoU&IM!3DTUvG z&xPmvQk@PjqihuM>zC8#&$+9g)dSihPm>_Ai@6E32r}|(AB4yJ!xN|($mVq4T2Dn@ z$C~#%>I1Bh($&|sEDmrjRLipCKR&leE~Z6HSjA;%+}^(3S780f z?cja;eGmels*JxV*_OFOTsI^t!-0+g4>sx`Kn?oh9?=U9=SFclESyaOS_Dp`6=UuL zLpaz|vBXPFCFl9Aw+m`PCk5(7oW^o6sdpIZS9&$Op0t=^<*s}sreES{$C>P*flt=A z0;)LPV>E-NL@RcH%gvPy3f9S)M3e~zzIoQ zP9Y=xmC9)*zn^LFS8_C&gLW>Gixem}d@q5erHT_=>fX8Hsk%m#$Fj-ZlNKBTtu+|< zPQ_f9yc6Iq*>o5MQn=!o9Qo_m3+vH`?E(M^#xOrT5ob&3!YrVI?rS^J%zpPBn)&1oc_hHx|Nf>KX zT>$fU`w(5Ojn-ye*nCVVWp6t=qVZQmSsSX1JdApg*aq6}1FBQo1iRo{r?f)-RqUaF zo0<7T2jtS*6;Crxzur6+zLQpWQh8YvQP@2WzOvi=4HDFm`Tbq^&by}YK;2;Thccl^ zPZIK)Sy)t_XHNO|+GL$!2J3ng=|2R_yMb*Lp005>y&>8%+acQTY34F@0=BSkMSKJ*hT=KL4R+eT8u_SDp z8~xU|R_izyPOtUOXBCS25;U~4?pXpbs0Vb#OkWDlpC3gmSniSB z5sDIDxE3LfwbG9|O*$4=cP4LmRxc8VeQHYv=?D5_Vx@RA%d6zps^6Si;+5j{eDfQ- zZ&0bJN{I(rX-hBopxhkxB5wfsXyB9>$f0zWxIeA?m-a63FYK-*QYu&cL+cuEFkVpedbYG!q zBlN?bm079ew#m&(w=>2Ocg=Kgbny+`nAC78Z)P&c!W=bjL&>HC&-LfEAG|*;4Aya% zCoU${IO*O6t`gGQ{K}o&T}sIjl$c6r&DSYW_<~O?yF}LW3v9gv99p@O$v};YZ!P+A zr&_@fwL|kLFvEJe&))X9`JIc72)jL(z5WPKN3sWe2cOC+J>^;wTk-|SWh0Yel0bFw zwV?Vv#{+!VXt>`GzKl*P+Lu^s6Ua7km?7|3$R3{Lc&Ye)^`B$+2765ICkO<7KS%Hd z`{JE0E+p|IHTXX)S@EJz#IxTvu%xY;@F$ckRysyu@Wny2!k{w2$93=0phh)=42C*F zbmWHuXUo=tI_ z2&rY3ko&2&aX5rQDXBt`hdqya4Y_OF8~(ryZf9+@MDvw~j}fI~q>OYO9>?em5GV^m zbuUy5Zn;M++ttRz#!KR{k=d9;uj)@dnrBig3x*HDLSn;vYB5&%#$z#G`dQrbB46WPrC&-0Kh{EJv&c z5G)36=t#m(0a>a7Uoj1nyK+>(%!{(-ERg@thcW^WJz;53@69;{+GY-9An(ud$tQkCBhQGdx0J`2$y6p z@MBTVq9vc@Oh>6MR&pOUhLs$Jc%#RPQLR90mNtbzx1H(@DoIj`)2roWlKs0A{gHJTjzZL>&wg|#un0On0Cy6XjpO}|UZgqb1w(JR0SvThoV?=Y9scFI$TON;X zv;jR^avv7>)2USW;JA$phxq#{Idw$DMDDk6G2R>P2R}oHjt_g6PP!T(^KZTaHDNFj z=1~$}%eR`B5xzUqE~Jj-$d@D1sb8DJiJ3K3?Thv=RNDX+AB&PyFWHV=kaMZGn1kb3 zdko>e9(7+OlFM!9JT9)x+d=C5EY&?hv-B#TeQ*^Nu?Z}HUf76Sk{2bx3XyL0S7CI) zR~>;F6|7yu=FIT=lO}i55RII(_Q4xr9h?M#?TeA1Hliw0$~hm|%WS+&BooU%g7 zyaSih+tIi;&nvq3>V@7ZY`-vWEuR|CL66TDjKr7=Tla+=CNh5!R=YZcrD;FX{AiWp z=B51hcd3c>@qk#GheZe(72jA->GIQyE6Yb?mf&a9+28y_SPgYc@ek)60YBEj32zbb zdfcz!9}D~PX0t=fwmE7mX8~Mg`-ek(F#mzPnIv0$6EmpkxvBt~gDbW?+w_wiGYOsl z4D*EHm@$&0A}YS3&g$@Mlg}v4xI9W5x5lF00Q^AX3m$vHO(0!Xhp%kXHBiEgFp#V2WgRNd~az{im6SkfT|vcw({=U8n#+?vaHbZ zjyf=Q#KZsXaM?+GkNTZXU^%H~U8)a`9v&z84rWRH#$6CQeNTwi`7WS}jREn;2Pg4A zB;yL?T_fnA-j6iTWKpN5sYT~ zpR*)8YB;-t#2mW!)KCi{YC6yM1oKA}>Vna7F7GZ+sVc4M%}x-N;wgPAjetnrPJ(u8 zKoyD;BO~x)P}Kuy1N)FOf7=&EF%7)&#+vPcb~{4!hklgwg}Os_MVDo2rcX%)`klWB}F}F zVq!c#GuSnrxPGM}OXI3a|Jk8jmmj14&m8ho-;(*WLsP!o5Vl>91qyI;HTuiJ@?tPi zmbjp|W0rdaw@W%s{06n`(9|n8q?9G`xCZy;TQIZRZEt$ZM}y8;8x9X%{g__w__SDH zBr!Xfrjbtl@3h2XBcp6uzlQrm_W>fipek@=Vuwbk@+|hY21^=%wD!yYD~7Qh z8tJ$<=th>s0txRZQCV(`6YkfX@VFj#Mri=u-mt1)ulUe?w}q{_^);WzPl4hI`w7K5 zkZY4h;vu+N^hR|p?JTf(lvA2JLnENQ*rmd#y*R!z-z zhvGsti-d*3qF8QYb@#!#+twGa|CR~9d~7WwU^^gC(1Q&@G{Z>XY}NRpe%nr6=?s|?#>U^$$CoP63(`3EKA&v>dE@q+@Dwx`rxI6iT3HW6R{B!A2_Cz zjh-Ntjn~@;dr&4dL~V7!a3z?tfUp~TreH)NGX6D%Ju*Ia(Zs^cA?VeITH3sme#I|M zBn3=&9nkoqJi8uNt7gV~PEJFMDJ3lrrRfdhsek}#RSoshYNia(b0?f5;FXvVQH1Bb zp^uPjenw5}(eRt7=|}%mLxu%l<7XK7P zZM>*CoAHXa%oMjP}$_P6c6Ou?N=~6YkV%AXtg%)Qn-)hw54!bSY ziWkX>b?r@gFk&<6baoNZbkZeW65WlGwlP$977C8gK9a4T_LK97O%OwKXo66U=eq8o z@d3vZ)GT7te~U$?8JoQ*y{h0(OSqbiKzDwi{F8q+wP4rk)&yS**K0*SwM(o`nq+xAi~r*;c)g>%Dq0TGA*n;`~Ic7R^FkWC3QlBeT$f&tNEV z*qipOv+7i(*3w$_&?9RT!|~mJ8{8MW%C&ddmn=bIR4goGk6GPtUXUneIGpIosS5Ed z-u62x@I&bYUoU#CRfT|y-H=XC#B?Uy3W|qwTDrC-YYx|*z18X*2Fb3Dy>UAD?=GLy@_n{Uc#mU=O2UsE)% zDRXQv*B|U{ldsq(1E)g2IocK}{R1OU)BXp>UieC>WTy!|h40k~+)09Q@) z8f}ZGzT?xxGQ%u?wbhoz?xJ3fPUiuMa0~mTM`>Tt+%q!UaJ|ogd9xL)cv~I~&;H zkzX^Y!Y7C6oLv@POv}H3Q{8qe`fO-+pIJHAi}8-G@;52DUs~o;DgEhf1ddLyC=Ib7{{dux Bt6~5E literal 0 HcmV?d00001 diff --git a/public/images/pokemon/icons/1/133-partner.png b/public/images/pokemon/icons/1/133-partner.png new file mode 100644 index 0000000000000000000000000000000000000000..b4022e26441bc7d027301de3ebafdf045786f58f GIT binary patch literal 394 zcmeAS@N?(olHy`uVBq!ia0vp^8bB<^!3-obnb*7rQjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`)dPG&Ton`)?rn`Kj&j=DV)g7?@&E7Ff`Wqn|Nnn(j+djOF8>IbYasEq!PB zK+OGhTT|`SY1Nu?$p_gyk18y`aC+A?3AyUYA{(aP7df}R^sd>7DLnor#_iF)!ctZ{ zYpU7K2ANKe{&L!%(|y+GrTtR5O1nDa775uO)HL%sFK_V4WeC~f{x c=-qGj9gp;%_ViCb5A+^`r>mdKI;Vst03$A^J^%m! literal 0 HcmV?d00001 diff --git a/public/images/pokemon/icons/1/133s-partner.png b/public/images/pokemon/icons/1/133s-partner.png new file mode 100644 index 0000000000000000000000000000000000000000..4cc0f1c73b438edf0c26b42a46fbd6ff8ae624db GIT binary patch literal 419 zcmeAS@N?(olHy`uVBq!ia0vp^8bB<^!3-obnb*7rQjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`%>sNvTon`)?%h6~pOvtCanMpx_Np7sn6_|F@So`I;33ST96QSXsUIf9h}c+@lP~n7_DB;M_!Hoz0UUu zlip1@wSM;bADVKhGf%Jmmm8%y|KbVf9l7$d)12NNe9*lZ=tl-mS3j3^P6Nn{1`)dPG&Ton`)f`Wp+T{S$qL~~BFlBSEK+1uX(yShFJLDy%Z?a ztiZ!^K~-YwgTLo*^Dby|IR5_Ki-sQxiyl?XtZ$d_d&mCp-NvkQ{v036_iqV*@Y&bk z(dpLM>EF2i95Os$nV!eRxSLh*d5^F6;ckv>p>6LNeGRFcyUW*xMePzWv(^mG&3t-u`DF>Go+%|Z-(EdW z(2@w|u-z|rd57VdsmDJ|DfxE0g!`Vs+hgC7YA>~~PMCDl(EmEmjcA>_c4vL+W@qja f+PtUb_(xWeIeN}aCensLA2N8l`njxgN@xNAv}mYM literal 0 HcmV?d00001 diff --git a/public/images/pokemon/icons/1/25s-partner.png b/public/images/pokemon/icons/1/25s-partner.png new file mode 100644 index 0000000000000000000000000000000000000000..58e2b3a35d1100ff5ee6c74929f3afca06d2a6ea GIT binary patch literal 397 zcmeAS@N?(olHy`uVBq!ia0vp^8bB<^!3-obnb*7rQjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`)dPG&Ton`)f`Wp+uQNQ_ra7m`>Hq)#-;;wJ9UZrZYc35j{8qi>|Iv3q zwakZezX2)Uk|4iepc*(}uzjJ_29)3|@Q5sCVBk9p!i>lBSEK+1uX(yShFJLDy%Z?a ztiZ!^K~-YwgTLo*^Dby|IR5_Ki-sQxiyl?XtZ$d_d&mCp-NvkQ{v036_iqV*@Y&bk z(dpLM>EF2i95Os$nV!eRxSLh*d5^F6;ckv>p>6LNeGRFcyUW*xMePzWv(^mG&3t-u`DF>Go+%|Z-(EdW z(2@w|u-z|rd57VdsmDJ|DfxE0g!`Vs+hgC7YA>~~PMCDl(EmEmjcA>_c4vL+W@qja f+PtUb_(xWeIeN}aCensLA2N8l`njxgN@xNAmcFP# literal 0 HcmV?d00001 diff --git a/public/images/pokemon/shiny/133-partner.json b/public/images/pokemon/shiny/133-partner.json new file mode 100644 index 00000000000..5d0e112e570 --- /dev/null +++ b/public/images/pokemon/shiny/133-partner.json @@ -0,0 +1,2834 @@ +{ + "textures": [ + { + "image": "133-partner.png", + "format": "RGBA8888", + "size": { + "w": 245, + "h": 245 + }, + "scale": 1, + "frames": [ + { + "filename": "0106.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 3, + "w": 35, + "h": 44 + }, + "frame": { + "x": 0, + "y": 0, + "w": 35, + "h": 44 + } + }, + { + "filename": "0107.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 3, + "w": 35, + "h": 44 + }, + "frame": { + "x": 0, + "y": 0, + "w": 35, + "h": 44 + } + }, + { + "filename": "0108.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 3, + "w": 35, + "h": 44 + }, + "frame": { + "x": 35, + "y": 0, + "w": 35, + "h": 44 + } + }, + { + "filename": "0109.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 3, + "w": 35, + "h": 44 + }, + "frame": { + "x": 35, + "y": 0, + "w": 35, + "h": 44 + } + }, + { + "filename": "0111.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 3, + "w": 35, + "h": 44 + }, + "frame": { + "x": 70, + "y": 0, + "w": 35, + "h": 44 + } + }, + { + "filename": "0112.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 2, + "y": 3, + "w": 35, + "h": 44 + }, + "frame": { + "x": 70, + "y": 0, + "w": 35, + "h": 44 + } + }, + { + "filename": "0113.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 3, + "w": 35, + "h": 44 + }, + "frame": { + "x": 105, + "y": 0, + "w": 35, + "h": 44 + } + }, + { + "filename": "0114.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 3, + "w": 35, + "h": 44 + }, + "frame": { + "x": 105, + "y": 0, + "w": 35, + "h": 44 + } + }, + { + "filename": "0008.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 2, + "w": 44, + "h": 45 + }, + "frame": { + "x": 140, + "y": 0, + "w": 44, + "h": 45 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 2, + "w": 44, + "h": 45 + }, + "frame": { + "x": 140, + "y": 0, + "w": 44, + "h": 45 + } + }, + { + "filename": "0041.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 2, + "w": 44, + "h": 45 + }, + "frame": { + "x": 140, + "y": 0, + "w": 44, + "h": 45 + } + }, + { + "filename": "0042.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 2, + "w": 44, + "h": 45 + }, + "frame": { + "x": 140, + "y": 0, + "w": 44, + "h": 45 + } + }, + { + "filename": "0075.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 2, + "w": 44, + "h": 45 + }, + "frame": { + "x": 140, + "y": 0, + "w": 44, + "h": 45 + } + }, + { + "filename": "0010.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 45, + "h": 45 + }, + "frame": { + "x": 184, + "y": 0, + "w": 45, + "h": 45 + } + }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 45, + "h": 45 + }, + "frame": { + "x": 184, + "y": 0, + "w": 45, + "h": 45 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 45, + "h": 45 + }, + "frame": { + "x": 184, + "y": 0, + "w": 45, + "h": 45 + } + }, + { + "filename": "0043.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 45, + "h": 45 + }, + "frame": { + "x": 184, + "y": 0, + "w": 45, + "h": 45 + } + }, + { + "filename": "0044.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 45, + "h": 45 + }, + "frame": { + "x": 184, + "y": 0, + "w": 45, + "h": 45 + } + }, + { + "filename": "0060.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 45, + "h": 45 + }, + "frame": { + "x": 184, + "y": 0, + "w": 45, + "h": 45 + } + }, + { + "filename": "0076.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 45, + "h": 45 + }, + "frame": { + "x": 184, + "y": 0, + "w": 45, + "h": 45 + } + }, + { + "filename": "0077.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 45, + "h": 45 + }, + "frame": { + "x": 184, + "y": 0, + "w": 45, + "h": 45 + } + }, + { + "filename": "0093.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 45, + "h": 45 + }, + "frame": { + "x": 184, + "y": 0, + "w": 45, + "h": 45 + } + }, + { + "filename": "0094.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 45, + "h": 45 + }, + "frame": { + "x": 184, + "y": 0, + "w": 45, + "h": 45 + } + }, + { + "filename": "0105.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 2, + "w": 36, + "h": 45 + }, + "frame": { + "x": 0, + "y": 44, + "w": 36, + "h": 45 + } + }, + { + "filename": "0110.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 34, + "h": 45 + }, + "frame": { + "x": 36, + "y": 44, + "w": 34, + "h": 45 + } + }, + { + "filename": "0126.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 2, + "w": 44, + "h": 45 + }, + "frame": { + "x": 70, + "y": 44, + "w": 44, + "h": 45 + } + }, + { + "filename": "0127.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 2, + "w": 44, + "h": 45 + }, + "frame": { + "x": 70, + "y": 44, + "w": 44, + "h": 45 + } + }, + { + "filename": "0005.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 42, + "h": 46 + }, + "frame": { + "x": 114, + "y": 45, + "w": 42, + "h": 46 + } + }, + { + "filename": "0038.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 42, + "h": 46 + }, + "frame": { + "x": 114, + "y": 45, + "w": 42, + "h": 46 + } + }, + { + "filename": "0039.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 42, + "h": 46 + }, + "frame": { + "x": 114, + "y": 45, + "w": 42, + "h": 46 + } + }, + { + "filename": "0071.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 42, + "h": 46 + }, + "frame": { + "x": 114, + "y": 45, + "w": 42, + "h": 46 + } + }, + { + "filename": "0072.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 42, + "h": 46 + }, + "frame": { + "x": 114, + "y": 45, + "w": 42, + "h": 46 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 43, + "h": 46 + }, + "frame": { + "x": 156, + "y": 45, + "w": 43, + "h": 46 + } + }, + { + "filename": "0012.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 43, + "h": 46 + }, + "frame": { + "x": 156, + "y": 45, + "w": 43, + "h": 46 + } + }, + { + "filename": "0028.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 43, + "h": 46 + }, + "frame": { + "x": 156, + "y": 45, + "w": 43, + "h": 46 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 43, + "h": 46 + }, + "frame": { + "x": 156, + "y": 45, + "w": 43, + "h": 46 + } + }, + { + "filename": "0045.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 43, + "h": 46 + }, + "frame": { + "x": 156, + "y": 45, + "w": 43, + "h": 46 + } + }, + { + "filename": "0061.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 43, + "h": 46 + }, + "frame": { + "x": 156, + "y": 45, + "w": 43, + "h": 46 + } + }, + { + "filename": "0062.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 43, + "h": 46 + }, + "frame": { + "x": 156, + "y": 45, + "w": 43, + "h": 46 + } + }, + { + "filename": "0078.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 43, + "h": 46 + }, + "frame": { + "x": 156, + "y": 45, + "w": 43, + "h": 46 + } + }, + { + "filename": "0079.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 43, + "h": 46 + }, + "frame": { + "x": 156, + "y": 45, + "w": 43, + "h": 46 + } + }, + { + "filename": "0095.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 43, + "h": 46 + }, + "frame": { + "x": 156, + "y": 45, + "w": 43, + "h": 46 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 44, + "h": 46 + }, + "frame": { + "x": 199, + "y": 45, + "w": 44, + "h": 46 + } + }, + { + "filename": "0058.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 44, + "h": 46 + }, + "frame": { + "x": 199, + "y": 45, + "w": 44, + "h": 46 + } + }, + { + "filename": "0059.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 44, + "h": 46 + }, + "frame": { + "x": 199, + "y": 45, + "w": 44, + "h": 46 + } + }, + { + "filename": "0091.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 44, + "h": 46 + }, + "frame": { + "x": 199, + "y": 45, + "w": 44, + "h": 46 + } + }, + { + "filename": "0092.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 44, + "h": 46 + }, + "frame": { + "x": 199, + "y": 45, + "w": 44, + "h": 46 + } + }, + { + "filename": "0115.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 36, + "h": 46 + }, + "frame": { + "x": 0, + "y": 89, + "w": 36, + "h": 46 + } + }, + { + "filename": "0125.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 44, + "h": 46 + }, + "frame": { + "x": 36, + "y": 89, + "w": 44, + "h": 46 + } + }, + { + "filename": "0128.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 44, + "h": 46 + }, + "frame": { + "x": 80, + "y": 91, + "w": 44, + "h": 46 + } + }, + { + "filename": "0129.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 44, + "h": 46 + }, + "frame": { + "x": 80, + "y": 91, + "w": 44, + "h": 46 + } + }, + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0002.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0016.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0018.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0034.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0050.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0051.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0052.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0066.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0067.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0068.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0069.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0083.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0084.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0085.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0100.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0101.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0102.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0118.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0119.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 39, + "h": 47 + }, + "frame": { + "x": 124, + "y": 91, + "w": 39, + "h": 47 + } + }, + { + "filename": "0003.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 163, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0004.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 163, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0020.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 163, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0036.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 163, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0037.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 163, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0053.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 163, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0054.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 163, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0070.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 163, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0086.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 163, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0087.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 163, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0015.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 204, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 204, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0032.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 204, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0048.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 204, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0049.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 204, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0065.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 204, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0081.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 204, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0082.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 204, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0098.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 204, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0099.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 204, + "y": 91, + "w": 41, + "h": 47 + } + }, + { + "filename": "0006.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 0, + "y": 135, + "w": 42, + "h": 47 + } + }, + { + "filename": "0007.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 0, + "y": 135, + "w": 42, + "h": 47 + } + }, + { + "filename": "0023.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 0, + "y": 135, + "w": 42, + "h": 47 + } + }, + { + "filename": "0024.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 0, + "y": 135, + "w": 42, + "h": 47 + } + }, + { + "filename": "0040.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 0, + "y": 135, + "w": 42, + "h": 47 + } + }, + { + "filename": "0056.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 0, + "y": 135, + "w": 42, + "h": 47 + } + }, + { + "filename": "0057.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 0, + "y": 135, + "w": 42, + "h": 47 + } + }, + { + "filename": "0073.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 0, + "y": 135, + "w": 42, + "h": 47 + } + }, + { + "filename": "0074.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 0, + "y": 135, + "w": 42, + "h": 47 + } + }, + { + "filename": "0090.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 0, + "y": 135, + "w": 42, + "h": 47 + } + }, + { + "filename": "0103.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 38, + "h": 47 + }, + "frame": { + "x": 42, + "y": 135, + "w": 38, + "h": 47 + } + }, + { + "filename": "0104.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 38, + "h": 47 + }, + "frame": { + "x": 42, + "y": 135, + "w": 38, + "h": 47 + } + }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 80, + "y": 137, + "w": 42, + "h": 47 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 80, + "y": 137, + "w": 42, + "h": 47 + } + }, + { + "filename": "0030.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 80, + "y": 137, + "w": 42, + "h": 47 + } + }, + { + "filename": "0046.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 80, + "y": 137, + "w": 42, + "h": 47 + } + }, + { + "filename": "0047.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 80, + "y": 137, + "w": 42, + "h": 47 + } + }, + { + "filename": "0063.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 80, + "y": 137, + "w": 42, + "h": 47 + } + }, + { + "filename": "0064.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 80, + "y": 137, + "w": 42, + "h": 47 + } + }, + { + "filename": "0080.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 80, + "y": 137, + "w": 42, + "h": 47 + } + }, + { + "filename": "0096.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 80, + "y": 137, + "w": 42, + "h": 47 + } + }, + { + "filename": "0097.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 80, + "y": 137, + "w": 42, + "h": 47 + } + }, + { + "filename": "0021.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 122, + "y": 138, + "w": 42, + "h": 47 + } + }, + { + "filename": "0022.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 122, + "y": 138, + "w": 42, + "h": 47 + } + }, + { + "filename": "0055.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 122, + "y": 138, + "w": 42, + "h": 47 + } + }, + { + "filename": "0088.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 122, + "y": 138, + "w": 42, + "h": 47 + } + }, + { + "filename": "0089.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 122, + "y": 138, + "w": 42, + "h": 47 + } + }, + { + "filename": "0116.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 38, + "h": 47 + }, + "frame": { + "x": 164, + "y": 138, + "w": 38, + "h": 47 + } + }, + { + "filename": "0117.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 38, + "h": 47 + }, + "frame": { + "x": 164, + "y": 138, + "w": 38, + "h": 47 + } + }, + { + "filename": "0120.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 202, + "y": 138, + "w": 41, + "h": 47 + } + }, + { + "filename": "0121.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 0, + "y": 182, + "w": 42, + "h": 47 + } + }, + { + "filename": "0122.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 0, + "y": 182, + "w": 42, + "h": 47 + } + }, + { + "filename": "0123.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 44, + "h": 47 + }, + "frame": { + "x": 42, + "y": 184, + "w": 44, + "h": 47 + } + }, + { + "filename": "0124.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 44, + "h": 47 + }, + "frame": { + "x": 42, + "y": 184, + "w": 44, + "h": 47 + } + }, + { + "filename": "0130.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 44, + "h": 47 + }, + "frame": { + "x": 86, + "y": 185, + "w": 44, + "h": 47 + } + }, + { + "filename": "0131.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 130, + "y": 185, + "w": 42, + "h": 47 + } + }, + { + "filename": "0132.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 42, + "h": 47 + }, + "frame": { + "x": 130, + "y": 185, + "w": 42, + "h": 47 + } + }, + { + "filename": "0133.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 172, + "y": 185, + "w": 41, + "h": 47 + } + }, + { + "filename": "0134.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 49, + "h": 47 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 41, + "h": 47 + }, + "frame": { + "x": 172, + "y": 185, + "w": 41, + "h": 47 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:39a6c90c2230b79cc97f35268535285c:583262468c88ec48aa6c02458d89d8c8:4e938f9dc8bce1cd7822677c800d242b$" + } +} diff --git a/public/images/pokemon/shiny/133-partner.png b/public/images/pokemon/shiny/133-partner.png new file mode 100644 index 0000000000000000000000000000000000000000..01352931fca228a7e8c507b0940aed75c814b0bb GIT binary patch literal 6532 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 WV=5QH!(aXTl~q&LR;pL9j{HB8&vS(U literal 0 HcmV?d00001 diff --git a/public/images/pokemon/shiny/female/25-partner.json b/public/images/pokemon/shiny/female/25-partner.json new file mode 100644 index 00000000000..b08b66a18b1 --- /dev/null +++ b/public/images/pokemon/shiny/female/25-partner.json @@ -0,0 +1,2456 @@ +{ + "textures": [ + { + "image": "25-partner.png", + "format": "RGBA8888", + "size": { + "w": 330, + "h": 330 + }, + "scale": 1, + "frames": [ + { + "filename": "0007.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 3, + "w": 50, + "h": 43 + }, + "frame": { + "x": 0, + "y": 0, + "w": 50, + "h": 43 + } + }, + { + "filename": "0008.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 3, + "w": 50, + "h": 43 + }, + "frame": { + "x": 0, + "y": 0, + "w": 50, + "h": 43 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 51, + "h": 43 + }, + "frame": { + "x": 50, + "y": 0, + "w": 51, + "h": 43 + } + }, + { + "filename": "0012.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 51, + "h": 43 + }, + "frame": { + "x": 50, + "y": 0, + "w": 51, + "h": 43 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 3, + "w": 49, + "h": 43 + }, + "frame": { + "x": 101, + "y": 0, + "w": 49, + "h": 43 + } + }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 3, + "w": 49, + "h": 43 + }, + "frame": { + "x": 101, + "y": 0, + "w": 49, + "h": 43 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 50, + "h": 43 + }, + "frame": { + "x": 150, + "y": 0, + "w": 50, + "h": 43 + } + }, + { + "filename": "0030.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 50, + "h": 43 + }, + "frame": { + "x": 150, + "y": 0, + "w": 50, + "h": 43 + } + }, + { + "filename": "0043.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 49, + "h": 43 + }, + "frame": { + "x": 200, + "y": 0, + "w": 49, + "h": 43 + } + }, + { + "filename": "0044.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 49, + "h": 43 + }, + "frame": { + "x": 200, + "y": 0, + "w": 49, + "h": 43 + } + }, + { + "filename": "0047.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 49, + "h": 43 + }, + "frame": { + "x": 249, + "y": 0, + "w": 49, + "h": 43 + } + }, + { + "filename": "0048.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 49, + "h": 43 + }, + "frame": { + "x": 249, + "y": 0, + "w": 49, + "h": 43 + } + }, + { + "filename": "0061.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 3, + "w": 50, + "h": 43 + }, + "frame": { + "x": 0, + "y": 43, + "w": 50, + "h": 43 + } + }, + { + "filename": "0062.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 3, + "w": 50, + "h": 43 + }, + "frame": { + "x": 0, + "y": 43, + "w": 50, + "h": 43 + } + }, + { + "filename": "0065.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 51, + "h": 43 + }, + "frame": { + "x": 50, + "y": 43, + "w": 51, + "h": 43 + } + }, + { + "filename": "0066.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 51, + "h": 43 + }, + "frame": { + "x": 50, + "y": 43, + "w": 51, + "h": 43 + } + }, + { + "filename": "0079.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 3, + "w": 48, + "h": 43 + }, + "frame": { + "x": 101, + "y": 43, + "w": 48, + "h": 43 + } + }, + { + "filename": "0080.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 3, + "w": 48, + "h": 43 + }, + "frame": { + "x": 101, + "y": 43, + "w": 48, + "h": 43 + } + }, + { + "filename": "0083.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 49, + "h": 43 + }, + "frame": { + "x": 149, + "y": 43, + "w": 49, + "h": 43 + } + }, + { + "filename": "0084.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 49, + "h": 43 + }, + "frame": { + "x": 149, + "y": 43, + "w": 49, + "h": 43 + } + }, + { + "filename": "0005.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 48, + "h": 44 + }, + "frame": { + "x": 198, + "y": 43, + "w": 48, + "h": 44 + } + }, + { + "filename": "0006.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 48, + "h": 44 + }, + "frame": { + "x": 198, + "y": 43, + "w": 48, + "h": 44 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 50, + "h": 44 + }, + "frame": { + "x": 246, + "y": 43, + "w": 50, + "h": 44 + } + }, + { + "filename": "0010.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 50, + "h": 44 + }, + "frame": { + "x": 246, + "y": 43, + "w": 50, + "h": 44 + } + }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 48, + "h": 44 + }, + "frame": { + "x": 0, + "y": 86, + "w": 48, + "h": 44 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 48, + "h": 44 + }, + "frame": { + "x": 0, + "y": 86, + "w": 48, + "h": 44 + } + }, + { + "filename": "0023.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 47, + "h": 44 + }, + "frame": { + "x": 48, + "y": 86, + "w": 47, + "h": 44 + } + }, + { + "filename": "0024.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 47, + "h": 44 + }, + "frame": { + "x": 48, + "y": 86, + "w": 47, + "h": 44 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 50, + "h": 44 + }, + "frame": { + "x": 95, + "y": 86, + "w": 50, + "h": 44 + } + }, + { + "filename": "0028.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 50, + "h": 44 + }, + "frame": { + "x": 95, + "y": 86, + "w": 50, + "h": 44 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 47, + "h": 44 + }, + "frame": { + "x": 145, + "y": 86, + "w": 47, + "h": 44 + } + }, + { + "filename": "0032.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 47, + "h": 44 + }, + "frame": { + "x": 145, + "y": 86, + "w": 47, + "h": 44 + } + }, + { + "filename": "0041.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 46, + "h": 44 + }, + "frame": { + "x": 192, + "y": 87, + "w": 46, + "h": 44 + } + }, + { + "filename": "0042.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 46, + "h": 44 + }, + "frame": { + "x": 192, + "y": 87, + "w": 46, + "h": 44 + } + }, + { + "filename": "0045.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 50, + "h": 44 + }, + "frame": { + "x": 238, + "y": 87, + "w": 50, + "h": 44 + } + }, + { + "filename": "0046.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 50, + "h": 44 + }, + "frame": { + "x": 238, + "y": 87, + "w": 50, + "h": 44 + } + }, + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 288, + "y": 87, + "w": 42, + "h": 46 + } + }, + { + "filename": "0002.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 288, + "y": 87, + "w": 42, + "h": 46 + } + }, + { + "filename": "0049.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 46, + "h": 44 + }, + "frame": { + "x": 0, + "y": 130, + "w": 46, + "h": 44 + } + }, + { + "filename": "0050.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 46, + "h": 44 + }, + "frame": { + "x": 0, + "y": 130, + "w": 46, + "h": 44 + } + }, + { + "filename": "0059.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 48, + "h": 44 + }, + "frame": { + "x": 46, + "y": 130, + "w": 48, + "h": 44 + } + }, + { + "filename": "0060.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 48, + "h": 44 + }, + "frame": { + "x": 46, + "y": 130, + "w": 48, + "h": 44 + } + }, + { + "filename": "0067.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 48, + "h": 44 + }, + "frame": { + "x": 46, + "y": 130, + "w": 48, + "h": 44 + } + }, + { + "filename": "0068.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 48, + "h": 44 + }, + "frame": { + "x": 46, + "y": 130, + "w": 48, + "h": 44 + } + }, + { + "filename": "0063.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 50, + "h": 44 + }, + "frame": { + "x": 94, + "y": 130, + "w": 50, + "h": 44 + } + }, + { + "filename": "0064.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 50, + "h": 44 + }, + "frame": { + "x": 94, + "y": 130, + "w": 50, + "h": 44 + } + }, + { + "filename": "0077.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 47, + "h": 44 + }, + "frame": { + "x": 144, + "y": 130, + "w": 47, + "h": 44 + } + }, + { + "filename": "0078.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 47, + "h": 44 + }, + "frame": { + "x": 144, + "y": 130, + "w": 47, + "h": 44 + } + }, + { + "filename": "0081.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 49, + "h": 44 + }, + "frame": { + "x": 191, + "y": 131, + "w": 49, + "h": 44 + } + }, + { + "filename": "0082.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 49, + "h": 44 + }, + "frame": { + "x": 191, + "y": 131, + "w": 49, + "h": 44 + } + }, + { + "filename": "0085.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 46, + "h": 44 + }, + "frame": { + "x": 240, + "y": 131, + "w": 46, + "h": 44 + } + }, + { + "filename": "0086.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 46, + "h": 44 + }, + "frame": { + "x": 240, + "y": 131, + "w": 46, + "h": 44 + } + }, + { + "filename": "0003.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 286, + "y": 133, + "w": 44, + "h": 46 + } + }, + { + "filename": "0004.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 286, + "y": 133, + "w": 44, + "h": 46 + } + }, + { + "filename": "0015.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 44, + "h": 46 + } + }, + { + "filename": "0016.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 44, + "h": 46 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 44, + "y": 174, + "w": 42, + "h": 46 + } + }, + { + "filename": "0018.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 44, + "y": 174, + "w": 42, + "h": 46 + } + }, + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 44, + "y": 174, + "w": 42, + "h": 46 + } + }, + { + "filename": "0036.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 44, + "y": 174, + "w": 42, + "h": 46 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 43, + "h": 46 + }, + "frame": { + "x": 86, + "y": 174, + "w": 43, + "h": 46 + } + }, + { + "filename": "0020.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 43, + "h": 46 + }, + "frame": { + "x": 86, + "y": 174, + "w": 43, + "h": 46 + } + }, + { + "filename": "0021.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 129, + "y": 174, + "w": 44, + "h": 46 + } + }, + { + "filename": "0022.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 129, + "y": 174, + "w": 44, + "h": 46 + } + }, + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 173, + "y": 175, + "w": 44, + "h": 46 + } + }, + { + "filename": "0034.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 173, + "y": 175, + "w": 44, + "h": 46 + } + }, + { + "filename": "0037.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 43, + "h": 46 + }, + "frame": { + "x": 217, + "y": 175, + "w": 43, + "h": 46 + } + }, + { + "filename": "0038.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 43, + "h": 46 + }, + "frame": { + "x": 217, + "y": 175, + "w": 43, + "h": 46 + } + }, + { + "filename": "0039.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 260, + "y": 179, + "w": 44, + "h": 46 + } + }, + { + "filename": "0040.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 260, + "y": 179, + "w": 44, + "h": 46 + } + }, + { + "filename": "0051.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 43, + "h": 46 + }, + "frame": { + "x": 0, + "y": 220, + "w": 43, + "h": 46 + } + }, + { + "filename": "0052.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 43, + "h": 46 + }, + "frame": { + "x": 0, + "y": 220, + "w": 43, + "h": 46 + } + }, + { + "filename": "0053.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 43, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0054.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 43, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0055.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0056.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0091.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0092.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0103.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0104.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0105.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0106.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0109.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0113.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0114.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 220, + "w": 42, + "h": 46 + } + }, + { + "filename": "0057.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 127, + "y": 220, + "w": 44, + "h": 46 + } + }, + { + "filename": "0058.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 127, + "y": 220, + "w": 44, + "h": 46 + } + }, + { + "filename": "0069.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 127, + "y": 220, + "w": 44, + "h": 46 + } + }, + { + "filename": "0070.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 127, + "y": 220, + "w": 44, + "h": 46 + } + }, + { + "filename": "0071.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 171, + "y": 221, + "w": 42, + "h": 46 + } + }, + { + "filename": "0072.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 171, + "y": 221, + "w": 42, + "h": 46 + } + }, + { + "filename": "0089.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 171, + "y": 221, + "w": 42, + "h": 46 + } + }, + { + "filename": "0090.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 171, + "y": 221, + "w": 42, + "h": 46 + } + }, + { + "filename": "0073.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 43, + "h": 46 + }, + "frame": { + "x": 213, + "y": 221, + "w": 43, + "h": 46 + } + }, + { + "filename": "0074.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 43, + "h": 46 + }, + "frame": { + "x": 213, + "y": 221, + "w": 43, + "h": 46 + } + }, + { + "filename": "0075.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 256, + "y": 225, + "w": 44, + "h": 46 + } + }, + { + "filename": "0076.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 44, + "h": 46 + }, + "frame": { + "x": 256, + "y": 225, + "w": 44, + "h": 46 + } + }, + { + "filename": "0087.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 43, + "h": 46 + }, + "frame": { + "x": 0, + "y": 266, + "w": 43, + "h": 46 + } + }, + { + "filename": "0088.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 43, + "h": 46 + }, + "frame": { + "x": 0, + "y": 266, + "w": 43, + "h": 46 + } + }, + { + "filename": "0093.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 43, + "y": 266, + "w": 42, + "h": 46 + } + }, + { + "filename": "0094.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 43, + "y": 266, + "w": 42, + "h": 46 + } + }, + { + "filename": "0101.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 43, + "y": 266, + "w": 42, + "h": 46 + } + }, + { + "filename": "0102.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 43, + "y": 266, + "w": 42, + "h": 46 + } + }, + { + "filename": "0095.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 266, + "w": 42, + "h": 46 + } + }, + { + "filename": "0096.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 266, + "w": 42, + "h": 46 + } + }, + { + "filename": "0099.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 266, + "w": 42, + "h": 46 + } + }, + { + "filename": "0100.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 85, + "y": 266, + "w": 42, + "h": 46 + } + }, + { + "filename": "0097.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 43, + "h": 46 + }, + "frame": { + "x": 127, + "y": 266, + "w": 43, + "h": 46 + } + }, + { + "filename": "0098.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 43, + "h": 46 + }, + "frame": { + "x": 127, + "y": 266, + "w": 43, + "h": 46 + } + }, + { + "filename": "0107.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 47, + "h": 46 + }, + "frame": { + "x": 170, + "y": 267, + "w": 47, + "h": 46 + } + }, + { + "filename": "0111.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 47, + "h": 46 + }, + "frame": { + "x": 170, + "y": 267, + "w": 47, + "h": 46 + } + }, + { + "filename": "0108.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 46, + "h": 46 + }, + "frame": { + "x": 217, + "y": 271, + "w": 46, + "h": 46 + } + }, + { + "filename": "0110.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 46, + "h": 46 + }, + "frame": { + "x": 217, + "y": 271, + "w": 46, + "h": 46 + } + }, + { + "filename": "0112.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 46, + "h": 46 + }, + "frame": { + "x": 217, + "y": 271, + "w": 46, + "h": 46 + } + }, + { + "filename": "0115.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 263, + "y": 271, + "w": 42, + "h": 46 + } + }, + { + "filename": "0116.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 51, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 42, + "h": 46 + }, + "frame": { + "x": 263, + "y": 271, + "w": 42, + "h": 46 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:a8eb580b04fb8bb2d490c8d497e097a7:a3da2c0b291ef91b4a6f8e0d1a1dd17f:d7d9e845c71962a58076b5efe962284e$" + } +} diff --git a/public/images/pokemon/shiny/female/25-partner.png b/public/images/pokemon/shiny/female/25-partner.png new file mode 100644 index 0000000000000000000000000000000000000000..030091ce605eece8f2dcd297926abc5b0d6f5135 GIT binary patch literal 7414 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

v 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 literal 72497 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�kp@F3(xOy};;n!h{|{9F+-3 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 DuPVEN diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index ae9e7fef547..4f6098734ec 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -917,7 +917,7 @@ export function initSpecies() { new PokemonSpecies(Species.ARBOK, 1, false, false, false, "Cobra Pokémon", Type.POISON, null, 3.5, 65, Abilities.INTIMIDATE, Abilities.SHED_SKIN, Abilities.UNNERVE, 448, 60, 95, 69, 65, 79, 80, 90, 70, 157, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.PIKACHU, 1, false, false, false, "Mouse Pokémon", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 320, 35, 55, 40, 50, 50, 90, 190, 50, 112, GrowthRate.MEDIUM_FAST, 50, true, true, new PokemonForm("Normal", "", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 320, 35, 55, 40, 50, 50, 90, 190, 50, 112, true, null, true), - new PokemonForm("Partner", "partner", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, "", true), + new PokemonForm("Partner", "partner", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), new PokemonForm("Cosplay", "cosplay", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom new PokemonForm("Cool Cosplay", "cool-cosplay", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom new PokemonForm("Beauty Cosplay", "beauty-cosplay", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom @@ -1066,7 +1066,7 @@ export function initSpecies() { new PokemonSpecies(Species.DITTO, 1, false, false, false, "Transform Pokémon", Type.NORMAL, null, 0.3, 4, Abilities.LIMBER, Abilities.NONE, Abilities.IMPOSTER, 288, 48, 48, 48, 48, 48, 48, 35, 50, 101, GrowthRate.MEDIUM_FAST, null, false), new PokemonSpecies(Species.EEVEE, 1, false, false, false, "Evolution Pokémon", Type.NORMAL, null, 0.3, 6.5, Abilities.RUN_AWAY, Abilities.ADAPTABILITY, Abilities.ANTICIPATION, 325, 55, 55, 50, 45, 65, 55, 45, 50, 65, GrowthRate.MEDIUM_FAST, 87.5, false, true, new PokemonForm("Normal", "", Type.NORMAL, null, 0.3, 6.5, Abilities.RUN_AWAY, Abilities.ADAPTABILITY, Abilities.ANTICIPATION, 325, 55, 55, 50, 45, 65, 55, 45, 50, 65, false, null, true), - new PokemonForm("Partner", "partner", Type.NORMAL, null, 0.3, 6.5, Abilities.RUN_AWAY, Abilities.ADAPTABILITY, Abilities.ANTICIPATION, 435, 65, 75, 70, 65, 85, 75, 45, 50, 65, false, "", true), + new PokemonForm("Partner", "partner", Type.NORMAL, null, 0.3, 6.5, Abilities.RUN_AWAY, Abilities.ADAPTABILITY, Abilities.ANTICIPATION, 435, 65, 75, 70, 65, 85, 75, 45, 50, 65, false, null, true), new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.NORMAL, null, 18, 6.5, Abilities.RUN_AWAY, Abilities.ADAPTABILITY, Abilities.ANTICIPATION, 425, 70, 75, 80, 60, 95, 45, 45, 50, 65), ), new PokemonSpecies(Species.VAPOREON, 1, false, false, false, "Bubble Jet Pokémon", Type.WATER, null, 1, 29, Abilities.WATER_ABSORB, Abilities.NONE, Abilities.HYDRATION, 525, 130, 65, 60, 110, 95, 65, 45, 50, 184, GrowthRate.MEDIUM_FAST, 87.5, false), From d592187f2ce514c45e0417c703254a555c5a3fec Mon Sep 17 00:00:00 2001 From: Lee ByungHoon Date: Sat, 8 Jun 2024 04:08:34 +0900 Subject: [PATCH 116/129] [Localization] Add localization in party-ui-handler (#1712) * [Localization] Add localization in party-ui-handler * [Localization] Add "Release", "Apply", "Teach" localization, changed translation of Korean * [Localization] Translated party-ui-handler's localization to Deutsch --- src/locales/de/config.ts | 4 +++- src/locales/de/party-ui-handler.ts | 10 ++++++++++ src/locales/en/config.ts | 4 +++- src/locales/en/party-ui-handler.ts | 10 ++++++++++ src/locales/es/config.ts | 4 +++- src/locales/es/party-ui-handler.ts | 10 ++++++++++ src/locales/fr/config.ts | 4 +++- src/locales/fr/party-ui-handler.ts | 10 ++++++++++ src/locales/it/config.ts | 4 +++- src/locales/it/party-ui-handler.ts | 10 ++++++++++ src/locales/ko/config.ts | 4 +++- src/locales/ko/party-ui-handler.ts | 10 ++++++++++ src/locales/pt_BR/config.ts | 4 +++- src/locales/pt_BR/party-ui-handler.ts | 10 ++++++++++ src/locales/zh_CN/config.ts | 4 +++- src/locales/zh_CN/party-ui-handler.ts | 10 ++++++++++ src/locales/zh_TW/config.ts | 4 +++- src/locales/zh_TW/party-ui-handler.ts | 10 ++++++++++ src/plugins/i18n.ts | 1 + src/ui/party-ui-handler.ts | 9 ++++++++- 20 files changed, 126 insertions(+), 10 deletions(-) create mode 100644 src/locales/de/party-ui-handler.ts create mode 100644 src/locales/en/party-ui-handler.ts create mode 100644 src/locales/es/party-ui-handler.ts create mode 100644 src/locales/fr/party-ui-handler.ts create mode 100644 src/locales/it/party-ui-handler.ts create mode 100644 src/locales/ko/party-ui-handler.ts create mode 100644 src/locales/pt_BR/party-ui-handler.ts create mode 100644 src/locales/zh_CN/party-ui-handler.ts create mode 100644 src/locales/zh_TW/party-ui-handler.ts diff --git a/src/locales/de/config.ts b/src/locales/de/config.ts index e243aac135d..afee437a652 100644 --- a/src/locales/de/config.ts +++ b/src/locales/de/config.ts @@ -35,6 +35,7 @@ import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; import { weather } from "./weather"; +import { partyUiHandler } from "./party-ui-handler"; export const deConfig = { ability: ability, @@ -73,5 +74,6 @@ export const deConfig = { trainerNames: trainerNames, tutorial: tutorial, voucher: voucher, - weather: weather + weather: weather, + partyUiHandler: partyUiHandler }; diff --git a/src/locales/de/party-ui-handler.ts b/src/locales/de/party-ui-handler.ts new file mode 100644 index 00000000000..103c6837889 --- /dev/null +++ b/src/locales/de/party-ui-handler.ts @@ -0,0 +1,10 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const partyUiHandler: SimpleTranslationEntries = { + "SEND_OUT": "Einwechseln", + "SUMMARY": "Bericht", + "CANCEL": "Abbrechen", + "RELEASE": "Freilassen", + "APPLY": "Anwenden", + "TEACH": "Erlernen" +} as const; diff --git a/src/locales/en/config.ts b/src/locales/en/config.ts index 0891a6a4c10..383b52d4c19 100644 --- a/src/locales/en/config.ts +++ b/src/locales/en/config.ts @@ -35,6 +35,7 @@ import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; import { weather } from "./weather"; +import { partyUiHandler } from "./party-ui-handler"; export const enConfig = { ability: ability, @@ -73,5 +74,6 @@ export const enConfig = { trainerNames: trainerNames, tutorial: tutorial, voucher: voucher, - weather: weather + weather: weather, + partyUiHandler: partyUiHandler }; diff --git a/src/locales/en/party-ui-handler.ts b/src/locales/en/party-ui-handler.ts new file mode 100644 index 00000000000..9d3c7baa9ae --- /dev/null +++ b/src/locales/en/party-ui-handler.ts @@ -0,0 +1,10 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const partyUiHandler: SimpleTranslationEntries = { + "SEND_OUT": "Send Out", + "SUMMARY": "Summary", + "CANCEL": "Cancel", + "RELEASE": "Release", + "APPLY": "Apply", + "TEACH": "Teach" +} as const; diff --git a/src/locales/es/config.ts b/src/locales/es/config.ts index 7e1935a23a0..f6d1ac0f1c1 100644 --- a/src/locales/es/config.ts +++ b/src/locales/es/config.ts @@ -35,6 +35,7 @@ import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; import { weather } from "./weather"; +import { partyUiHandler } from "./party-ui-handler"; export const esConfig = { ability: ability, @@ -73,5 +74,6 @@ export const esConfig = { trainerNames: trainerNames, tutorial: tutorial, voucher: voucher, - weather: weather + weather: weather, + partyUiHandler: partyUiHandler }; diff --git a/src/locales/es/party-ui-handler.ts b/src/locales/es/party-ui-handler.ts new file mode 100644 index 00000000000..9d3c7baa9ae --- /dev/null +++ b/src/locales/es/party-ui-handler.ts @@ -0,0 +1,10 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const partyUiHandler: SimpleTranslationEntries = { + "SEND_OUT": "Send Out", + "SUMMARY": "Summary", + "CANCEL": "Cancel", + "RELEASE": "Release", + "APPLY": "Apply", + "TEACH": "Teach" +} as const; diff --git a/src/locales/fr/config.ts b/src/locales/fr/config.ts index ee9e9a1e2ac..d523d35bb87 100644 --- a/src/locales/fr/config.ts +++ b/src/locales/fr/config.ts @@ -35,6 +35,7 @@ import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; import { weather } from "./weather"; +import { partyUiHandler } from "./party-ui-handler"; export const frConfig = { ability: ability, @@ -73,5 +74,6 @@ export const frConfig = { trainerNames: trainerNames, tutorial: tutorial, voucher: voucher, - weather: weather + weather: weather, + partyUiHandler: partyUiHandler }; diff --git a/src/locales/fr/party-ui-handler.ts b/src/locales/fr/party-ui-handler.ts new file mode 100644 index 00000000000..9d3c7baa9ae --- /dev/null +++ b/src/locales/fr/party-ui-handler.ts @@ -0,0 +1,10 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const partyUiHandler: SimpleTranslationEntries = { + "SEND_OUT": "Send Out", + "SUMMARY": "Summary", + "CANCEL": "Cancel", + "RELEASE": "Release", + "APPLY": "Apply", + "TEACH": "Teach" +} as const; diff --git a/src/locales/it/config.ts b/src/locales/it/config.ts index 8549afb12be..3f53c8fca01 100644 --- a/src/locales/it/config.ts +++ b/src/locales/it/config.ts @@ -35,6 +35,7 @@ import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; import { weather } from "./weather"; +import { partyUiHandler } from "./party-ui-handler"; export const itConfig = { ability: ability, @@ -73,5 +74,6 @@ export const itConfig = { trainerNames: trainerNames, tutorial: tutorial, voucher: voucher, - weather: weather + weather: weather, + partyUiHandler: partyUiHandler }; diff --git a/src/locales/it/party-ui-handler.ts b/src/locales/it/party-ui-handler.ts new file mode 100644 index 00000000000..9d3c7baa9ae --- /dev/null +++ b/src/locales/it/party-ui-handler.ts @@ -0,0 +1,10 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const partyUiHandler: SimpleTranslationEntries = { + "SEND_OUT": "Send Out", + "SUMMARY": "Summary", + "CANCEL": "Cancel", + "RELEASE": "Release", + "APPLY": "Apply", + "TEACH": "Teach" +} as const; diff --git a/src/locales/ko/config.ts b/src/locales/ko/config.ts index ad0035ede0f..936154153be 100644 --- a/src/locales/ko/config.ts +++ b/src/locales/ko/config.ts @@ -35,6 +35,7 @@ import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; import { weather } from "./weather"; +import { partyUiHandler } from "./party-ui-handler"; export const koConfig = { ability: ability, @@ -73,5 +74,6 @@ export const koConfig = { trainerNames: trainerNames, tutorial: tutorial, voucher: voucher, - weather: weather + weather: weather, + partyUiHandler: partyUiHandler }; diff --git a/src/locales/ko/party-ui-handler.ts b/src/locales/ko/party-ui-handler.ts new file mode 100644 index 00000000000..ce731e4915f --- /dev/null +++ b/src/locales/ko/party-ui-handler.ts @@ -0,0 +1,10 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const partyUiHandler: SimpleTranslationEntries = { + "SEND_OUT": "교체한다", + "SUMMARY": "능력치를 본다", + "CANCEL": "그만둔다", + "RELEASE": "놓아준다", + "APPLY": "사용한다", + "TEACH": "가르친다" +} as const; diff --git a/src/locales/pt_BR/config.ts b/src/locales/pt_BR/config.ts index 1aadb5b20b1..3baca7df382 100644 --- a/src/locales/pt_BR/config.ts +++ b/src/locales/pt_BR/config.ts @@ -35,6 +35,7 @@ import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; import { weather } from "./weather"; +import { partyUiHandler } from "./party-ui-handler"; export const ptBrConfig = { ability: ability, @@ -73,5 +74,6 @@ export const ptBrConfig = { trainerNames: trainerNames, tutorial: tutorial, voucher: voucher, - weather: weather + weather: weather, + partyUiHandler: partyUiHandler }; diff --git a/src/locales/pt_BR/party-ui-handler.ts b/src/locales/pt_BR/party-ui-handler.ts new file mode 100644 index 00000000000..9d3c7baa9ae --- /dev/null +++ b/src/locales/pt_BR/party-ui-handler.ts @@ -0,0 +1,10 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const partyUiHandler: SimpleTranslationEntries = { + "SEND_OUT": "Send Out", + "SUMMARY": "Summary", + "CANCEL": "Cancel", + "RELEASE": "Release", + "APPLY": "Apply", + "TEACH": "Teach" +} as const; diff --git a/src/locales/zh_CN/config.ts b/src/locales/zh_CN/config.ts index 7a8ab5595f7..0560b829dea 100644 --- a/src/locales/zh_CN/config.ts +++ b/src/locales/zh_CN/config.ts @@ -35,6 +35,7 @@ import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; import { weather } from "./weather"; +import { partyUiHandler } from "./party-ui-handler"; export const zhCnConfig = { ability: ability, @@ -73,5 +74,6 @@ export const zhCnConfig = { trainerNames: trainerNames, tutorial: tutorial, voucher: voucher, - weather: weather + weather: weather, + partyUiHandler: partyUiHandler }; diff --git a/src/locales/zh_CN/party-ui-handler.ts b/src/locales/zh_CN/party-ui-handler.ts new file mode 100644 index 00000000000..9d3c7baa9ae --- /dev/null +++ b/src/locales/zh_CN/party-ui-handler.ts @@ -0,0 +1,10 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const partyUiHandler: SimpleTranslationEntries = { + "SEND_OUT": "Send Out", + "SUMMARY": "Summary", + "CANCEL": "Cancel", + "RELEASE": "Release", + "APPLY": "Apply", + "TEACH": "Teach" +} as const; diff --git a/src/locales/zh_TW/config.ts b/src/locales/zh_TW/config.ts index 1b942e0234f..c7045da3d97 100644 --- a/src/locales/zh_TW/config.ts +++ b/src/locales/zh_TW/config.ts @@ -35,6 +35,7 @@ import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; import { weather } from "./weather"; +import { partyUiHandler } from "./party-ui-handler"; export const zhTwConfig = { ability: ability, @@ -73,5 +74,6 @@ export const zhTwConfig = { trainerNames: trainerNames, tutorial: tutorial, voucher: voucher, - weather: weather + weather: weather, + partyUiHandler: partyUiHandler }; diff --git a/src/locales/zh_TW/party-ui-handler.ts b/src/locales/zh_TW/party-ui-handler.ts new file mode 100644 index 00000000000..9d3c7baa9ae --- /dev/null +++ b/src/locales/zh_TW/party-ui-handler.ts @@ -0,0 +1,10 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const partyUiHandler: SimpleTranslationEntries = { + "SEND_OUT": "Send Out", + "SUMMARY": "Summary", + "CANCEL": "Cancel", + "RELEASE": "Release", + "APPLY": "Apply", + "TEACH": "Teach" +} as const; diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts index 7d485d3f046..5356c94fe7f 100644 --- a/src/plugins/i18n.ts +++ b/src/plugins/i18n.ts @@ -233,6 +233,7 @@ declare module "i18next" { PGFbattleSpecDialogue: SimpleTranslationEntries; PGFmiscDialogue: SimpleTranslationEntries; PGFdoubleBattleDialogue: DialogueTranslationEntries; + partyUiHandler: SimpleTranslationEntries; }; } } diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index c51fea747a9..425a9dd788d 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -18,6 +18,7 @@ import { SpeciesFormChangeItemTrigger } from "../data/pokemon-forms"; import { getVariantTint } from "#app/data/variant"; import {Button} from "../enums/buttons"; import MoveInfoOverlay from "./move-info-overlay"; +import i18next from "i18next"; const defaultMessage = "Choose a Pokémon."; @@ -131,6 +132,8 @@ export default class PartyUiHandler extends MessageUiHandler { public static NoEffectMessage = "It won't have any effect."; + private localizedOptions = [PartyOption.SEND_OUT, PartyOption.SUMMARY, PartyOption.CANCEL, PartyOption.APPLY, PartyOption.RELEASE, PartyOption.TEACH]; + constructor(scene: BattleScene) { super(scene, Mode.PARTY); } @@ -810,7 +813,11 @@ export default class PartyUiHandler extends MessageUiHandler { const modifier = formChangeItemModifiers[option - PartyOption.FORM_CHANGE_ITEM]; optionName = `${modifier.active ? "Deactivate" : "Activate"} ${modifier.type.name}`; } else { - optionName = Utils.toReadableString(PartyOption[option]); + if (this.localizedOptions.includes(option)) { + optionName = i18next.t(`partyUiHandler:${PartyOption[option]}`); + } else { + optionName = Utils.toReadableString(PartyOption[option]); + } } break; } From c177f3c1fbced53969ebff615cbe2b06d27ee2cb Mon Sep 17 00:00:00 2001 From: Corrade <49605314+Corrade@users.noreply.github.com> Date: Sat, 8 Jun 2024 05:15:24 +1000 Subject: [PATCH 117/129] [Bug] Ignored strong winds for stealth rock and anticipation (#1682) --- src/data/ability.ts | 2 +- src/data/arena-tag.ts | 2 +- src/field/pokemon.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 4b0b380a42f..77fb31b9a6d 100755 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2214,7 +2214,7 @@ function getAnticipationCondition(): AbAttrCondition { for (const opponent of pokemon.getOpponents()) { for (const move of opponent.moveset) { // move is super effective - if (move.getMove() instanceof AttackMove && pokemon.getAttackTypeEffectiveness(move.getMove().type, opponent) >= 2) { + if (move.getMove() instanceof AttackMove && pokemon.getAttackTypeEffectiveness(move.getMove().type, opponent, true) >= 2) { return true; } // move is a OHKO diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index a0c36649e84..37420c2ac0b 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -518,7 +518,7 @@ class StealthRockTag extends ArenaTrapTag { } getDamageHpRatio(pokemon: Pokemon): number { - const effectiveness = pokemon.getAttackTypeEffectiveness(Type.ROCK); + const effectiveness = pokemon.getAttackTypeEffectiveness(Type.ROCK, undefined, true); let damageHpRatio: number; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index a973297567a..6052d47b8de 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1070,7 +1070,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return (!cancelled.value ? typeMultiplier.value : 0) as TypeDamageMultiplier; } - getAttackTypeEffectiveness(moveType: Type, source?: Pokemon): TypeDamageMultiplier { + getAttackTypeEffectiveness(moveType: Type, source?: Pokemon, ignoreStrongWinds: boolean = false): TypeDamageMultiplier { if (moveType === Type.STELLAR) { return this.isTerastallized() ? 2 : 1; } @@ -1089,7 +1089,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { }).reduce((acc, cur) => acc * cur, 1) as TypeDamageMultiplier; // Handle strong winds lowering effectiveness of types super effective against pure flying - if (this.scene.arena.weather?.weatherType === WeatherType.STRONG_WINDS && !this.scene.arena.weather.isEffectSuppressed(this.scene) && multiplier >= 2 && this.isOfType(Type.FLYING) && getTypeDamageMultiplier(moveType, Type.FLYING) === 2) { + if (!ignoreStrongWinds && this.scene.arena.weather?.weatherType === WeatherType.STRONG_WINDS && !this.scene.arena.weather.isEffectSuppressed(this.scene) && multiplier >= 2 && this.isOfType(Type.FLYING) && getTypeDamageMultiplier(moveType, Type.FLYING) === 2) { multiplier /= 2; } return multiplier; From 7c3ace7204950809b0a07f135cda08d744445828 Mon Sep 17 00:00:00 2001 From: Arxalc <63990624+Arxalc@users.noreply.github.com> Date: Fri, 7 Jun 2024 15:22:06 -0400 Subject: [PATCH 118/129] [Bug] Fixed primal weather interaction. (#1744) * Fixed primal weather interaction. * Made adjustments * Improved code readability --- src/data/ability.ts | 114 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 110 insertions(+), 4 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 77fb31b9a6d..017a89145ec 100755 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -1726,7 +1726,9 @@ export class PostSummonWeatherChangeAbAttr extends PostSummonAbAttr { } applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean { - if (!pokemon.scene.arena.weather?.isImmutable()) { + if ((this.weatherType === WeatherType.HEAVY_RAIN || + this.weatherType === WeatherType.HARSH_SUN || + this.weatherType === WeatherType.STRONG_WINDS) || !pokemon.scene.arena.weather?.isImmutable()) { return pokemon.scene.arena.trySetWeather(this.weatherType, true); } @@ -1854,6 +1856,52 @@ export class PreSwitchOutResetStatusAbAttr extends PreSwitchOutAbAttr { } } +/** + * Clears Desolate Land/Primordial Sea/Delta Stream upon the Pokemon switching out. + */ +export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr { + + /** + * @param pokemon The {@linkcode Pokemon} with the ability + * @param passive N/A + * @param args N/A + * @returns {boolean} Returns true if the weather clears, otherwise false. + */ + applyPreSwitchOut(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise { + const weatherType = pokemon.scene.arena.weather.weatherType; + let turnOffWeather = false; + + // Clear weather only if user's ability matches the weather and no other pokemon has the ability. + switch (weatherType) { + case (WeatherType.HARSH_SUN): + if (pokemon.hasAbility(Abilities.DESOLATE_LAND) + && pokemon.scene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.DESOLATE_LAND)).length === 0) { + turnOffWeather = true; + } + break; + case (WeatherType.HEAVY_RAIN): + if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA) + && pokemon.scene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.PRIMORDIAL_SEA)).length === 0) { + turnOffWeather = true; + } + break; + case (WeatherType.STRONG_WINDS): + if (pokemon.hasAbility(Abilities.DELTA_STREAM) + && pokemon.scene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.DELTA_STREAM)).length === 0) { + turnOffWeather = true; + } + break; + } + + if (turnOffWeather) { + pokemon.scene.arena.trySetWeather(WeatherType.NONE, false); + return true; + } + + return false; + } +} + export class PreSwitchOutHealAbAttr extends PreSwitchOutAbAttr { applyPreSwitchOut(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise { if (pokemon.getHpRatio() < 1 ) { @@ -2985,6 +3033,55 @@ export class PostFaintAbAttr extends AbAttr { } } +/** + * Clears Desolate Land/Primordial Sea/Delta Stream upon the Pokemon fainting + */ +export class PostFaintClearWeatherAbAttr extends PostFaintAbAttr { + + /** + * @param pokemon The {@linkcode Pokemon} with the ability + * @param passive N/A + * @param attacker N/A + * @param move N/A + * @param hitResult N/A + * @param args N/A + * @returns {boolean} Returns true if the weather clears, otherwise false. + */ + applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { + const weatherType = pokemon.scene.arena.weather.weatherType; + let turnOffWeather = false; + + // Clear weather only if user's ability matches the weather and no other pokemon has the ability. + switch (weatherType) { + case (WeatherType.HARSH_SUN): + if (pokemon.hasAbility(Abilities.DESOLATE_LAND) + && pokemon.scene.getField(true).filter(p => p.hasAbility(Abilities.DESOLATE_LAND)).length === 0) { + turnOffWeather = true; + } + break; + case (WeatherType.HEAVY_RAIN): + if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA) + && pokemon.scene.getField(true).filter(p => p.hasAbility(Abilities.PRIMORDIAL_SEA)).length === 0) { + turnOffWeather = true; + } + break; + case (WeatherType.STRONG_WINDS): + if (pokemon.hasAbility(Abilities.DELTA_STREAM) + && pokemon.scene.getField(true).filter(p => p.hasAbility(Abilities.DELTA_STREAM)).length === 0) { + turnOffWeather = true; + } + break; + } + + if (turnOffWeather) { + pokemon.scene.arena.trySetWeather(WeatherType.NONE, false); + return true; + } + + return false; + } +} + export class PostFaintContactDamageAbAttr extends PostFaintAbAttr { private damageRatio: integer; @@ -4161,13 +4258,22 @@ export function initAbilities() { .unimplemented(), new Ability(Abilities.PRIMORDIAL_SEA, 6) .attr(PostSummonWeatherChangeAbAttr, WeatherType.HEAVY_RAIN) - .attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.HEAVY_RAIN), + .attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.HEAVY_RAIN) + .attr(PreSwitchOutClearWeatherAbAttr) + .attr(PostFaintClearWeatherAbAttr) + .bypassFaint(), new Ability(Abilities.DESOLATE_LAND, 6) .attr(PostSummonWeatherChangeAbAttr, WeatherType.HARSH_SUN) - .attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.HARSH_SUN), + .attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.HARSH_SUN) + .attr(PreSwitchOutClearWeatherAbAttr) + .attr(PostFaintClearWeatherAbAttr) + .bypassFaint(), new Ability(Abilities.DELTA_STREAM, 6) .attr(PostSummonWeatherChangeAbAttr, WeatherType.STRONG_WINDS) - .attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.STRONG_WINDS), + .attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.STRONG_WINDS) + .attr(PreSwitchOutClearWeatherAbAttr) + .attr(PostFaintClearWeatherAbAttr) + .bypassFaint(), new Ability(Abilities.STAMINA, 7) .attr(PostDefendStatChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, BattleStat.DEF, 1), new Ability(Abilities.WIMP_OUT, 7) From 97dde2d1f39ba835eef5279ad3a2808598163c95 Mon Sep 17 00:00:00 2001 From: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> Date: Fri, 7 Jun 2024 22:43:32 +0200 Subject: [PATCH 119/129] [QoL] Added https and server url is read from the env now (#1764) * Added https and server url is read from the env now * Added the new key to the vite.env.d.ts --- .env | 3 ++- .env.development | 3 ++- src/utils.ts | 4 +++- src/vite.env.d.ts | 1 + 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.env b/.env index 86feafaa143..6ac42ba97b4 100644 --- a/.env +++ b/.env @@ -1,2 +1,3 @@ VITE_BYPASS_LOGIN=0 -VITE_BYPASS_TUTORIAL=0 \ No newline at end of file +VITE_BYPASS_TUTORIAL=0 +VITE_SERVER_URL=http://localhost:8001 \ No newline at end of file diff --git a/.env.development b/.env.development index 88dcdce619c..e9180f0875d 100644 --- a/.env.development +++ b/.env.development @@ -1,2 +1,3 @@ VITE_BYPASS_LOGIN=1 -VITE_BYPASS_TUTORIAL=0 \ No newline at end of file +VITE_BYPASS_TUTORIAL=0 +VITE_SERVER_URL=http://localhost:8001 \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index 42736add1fa..7b25c4990e1 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -277,8 +277,10 @@ export const isLocal = ( /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/.test(window.location.hostname)) && window.location.port !== "") || window.location.hostname === ""; +export const localServerUrl = import.meta.env.VITE_SERVER_URL ?? `http://${window.location.hostname}:${window.location.port+1}`; + // Set the server URL based on whether it's local or not -export const serverUrl = isLocal ? `${window.location.hostname}:${window.location.port}` : ""; +export const serverUrl = isLocal ? localServerUrl : ""; export const apiUrl = isLocal ? serverUrl : "https://api.pokerogue.net"; // used to disable api calls when isLocal is true and a server is not found export let isLocalServerConnected = false; diff --git a/src/vite.env.d.ts b/src/vite.env.d.ts index 50bb7dddc02..b588b5b1145 100644 --- a/src/vite.env.d.ts +++ b/src/vite.env.d.ts @@ -4,6 +4,7 @@ interface ImportMetaEnv { readonly VITE_BYPASS_LOGIN?: string; readonly VITE_BYPASS_TUTORIAL?: string; readonly VITE_API_BASE_URL?: string; + readonly VITE_SERVER_URL?: string; } interface ImportMeta { From f5f98ec5377e836e6ed972e60715f0302baf8074 Mon Sep 17 00:00:00 2001 From: prime <10091050+prime-dialga@users.noreply.github.com> Date: Fri, 7 Jun 2024 22:45:49 +0200 Subject: [PATCH 120/129] [Bug] fixed wrong stacking of move info overlay issues (#1888) * fixed wrongly stacking overlay issues - starter selection - IVs are now behind the overlay - the overlay should clear when exiting it via controller (requires tests as i don't have a controller) - TM - will prevent C/Shift from showing the overlay until the next item selection, when selecting a TM as item reward. - will prevent C/Shift from showing the overlay when canceling item selection * removed reference to previously deleted resource * fixed override --- src/loading-scene.ts | 1 - src/ui/modifier-select-ui-handler.ts | 11 +++++++++++ src/ui/starter-select-ui-handler.ts | 20 +++++++++++--------- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/loading-scene.ts b/src/loading-scene.ts index 522962d5829..5713bf69fde 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -135,7 +135,6 @@ export class LoadingScene extends SceneBase { this.loadImage("summary_stats_overlay_exp", "ui"); this.loadImage("summary_moves", "ui"); this.loadImage("summary_moves_effect", "ui"); - this.loadImage("summary_moves_effect_type", "ui"); this.loadImage("summary_moves_overlay_row", "ui"); this.loadImage("summary_moves_overlay_pp", "ui"); this.loadAtlas("summary_moves_cursor", "ui"); diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index f6738a33d98..61941c28b2c 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -20,6 +20,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { private rerollCostText: Phaser.GameObjects.Text; private lockRarityButtonText: Phaser.GameObjects.Text; private moveInfoOverlay : MoveInfoOverlay; + private moveInfoOverlayActive : boolean = false; private rowCursor: integer = 0; private player: boolean; @@ -99,6 +100,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.awaitingActionInput = true; this.onActionInput = args[2]; } + this.moveInfoOverlay.active = this.moveInfoOverlayActive; return false; } @@ -242,6 +244,10 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { if (!originalOnActionInput(this.rowCursor, this.cursor)) { this.awaitingActionInput = true; this.onActionInput = originalOnActionInput; + } else { + this.moveInfoOverlayActive = this.moveInfoOverlay.active; + this.moveInfoOverlay.setVisible(false); + this.moveInfoOverlay.active = false; // this is likely unnecessary, but it should help future prove the UI } } } else if (button === Button.CANCEL) { @@ -252,6 +258,9 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.awaitingActionInput = false; this.onActionInput = null; originalOnActionInput(-1); + this.moveInfoOverlayActive = this.moveInfoOverlay.active; + this.moveInfoOverlay.setVisible(false); + this.moveInfoOverlay.active = false; // don't clear here as we might need to restore the UI in case the user cancels the action } } } else { @@ -403,6 +412,8 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { clear() { super.clear(); + this.moveInfoOverlay.clear(); + this.moveInfoOverlayActive = false; this.awaitingActionInput = false; this.onActionInput = null; this.getUi().clearText(); diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 1dbb60915ca..29926568d80 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -663,15 +663,6 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.message.setOrigin(0, 0); this.starterSelectMessageBoxContainer.add(this.message); - const overlayScale = 1; // scale for the move info. "2/3" might be another good option... - this.moveInfoOverlay = new MoveInfoOverlay(this.scene, { - scale: overlayScale, - top: true, - x: 1, - y: this.scene.game.canvas.height / 6 - MoveInfoOverlay.getHeight(overlayScale) - 29, - }); - this.starterSelectContainer.add(this.moveInfoOverlay); - const date = new Date(); date.setUTCHours(0, 0, 0, 0); @@ -716,12 +707,23 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.starterSelectContainer.add(this.statsContainer); + // add the info overlay last to be the top most ui element and prevent the IVs from overlaying this + const overlayScale = 1; + this.moveInfoOverlay = new MoveInfoOverlay(this.scene, { + scale: overlayScale, + top: true, + x: 1, + y: this.scene.game.canvas.height / 6 - MoveInfoOverlay.getHeight(overlayScale) - 29, + }); + this.starterSelectContainer.add(this.moveInfoOverlay); + this.scene.eventTarget.addEventListener(BattleSceneEventType.CANDY_UPGRADE_NOTIFICATION_CHANGED, (e) => this.onCandyUpgradeDisplayChanged(e)); this.updateInstructions(); } show(args: any[]): boolean { + this.moveInfoOverlay.clear(); // clear this when removing a menu; the cancel button doesn't seem to trigger this automatically on controllers if (args.length >= 2 && args[0] instanceof Function && typeof args[1] === "number") { super.show(args); this.starterSelectCallback = args[0] as StarterSelectCallback; From 636cb9c8f2a33340415a3a97cbf9acdf4f4524e2 Mon Sep 17 00:00:00 2001 From: sodam <66295123+sodaMelon@users.noreply.github.com> Date: Sat, 8 Jun 2024 05:52:31 +0900 Subject: [PATCH 121/129] [Localization] #1761 Korean trainer dialogue (some unnamed trainers) (#1911) * localized to korean (some unnamed trainers) * fixed dialogue's meaning clear Co-authored-by: returntoice * fixed spacing right Co-authored-by: Sangmin Lee <66083363+GINK-SS@users.noreply.github.com> --------- Co-authored-by: returntoice Co-authored-by: Sangmin Lee <66083363+GINK-SS@users.noreply.github.com> --- src/locales/ko/dialogue.ts | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/locales/ko/dialogue.ts b/src/locales/ko/dialogue.ts index 27e43fe17a3..cdb6670650f 100644 --- a/src/locales/ko/dialogue.ts +++ b/src/locales/ko/dialogue.ts @@ -243,64 +243,64 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "scientist": { "encounter": { - 1: "My research will lead this world to peace and joy.", + 1: "제 연구는 이 세상을 평화와 기쁨으로 이끌 겁니다.", }, "victory": { - 1: "I am a genius… I am not supposed to lose against someone like you…", + 1: "전 천재니까… 당신 같은 사람에게 질 수 없는데…", }, }, "school_kid": { "encounter": { - 1: "…Heehee. I'm confident in my calculations and analysis.", - 2: "I'm gaining as much experience as I can because I want to be a Gym Leader someday." + 1: "…헤헷. 계산과 분석에는 자신 있어.", + 2: "언젠가 체육관 관장이 되고 싶어서, 최대한 많은 경험을 쌓고 있어." }, "victory": { - 1: "Ohhhh… Calculation and analysis are perhaps no match for chance…", - 2: "Even difficult, trying experiences have their purpose, I suppose." + 1: "으아아… 이번에는 아마 계산과 분석이 빗나간 것 같아…", + 2: "내가 보기엔, 어렵고 힘든 경험도 나름의 의미가 있는 것 같아." } }, "artist": { "encounter": { - 1: "I used to be popular, but now I am all washed up.", + 1: "예전엔 인기가 많았지만, 지금은 모두 사라졌다네.", }, "victory": { - 1: "As times change, values also change. I realized that too late.", + 1: "시대가 변하면, 가치관도 변하지. 난 그걸 너무 늦게 깨달았어.", }, }, "guitarist": { "encounter": { - 1: "Get ready to feel the rhythm of defeat as I strum my way to victory!", + 1: "패배의 리듬을 느낄 준비는 됐겠지? 내가 승리할 거니까!", }, "victory": { - 1: "Silenced for now, but my melody of resilience will play on.", + 1: "지금은 조용하지만, 회복의 멜로디를 연주할 거야.", }, }, "worker": { "encounter": { - 1: "It bothers me that people always misunderstand me. I'm a lot more pure than everyone thinks.", + 1: "사람들이 저를 오해하는 게 신경 쓰여요. 전 생각보다 훨씬 깨끗하답니다.", }, "victory": { - 1: "I really don't want my skin to burn, so I want to stay in the shade while I work.", + 1: "피부가 타는 게 싫어서, 일하는 동안엔 그늘에 머물고 싶어요.", }, }, "worker_female": { "encounter": { - 1: `It bothers me that people always misunderstand me. - $I'm a lot more pure than everyone thinks.` + 1: `사람들이 나를 오해하는 게 신경 쓰여. + $나는 생각보다 훨씬 깨끗한데.` }, "victory": { - 1: "I really don't want my skin to burn, so I want to stay in the shade while I work." + 1: "피부가 타는 게 싫어서, 일하는 동안엔 그늘에 머물고 싶어." }, "defeat": { - 1: "My body and mind aren't necessarily always in sync." + 1: "생각처럼 몸이 잘 안따라주네." } }, "worker_double": { "encounter": { - 1: "I'll show you we can break you. We've been training in the field!", + 1: "너를 무너뜨릴 수 있다는 것을 보여줄게. 우리는 실전 경험이 있거든!", }, "victory": { - 1: "How strange… How could this be… I shouldn't have been outmuscled.", + 1: "이상하네… 어떻게 이럴 수 있지… 힘으로 압도할 수 없다니.", }, }, "hex_maniac": { From 0a17c2495a5aefb5583aa0bf47fe9308a4a0d909 Mon Sep 17 00:00:00 2001 From: td76099 <85713900+td76099@users.noreply.github.com> Date: Fri, 7 Jun 2024 16:57:57 -0400 Subject: [PATCH 122/129] Bugfix: Abilities check final move type instead of default move type (#1440) * Added check for move changing type before determining if defending is immune to it because of an ability * Remove duplicate Ability change class and added getType() function under a move * Reworking how moves get passed into hit calc * Fixing exceptions and overreaching changes * reverting forwarn and dancing move back to original since they are not being changed * fixing some small move related bugs * Fixing file permissions after testing * Fixing move type not resetting after individual MoveEffectPhase * Fixing move.ts permissions (again) * Addressing some MR feedback and adding some documentation for PokemonMove class --- src/data/ability.ts | 267 ++++++++++++++++++++----------------------- src/data/move.ts | 76 ++++++------ src/field/pokemon.ts | 85 +++++++------- src/phases.ts | 55 ++++----- 4 files changed, 232 insertions(+), 251 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 017a89145ec..2af456a2f70 100755 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -235,10 +235,10 @@ export class PostBattleInitStatChangeAbAttr extends PostBattleInitAbAttr { } } -type PreDefendAbAttrCondition = (pokemon: Pokemon, attacker: Pokemon, move: PokemonMove) => boolean; +type PreDefendAbAttrCondition = (pokemon: Pokemon, attacker: Pokemon, move: Move) => boolean; export class PreDefendAbAttr extends AbAttr { - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { + applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { return false; } } @@ -252,7 +252,7 @@ export class PreDefendFormChangeAbAttr extends PreDefendAbAttr { this.formFunc = formFunc; } - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { + applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { const formIndex = this.formFunc(pokemon); if (formIndex !== pokemon.formIndex) { pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); @@ -263,7 +263,7 @@ export class PreDefendFormChangeAbAttr extends PreDefendAbAttr { } } export class PreDefendFullHpEndureAbAttr extends PreDefendAbAttr { - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { + applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { if (pokemon.hp === pokemon.getMaxHp() && pokemon.getMaxHp() > 1 && //Checks if pokemon has wonder_guard (which forces 1hp) (args[0] as Utils.NumberHolder).value >= pokemon.hp) { //Damage >= hp @@ -308,8 +308,8 @@ export class ReceivedMoveDamageMultiplierAbAttr extends PreDefendAbAttr { this.powerMultiplier = powerMultiplier; } - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (this.condition(pokemon, attacker, move.getMove())) { + applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { + if (this.condition(pokemon, attacker, move)) { (args[0] as Utils.NumberHolder).value *= this.powerMultiplier; return true; } @@ -329,8 +329,8 @@ export class PreDefendMovePowerToOneAbAttr extends ReceivedMoveDamageMultiplierA super(condition, 1); } - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (this.condition(pokemon, attacker, move.getMove())) { + applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { + if (this.condition(pokemon, attacker, move)) { (args[0] as Utils.NumberHolder).value = 1; return true; } @@ -339,6 +339,12 @@ export class PreDefendMovePowerToOneAbAttr extends ReceivedMoveDamageMultiplierA } } +/** + * Determines whether a Pokemon is immune to a move because of an ability. + * @extends PreDefendAbAttr + * @see {@linkcode applyPreDefend} + * @see {@linkcode getCondition} + */ export class TypeImmunityAbAttr extends PreDefendAbAttr { private immuneType: Type; private condition: AbAttrCondition; @@ -350,8 +356,17 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr { this.condition = condition; } - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if ((move.getMove() instanceof AttackMove || move.getMove().getAttrs(StatusMoveTypeImmunityAttr).find(attr => attr.immuneType === this.immuneType)) && move.getMove().type === this.immuneType) { + /** + * @param pokemon {@linkcode Pokemon} the defending Pokemon + * @param passive N/A + * @param attacker {@linkcode Pokemon} the attacking Pokemon + * @param move {@linkcode Move} the attacking move + * @param cancelled N/A + * @param args [0] {@linkcode Utils.NumberHolder} gets set to 0 if move is immuned by an ability. + * @param args [1] {@linkcode Utils.NumberHolder} type of move being defended against in case it has changed from default type + */ + applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { + if ((move instanceof AttackMove || move.getAttrs(StatusMoveTypeImmunityAttr).find(attr => attr.immuneType === this.immuneType)) && move.type === this.immuneType) { (args[0] as Utils.NumberHolder).value = 0; return true; } @@ -369,7 +384,7 @@ export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr { super(immuneType); } - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { + applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args); if (ret) { @@ -399,7 +414,7 @@ class TypeImmunityStatChangeAbAttr extends TypeImmunityAbAttr { this.levels = levels; } - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { + applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args); if (ret) { @@ -425,7 +440,7 @@ class TypeImmunityAddBattlerTagAbAttr extends TypeImmunityAbAttr { this.turnCount = turnCount; } - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { + applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args); if (ret) { @@ -445,8 +460,8 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr { super(null, condition); } - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (move.getMove() instanceof AttackMove && pokemon.getAttackTypeEffectiveness(move.getMove().type, attacker) < 2) { + applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { + if (move instanceof AttackMove && pokemon.getAttackTypeEffectiveness(move.type, attacker) < 2) { cancelled.value = true; (args[0] as Utils.NumberHolder).value = 0; return true; @@ -461,15 +476,15 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr { } export class PostDefendAbAttr extends AbAttr { - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean | Promise { + applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean | Promise { return false; } } export class PostDefendDisguiseAbAttr extends PostDefendAbAttr { - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { - if (pokemon.formIndex === 0 && pokemon.battleData.hitCount !== 0 && (move.getMove().category === MoveCategory.SPECIAL || move.getMove().category === MoveCategory.PHYSICAL)) { + applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + if (pokemon.formIndex === 0 && pokemon.battleData.hitCount !== 0 && (move.category === MoveCategory.SPECIAL || move.category === MoveCategory.PHYSICAL)) { const recoilDamage = Math.ceil((pokemon.getMaxHp() / 8) - attacker.turnData.damageDealt); if (!recoilDamage) { @@ -494,7 +509,7 @@ export class PostDefendFormChangeAbAttr extends PostDefendAbAttr { this.formFunc = formFunc; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { + applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { const formIndex = this.formFunc(pokemon); if (formIndex !== pokemon.formIndex) { pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); @@ -506,16 +521,16 @@ export class PostDefendFormChangeAbAttr extends PostDefendAbAttr { } export class FieldPriorityMoveImmunityAbAttr extends PreDefendAbAttr { - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { - const attackPriority = new Utils.IntegerHolder(move.getMove().priority); - applyMoveAttrs(IncrementMovePriorityAttr,attacker,null,move.getMove(),attackPriority); - applyAbAttrs(IncrementMovePriorityAbAttr, attacker, null, move.getMove(), attackPriority); + applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { + const attackPriority = new Utils.IntegerHolder(move.priority); + applyMoveAttrs(IncrementMovePriorityAttr,attacker,null,move,attackPriority); + applyAbAttrs(IncrementMovePriorityAbAttr, attacker, null, move, attackPriority); - if (move.getMove().moveTarget===MoveTarget.USER || move.getMove().moveTarget===MoveTarget.NEAR_ALLY) { + if (move.moveTarget===MoveTarget.USER || move.moveTarget===MoveTarget.NEAR_ALLY) { return false; } - if (attackPriority.value > 0 && !move.getMove().isMultiTarget()) { + if (attackPriority.value > 0 && !move.isMultiTarget()) { cancelled.value = true; return true; } @@ -539,7 +554,7 @@ export class MoveImmunityAbAttr extends PreDefendAbAttr { this.immuneCondition = immuneCondition; } - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { + applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { if (this.immuneCondition(pokemon, attacker, move)) { cancelled.value = true; return true; @@ -563,7 +578,7 @@ export class MoveImmunityStatChangeAbAttr extends MoveImmunityAbAttr { this.levels = levels; } - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { + applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args); if (ret) { const simulated = args.length > 1 && args[1]; @@ -593,8 +608,8 @@ export class ReverseDrainAbAttr extends PostDefendAbAttr { * @args N/A * @returns true if healing should be reversed on a healing move, false otherwise. */ - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { - if (move.getMove().hasAttr(HitHealAttr)) { + applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + if (move.hasAttr(HitHealAttr)) { pokemon.scene.queueMessage(getPokemonMessage(attacker, " sucked up the liquid ooze!")); return true; } @@ -619,8 +634,8 @@ export class PostDefendStatChangeAbAttr extends PostDefendAbAttr { this.allOthers = allOthers; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { - if (this.condition(pokemon, attacker, move.getMove())) { + applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + if (this.condition(pokemon, attacker, move)) { if (this.allOthers) { const otherPokemon = pokemon.getAlly() ? pokemon.getOpponents().concat([ pokemon.getAlly() ]) : pokemon.getOpponents(); for (const other of otherPokemon) { @@ -653,10 +668,10 @@ export class PostDefendHpGatedStatChangeAbAttr extends PostDefendAbAttr { this.selfTarget = selfTarget; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { + applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { const hpGateFlat: integer = Math.ceil(pokemon.getMaxHp() * this.hpGate); const lastAttackReceived = pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1]; - if (this.condition(pokemon, attacker, move.getMove()) && (pokemon.hp <= hpGateFlat && (pokemon.hp + lastAttackReceived.damage) > hpGateFlat)) { + if (this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + lastAttackReceived.damage) > hpGateFlat)) { pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, (this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.levels)); return true; } @@ -676,8 +691,8 @@ export class PostDefendApplyArenaTrapTagAbAttr extends PostDefendAbAttr { this.tagType = tagType; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { - if (this.condition(pokemon, attacker, move.getMove())) { + applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + if (this.condition(pokemon, attacker, move)) { const tag = pokemon.scene.arena.getTag(this.tagType) as ArenaTrapTag; if (!pokemon.scene.arena.getTag(this.tagType) || tag.layers < tag.maxLayers) { pokemon.scene.arena.addTag(this.tagType, 0, undefined, pokemon.id, pokemon.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER); @@ -698,11 +713,11 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr { this.tagType = tagType; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { - if (this.condition(pokemon, attacker, move.getMove())) { + applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + if (this.condition(pokemon, attacker, move)) { if (!pokemon.getTag(this.tagType)) { pokemon.addTag(this.tagType, undefined, undefined, pokemon.id); - pokemon.scene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: pokemon.name, moveName: move.getName() })); + pokemon.scene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: pokemon.name, moveName: move.name })); } return true; } @@ -711,9 +726,9 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr { } export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr { - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { + applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { if (hitResult < HitResult.NO_EFFECT) { - const type = move.getMove().type; + const type = move.type; const pokemonTypes = pokemon.getTypes(true); if (pokemonTypes.length !== 1 || pokemonTypes[0] !== type) { pokemon.summonData.types = [ type ]; @@ -738,7 +753,7 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr { this.terrainType = terrainType; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { + applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { if (hitResult < HitResult.NO_EFFECT) { return pokemon.scene.arena.trySetTerrain(this.terrainType, true); } @@ -758,8 +773,8 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr { this.effects = effects; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { - if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) { + applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) { const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)]; return attacker.trySetStatus(effect, true, pokemon); } @@ -773,7 +788,7 @@ export class EffectSporeAbAttr extends PostDefendContactApplyStatusEffectAbAttr super(10, StatusEffect.POISON, StatusEffect.PARALYSIS, StatusEffect.SLEEP); } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { + applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { if (attacker.hasAbility(Abilities.OVERCOAT) || attacker.isOfType(Type.GRASS)) { return false; } @@ -794,9 +809,9 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr { this.turnCount = turnCount; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { - if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance) { - return attacker.addTag(this.tagType, this.turnCount, move.moveId, attacker.id); + applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance) { + return attacker.addTag(this.tagType, this.turnCount, move.id, attacker.id); } return false; @@ -814,7 +829,7 @@ export class PostDefendCritStatChangeAbAttr extends PostDefendAbAttr { this.levels = levels; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { + applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.levels)); return true; @@ -834,8 +849,8 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr { this.damageRatio = damageRatio; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { - if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) { + applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) { attacker.damageAndUpdate(Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER); attacker.turnData.damageTaken += Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio)); return true; @@ -864,8 +879,8 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr { this.turns = turns; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { - if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) { + applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) { if (pokemon.getTag(BattlerTagType.PERISH_SONG) || attacker.getTag(BattlerTagType.PERISH_SONG)) { return false; } else { @@ -891,7 +906,7 @@ export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr { this.weatherType = weatherType; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { + applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { if (!pokemon.scene.arena.weather?.isImmutable()) { return pokemon.scene.arena.trySetWeather(this.weatherType, true); } @@ -905,8 +920,8 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr { super(); } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { - if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr)) { + applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr)) { const tempAbilityId = attacker.getAbility().id; attacker.summonData.ability = pokemon.getAbility().id; pokemon.summonData.ability = tempAbilityId; @@ -929,8 +944,8 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr { this.ability = ability; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { - if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr) && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr)) { + applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr) && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr)) { attacker.summonData.ability = this.ability; return true; @@ -947,7 +962,7 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr { export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr { private chance: integer; private attacker: Pokemon; - private move: PokemonMove; + private move: Move; constructor(chance: integer) { super(); @@ -955,13 +970,13 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr { this.chance = chance; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { + applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { if (!attacker.summonData.disabledMove) { - if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !attacker.isMax()) { + if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !attacker.isMax()) { this.attacker = attacker; this.move = move; - attacker.summonData.disabledMove = move.moveId; + attacker.summonData.disabledMove = move.id; attacker.summonData.disabledTurns = 4; return true; } @@ -970,7 +985,7 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr { } getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { - return getPokemonMessage(this.attacker, `'s ${this.move.getName()}\nwas disabled!`); + return getPokemonMessage(this.attacker, `'s ${this.move.name}\nwas disabled!`); } } @@ -998,49 +1013,18 @@ export class PostStatChangeStatChangeAbAttr extends PostStatChangeAbAttr { } export class PreAttackAbAttr extends AbAttr { - applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean | Promise { + applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean | Promise { return false; } } export class VariableMovePowerAbAttr extends PreAttackAbAttr { - applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean { + applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean { //const power = args[0] as Utils.NumberHolder; return false; } } -export class VariableMoveTypeAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { - //const power = args[0] as Utils.IntegerHolder; - return false; - } -} - -export class MoveTypeChangePowerMultiplierAbAttr extends VariableMoveTypeAbAttr { - private matchType: Type; - private newType: Type; - private powerMultiplier: number; - - constructor(matchType: Type, newType: Type, powerMultiplier: number) { - super(true); - this.matchType = matchType; - this.newType = newType; - this.powerMultiplier = powerMultiplier; - } - - apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { - const type = (args[0] as Utils.IntegerHolder); - if (type.value === this.matchType) { - type.value = this.newType; - (args[1] as Utils.NumberHolder).value *= this.powerMultiplier; - return true; - } - - return false; - } -} - export class FieldPreventExplosiveMovesAbAttr extends AbAttr { apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { cancelled.value = true; @@ -1060,11 +1044,10 @@ export class MoveTypeChangeAttr extends PreAttackAbAttr { this.condition = condition; } - applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean { - if (this.condition(pokemon, defender, move.getMove())) { - const type = (args[0] as Utils.IntegerHolder); - type.value = this.newType; - (args[1] as Utils.NumberHolder).value *= this.powerMultiplier; + applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean { + if (this.condition(pokemon, defender, move)) { + move.type = this.newType; + (args[0] as Utils.NumberHolder).value *= this.powerMultiplier; return true; } @@ -1097,8 +1080,8 @@ export class DamageBoostAbAttr extends PreAttackAbAttr { * @param args Utils.NumberHolder as damage * @returns true if the function succeeds */ - applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean { - if (this.condition(pokemon, defender, move.getMove())) { + applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean { + if (this.condition(pokemon, defender, move)) { const power = args[0] as Utils.NumberHolder; power.value = Math.floor(power.value * this.damageMultiplier); return true; @@ -1118,8 +1101,8 @@ export class MovePowerBoostAbAttr extends VariableMovePowerAbAttr { this.powerMultiplier = powerMultiplier; } - applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean { - if (this.condition(pokemon, defender, move.getMove())) { + applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean { + if (this.condition(pokemon, defender, move)) { (args[0] as Utils.NumberHolder).value *= this.powerMultiplier; return true; @@ -1165,8 +1148,8 @@ export class VariableMovePowerBoostAbAttr extends VariableMovePowerAbAttr { /** * @override */ - applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean { - const multiplier = this.mult(pokemon, defender, move.getMove()); + applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move, args: any[]): boolean { + const multiplier = this.mult(pokemon, defender, move); if (multiplier !== 1) { (args[0] as Utils.NumberHolder).value *= multiplier; return true; @@ -1177,7 +1160,7 @@ export class VariableMovePowerBoostAbAttr extends VariableMovePowerAbAttr { } export class FieldVariableMovePowerAbAttr extends AbAttr { - applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean { + applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean { //const power = args[0] as Utils.NumberHolder; return false; } @@ -1193,8 +1176,8 @@ export class FieldMovePowerBoostAbAttr extends FieldVariableMovePowerAbAttr { this.powerMultiplier = powerMultiplier; } - applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean { - if (this.condition(pokemon, defender, move.getMove())) { + applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean { + if (this.condition(pokemon, defender, move)) { (args[0] as Utils.NumberHolder).value *= this.powerMultiplier; return true; @@ -1235,7 +1218,7 @@ export class BattleStatMultiplierAbAttr extends AbAttr { } export class PostAttackAbAttr extends AbAttr { - applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean | Promise { + applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean | Promise { return false; } } @@ -1249,9 +1232,9 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { this.condition = condition; } - applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): Promise { + applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise { return new Promise(resolve => { - if (hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, defender, move.getMove()))) { + if (hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, defender, move))) { const heldItems = this.getTargetHeldItems(defender).filter(i => i.getTransferrable(false)); if (heldItems.length) { const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; @@ -1287,8 +1270,8 @@ export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr { this.effects = effects; } - applyPostAttack(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { - if (pokemon !== attacker && (!this.contactRequired || move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance && !pokemon.status) { + applyPostAttack(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + if (pokemon !== attacker && (!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance && !pokemon.status) { const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)]; return attacker.trySetStatus(effect, true, pokemon); } @@ -1305,11 +1288,11 @@ export class PostAttackContactApplyStatusEffectAbAttr extends PostAttackApplySta export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr { private contactRequired: boolean; - private chance: (user: Pokemon, target: Pokemon, move: PokemonMove) => integer; + private chance: (user: Pokemon, target: Pokemon, move: Move) => integer; private effects: BattlerTagType[]; - constructor(contactRequired: boolean, chance: (user: Pokemon, target: Pokemon, move: PokemonMove) => integer, ...effects: BattlerTagType[]) { + constructor(contactRequired: boolean, chance: (user: Pokemon, target: Pokemon, move: Move) => integer, ...effects: BattlerTagType[]) { super(); this.contactRequired = contactRequired; @@ -1317,8 +1300,8 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr { this.effects = effects; } - applyPostAttack(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { - if (pokemon !== attacker && (!this.contactRequired || move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance(attacker, pokemon, move) && !pokemon.status) { + applyPostAttack(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + if (pokemon !== attacker && (!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance(attacker, pokemon, move) && !pokemon.status) { const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)]; @@ -1338,9 +1321,9 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { this.condition = condition; } - applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): Promise { + applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise { return new Promise(resolve => { - if (hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move.getMove()))) { + if (hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move))) { const heldItems = this.getTargetHeldItems(attacker).filter(i => i.getTransferrable(false)); if (heldItems.length) { const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; @@ -2002,9 +1985,9 @@ export class ConfusionOnStatusEffectAbAttr extends PostAttackAbAttr { * @param args [0] {@linkcode StatusEffect} applied by move * @returns true if defender is confused */ - applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { + applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { if (this.effects.indexOf(args[0]) > -1 && !defender.isFainted()) { - return defender.addTag(BattlerTagType.CONFUSED, pokemon.randSeedInt(3,2), move.moveId, defender.id); + return defender.addTag(BattlerTagType.CONFUSED, pokemon.randSeedInt(3,2), move.id, defender.id); } return false; } @@ -3028,7 +3011,7 @@ export class PostBattleLootAbAttr extends PostBattleAbAttr { } export class PostFaintAbAttr extends AbAttr { - applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { + applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { return false; } } @@ -3091,8 +3074,8 @@ export class PostFaintContactDamageAbAttr extends PostFaintAbAttr { this.damageRatio = damageRatio; } - applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { - if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) { + applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) { const cancelled = new Utils.BooleanHolder(false); pokemon.scene.getField(true).map(p=>applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled)); if (cancelled.value) { @@ -3119,7 +3102,7 @@ export class PostFaintHPDamageAbAttr extends PostFaintAbAttr { super (); } - applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { + applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { const damage = pokemon.turnData.attacksReceived[0].damage; attacker.damageAndUpdate((damage), HitResult.OTHER); attacker.turnData.damageTaken += damage; @@ -3566,13 +3549,13 @@ export function applyPostBattleInitAbAttrs(attrType: { new(...args: any[]): Post } export function applyPreDefendAbAttrs(attrType: { new(...args: any[]): PreDefendAbAttr }, - pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, ...args: any[]): Promise { + pokemon: Pokemon, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, ...args: any[]): Promise { const simulated = args.length > 1 && args[1]; return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreDefend(pokemon, passive, attacker, move, cancelled, args), args, false, false, simulated); } export function applyPostDefendAbAttrs(attrType: { new(...args: any[]): PostDefendAbAttr }, - pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, ...args: any[]): Promise { + pokemon: Pokemon, attacker: Pokemon, move: Move, hitResult: HitResult, ...args: any[]): Promise { return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostDefend(pokemon, passive, attacker, move, hitResult, args), args); } @@ -3587,12 +3570,12 @@ export function applyBattleStatMultiplierAbAttrs(attrType: { new(...args: any[]) } export function applyPreAttackAbAttrs(attrType: { new(...args: any[]): PreAttackAbAttr }, - pokemon: Pokemon, defender: Pokemon, move: PokemonMove, ...args: any[]): Promise { + pokemon: Pokemon, defender: Pokemon, move: Move, ...args: any[]): Promise { return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreAttack(pokemon, passive, defender, move, args), args); } export function applyPostAttackAbAttrs(attrType: { new(...args: any[]): PostAttackAbAttr }, - pokemon: Pokemon, defender: Pokemon, move: PokemonMove, hitResult: HitResult, ...args: any[]): Promise { + pokemon: Pokemon, defender: Pokemon, move: Move, hitResult: HitResult, ...args: any[]): Promise { return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostAttack(pokemon, passive, defender, move, hitResult, args), args); } @@ -3673,7 +3656,7 @@ export function applyPostBattleAbAttrs(attrType: { new(...args: any[]): PostBatt } export function applyPostFaintAbAttrs(attrType: { new(...args: any[]): PostFaintAbAttr }, - pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, ...args: any[]): Promise { + pokemon: Pokemon, attacker: Pokemon, move: Move, hitResult: HitResult, ...args: any[]): Promise { return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostFaint(pokemon, passive, attacker, move, hitResult, args), args); } @@ -3692,7 +3675,7 @@ export const allAbilities = [ new Ability(Abilities.NONE, 3) ]; export function initAbilities() { allAbilities.push( new Ability(Abilities.STENCH, 3) - .attr(PostAttackApplyBattlerTagAbAttr, false, (user, target, move) => (move.getMove().category !== MoveCategory.STATUS && !move.getMove().hasAttr(FlinchAttr)) ? 10 : 0, BattlerTagType.FLINCHED), + .attr(PostAttackApplyBattlerTagAbAttr, false, (user, target, move) => (move.category !== MoveCategory.STATUS && !move.hasAttr(FlinchAttr)) ? 10 : 0, BattlerTagType.FLINCHED), new Ability(Abilities.DRIZZLE, 3) .attr(PostSummonWeatherChangeAbAttr, WeatherType.RAIN) .attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.RAIN), @@ -3829,7 +3812,7 @@ export function initAbilities() { return false; }), new Ability(Abilities.SOUNDPROOF, 3) - .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.getMove().hasFlag(MoveFlags.SOUND_BASED)) + .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.SOUND_BASED)) .ignorable(), new Ability(Abilities.RAIN_DISH, 3) .attr(PostWeatherLapseHealAbAttr, 1, WeatherType.RAIN, WeatherType.HEAVY_RAIN) @@ -4115,13 +4098,13 @@ export function initAbilities() { ) .partial(), new Ability(Abilities.TELEPATHY, 5) - .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon.getAlly() === attacker && move.getMove() instanceof AttackMove) + .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon.getAlly() === attacker && move instanceof AttackMove) .ignorable(), new Ability(Abilities.MOODY, 5) .attr(MoodyAbAttr), new Ability(Abilities.OVERCOAT, 5) .attr(BlockWeatherDamageAttr) - .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.getMove().hasFlag(MoveFlags.POWDER_MOVE)) + .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.POWDER_MOVE)) .ignorable(), new Ability(Abilities.POISON_TOUCH, 5) .attr(PostAttackContactApplyStatusEffectAbAttr, 30, StatusEffect.POISON), @@ -4210,14 +4193,14 @@ export function initAbilities() { new Ability(Abilities.MAGICIAN, 6) .attr(PostAttackStealHeldItemAbAttr), new Ability(Abilities.BULLETPROOF, 6) - .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.getMove().hasFlag(MoveFlags.BALLBOMB_MOVE)) + .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.BALLBOMB_MOVE)) .ignorable(), new Ability(Abilities.COMPETITIVE, 6) .attr(PostStatChangeStatChangeAbAttr, (target, statsChanged, levels) => levels < 0, [BattleStat.SPATK], 2), new Ability(Abilities.STRONG_JAW, 6) .attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.BITING_MOVE), 1.5), new Ability(Abilities.REFRIGERATE, 6) - .attr(MoveTypeChangePowerMultiplierAbAttr, Type.NORMAL, Type.ICE, 1.2), + .attr(MoveTypeChangeAttr, Type.ICE, 1.2, (user, target, move) => move.type === Type.NORMAL), new Ability(Abilities.SWEET_VEIL, 6) .attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP) .attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY) @@ -4240,11 +4223,11 @@ export function initAbilities() { new Ability(Abilities.TOUGH_CLAWS, 6) .attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), 1.3), new Ability(Abilities.PIXILATE, 6) - .attr(MoveTypeChangePowerMultiplierAbAttr, Type.NORMAL, Type.FAIRY, 1.2), + .attr(MoveTypeChangeAttr, Type.FAIRY, 1.2, (user, target, move) => move.type === Type.NORMAL), new Ability(Abilities.GOOEY, 6) .attr(PostDefendStatChangeAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), BattleStat.SPD, -1, false), new Ability(Abilities.AERILATE, 6) - .attr(MoveTypeChangePowerMultiplierAbAttr, Type.NORMAL, Type.FLYING, 1.2), + .attr(MoveTypeChangeAttr, Type.FLYING, 1.2, (user, target, move) => move.type === Type.NORMAL), new Ability(Abilities.PARENTAL_BOND, 6) .unimplemented(), new Ability(Abilities.DARK_AURA, 6) @@ -4314,7 +4297,7 @@ export function initAbilities() { new Ability(Abilities.TRIAGE, 7) .attr(IncrementMovePriorityAbAttr, (pokemon, move) => move.hasFlag(MoveFlags.TRIAGE_MOVE), 3), new Ability(Abilities.GALVANIZE, 7) - .attr(MoveTypeChangePowerMultiplierAbAttr, Type.NORMAL, Type.ELECTRIC, 1.2), + .attr(MoveTypeChangeAttr, Type.ELECTRIC, 1.2, (user, target, move) => move.type === Type.NORMAL), new Ability(Abilities.SURGE_SURFER, 7) .conditionalAttr(getTerrainCondition(TerrainType.ELECTRIC), BattleStatMultiplierAbAttr, BattleStat.SPD, 2), new Ability(Abilities.SCHOOLING, 7) @@ -4567,7 +4550,7 @@ export function initAbilities() { .attr(TypeImmunityStatChangeAbAttr, Type.FIRE, BattleStat.DEF, 2) .ignorable(), new Ability(Abilities.WIND_RIDER, 9) - .attr(MoveImmunityStatChangeAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.getMove().hasFlag(MoveFlags.WIND_MOVE) && move.getMove().category !== MoveCategory.STATUS, BattleStat.ATK, 1) + .attr(MoveImmunityStatChangeAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.WIND_MOVE) && move.category !== MoveCategory.STATUS, BattleStat.ATK, 1) .attr(PostSummonStatChangeOnArenaAbAttr, ArenaTagType.TAILWIND) .ignorable(), new Ability(Abilities.GUARD_DOG, 9) @@ -4607,7 +4590,7 @@ export function initAbilities() { .attr(NoTransformAbilityAbAttr) .partial(), // While setting the tag, the getbattlestat should ignore all modifiers to stats except stat stages new Ability(Abilities.GOOD_AS_GOLD, 9) - .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.getMove().category === MoveCategory.STATUS) + .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.category === MoveCategory.STATUS) .ignorable() .partial(), new Ability(Abilities.VESSEL_OF_RUIN, 9) diff --git a/src/data/move.ts b/src/data/move.ts index 9bdfc90903f..82d1bec32f7 100755 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -99,6 +99,7 @@ export default class Move implements Localizable { public id: Moves; public name: string; public type: Type; + public defaultType: Type; public category: MoveCategory; public moveTarget: MoveTarget; public power: integer; @@ -118,6 +119,7 @@ export default class Move implements Localizable { this.nameAppend = ""; this.type = type; + this.defaultType = type; this.category = category; this.moveTarget = defaultMoveTarget; this.power = power; @@ -1629,7 +1631,7 @@ export class StatusEffectAttr extends MoveEffectAttr { } if ((!pokemon.status || (pokemon.status.effect === this.effect && move.chance < 0)) && pokemon.trySetStatus(this.effect, true, user, this.cureTurn)) { - applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, new PokemonMove(move.id), null,this.effect); + applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null,this.effect); return true; } } @@ -3323,23 +3325,22 @@ export class TechnoBlastTypeAttr extends VariableMoveTypeAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.GENESECT)) { const form = user.species.speciesId === Species.GENESECT ? user.formIndex : user.fusionSpecies.formIndex; - const type = (args[0] as Utils.IntegerHolder); switch (form) { case 1: // Shock Drive - type.value = Type.ELECTRIC; + move.type = Type.ELECTRIC; break; case 2: // Burn Drive - type.value = Type.FIRE; + move.type = Type.FIRE; break; case 3: // Chill Drive - type.value = Type.ICE; + move.type = Type.ICE; break; case 4: // Douse Drive - type.value = Type.WATER; + move.type = Type.WATER; break; default: - type.value = Type.NORMAL; + move.type = Type.NORMAL; break; } return true; @@ -3353,14 +3354,13 @@ export class AuraWheelTypeAttr extends VariableMoveTypeAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.MORPEKO)) { const form = user.species.speciesId === Species.MORPEKO ? user.formIndex : user.fusionSpecies.formIndex; - const type = (args[0] as Utils.IntegerHolder); switch (form) { case 1: // Hangry Mode - type.value = Type.DARK; + move.type = Type.DARK; break; default: // Full Belly Mode - type.value = Type.ELECTRIC; + move.type = Type.ELECTRIC; break; } return true; @@ -3374,17 +3374,16 @@ export class RagingBullTypeAttr extends VariableMoveTypeAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.PALDEA_TAUROS)) { const form = user.species.speciesId === Species.PALDEA_TAUROS ? user.formIndex : user.fusionSpecies.formIndex; - const type = (args[0] as Utils.IntegerHolder); switch (form) { case 1: // Blaze breed - type.value = Type.FIRE; + move.type = Type.FIRE; break; case 2: // Aqua breed - type.value = Type.WATER; + move.type = Type.WATER; break; default: - type.value = Type.FIGHTING; + move.type = Type.FIGHTING; break; } return true; @@ -3398,32 +3397,31 @@ export class IvyCudgelTypeAttr extends VariableMoveTypeAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.OGERPON)) { const form = user.species.speciesId === Species.OGERPON ? user.formIndex : user.fusionSpecies.formIndex; - const type = (args[0] as Utils.IntegerHolder); switch (form) { case 1: // Wellspring Mask - type.value = Type.WATER; + move.type = Type.WATER; break; case 2: // Hearthflame Mask - type.value = Type.FIRE; + move.type = Type.FIRE; break; case 3: // Cornerstone Mask - type.value = Type.ROCK; + move.type = Type.ROCK; break; case 4: // Teal Mask Tera - type.value = Type.GRASS; + move.type = Type.GRASS; break; case 5: // Wellspring Mask Tera - type.value = Type.WATER; + move.type = Type.WATER; break; case 6: // Hearthflame Mask Tera - type.value = Type.FIRE; + move.type = Type.FIRE; break; case 7: // Cornerstone Mask Tera - type.value = Type.ROCK; + move.type = Type.ROCK; break; default: - type.value = Type.GRASS; + move.type = Type.GRASS; break; } return true; @@ -3436,23 +3434,21 @@ export class IvyCudgelTypeAttr extends VariableMoveTypeAttr { export class WeatherBallTypeAttr extends VariableMoveTypeAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (!user.scene.arena.weather?.isEffectSuppressed(user.scene)) { - const type = (args[0] as Utils.IntegerHolder); - switch (user.scene.arena.weather?.weatherType) { case WeatherType.SUNNY: case WeatherType.HARSH_SUN: - type.value = Type.FIRE; + move.type = Type.FIRE; break; case WeatherType.RAIN: case WeatherType.HEAVY_RAIN: - type.value = Type.WATER; + move.type = Type.WATER; break; case WeatherType.SANDSTORM: - type.value = Type.ROCK; + move.type = Type.ROCK; break; case WeatherType.HAIL: case WeatherType.SNOW: - type.value = Type.ICE; + move.type = Type.ICE; break; default: return false; @@ -3484,20 +3480,18 @@ export class TerrainPulseTypeAttr extends VariableMoveTypeAttr { } const currentTerrain = user.scene.arena.getTerrainType(); - const type = (args[0] as Utils.IntegerHolder); - switch (currentTerrain) { case TerrainType.MISTY: - type.value = Type.FAIRY; + move.type = Type.FAIRY; break; case TerrainType.ELECTRIC: - type.value = Type.ELECTRIC; + move.type = Type.ELECTRIC; break; case TerrainType.GRASSY: - type.value = Type.GRASS; + move.type = Type.GRASS; break; case TerrainType.PSYCHIC: - type.value = Type.PSYCHIC; + move.type = Type.PSYCHIC; break; default: return false; @@ -3508,8 +3502,6 @@ export class TerrainPulseTypeAttr extends VariableMoveTypeAttr { export class HiddenPowerTypeAttr extends VariableMoveTypeAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const type = (args[0] as Utils.IntegerHolder); - const iv_val = Math.floor(((user.ivs[Stat.HP] & 1) +(user.ivs[Stat.ATK] & 1) * 2 +(user.ivs[Stat.DEF] & 1) * 4 @@ -3517,7 +3509,7 @@ export class HiddenPowerTypeAttr extends VariableMoveTypeAttr { +(user.ivs[Stat.SPATK] & 1) * 16 +(user.ivs[Stat.SPDEF] & 1) * 32) * 15/63); - type.value = [ + move.type = [ Type.FIGHTING, Type.FLYING, Type.POISON, Type.GROUND, Type.ROCK, Type.BUG, Type.GHOST, Type.STEEL, Type.FIRE, Type.WATER, Type.GRASS, Type.ELECTRIC, @@ -3529,16 +3521,14 @@ export class HiddenPowerTypeAttr extends VariableMoveTypeAttr { export class MatchUserTypeAttr extends VariableMoveTypeAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const type = (args[0] as Utils.IntegerHolder); - const userTypes = user.getTypes(true); if (userTypes.includes(Type.STELLAR)) { // will not change to stellar type const nonTeraTypes = user.getTypes(); - type.value = nonTeraTypes[0]; + move.type = nonTeraTypes[0]; return true; } else if (userTypes.length > 0) { - type.value = userTypes[0]; + move.type = userTypes[0]; return true; } else { return false; @@ -4345,7 +4335,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { // Check if the move category is not STATUS or if the switch out condition is not met if (!this.getSwitchOutCondition()(user, target, move)) { //Apply effects before switch out i.e. poison point, flame body, etc - applyPostDefendAbAttrs(PostDefendContactApplyStatusEffectAbAttr, target, user, new PokemonMove(move.id), null); + applyPostDefendAbAttrs(PostDefendContactApplyStatusEffectAbAttr, target, user, move, null); return resolve(false); } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 6052d47b8de..65742d18b94 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -4,7 +4,7 @@ import { Variant, VariantSet, variantColorCache } from "#app/data/variant"; import { variantData } from "#app/data/variant"; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "../ui/battle-info"; import { Moves } from "../data/enums/moves"; -import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveTypeAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, MoveFlags } from "../data/move"; +import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, VariableMoveTypeAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, MoveFlags } from "../data/move"; import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species"; import * as Utils from "../utils"; import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type"; @@ -27,7 +27,7 @@ import { TempBattleStat } from "../data/temp-battle-stat"; import { ArenaTagSide, WeakenMoveScreenTag, WeakenMoveTypeTag } from "../data/arena-tag"; import { ArenaTagType } from "../data/enums/arena-tag-type"; import { Biome } from "../data/enums/biome"; -import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, FieldVariableMovePowerAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, VariableMoveTypeAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr } from "../data/ability"; +import { Ability, AbAttr, BattleStatMultiplierAbAttr, MoveTypeChangeAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, FieldVariableMovePowerAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr } from "../data/ability"; import { Abilities } from "#app/data/enums/abilities"; import PokemonData from "../system/pokemon-data"; import { BattlerIndex } from "../battle"; @@ -1057,9 +1057,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return !this.isOfType(Type.FLYING, true) && !this.hasAbility(Abilities.LEVITATE); } - getAttackMoveEffectiveness(source: Pokemon, move: PokemonMove): TypeDamageMultiplier { - const typeless = move.getMove().hasAttr(TypelessAttr); - const typeMultiplier = new Utils.NumberHolder(this.getAttackTypeEffectiveness(move.getMove().type, source)); + getAttackMoveEffectiveness(source: Pokemon, pokemonMove: PokemonMove): TypeDamageMultiplier { + const move = pokemonMove.getMove(); + const typeless = move.hasAttr(TypelessAttr); + const typeMultiplier = new Utils.NumberHolder(this.getAttackTypeEffectiveness(move.type, source)); const cancelled = new Utils.BooleanHolder(false); if (!typeless) { applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, typeMultiplier, true); @@ -1620,9 +1621,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return (this.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField())[this.getFieldIndex() ? 0 : 1]; } - apply(source: Pokemon, battlerMove: PokemonMove): HitResult { + apply(source: Pokemon, move: Move): HitResult { let result: HitResult; - const move = battlerMove.getMove(); const damage = new Utils.NumberHolder(0); const defendingSidePlayField = this.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField(); @@ -1630,19 +1630,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyMoveAttrs(VariableMoveCategoryAttr, source, this, move, variableCategory); const moveCategory = variableCategory.value as MoveCategory; - const variableType = new Utils.IntegerHolder(move.type); const typeChangeMovePowerMultiplier = new Utils.NumberHolder(1); - applyMoveAttrs(VariableMoveTypeAttr, source, this, move, variableType); - // 2nd argument is for MoveTypeChangePowerMultiplierAbAttr - applyAbAttrs(VariableMoveTypeAbAttr, source, null, variableType, typeChangeMovePowerMultiplier); - applyPreAttackAbAttrs(MoveTypeChangeAttr, source, this, battlerMove, variableType, typeChangeMovePowerMultiplier); - const type = variableType.value as Type; + applyMoveAttrs(VariableMoveTypeAttr, source, this, move); + applyPreAttackAbAttrs(MoveTypeChangeAttr, source, this, move, typeChangeMovePowerMultiplier); const types = this.getTypes(true, true); const cancelled = new Utils.BooleanHolder(false); const typeless = move.hasAttr(TypelessAttr); const typeMultiplier = new Utils.NumberHolder(!typeless && (moveCategory !== MoveCategory.STATUS || move.getAttrs(StatusMoveTypeImmunityAttr).find(attr => types.includes(attr.immuneType))) - ? this.getAttackTypeEffectiveness(type, source) + ? this.getAttackTypeEffectiveness(move.type, source) : 1); applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier); if (typeless) { @@ -1667,44 +1663,44 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const isPhysical = moveCategory === MoveCategory.PHYSICAL; const power = new Utils.NumberHolder(move.power); const sourceTeraType = source.getTeraType(); - if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === type && power.value < 60 && move.priority <= 0 && !move.hasAttr(MultiHitAttr) && !this.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { + if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === move.type && power.value < 60 && move.priority <= 0 && !move.hasAttr(MultiHitAttr) && !this.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { power.value = 60; } - applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, this, battlerMove, power); - this.scene.getField(true).map(p => applyPreAttackAbAttrs(FieldVariableMovePowerAbAttr, this, source, battlerMove, power)); + applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, this, move, power); + this.scene.getField(true).map(p => applyPreAttackAbAttrs(FieldVariableMovePowerAbAttr, this, source, move, power)); - applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, this, source, battlerMove, cancelled, power); + applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, this, source, move, cancelled, power); power.value *= typeChangeMovePowerMultiplier.value; if (!typeless) { - applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier); + applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, typeMultiplier); } if (!cancelled.value) { - applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier); - defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, battlerMove, cancelled, typeMultiplier)); + applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, typeMultiplier); + defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, move, cancelled, typeMultiplier)); } if (cancelled.value) { result = HitResult.NO_EFFECT; } else { - const typeBoost = source.findTag(t => t instanceof TypeBoostTag && (t as TypeBoostTag).boostedType === type) as TypeBoostTag; + const typeBoost = source.findTag(t => t instanceof TypeBoostTag && t.boostedType === move.type) as TypeBoostTag; if (typeBoost) { power.value *= typeBoost.boostValue; if (typeBoost.oneUse) { source.removeTag(typeBoost.tagType); } } - const arenaAttackTypeMultiplier = new Utils.NumberHolder(this.scene.arena.getAttackTypeMultiplier(type, source.isGrounded())); + const arenaAttackTypeMultiplier = new Utils.NumberHolder(this.scene.arena.getAttackTypeMultiplier(move.type, source.isGrounded())); applyMoveAttrs(IgnoreWeatherTypeDebuffAttr, source, this, move, arenaAttackTypeMultiplier); - if (this.scene.arena.getTerrainType() === TerrainType.GRASSY && this.isGrounded() && type === Type.GROUND && move.moveTarget === MoveTarget.ALL_NEAR_OTHERS) { + if (this.scene.arena.getTerrainType() === TerrainType.GRASSY && this.isGrounded() && move.type === Type.GROUND && move.moveTarget === MoveTarget.ALL_NEAR_OTHERS) { power.value /= 2; } applyMoveAttrs(VariablePowerAttr, source, this, move, power); this.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, new Utils.IntegerHolder(0), power); if (!typeless) { - this.scene.arena.applyTags(WeakenMoveTypeTag, type, power); - this.scene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, type, power); + this.scene.arena.applyTags(WeakenMoveTypeTag, move.type, power); + this.scene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, move.type, power); } if (source.getTag(HelpingHandTag)) { power.value *= 1.5; @@ -1749,11 +1745,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } const isTypeImmune = (typeMultiplier.value * arenaAttackTypeMultiplier.value) === 0; const sourceTypes = source.getTypes(); - const matchesSourceType = sourceTypes[0] === type || (sourceTypes.length > 1 && sourceTypes[1] === type); + const matchesSourceType = sourceTypes[0] === move.type || (sourceTypes.length > 1 && sourceTypes[1] === move.type); const stabMultiplier = new Utils.NumberHolder(1); if (sourceTeraType === Type.UNKNOWN && matchesSourceType) { stabMultiplier.value += 0.5; - } else if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === type) { + } else if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === move.type) { stabMultiplier.value += 0.5; } @@ -1778,7 +1774,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - applyPreAttackAbAttrs(DamageBoostAbAttr, source, this, battlerMove, damage); + applyPreAttackAbAttrs(DamageBoostAbAttr, source, this, move, damage); /** * For each {@link HitsTagAttr} the move has, doubles the damage of the move if: @@ -1793,7 +1789,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { }); } - if (this.scene.arena.terrain?.terrainType === TerrainType.MISTY && this.isGrounded() && type === Type.DRAGON) { + if (this.scene.arena.terrain?.terrainType === TerrainType.MISTY && this.isGrounded() && move.type === Type.DRAGON) { damage.value = Math.floor(damage.value / 2); } @@ -1848,7 +1844,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const oneHitKo = result === HitResult.ONE_HIT_KO; if (damage.value) { if (this.getHpRatio() === 1) { - applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, this, source, battlerMove, cancelled, damage); + applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, this, source, move, cancelled, damage); } else if (!this.isPlayer() && damage.value >= this.hp) { this.scene.applyModifiers(EnemyEndureChanceModifier, false, this); } @@ -1913,11 +1909,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { break; case MoveCategory.STATUS: if (!typeless) { - applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier); + applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, typeMultiplier); } if (!cancelled.value) { - applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier); - defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, battlerMove, cancelled, typeMultiplier)); + applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, typeMultiplier); + defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, move, cancelled, typeMultiplier)); } if (!typeMultiplier.value) { this.scene.queueMessage(i18next.t("battle:hitResultNoEffect", { pokemonName: this.name })); @@ -3381,10 +3377,6 @@ export class EnemyPokemon extends Pokemon { const pokemonMove = movePool[m]; const move = pokemonMove.getMove(); - const variableType = new Utils.IntegerHolder(move.type); - applyAbAttrs(VariableMoveTypeAbAttr, this, null, variableType); - const moveType = variableType.value as Type; - let moveScore = moveScores[m]; const targetScores: integer[] = []; @@ -3402,12 +3394,12 @@ export class EnemyPokemon extends Pokemon { const effectiveness = target.getAttackMoveEffectiveness(this, pokemonMove); if (target.isPlayer() !== this.isPlayer()) { targetScore *= effectiveness; - if (this.isOfType(moveType)) { + if (this.isOfType(move.type)) { targetScore *= 1.5; } } else if (effectiveness) { targetScore /= effectiveness; - if (this.isOfType(moveType)) { + if (this.isOfType(move.type)) { targetScore /= 1.5; } } @@ -3789,6 +3781,19 @@ export enum HitResult { export type DamageResult = HitResult.EFFECTIVE | HitResult.SUPER_EFFECTIVE | HitResult.NOT_VERY_EFFECTIVE | HitResult.ONE_HIT_KO | HitResult.OTHER; +/** + * Wrapper class for the {@linkcode Move} class for Pokemon to interact with. + * These are the moves assigned to a {@linkcode Pokemon} object. + * It links to {@linkcode Move} class via the move ID. + * Compared to {@linkcode Move}, this class also tracks if a move has received. + * PP Ups, amount of PP used, and things like that. + * @see {@linkcode isUsable} - checks if move is disabled, out of PP, or not implemented. + * @see {@linkcode getMove} - returns {@linkcode Move} object by looking it up via ID. + * @see {@linkcode usePp} - removes a point of PP from the move. + * @see {@linkcode getMovePp} - returns amount of PP a move currently has. + * @see {@linkcode getPpRatio} - returns the current PP amount / max PP amount. + * @see {@linkcode getName} - returns name of {@linkcode Move}. + **/ export class PokemonMove { public moveId: Moves; public ppUsed: integer; diff --git a/src/phases.ts b/src/phases.ts index 0bd4cb9469d..ac38796784b 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -2710,9 +2710,10 @@ export class MoveEffectPhase extends PokemonPhase { } const overridden = new Utils.BooleanHolder(false); + const move = this.move.getMove(); // Assume single target for override - applyMoveAttrs(OverrideMoveEffectAttr, user, this.getTarget(), this.move.getMove(), overridden, this.move.virtual).then(() => { + applyMoveAttrs(OverrideMoveEffectAttr, user, this.getTarget(), move, overridden, this.move.virtual).then(() => { if (overridden.value) { return this.end(); @@ -2723,8 +2724,8 @@ export class MoveEffectPhase extends PokemonPhase { if (user.turnData.hitsLeft === undefined) { const hitCount = new Utils.IntegerHolder(1); // Assume single target for multi hit - applyMoveAttrs(MultiHitAttr, user, this.getTarget(), this.move.getMove(), hitCount); - if (this.move.getMove() instanceof AttackMove && !this.move.getMove().hasAttr(FixedDamageAttr)) { + applyMoveAttrs(MultiHitAttr, user, this.getTarget(), move, hitCount); + if (move instanceof AttackMove && !move.hasAttr(FixedDamageAttr)) { this.scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, hitCount, new Utils.IntegerHolder(0)); } user.turnData.hitsLeft = user.turnData.hitCount = hitCount.value; @@ -2735,13 +2736,13 @@ export class MoveEffectPhase extends PokemonPhase { const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ])); const activeTargets = targets.map(t => t.isActive(true)); - if (!activeTargets.length || (!this.move.getMove().hasAttr(VariableTargetAttr) && !this.move.getMove().isMultiTarget() && !targetHitChecks[this.targets[0]])) { + if (!activeTargets.length || (!move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]])) { user.turnData.hitCount = 1; user.turnData.hitsLeft = 1; if (activeTargets.length) { this.scene.queueMessage(getPokemonMessage(user, "'s\nattack missed!")); moveHistoryEntry.result = MoveResult.MISS; - applyMoveAttrs(MissEffectAttr, user, null, this.move.getMove()); + applyMoveAttrs(MissEffectAttr, user, null, move); } else { this.scene.queueMessage(i18next.t("battle:attackFailed")); moveHistoryEntry.result = MoveResult.FAIL; @@ -2752,7 +2753,7 @@ export class MoveEffectPhase extends PokemonPhase { const applyAttrs: Promise[] = []; // Move animation only needs one target - new MoveAnim(this.move.getMove().id as Moves, user, this.getTarget()?.getBattlerIndex()).play(this.scene, () => { + new MoveAnim(move.id as Moves, user, this.getTarget()?.getBattlerIndex()).play(this.scene, () => { for (const target of targets) { if (!targetHitChecks[target.getBattlerIndex()]) { user.turnData.hitCount = 1; @@ -2761,31 +2762,31 @@ export class MoveEffectPhase extends PokemonPhase { if (moveHistoryEntry.result === MoveResult.PENDING) { moveHistoryEntry.result = MoveResult.MISS; } - applyMoveAttrs(MissEffectAttr, user, null, this.move.getMove()); + applyMoveAttrs(MissEffectAttr, user, null, move); continue; } - const isProtected = !this.move.getMove().hasFlag(MoveFlags.IGNORE_PROTECT) && target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType)); + const isProtected = !move.hasFlag(MoveFlags.IGNORE_PROTECT) && target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType)); const firstHit = moveHistoryEntry.result !== MoveResult.SUCCESS; moveHistoryEntry.result = MoveResult.SUCCESS; - const hitResult = !isProtected ? target.apply(user, this.move) : HitResult.NO_EFFECT; + const hitResult = !isProtected ? target.apply(user, move) : HitResult.NO_EFFECT; this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger); applyAttrs.push(new Promise(resolve => { - applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit), - user, target, this.move.getMove()).then(() => { + applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit), + user, target, move).then(() => { if (hitResult !== HitResult.FAIL) { - const chargeEffect = !!this.move.getMove().getAttrs(ChargeAttr).find(ca => ca.usedChargeEffect(user, this.getTarget(), this.move.getMove())); + const chargeEffect = !!move.getAttrs(ChargeAttr).find(ca => ca.usedChargeEffect(user, this.getTarget(), move)); // Charge attribute with charge effect takes all effect attributes and applies them to charge stage, so ignore them if this is present - Utils.executeIf(!chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY - && (attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit), user, target, this.move.getMove())).then(() => { + Utils.executeIf(!chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY + && attr.selfTarget && (!attr.firstHitOnly || firstHit), user, target, move)).then(() => { if (hitResult !== HitResult.NO_EFFECT) { - applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY - && !(attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit), user, target, this.move.getMove()).then(() => { + applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY + && !attr.selfTarget && (!attr.firstHitOnly || firstHit), user, target, move).then(() => { if (hitResult < HitResult.NO_EFFECT) { const flinched = new Utils.BooleanHolder(false); user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched); @@ -2793,15 +2794,15 @@ export class MoveEffectPhase extends PokemonPhase { target.addTag(BattlerTagType.FLINCHED, undefined, this.move.moveId, user.id); } } - Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT && (!attr.firstHitOnly || firstHit), - user, target, this.move.getMove()).then(() => { - return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move, hitResult).then(() => { - if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) { + Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.HIT && (!attr.firstHitOnly || firstHit), + user, target, move).then(() => { + return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, move, hitResult).then(() => { + if (!user.isPlayer() && move instanceof AttackMove) { user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target); } })).then(() => { - applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move, hitResult).then(() => { - if (this.move.getMove() instanceof AttackMove) { + applyPostAttackAbAttrs(PostAttackAbAttr, user, target, move, hitResult).then(() => { + if (move instanceof AttackMove) { this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target.getFieldIndex()); } resolve(); @@ -2811,7 +2812,7 @@ export class MoveEffectPhase extends PokemonPhase { ).then(() => resolve()); }); } else { - applyMoveAttrs(NoEffectAttr, user, null, this.move.getMove()).then(() => resolve()); + applyMoveAttrs(NoEffectAttr, user, null, move).then(() => resolve()); } }); } else { @@ -2821,8 +2822,8 @@ export class MoveEffectPhase extends PokemonPhase { })); } // Trigger effect which should only apply one time after all targeted effects have already applied - const postTarget = applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_TARGET, - user, null, this.move.getMove()); + const postTarget = applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_TARGET, + user, null, move); if (applyAttrs.length) { // If there is a pending asynchronous move effect, do this after applyAttrs[applyAttrs.length - 1]?.then(() => postTarget); @@ -2836,6 +2837,8 @@ export class MoveEffectPhase extends PokemonPhase { } end() { + const move = this.move.getMove(); + move.type = move.defaultType; const user = this.getUserPokemon(); if (user) { if (--user.turnData.hitsLeft >= 1 && this.getTarget()?.isActive()) { @@ -3542,7 +3545,7 @@ export class FaintPhase extends PokemonPhase { if (pokemon.turnData?.attacksReceived?.length) { const lastAttack = pokemon.turnData.attacksReceived[0]; - applyPostFaintAbAttrs(PostFaintAbAttr, pokemon, this.scene.getPokemonById(lastAttack.sourceId), new PokemonMove(lastAttack.move), lastAttack.result); + applyPostFaintAbAttrs(PostFaintAbAttr, pokemon, this.scene.getPokemonById(lastAttack.sourceId), new PokemonMove(lastAttack.move).getMove(), lastAttack.result); } const alivePlayField = this.scene.getField(true); From 63d98fe70e22b2ddedea5ffa6bc97e0482e55cc1 Mon Sep 17 00:00:00 2001 From: Matthew Olker Date: Fri, 7 Jun 2024 17:05:44 -0400 Subject: [PATCH 123/129] Hotfix: types breaking gh-pages action --- src/data/ability.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 2af456a2f70..1e132b3d079 100755 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -3030,7 +3030,7 @@ export class PostFaintClearWeatherAbAttr extends PostFaintAbAttr { * @param args N/A * @returns {boolean} Returns true if the weather clears, otherwise false. */ - applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { + applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { const weatherType = pokemon.scene.arena.weather.weatherType; let turnOffWeather = false; @@ -3439,7 +3439,7 @@ export class IceFaceMoveImmunityAbAttr extends MoveImmunityAbAttr { * @param {any[]} args - Additional arguments. * @returns {boolean} - Whether the immunity was applied. */ - applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { + applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { const isImmune = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args); if (isImmune) { @@ -4465,7 +4465,7 @@ export function initAbilities() { .conditionalAttr(getWeatherCondition(WeatherType.HAIL, WeatherType.SNOW), PostSummonAddBattlerTagAbAttr, BattlerTagType.ICE_FACE, 0) // When weather changes to HAIL or SNOW while pokemon is fielded, add BattlerTagType.ICE_FACE .attr(PostWeatherChangeAddBattlerTagAttr, BattlerTagType.ICE_FACE, 0, WeatherType.HAIL, WeatherType.SNOW) - .attr(IceFaceMoveImmunityAbAttr, (target, user, move) => move.getMove().category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE)) + .attr(IceFaceMoveImmunityAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE)) .ignorable(), new Ability(Abilities.POWER_SPOT, 8) .unimplemented(), From 83c11a086505880d28b662c31945d8260f104c04 Mon Sep 17 00:00:00 2001 From: Madmadness65 Date: Fri, 7 Jun 2024 16:21:18 -0500 Subject: [PATCH 124/129] Allow Partner Pikachu & Eevee to Gigantamax Note that they will revert back to regular Pikachus and Eevees if deactivated due to an existing bug with the form changes. --- src/data/pokemon-forms.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index da6bac42ac7..77991ee6434 100644 --- a/src/data/pokemon-forms.ts +++ b/src/data/pokemon-forms.ts @@ -363,7 +363,8 @@ export const pokemonFormChanges: PokemonFormChanges = { new SpeciesFormChange(Species.PIDGEOT, "", SpeciesFormKey.MEGA, new SpeciesFormChangeItemTrigger(FormChangeItem.PIDGEOTITE)) ], [Species.PIKACHU]: [ - new SpeciesFormChange(Species.PIKACHU, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS)) + new SpeciesFormChange(Species.PIKACHU, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS)), + new SpeciesFormChange(Species.PIKACHU, "partner", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS)) ], [Species.MEOWTH]: [ new SpeciesFormChange(Species.MEOWTH, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS)) @@ -397,7 +398,8 @@ export const pokemonFormChanges: PokemonFormChanges = { new SpeciesFormChange(Species.LAPRAS, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS)) ], [Species.EEVEE]: [ - new SpeciesFormChange(Species.EEVEE, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS)) + new SpeciesFormChange(Species.EEVEE, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS)), + new SpeciesFormChange(Species.EEVEE, "partner", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS)) ], [Species.SNORLAX]: [ new SpeciesFormChange(Species.SNORLAX, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS)) From eb058d7eb7c4a59edb5452fd035d7412aa543f5e Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Fri, 7 Jun 2024 17:42:48 -0400 Subject: [PATCH 125/129] [Bug] Fix Harvest not checking stack limits for berries (#1920) --- src/data/ability.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 1e132b3d079..ebaf1f47504 100755 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2590,7 +2590,7 @@ export class PostTurnLootAbAttr extends PostTurnAbAttr { if (!berryModifier) { pokemon.scene.addModifier(new BerryModifier(chosenBerry, pokemon.id, chosenBerryType, 1)); - } else { + } else if (berryModifier.stackCount < berryModifier.getMaxHeldItemCount(pokemon)) { berryModifier.stackCount++; } From f3898dbabd541026304d931eacc548dc0bec1087 Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Fri, 7 Jun 2024 17:44:35 -0400 Subject: [PATCH 126/129] [Bug] Fix female trainer names on male Backpackers/Hikers (#1919) * Fix female trainer names on male Backpackers/Hikers * Remove duplicate Backpacker Ruth --- src/data/trainer-names.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/trainer-names.ts b/src/data/trainer-names.ts index 2ad2060f233..5cd6850fba0 100644 --- a/src/data/trainer-names.ts +++ b/src/data/trainer-names.ts @@ -78,7 +78,7 @@ export const trainerNamePools = { [TrainerType.ACE_TRAINER]: [["Aaron","Allen","Blake","Brian","Gaven","Jake","Kevin","Mike","Nick","Paul","Ryan","Sean","Darin","Albert","Berke","Clyde","Edgar","George","Leroy","Owen","Parker","Randall","Ruben","Samuel","Vincent","Warren","Wilton","Zane","Alfred","Braxton","Felix","Gerald","Jonathan","Leonel","Marcel","Mitchell","Quincy","Roderick","Colby","Rolando","Yuji","Abel","Anton","Arthur","Cesar","Dalton","Dennis","Ernest","Garrett","Graham","Henry","Isaiah","Jonah","Jose","Keenan","Micah","Omar","Quinn","Rodolfo","Saul","Sergio","Skylar","Stefan","Zachery","Alton","Arabella","Bonita","Cal","Cody","French","Kobe","Paulo","Shaye","Austin","Beckett","Charlie","Corky","David","Dwayne","Elmer","Jesse","Jared","Johan","Jordan","Kipp","Lou","Terry","Tom","Webster","Billy","Doyle","Enzio","Geoff","Grant","Kelsey","Miguel","Pierce","Ray","Santino","Shel","Adelbert","Bence","Emil","Evan","Mathis","Maxim","Neil","Rico","Robbie","Theo","Viktor","Benedict","Cornelius","Hisato","Leopold","Neville","Vito","Chase","Cole","Hiroshi","Jackson","Jim","Kekoa","Makana","Yuki","Elwood","Seth","Alvin","Arjun","Arnold","Cameron","Carl","Carlton","Christopher","Dave","Dax","Dominic","Edmund","Finn","Fred","Garret","Grayson","Jace","Jaxson","Jay","Jirard","Johnson","Kayden","Kite","Louis","Mac","Marty","Percy","Raymond","Ronnie","Satch","Tim","Zach","Conner","Vince","Bedro","Boda","Botan","Daras","Dury","Herton","Rewn","Stum","Tock","Trilo","Berki","Cruik","Dazon","Desid","Dillot","Farfin","Forgon","Hebel","Morfon","Moril","Shadd","Vanhub","Bardo","Carben","Degin","Gorps","Klept","Lask","Malex","Mopar","Niled","Noxon","Teslor","Tetil"],["Beth","Carol","Cybil","Emma","Fran","Gwen","Irene","Jenn","Joyce","Kate","Kelly","Lois","Lola","Megan","Quinn","Reena","Cara","Alexa","Brooke","Caroline","Elaine","Hope","Jennifer","Jody","Julie","Lori","Mary","Michelle","Shannon","Wendy","Alexia","Alicia","Athena","Carolina","Cristin","Darcy","Dianne","Halle","Jazmyn","Katelynn","Keira","Marley","Allyson","Kathleen","Naomi","Alyssa","Ariana","Brandi","Breanna","Brenda","Brenna","Catherine","Clarice","Dana","Deanna","Destiny","Jamie","Jasmin","Kassandra","Laura","Maria","Mariah","Maya","Meagan","Mikayla","Monique","Natasha","Olivia","Sandra","Savannah","Sydney","Moira","Piper","Salma","Allison","Beverly","Cathy","Cheyenne","Clara","Dara","Eileen","Glinda","Junko","Lena","Lucille","Mariana","Olwen","Shanta","Stella","Angi","Belle","Chandra","Cora","Eve","Jacqueline","Jeanne","Juliet","Kathrine","Layla","Lucca","Melina","Miki","Nina","Sable","Shelly","Summer","Trish","Vicki","Alanza","Cordelia","Hilde","Imelda","Michele","Mireille","Claudia","Constance","Harriet","Honor","Melba","Portia","Alexis","Angela","Karla","Lindsey","Tori","Sheri","Jada","Kailee","Amanda","Annie","Kindra","Kyla","Sofia","Yvette","Becky","Flora","Gloria","Buna","Ferda","Lehan","Liqui","Lomen","Neira","Atilo","Detta","Gilly","Gosney","Levens","Moden","Rask","Rateis","Rosno","Tynan","Veron","Zoel","Cida","Dibsin","Dodin","Ebson","Equin","Flostin","Gabsen","Halsion","Hileon","Quelor","Rapeel","Roze","Tensin"]], [TrainerType.ARTIST]: [["Ismael","William","Horton","Pierre","Zach","Gough","Salvador","Vincent","Duncan"],["Georgia"]], [TrainerType.BACKERS]: [["Alf & Fred","Hawk & Dar","Joe & Ross","Les & Web","Masa & Yas","Stu & Art"],["Ai & Ciel","Ami & Eira","Cam & Abby","Fey & Sue","Kat & Phae","Kay & Ali","Ava & Aya","Cleo & Rio","May & Mal"]], - [TrainerType.BACKPACKER]: [["Alexander","Carlos","Herman","Jerome","Keane","Kelsey","Kiyo","Michael","Nate","Peter","Sam","Stephen","Talon","Terrance","Toru","Waylon","Boone","Clifford","Ivan","Kendall","Lowell","Randall","Reece","Roland","Shane","Walt","Farid","Heike","Joren","Lane","Roderick","Darnell","Deon","Emory","Graeme","Grayson","Ashley","Mikiko","Kiana","Perdy","Maria","Yuho","Peren","Barbara","Diane","Ruth","Aitor","Alex","Arturo","Asier","Jaime","Jonathan","Julio","Kevin","Kosuke","Lander","Markel","Mateo","Nil","Pau","Samuel"],["Anna","Corin","Elaine","Emi","Jill","Kumiko","Liz","Lois","Lora","Molly","Patty","Ruth","Vicki","Annie","Blossom","Clara","Eileen","Mae","Myra","Rachel","Tami"]], + [TrainerType.BACKPACKER]: [["Alexander","Carlos","Herman","Jerome","Keane","Kelsey","Kiyo","Michael","Nate","Peter","Sam","Stephen","Talon","Terrance","Toru","Waylon","Boone","Clifford","Ivan","Kendall","Lowell","Randall","Reece","Roland","Shane","Walt","Farid","Heike","Joren","Lane","Roderick","Darnell","Deon","Emory","Graeme","Grayson","Aitor","Alex","Arturo","Asier","Jaime","Jonathan","Julio","Kevin","Kosuke","Lander","Markel","Mateo","Nil","Pau","Samuel"],["Anna","Corin","Elaine","Emi","Jill","Kumiko","Liz","Lois","Lora","Molly","Patty","Ruth","Vicki","Annie","Blossom","Clara","Eileen","Mae","Myra","Rachel","Tami","Ashley","Mikiko","Kiana","Perdy","Maria","Yuho","Peren","Barbara","Diane"]], [TrainerType.BAKER]: ["Chris","Jenn","Lilly"], [TrainerType.BEAUTY]: ["Cassie","Julia","Olivia","Samantha","Valerie","Victoria","Bridget","Connie","Jessica","Johanna","Melissa","Sheila","Shirley","Tiffany","Namiko","Thalia","Grace","Lola","Lori","Maura","Tamia","Cyndy","Devon","Gabriella","Harley","Lindsay","Nicola","Callie","Charlotte","Kassandra","December","Fleming","Nikola","Aimee","Anais","Brigitte","Cassandra","Andrea","Brittney","Carolyn","Krystal","Alexis","Alice","Aina","Anya","Arianna","Aubrey","Beverly","Camille","Beauty","Evette","Hansol","Haruka","Jill","Jo","Lana","Lois","Lucy","Mai","Nickie","Nicole","Prita","Rose","Shelly","Suzy","Tessa","Anita","Alissa","Rita","Cudsy","Eloff","Miru","Minot","Nevah","Niven","Ogoin"], [TrainerType.BIKER]: ["Charles","Dwayne","Glenn","Harris","Joel","Riley","Zeke","Alex","Billy","Ernest","Gerald","Hideo","Isaac","Jared","Jaren","Jaxon","Jordy","Lao","Lukas","Malik","Nikolas","Ricardo","Ruben","Virgil","William","Aiden","Dale","Dan","Jacob","Markey","Reese","Teddy","Theron","Jeremy","Morgann","Phillip","Philip","Stanley","Dillon"], @@ -93,7 +93,7 @@ export const trainerNamePools = { [TrainerType.FISHERMAN]: ["Andre","Arnold","Barney","Chris","Edgar","Henry","Jonah","Justin","Kyle","Martin","Marvin","Ralph","Raymond","Scott","Stephen","Wilton","Tully","Andrew","Barny","Carter","Claude","Dale","Elliot","Eugene","Ivan","Ned","Nolan","Roger","Ronald","Wade","Wayne","Darian","Kai","Chip","Hank","Kaden","Tommy","Tylor","Alec","Brett","Cameron","Cody","Cole","Cory","Erick","George","Joseph","Juan","Kenneth","Luc","Miguel","Travis","Walter","Zachary","Josh","Gideon","Kyler","Liam","Murphy","Bruce","Damon","Devon","Hubert","Jones","Lydon","Mick","Pete","Sean","Sid","Vince","Bucky","Dean","Eustace","Kenzo","Leroy","Mack","Ryder","Ewan","Finn","Murray","Seward","Shad","Wharton","Finley","Fisher","Fisk","River","Sheaffer","Timin","Carl","Ernest","Hal","Herbert","Hisato","Mike","Vernon","Harriet","Marina","Chase"], [TrainerType.GUITARIST]: ["Anna","Beverly","January","Tina","Alicia","Claudia","Julia","Lidia","Mireia","Noelia","Sara","Sheila","Tatiana"], [TrainerType.HARLEQUIN]: ["Charley","Ian","Jack","Kerry","Louis","Pat","Paul","Rick","Anders","Clarence","Gary"], - [TrainerType.HIKER]: ["Anthony","Bailey","Benjamin","Daniel","Erik","Jim","Kenny","Leonard","Michael","Parry","Phillip","Russell","Sidney","Tim","Timothy","Alan","Brice","Clark","Eric","Lenny","Lucas","Mike","Trent","Devan","Eli","Marc","Sawyer","Allen","Daryl","Dudley","Earl","Franklin","Jeremy","Marcos","Nob","Oliver","Wayne","Alexander","Damon","Jonathan","Justin","Kevin","Lorenzo","Louis","Maurice","Nicholas","Reginald","Robert","Theodore","Bruce","Clarke","Devin","Dwight","Edwin","Eoin","Noland","Russel","Andy","Bret","Darrell","Gene","Hardy","Hugh","Jebediah","Jeremiah","Kit","Neil","Terrell","Don","Doug","Hunter","Jared","Jerome","Keith","Manuel","Markus","Otto","Shelby","Stephen","Teppei","Tobias","Wade","Zaiem","Aaron","Alain","Bergin","Bernard","Brent","Corwin","Craig","Delmon","Dunstan","Orestes","Ross","Davian","Calhoun","David","Gabriel","Ryan","Thomas","Travis","Zachary","Anuhea","Barnaby","Claus","Collin","Colson","Dexter","Dillan","Eugine","Farkas","Hisato","Julius","Kenji","Irwin","Lionel","Paul","Richter","Valentino","Donald","Douglas","Kevyn","Angela","Carla","Celia","Daniela","Estela","Fatima","Helena","Leire","Lucia","Luna","Manuela","Mar","Marina","Miyu","Nancy","Nerea","Paula","Rocio","Yanira","Chester"], + [TrainerType.HIKER]: ["Anthony","Bailey","Benjamin","Daniel","Erik","Jim","Kenny","Leonard","Michael","Parry","Phillip","Russell","Sidney","Tim","Timothy","Alan","Brice","Clark","Eric","Lenny","Lucas","Mike","Trent","Devan","Eli","Marc","Sawyer","Allen","Daryl","Dudley","Earl","Franklin","Jeremy","Marcos","Nob","Oliver","Wayne","Alexander","Damon","Jonathan","Justin","Kevin","Lorenzo","Louis","Maurice","Nicholas","Reginald","Robert","Theodore","Bruce","Clarke","Devin","Dwight","Edwin","Eoin","Noland","Russel","Andy","Bret","Darrell","Gene","Hardy","Hugh","Jebediah","Jeremiah","Kit","Neil","Terrell","Don","Doug","Hunter","Jared","Jerome","Keith","Manuel","Markus","Otto","Shelby","Stephen","Teppei","Tobias","Wade","Zaiem","Aaron","Alain","Bergin","Bernard","Brent","Corwin","Craig","Delmon","Dunstan","Orestes","Ross","Davian","Calhoun","David","Gabriel","Ryan","Thomas","Travis","Zachary","Anuhea","Barnaby","Claus","Collin","Colson","Dexter","Dillan","Eugine","Farkas","Hisato","Julius","Kenji","Irwin","Lionel","Paul","Richter","Valentino","Donald","Douglas","Kevyn","Chester"], //["Angela","Carla","Celia","Daniela","Estela","Fatima","Helena","Leire","Lucia","Luna","Manuela","Mar","Marina","Miyu","Nancy","Nerea","Paula","Rocio","Yanira"] [TrainerType.HOOLIGANS]: ["Jim & Cas","Rob & Sal"], [TrainerType.HOOPSTER]: ["Bobby","John","Lamarcus","Derrick","Nicolas"], [TrainerType.INFIELDER]: ["Alex","Connor","Todd"], From ece7c9f2d553cc20278bd1f54402eb70b818d0f2 Mon Sep 17 00:00:00 2001 From: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> Date: Fri, 7 Jun 2024 23:51:49 +0200 Subject: [PATCH 127/129] Added missing german dialogue for sailor and firebreather (#1883) --- src/locales/de/dialogue.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/locales/de/dialogue.ts b/src/locales/de/dialogue.ts index d9ad65ee8d4..6b0bdc7c16a 100644 --- a/src/locales/de/dialogue.ts +++ b/src/locales/de/dialogue.ts @@ -369,26 +369,26 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "firebreather": { "encounter": { - 1: "My flames shall devour you!", - 2: "My soul is on fire. I'll show you how hot it burns!", - 3: "Step right up and take a look!" + 1: "Meine Flammen werden dich verschlingen!", + 2: "Meine Seele hat Feuer gefangen. Ich werde dir zeigen, wie heiß sie brennt!", + 3: "Komm näher und sieh dir meine Flammen an!" }, "victory": { - 1: "I burned down to ashes...", - 2: "Yow! That's hot!", - 3: "Ow! I scorched the tip of my nose!" + 1: "Verbrannt bis zur Asche...", + 2: "Yow! Das ist heiß!", + 3: "Auuu! Ich habe mir die Nasenspitze verbrannt!" }, }, "sailor": { "encounter": { - 1: "Matey, you're walking the plank if you lose!", - 2: "Come on then! My sailor's pride is at stake!", - 3: "Ahoy there! Are you seasick?" + 1: "Matrose, du gehst über Bord, wenn du verlierst!", + 2: "Komm schon! Mein Stolz als Seemann steht auf dem Spiel!", + 3: "Ahoj! Bist du seekrank?" }, "victory": { - 1: "Argh! Beaten by a kid!", - 2: "Your spirit sank me!", - 3: "I think it's me that's seasick..." + 1: "Argh! Von einem Kind besiegt!", + 2: "Dein Geist hat mich versenkt!", + 3: "Ich glaube, ich bin der der seekrank ist..." }, }, "brock": { From 3d87e86e5869c68fadf53a58ff19755a3978720e Mon Sep 17 00:00:00 2001 From: Madmadness65 Date: Fri, 7 Jun 2024 16:57:26 -0500 Subject: [PATCH 128/129] Fix minor typo in Surskit's German name --- src/locales/de/pokemon.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locales/de/pokemon.ts b/src/locales/de/pokemon.ts index 91b97052879..bd2ff964a9b 100644 --- a/src/locales/de/pokemon.ts +++ b/src/locales/de/pokemon.ts @@ -283,7 +283,7 @@ export const pokemon: SimpleTranslationEntries = { "ralts": "Trasla", "kirlia": "Kirlia", "gardevoir": "Gardevoir", - "surskit": "Geweiher", + "surskit": "Gehweiher", "masquerain": "Maskeregen", "shroomish": "Knilz", "breloom": "Kapilz", From 1a149bfa04133b606fad0edc150e2cce14978e94 Mon Sep 17 00:00:00 2001 From: Greenlamp2 <44787002+Greenlamp2@users.noreply.github.com> Date: Sat, 8 Jun 2024 00:33:45 +0200 Subject: [PATCH 129/129] [Testing] Flexible Testing Wrapper for Phaser-Based Battle-Scenes (#1908) * refactor executed code while importing and initializing all of these in loading-scene * reset to main * fix server url * added rule no-trailing-spaces * made progress * test somme data from a session save is working * trying to launch a battle * added fetch wrapper to load data locally * trying to mockAllSettled * pushPhase & shiftPhase * check integrity of exported session * set toke + loggedInUser in tests * progress on starting new battle * tring to test phase but it's async * mocking fetch * working mock fetch * need to handle pile of data * attempt to use real phaser classes * reorder overrides * refactored to use some real classes from phaser * removed useless things * started to work on some container mock * finished the mockContainer time to add some logic * some more mock containers * removed addMethods since there is the mock classes now * commented issues * attempt to create mockTextureManager * fix tests * mockSprite & mockText * yes but not really * yes but not really * fix tutorial callback * reached mode title * added achievement tests * fix test achievements with current state of mock * correct sequence loading for BattleScene with mockLoader ! * deep dive into next step * working wait until starter selection screen * added newGame method into wrapper * expect to save_slot * trying to manage pokemon sprite for getAll without success yet * added test for egg output * fixed egg test for June * fix tests + locate next issue to fix * we are in battle baby * added new game in one-line * export is working but export only what's in the fetch * fix start game as guest * refactored how we start a battle + cleanup * overrided mewtwo but issue with currentBattle * refactor: rename InitAchievements to initAchievements * added missing mock method * override level and pokemon forms working as intended * bringToTop Obj * remove launch battle in achivement test * fix getIndex when same pokemon * can run all tests * first attack, faint, and shop modifiers, MockClock * on method for container * added doAttack one-liner * one-line export data * removed throw error * feat: Make `scenes` property of `GameWrapper` class public The `scenes` property of the `GameWrapper` class was changed from private to public. This change allows external access to the `scenes` map, which is used to store Phaser scenes. This modification was made to enable easier manipulation and interaction with the scenes in the game. * correction * removed CanvasRenderer * added a param to remove console.log and added a param to preven scene create call * fix encounter wave 30 when it's a trainer * test double-battle * test fight without KO * test double fight no ko * fix crashing texture + added Text wrapper to log fight * fix tests on boss - trainer - rival * chore: Refactor BattleScene initialization and add new phases Refactor the BattleScene initialization code to remove unnecessary delay and improve performance. Also, add new phases for the title and unavailable states to enhance the game experience. * rework of Game tests * skipFn is working * added onNextPrompt and restore Og Start * better newGame * added skipFN in remove * not yet working test but updated interceptors * do attack work but not on PostSummonPhase phase when there is mention of silcoon and wurmple * error located, it's just a double fight, i was not there yet * single OHKO & double no OHKO * added expirationFn into next prompt * all tests are passing * working test on non damaging move from opponent * cleaned a bit * removed phaser initialisation on every tests * renamed test file * added load system data * added some ability support * added onKill & onSummon abilities test * removed useless test + cleanup * removed useless test + cleanup * fixed tests after merge main * added itemHeld endTurn trigger test (toxic orb) * added runFrom..To * added mustRun to assert currentPhase * added no-miss move to test things * cleaner restore mock * fix test * fix moxie test + game speed * improve test speed * added onOurself and onOpponent mvoe test * added onDamage test for tackle * removed timeout in intervals to run tests faster * cleanup * added never crit override + separate file per test + remove randomness in randBattleSeedInt * move folders * better org * renamed itemHeld folder to items * fix deploy.yml * cleanup * simplified the gameManager start battle and allow single pokemon in party * remove the need of mode development * added input handler to test inputs + remove time from phaser into inputController * added keyboard support * added fakeMobile support * added details * removed a console.log + added logUp * move test to folder * fixed canvas issue * added starter select tests * added some more test on starter-select * added battle-order tests * added battle-order tests * fixing Phaser RNG * ordering stats for better reading * fix tests for main * adapt battle-order test to be more readable * fix merge * fix some errors and silent all errors from gameWrapper since it's not possible to avoid them * fix mocks to manage childs & stuffs * added some docs * fix achievement test * removed an unused file * separate misc tests to clean battle.test file * added a basic french lokalization test * added i18n where it needs to be used only * revers extracted method * removed unused method * removed handler fetch since we do not test anything server related * fix test with handlers removed * added intrepid sword test * fix enum exp party --------- Co-authored-by: Frederico Santos --- src/battle-scene.ts | 13 +- src/field/pokemon.ts | 3 + src/inputs-controller.ts | 15 +- src/loading-scene.ts | 2 + src/overrides.ts | 2 + src/phases.ts | 107 +-- src/system/achv.ts | 22 +- src/system/game-data.ts | 8 +- src/test/abilities/intimidate.test.ts | 83 +++ src/test/abilities/intrepid_sword.test.ts | 65 ++ src/test/abilities/moxie.test.ts | 67 ++ src/test/achievement.test.ts | 9 - src/test/achievements/achievement.test.ts | 274 ++++++++ src/test/battle/battle-order.test.ts | 220 +++++++ src/test/battle/battle.test.ts | 248 +++++++ src/test/eggs/egg.test.ts | 33 + .../{debugImports.test.ts => imports.test.ts} | 0 src/test/inputs/inputs.test.ts | 105 +++ src/test/items/toxic_orb.test.ts | 80 +++ src/test/lokalisation/french.test.ts | 42 ++ src/test/moves/growth.test.ts | 69 ++ src/test/moves/tackle.test.ts | 85 +++ src/test/moves/tail_whip.test.ts | 66 ++ src/test/phaser.setup.ts | 5 - src/test/phases/phases.test.ts | 51 ++ src/test/pokemon.test.ts | 57 -- .../{ => settingMenu}/helpers/inGameManip.ts | 0 .../{ => settingMenu}/helpers/menuManip.ts | 0 .../rebinding_setting.test.ts | 4 +- src/test/{ => sprites}/pokemonSprite.test.ts | 4 +- .../{testUtils.ts => sprites/spritesUtils.ts} | 0 src/test/ui/starter-select.test.ts | 612 ++++++++++++++++++ src/test/utils/TextInterceptor.ts | 16 + src/test/utils/fakeMobile.html | 50 ++ src/test/utils/gameManager.ts | 223 +++++++ src/test/utils/gameManagerUtils.ts | 87 +++ src/test/utils/gameWrapper.ts | 252 ++++++++ src/test/utils/inputsHandler.ts | 115 ++++ src/test/utils/misc.test.ts | 79 +++ src/test/utils/mocks/mockClock.ts | 17 + src/test/utils/mocks/mockConsoleLog.ts | 82 +++ src/test/utils/mocks/mockFetch.ts | 32 + src/test/utils/mocks/mockLoader.ts | 42 ++ src/test/utils/mocks/mockLocalStorage.ts | 27 + src/test/utils/mocks/mockTextureManager.ts | 85 +++ .../mocks/mocksContainer/mockContainer.ts | 207 ++++++ .../mocks/mocksContainer/mockGraphics.ts | 96 +++ .../utils/mocks/mocksContainer/mockImage.ts | 11 + .../mocks/mocksContainer/mockNineslice.ts | 20 + .../utils/mocks/mocksContainer/mockPolygon.ts | 9 + .../mocks/mocksContainer/mockRectangle.ts | 74 +++ .../utils/mocks/mocksContainer/mockSprite.ts | 205 ++++++ .../utils/mocks/mocksContainer/mockText.ts | 265 ++++++++ src/test/utils/phaseInterceptor.ts | 279 ++++++++ src/test/utils/saves/everything.prsv | 1 + src/test/vitest.setup.ts | 3 +- src/ui/game-stats-ui-handler.ts | 1 + src/ui/starter-select-ui-handler.ts | 6 +- src/utils.test.ts | 8 +- vitest.config.js | 1 + 60 files changed, 4487 insertions(+), 157 deletions(-) create mode 100644 src/test/abilities/intimidate.test.ts create mode 100644 src/test/abilities/intrepid_sword.test.ts create mode 100644 src/test/abilities/moxie.test.ts delete mode 100644 src/test/achievement.test.ts create mode 100644 src/test/achievements/achievement.test.ts create mode 100644 src/test/battle/battle-order.test.ts create mode 100644 src/test/battle/battle.test.ts create mode 100644 src/test/eggs/egg.test.ts rename src/test/{debugImports.test.ts => imports.test.ts} (100%) create mode 100644 src/test/inputs/inputs.test.ts create mode 100644 src/test/items/toxic_orb.test.ts create mode 100644 src/test/lokalisation/french.test.ts create mode 100644 src/test/moves/growth.test.ts create mode 100644 src/test/moves/tackle.test.ts create mode 100644 src/test/moves/tail_whip.test.ts delete mode 100644 src/test/phaser.setup.ts create mode 100644 src/test/phases/phases.test.ts delete mode 100644 src/test/pokemon.test.ts rename src/test/{ => settingMenu}/helpers/inGameManip.ts (100%) rename src/test/{ => settingMenu}/helpers/menuManip.ts (100%) rename src/test/{ => settingMenu}/rebinding_setting.test.ts (99%) rename src/test/{ => sprites}/pokemonSprite.test.ts (98%) rename src/test/{testUtils.ts => sprites/spritesUtils.ts} (100%) create mode 100644 src/test/ui/starter-select.test.ts create mode 100644 src/test/utils/TextInterceptor.ts create mode 100644 src/test/utils/fakeMobile.html create mode 100644 src/test/utils/gameManager.ts create mode 100644 src/test/utils/gameManagerUtils.ts create mode 100644 src/test/utils/gameWrapper.ts create mode 100644 src/test/utils/inputsHandler.ts create mode 100644 src/test/utils/misc.test.ts create mode 100644 src/test/utils/mocks/mockClock.ts create mode 100644 src/test/utils/mocks/mockConsoleLog.ts create mode 100644 src/test/utils/mocks/mockFetch.ts create mode 100644 src/test/utils/mocks/mockLoader.ts create mode 100644 src/test/utils/mocks/mockLocalStorage.ts create mode 100644 src/test/utils/mocks/mockTextureManager.ts create mode 100644 src/test/utils/mocks/mocksContainer/mockContainer.ts create mode 100644 src/test/utils/mocks/mocksContainer/mockGraphics.ts create mode 100644 src/test/utils/mocks/mocksContainer/mockImage.ts create mode 100644 src/test/utils/mocks/mocksContainer/mockNineslice.ts create mode 100644 src/test/utils/mocks/mocksContainer/mockPolygon.ts create mode 100644 src/test/utils/mocks/mocksContainer/mockRectangle.ts create mode 100644 src/test/utils/mocks/mocksContainer/mockSprite.ts create mode 100644 src/test/utils/mocks/mocksContainer/mockText.ts create mode 100644 src/test/utils/phaseInterceptor.ts create mode 100644 src/test/utils/saves/everything.prsv diff --git a/src/battle-scene.ts b/src/battle-scene.ts index cce26a5adf6..df827f5201c 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -159,7 +159,7 @@ export default class BattleScene extends SceneBase { public gameData: GameData; public sessionSlotId: integer; - private phaseQueue: Phase[]; + public phaseQueue: Phase[]; private phaseQueuePrepend: Phase[]; private phaseQueuePrependSpliceIndex: integer; private nextCommandPhaseQueue: Phase[]; @@ -201,7 +201,7 @@ export default class BattleScene extends SceneBase { public arenaFlyout: ArenaFlyout; private fieldOverlay: Phaser.GameObjects.Rectangle; - private modifiers: PersistentModifier[]; + public modifiers: PersistentModifier[]; private enemyModifiers: PersistentModifier[]; public uiContainer: Phaser.GameObjects.Container; public ui: UI; @@ -300,7 +300,8 @@ export default class BattleScene extends SceneBase { this.fieldSpritePipeline = new FieldSpritePipeline(this.game); (this.renderer as Phaser.Renderer.WebGL.WebGLRenderer).pipelines.add("FieldSprite", this.fieldSpritePipeline); - this.time.delayedCall(20, () => this.launchBattle()); + + this.launchBattle(); } update() { @@ -947,7 +948,8 @@ export default class BattleScene extends SceneBase { } newBattle(waveIndex?: integer, battleType?: BattleType, trainerData?: TrainerData, double?: boolean): Battle { - const newWaveIndex = waveIndex || ((this.currentBattle?.waveIndex || (startingWave - 1)) + 1); + const _startingWave = Overrides.STARTING_WAVE_OVERRIDE || startingWave; + const newWaveIndex = waveIndex || ((this.currentBattle?.waveIndex || (_startingWave - 1)) + 1); let newDouble: boolean; let newBattleType: BattleType; let newTrainer: Trainer; @@ -1007,6 +1009,9 @@ export default class BattleScene extends SceneBase { if (Overrides.DOUBLE_BATTLE_OVERRIDE) { newDouble = true; } + if (Overrides.SINGLE_BATTLE_OVERRIDE) { + newDouble = false; + } const lastBattle = this.currentBattle; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 65742d18b94..ff277fc865b 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1727,6 +1727,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } const critChance = [24, 8, 2, 1][Math.max(0, Math.min(critLevel.value, 3))]; isCritical = !source.getTag(BattlerTagType.NO_CRIT) && (critChance === 1 || !this.scene.randBattleSeedInt(critChance)); + if (Overrides.NEVER_CRIT_OVERRIDE) { + isCritical = false; + } } if (isCritical) { const blockCrit = new Utils.BooleanHolder(false); diff --git a/src/inputs-controller.ts b/src/inputs-controller.ts index ac5d339c048..3ef8fa24115 100644 --- a/src/inputs-controller.ts +++ b/src/inputs-controller.ts @@ -94,7 +94,6 @@ export class InputsController { private buttonLock: Button; private interactions: Map> = new Map(); - private time: Phaser.Time.Clock; private configs: Map = new Map(); public gamepadSupport: boolean = true; @@ -121,7 +120,6 @@ export class InputsController { constructor(scene: BattleScene) { this.scene = scene; - this.time = this.scene.time; this.selectedDevice = { [Device.GAMEPAD]: null, [Device.KEYBOARD]: "default" @@ -246,6 +244,9 @@ export class InputsController { * If an interaction is valid and should be processed, it emits an 'input_down' event with details of the interaction. */ update(): void { + if (this.pauseUpdate) { + return; + } for (const b of Utils.getEnumValues(Button).reverse()) { if ( this.interactions.hasOwnProperty(b) && @@ -256,8 +257,7 @@ export class InputsController { if ( (!this.gamepadSupport && this.interactions[b].source === "gamepad") || (this.interactions[b].source === "gamepad" && this.interactions[b].sourceName && this.interactions[b].sourceName !== this.selectedDevice[Device.GAMEPAD]) || - (this.interactions[b].source === "keyboard" && this.interactions[b].sourceName && this.interactions[b].sourceName !== this.selectedDevice[Device.KEYBOARD]) || - this.pauseUpdate + (this.interactions[b].source === "keyboard" && this.interactions[b].sourceName && this.interactions[b].sourceName !== this.selectedDevice[Device.KEYBOARD]) ) { // Deletes the last interaction for a button if gamepad is disabled. this.delLastProcessedMovementTime(b as Button); @@ -548,7 +548,8 @@ export class InputsController { if (!this.isButtonLocked(button)) { return false; } - if (this.time.now - this.interactions[button].pressTime >= repeatInputDelayMillis) { + const duration = Date.now() - this.interactions[button].pressTime; + if (duration >= repeatInputDelayMillis) { return true; } } @@ -573,7 +574,7 @@ export class InputsController { return; } this.setButtonLock(button); - this.interactions[button].pressTime = this.time.now; + this.interactions[button].pressTime = Date.now(); this.interactions[button].isPressed = true; this.interactions[button].source = source; this.interactions[button].sourceName = sourceName.toLowerCase(); @@ -633,7 +634,7 @@ export class InputsController { this.interactions[b].sourceName = null; } } - setTimeout(() => this.pauseUpdate = false, 500); + this.pauseUpdate = false; } /** diff --git a/src/loading-scene.ts b/src/loading-scene.ts index 5713bf69fde..8bb811e3d9b 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -16,6 +16,7 @@ import {initPokemonForms} from "#app/data/pokemon-forms"; import {initSpecies} from "#app/data/pokemon-species"; import {initMoves} from "#app/data/move"; import {initAbilities} from "#app/data/ability"; +import {initAchievements} from "#app/system/achv"; import {initTrainerTypeDialogue} from "#app/data/dialogue"; import i18next from "i18next"; import { initStatsKeys } from "./ui/game-stats-ui-handler"; @@ -328,6 +329,7 @@ export class LoadingScene extends SceneBase { this.loadLoadingScreen(); + initAchievements(); initStatsKeys(); initPokemonPrevolutions(); initBiomes(); diff --git a/src/overrides.ts b/src/overrides.ts index 148dc352ae9..f8e3152de98 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -29,6 +29,7 @@ import { modifierTypes } from "./modifier/modifier-type"; export const SEED_OVERRIDE: string = ""; export const WEATHER_OVERRIDE: WeatherType = WeatherType.NONE; export const DOUBLE_BATTLE_OVERRIDE: boolean = false; +export const SINGLE_BATTLE_OVERRIDE: boolean = false; export const STARTING_WAVE_OVERRIDE: integer = 0; export const STARTING_BIOME_OVERRIDE: Biome = Biome.TOWN; export const ARENA_TINT_OVERRIDE: TimeOfDay = null; @@ -110,6 +111,7 @@ export const OPP_MODIFIER_OVERRIDE: Array = []; export const STARTING_HELD_ITEMS_OVERRIDE: Array = []; export const OPP_HELD_ITEMS_OVERRIDE: Array = []; +export const NEVER_CRIT_OVERRIDE: boolean = false; /** * An array of items by keys as defined in the "modifierTypes" object in the "modifier/modifier-type.ts" file. diff --git a/src/phases.ts b/src/phases.ts index ac38796784b..a235349cd6f 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -149,7 +149,7 @@ export class LoginPhase extends Phase { export class TitlePhase extends Phase { private loaded: boolean; private lastSessionData: SessionSaveData; - private gameMode: GameModes; + public gameMode: GameModes; constructor(scene: BattleScene) { super(scene); @@ -527,60 +527,63 @@ export class SelectStarterPhase extends Phase { return this.end(); } this.scene.sessionSlotId = slotId; - - const party = this.scene.getParty(); - const loadPokemonAssets: Promise[] = []; - starters.forEach((starter: Starter, i: integer) => { - if (!i && Overrides.STARTER_SPECIES_OVERRIDE) { - starter.species = getPokemonSpecies(Overrides.STARTER_SPECIES_OVERRIDE as Species); - } - const starterProps = this.scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); - let starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0)); - if (!i && Overrides.STARTER_SPECIES_OVERRIDE) { - starterFormIndex = Overrides.STARTER_FORM_OVERRIDE; - } - let starterGender = starter.species.malePercent !== null - ? !starterProps.female ? Gender.MALE : Gender.FEMALE - : Gender.GENDERLESS; - if (Overrides.GENDER_OVERRIDE !== null) { - starterGender = Overrides.GENDER_OVERRIDE; - } - const starterIvs = this.scene.gameData.dexData[starter.species.speciesId].ivs.slice(0); - const starterPokemon = this.scene.addPlayerPokemon(starter.species, this.scene.gameMode.getStartingLevel(), starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, starterIvs, starter.nature); - starterPokemon.tryPopulateMoveset(starter.moveset); - if (starter.passive) { - starterPokemon.passive = true; - } - starterPokemon.luck = this.scene.gameData.getDexAttrLuck(this.scene.gameData.dexData[starter.species.speciesId].caughtAttr); - if (starter.pokerus) { - starterPokemon.pokerus = true; - } - if (this.scene.gameMode.isSplicedOnly) { - starterPokemon.generateFusionSpecies(true); - } - starterPokemon.setVisible(false); - party.push(starterPokemon); - loadPokemonAssets.push(starterPokemon.loadAssets()); - }); - overrideModifiers(this.scene); - overrideHeldItems(this.scene, party[0]); - Promise.all(loadPokemonAssets).then(() => { - SoundFade.fadeOut(this.scene, this.scene.sound.get("menu"), 500, true); - this.scene.time.delayedCall(500, () => this.scene.playBgm()); - if (this.scene.gameMode.isClassic) { - this.scene.gameData.gameStats.classicSessionsPlayed++; - } else { - this.scene.gameData.gameStats.endlessSessionsPlayed++; - } - this.scene.newBattle(); - this.scene.arena.init(); - this.scene.sessionPlayTime = 0; - this.scene.lastSavePlayTime = 0; - this.end(); - }); + this.initBattle(starters); }); }, this.gameMode); } + + initBattle(starters: Starter[]) { + const party = this.scene.getParty(); + const loadPokemonAssets: Promise[] = []; + starters.forEach((starter: Starter, i: integer) => { + if (!i && Overrides.STARTER_SPECIES_OVERRIDE) { + starter.species = getPokemonSpecies(Overrides.STARTER_SPECIES_OVERRIDE as Species); + } + const starterProps = this.scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); + let starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0)); + if (!i && Overrides.STARTER_SPECIES_OVERRIDE) { + starterFormIndex = Overrides.STARTER_FORM_OVERRIDE; + } + let starterGender = starter.species.malePercent !== null + ? !starterProps.female ? Gender.MALE : Gender.FEMALE + : Gender.GENDERLESS; + if (Overrides.GENDER_OVERRIDE !== null) { + starterGender = Overrides.GENDER_OVERRIDE; + } + const starterIvs = this.scene.gameData.dexData[starter.species.speciesId].ivs.slice(0); + const starterPokemon = this.scene.addPlayerPokemon(starter.species, this.scene.gameMode.getStartingLevel(), starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, starterIvs, starter.nature); + starterPokemon.tryPopulateMoveset(starter.moveset); + if (starter.passive) { + starterPokemon.passive = true; + } + starterPokemon.luck = this.scene.gameData.getDexAttrLuck(this.scene.gameData.dexData[starter.species.speciesId].caughtAttr); + if (starter.pokerus) { + starterPokemon.pokerus = true; + } + if (this.scene.gameMode.isSplicedOnly) { + starterPokemon.generateFusionSpecies(true); + } + starterPokemon.setVisible(false); + party.push(starterPokemon); + loadPokemonAssets.push(starterPokemon.loadAssets()); + }); + overrideModifiers(this.scene); + overrideHeldItems(this.scene, party[0]); + Promise.all(loadPokemonAssets).then(() => { + SoundFade.fadeOut(this.scene, this.scene.sound.get("menu"), 500, true); + this.scene.time.delayedCall(500, () => this.scene.playBgm()); + if (this.scene.gameMode.isClassic) { + this.scene.gameData.gameStats.classicSessionsPlayed++; + } else { + this.scene.gameData.gameStats.endlessSessionsPlayed++; + } + this.scene.newBattle(); + this.scene.arena.init(); + this.scene.sessionPlayTime = 0; + this.scene.lastSavePlayTime = 0; + this.end(); + }); + } } export class BattlePhase extends Phase { diff --git a/src/system/achv.ts b/src/system/achv.ts index f52d547a53f..364b7e0c579 100644 --- a/src/system/achv.ts +++ b/src/system/achv.ts @@ -40,6 +40,10 @@ export class Achv { return i18next.t(`achv:${this.localizationKey}.name`); } + getDescription(): string { + return this.description; + } + getIconImage(): string { return this.iconImage; } @@ -259,14 +263,12 @@ export const achvs = { CLASSIC_VICTORY: new Achv("CLASSIC_VICTORY","", "CLASSIC_VICTORY.description", "relic_crown", 150), }; -{ - (function() { - const achvKeys = Object.keys(achvs); - achvKeys.forEach((a: string, i: integer) => { - achvs[a].id = a; - if (achvs[a].hasParent) { - achvs[a].parentId = achvKeys[i - 1]; - } - }); - })(); +export function initAchievements() { + const achvKeys = Object.keys(achvs); + achvKeys.forEach((a: string, i: integer) => { + achvs[a].id = a; + if (achvs[a].hasParent) { + achvs[a].parentId = achvKeys[i - 1]; + } + }); } diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 8ed7f521507..c5e2cd6eb91 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -60,13 +60,13 @@ export function getDataTypeKey(dataType: GameDataType, slotId: integer = 0): str } } -function encrypt(data: string, bypassLogin: boolean): string { +export function encrypt(data: string, bypassLogin: boolean): string { return (bypassLogin ? (data: string) => btoa(data) : (data: string) => AES.encrypt(data, saveKey))(data); } -function decrypt(data: string, bypassLogin: boolean): string { +export function decrypt(data: string, bypassLogin: boolean): string { return (bypassLogin ? (data: string) => atob(data) : (data: string) => AES.decrypt(data, saveKey).toString(enc.Utf8))(data); @@ -493,7 +493,7 @@ export class GameData { }); } - private parseSystemData(dataStr: string): SystemSaveData { + parseSystemData(dataStr: string): SystemSaveData { return JSON.parse(dataStr, (k: string, v: any) => { if (k === "gameStats") { return new GameStats(v); @@ -512,7 +512,7 @@ export class GameData { }) as SystemSaveData; } - private convertSystemDataStr(dataStr: string, shorten: boolean = false): string { + convertSystemDataStr(dataStr: string, shorten: boolean = false): string { if (!shorten) { // Account for past key oversight dataStr = dataStr.replace(/\$pAttr/g, "$pa"); diff --git a/src/test/abilities/intimidate.test.ts b/src/test/abilities/intimidate.test.ts new file mode 100644 index 00000000000..b8a1d65088f --- /dev/null +++ b/src/test/abilities/intimidate.test.ts @@ -0,0 +1,83 @@ +import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest"; +import Phaser from "phaser"; +import GameManager from "#app/test/utils/gameManager"; +import * as overrides from "#app/overrides"; +import {Abilities} from "#app/data/enums/abilities"; +import {Species} from "#app/data/enums/species"; +import { + CheckSwitchPhase, CommandPhase, MessagePhase, + PostSummonPhase, + ShinySparklePhase, + ShowAbilityPhase, + StatChangePhase, + SummonPhase, + ToggleDoublePositionPhase, TurnInitPhase +} from "#app/phases"; +import {Mode} from "#app/ui/ui"; +import {BattleStat} from "#app/data/battle-stat"; + + +describe("Abilities - Intimidate", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MIGHTYENA); + vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INTIMIDATE); + vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INTIMIDATE); + }); + + it("INTIMIDATE", async() => { + await game.runToSummon([ + Species.MIGHTYENA, + Species.MIGHTYENA, + ]); + await game.phaseInterceptor.run(PostSummonPhase); + + + expect(game.scene.getParty()[0].summonData).not.toBeUndefined(); + let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats; + expect(battleStatsPokemon[BattleStat.ATK]).toBe(0); + await game.phaseInterceptor.run(ShowAbilityPhase); + await game.phaseInterceptor.run(StatChangePhase); + battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats; + expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1); + + + await game.phaseInterceptor.run(SummonPhase); + await game.phaseInterceptor.run(ShinySparklePhase, () => game.isCurrentPhase(ToggleDoublePositionPhase)); + await game.phaseInterceptor.run(ToggleDoublePositionPhase); + game.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => { + game.setMode(Mode.MESSAGE); + game.endPhase(); + }); + await game.phaseInterceptor.run(CheckSwitchPhase); + await game.phaseInterceptor.run(PostSummonPhase); + + + let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats; + expect(battleStatsOpponent[BattleStat.ATK]).toBe(0); + await game.phaseInterceptor.run(ShowAbilityPhase); + game.scene.moveAnimations = null; // Mandatory to avoid crash + await game.phaseInterceptor.run(StatChangePhase); + battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats; + expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1); + + + await game.phaseInterceptor.run(MessagePhase); + await game.phaseInterceptor.run(TurnInitPhase); + await game.phaseInterceptor.run(CommandPhase); + }, 20000); +}); diff --git a/src/test/abilities/intrepid_sword.test.ts b/src/test/abilities/intrepid_sword.test.ts new file mode 100644 index 00000000000..f2e8eeb12cb --- /dev/null +++ b/src/test/abilities/intrepid_sword.test.ts @@ -0,0 +1,65 @@ +import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest"; +import Phaser from "phaser"; +import GameManager from "#app/test/utils/gameManager"; +import * as overrides from "#app/overrides"; +import {Abilities} from "#app/data/enums/abilities"; +import {Species} from "#app/data/enums/species"; +import { + MessagePhase, + PostSummonPhase, + ShowAbilityPhase, + StatChangePhase, + ToggleDoublePositionPhase +} from "#app/phases"; +import {BattleStat} from "#app/data/battle-stat"; + + +describe("Abilities - Intrepid Sword", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.ZACIAN); + vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INTREPID_SWORD); + vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INTREPID_SWORD); + }); + + it("INTREPID SWORD on player", async() => { + await game.runToSummon([ + Species.ZACIAN, + ]); + await game.phaseInterceptor.runFrom(PostSummonPhase).to(PostSummonPhase); + expect(game.scene.getParty()[0].summonData).not.toBeUndefined(); + let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats; + expect(battleStatsPokemon[BattleStat.ATK]).toBe(0); + await game.phaseInterceptor.mustRun(ShowAbilityPhase).catch((error) => expect(error).toBe(ShowAbilityPhase)); + await game.phaseInterceptor.mustRun(StatChangePhase).catch((error) => expect(error).toBe(StatChangePhase)); + battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats; + expect(battleStatsPokemon[BattleStat.ATK]).toBe(1); + }, 20000); + + it("INTREPID SWORD on opponent", async() => { + await game.runToSummon([ + Species.ZACIAN, + ]); + let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats; + expect(battleStatsOpponent[BattleStat.ATK]).toBe(0); + await game.phaseInterceptor.runFrom(PostSummonPhase).to(ToggleDoublePositionPhase); + await game.phaseInterceptor.mustRun(StatChangePhase).catch((error) => expect(error).toBe(StatChangePhase)); + await game.phaseInterceptor.whenAboutToRun(MessagePhase); + battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats; + expect(battleStatsOpponent[BattleStat.ATK]).toBe(1); + }, 20000); +}); diff --git a/src/test/abilities/moxie.test.ts b/src/test/abilities/moxie.test.ts new file mode 100644 index 00000000000..ed27c670c87 --- /dev/null +++ b/src/test/abilities/moxie.test.ts @@ -0,0 +1,67 @@ +import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest"; +import Phaser from "phaser"; +import GameManager from "#app/test/utils/gameManager"; +import * as overrides from "#app/overrides"; +import {Abilities} from "#app/data/enums/abilities"; +import {Species} from "#app/data/enums/species"; +import { + CommandPhase, + EnemyCommandPhase, + VictoryPhase +} from "#app/phases"; +import {Mode} from "#app/ui/ui"; +import {Stat} from "#app/data/pokemon-stat"; +import {Moves} from "#app/data/enums/moves"; +import {getMovePosition} from "#app/test/utils/gameManagerUtils"; +import {Command} from "#app/ui/command-ui-handler"; +import {BattleStat} from "#app/data/battle-stat"; + + +describe("Abilities - Moxie", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + const moveToUse = Moves.AERIAL_ACE; + vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA); + vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.MOXIE); + vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.MOXIE); + vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000); + vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]); + vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]); + }); + + it("MOXIE", async() => { + const moveToUse = Moves.AERIAL_ACE; + await game.startBattle([ + Species.MIGHTYENA, + Species.MIGHTYENA, + ]); + + let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats; + expect(battleStatsPokemon[Stat.ATK]).toBe(0); + + game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); + }); + game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { + const movePosition = getMovePosition(game.scene, 0, moveToUse); + (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); + }); + await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(VictoryPhase); + battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats; + expect(battleStatsPokemon[BattleStat.ATK]).toBe(1); + }, 20000); +}); diff --git a/src/test/achievement.test.ts b/src/test/achievement.test.ts deleted file mode 100644 index c33707cf5b2..00000000000 --- a/src/test/achievement.test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { MoneyAchv } from "#app/system/achv"; -import { describe, expect, it } from "vitest"; - -describe("check some Achievement related stuff", () => { - it ("should check Achievement creation", () => { - const ach = new MoneyAchv("", "Achievement", 1000, null, 100); - expect(ach.name).toBe("Achievement"); - }); -}); diff --git a/src/test/achievements/achievement.test.ts b/src/test/achievements/achievement.test.ts new file mode 100644 index 00000000000..f24529187c6 --- /dev/null +++ b/src/test/achievements/achievement.test.ts @@ -0,0 +1,274 @@ +import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest"; +import {MoneyAchv, Achv, AchvTier, RibbonAchv, DamageAchv, HealAchv, LevelAchv, ModifierAchv, achvs} from "#app/system/achv"; +import BattleScene from "../../battle-scene"; +import { IntegerHolder, NumberHolder } from "#app/utils.js"; +import { TurnHeldItemTransferModifier } from "#app/modifier/modifier.js"; +import Phaser from "phaser"; +import GameManager from "#app/test/utils/gameManager"; +import * as overrides from "#app/overrides"; + +describe("check some Achievement related stuff", () => { + it ("should check Achievement creation", () => { + const ach = new MoneyAchv("", "Achievement", 1000, null, 100); + expect(ach.name).toBe("Achievement"); + }); +}); + + +describe("Achv", () => { + let achv: Achv; + + beforeEach(() => { + achv = new Achv("", "Test Achievement", "This is a test achievement", "test_icon", 10); + }); + + it("should have the correct name", () => { + expect(achv.getDescription()).toBe("This is a test achievement"); + }); + + it("should have the correct icon image", () => { + expect(achv.getIconImage()).toBe("test_icon"); + }); + + it("should set the achievement as secret", () => { + achv.setSecret(); + expect(achv.secret).toBe(true); + expect(achv.hasParent).toBe(false); + + achv.setSecret(true); + expect(achv.secret).toBe(true); + expect(achv.hasParent).toBe(true); + + achv.setSecret(false); + expect(achv.secret).toBe(true); + expect(achv.hasParent).toBe(false); + }); + + it("should return the correct tier based on the score", () => { + const achv1 = new Achv("", "Test Achievement 1", "Test Description", "test_icon", 10); + const achv2 = new Achv("", "Test Achievement 2", "Test Description", "test_icon", 25); + const achv3 = new Achv("", "Test Achievement 3", "Test Description", "test_icon", 50); + const achv4 = new Achv("", "Test Achievement 4", "Test Description", "test_icon", 75); + const achv5 = new Achv("", "Test Achievement 5", "Test Description", "test_icon", 100); + + expect(achv1.getTier()).toBe(AchvTier.COMMON); + expect(achv2.getTier()).toBe(AchvTier.GREAT); + expect(achv3.getTier()).toBe(AchvTier.ULTRA); + expect(achv4.getTier()).toBe(AchvTier.ROGUE); + expect(achv5.getTier()).toBe(AchvTier.MASTER); + }); + + it("should validate the achievement based on the condition function", () => { + const conditionFunc = jest.fn((scene: BattleScene, args: any[]) => args[0] === 10); + const achv = new Achv("", "Test Achievement", "Test Description", "test_icon", 10, conditionFunc); + + expect(achv.validate(new BattleScene(), [5])).toBe(false); + expect(achv.validate(new BattleScene(), [10])).toBe(true); + expect(conditionFunc).toHaveBeenCalledTimes(2); + }); +}); + +describe("MoneyAchv", () => { + it("should create an instance of MoneyAchv", () => { + const moneyAchv = new MoneyAchv("", "Test Money Achievement", 10000, "money_icon", 10); + expect(moneyAchv).toBeInstanceOf(MoneyAchv); + expect(moneyAchv instanceof Achv).toBe(true); + }); + + it("should validate the achievement based on the money amount", () => { + const moneyAchv = new MoneyAchv("", "Test Money Achievement", 10000, "money_icon", 10); + const scene = new BattleScene(); + scene.money = 5000; + + expect(moneyAchv.validate(scene, [])).toBe(false); + + scene.money = 15000; + expect(moneyAchv.validate(scene, [])).toBe(true); + }); +}); + +describe("RibbonAchv", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + let scene: BattleScene; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(0); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(0); + vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(0); + vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(0); + vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([]); + vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([]); + vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(false); + vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(false); + game = new GameManager(phaserGame); + scene = game.scene; + }); + + it("should create an instance of RibbonAchv", () => { + const ribbonAchv = new RibbonAchv("", "Test Ribbon Achievement", 10, "ribbon_icon", 10); + expect(ribbonAchv).toBeInstanceOf(RibbonAchv); + expect(ribbonAchv instanceof Achv).toBe(true); + }); + + it("should validate the achievement based on the ribbon amount", () => { + const ribbonAchv = new RibbonAchv("", "Test Ribbon Achievement", 10, "ribbon_icon", 10); + scene.gameData.gameStats.ribbonsOwned = 5; + + expect(ribbonAchv.validate(scene, [])).toBe(false); + + scene.gameData.gameStats.ribbonsOwned = 15; + expect(ribbonAchv.validate(scene, [])).toBe(true); + }); +}); + +describe("DamageAchv", () => { + it("should create an instance of DamageAchv", () => { + const damageAchv = new DamageAchv("", "Test Damage Achievement", 250, "damage_icon", 10); + expect(damageAchv).toBeInstanceOf(DamageAchv); + expect(damageAchv instanceof Achv).toBe(true); + }); + + it("should validate the achievement based on the damage amount", () => { + const damageAchv = new DamageAchv("", "Test Damage Achievement", 250, "damage_icon", 10); + const scene = new BattleScene(); + const numberHolder = new NumberHolder(200); + + expect(damageAchv.validate(scene, [numberHolder])).toBe(false); + + numberHolder.value = 300; + expect(damageAchv.validate(scene, [numberHolder])).toBe(true); + }); +}); + +describe("HealAchv", () => { + it("should create an instance of HealAchv", () => { + const healAchv = new HealAchv("", "Test Heal Achievement", 250, "heal_icon", 10); + expect(healAchv).toBeInstanceOf(HealAchv); + expect(healAchv instanceof Achv).toBe(true); + }); + + it("should validate the achievement based on the heal amount", () => { + const healAchv = new HealAchv("", "Test Heal Achievement", 250, "heal_icon", 10); + const scene = new BattleScene(); + const numberHolder = new NumberHolder(200); + + expect(healAchv.validate(scene, [numberHolder])).toBe(false); + + numberHolder.value = 300; + expect(healAchv.validate(scene, [numberHolder])).toBe(true); + }); +}); + +describe("LevelAchv", () => { + it("should create an instance of LevelAchv", () => { + const levelAchv = new LevelAchv("", "Test Level Achievement", 100, "level_icon", 10); + expect(levelAchv).toBeInstanceOf(LevelAchv); + expect(levelAchv instanceof Achv).toBe(true); + }); + + it("should validate the achievement based on the level", () => { + const levelAchv = new LevelAchv("", "Test Level Achievement", 100, "level_icon", 10); + const scene = new BattleScene(); + const integerHolder = new IntegerHolder(50); + + expect(levelAchv.validate(scene, [integerHolder])).toBe(false); + + integerHolder.value = 150; + expect(levelAchv.validate(scene, [integerHolder])).toBe(true); + }); +}); + +describe("ModifierAchv", () => { + it("should create an instance of ModifierAchv", () => { + const modifierAchv = new ModifierAchv("", "Test Modifier Achievement", "Test Description", "modifier_icon", 10, () => true); + expect(modifierAchv).toBeInstanceOf(ModifierAchv); + expect(modifierAchv instanceof Achv).toBe(true); + }); + + it("should validate the achievement based on the modifier function", () => { + const modifierAchv = new ModifierAchv("", "Test Modifier Achievement", "Test Description", "modifier_icon", 10, () => true); + const scene = new BattleScene(); + const modifier = new TurnHeldItemTransferModifier(null, 3, 1); + + expect(modifierAchv.validate(scene, [modifier])).toBe(true); + }); +}); + +describe("achvs", () => { + it("should contain the predefined achievements", () => { + expect(achvs._10K_MONEY).toBeInstanceOf(MoneyAchv); + expect(achvs._100K_MONEY).toBeInstanceOf(MoneyAchv); + expect(achvs._1M_MONEY).toBeInstanceOf(MoneyAchv); + expect(achvs._10M_MONEY).toBeInstanceOf(MoneyAchv); + expect(achvs._250_DMG).toBeInstanceOf(DamageAchv); + expect(achvs._1000_DMG).toBeInstanceOf(DamageAchv); + expect(achvs._2500_DMG).toBeInstanceOf(DamageAchv); + expect(achvs._10000_DMG).toBeInstanceOf(DamageAchv); + expect(achvs._250_HEAL).toBeInstanceOf(HealAchv); + expect(achvs._1000_HEAL).toBeInstanceOf(HealAchv); + expect(achvs._2500_HEAL).toBeInstanceOf(HealAchv); + expect(achvs._10000_HEAL).toBeInstanceOf(HealAchv); + expect(achvs.LV_100).toBeInstanceOf(LevelAchv); + expect(achvs.LV_250).toBeInstanceOf(LevelAchv); + expect(achvs.LV_1000).toBeInstanceOf(LevelAchv); + expect(achvs._10_RIBBONS).toBeInstanceOf(RibbonAchv); + expect(achvs._25_RIBBONS).toBeInstanceOf(RibbonAchv); + expect(achvs._50_RIBBONS).toBeInstanceOf(RibbonAchv); + expect(achvs._75_RIBBONS).toBeInstanceOf(RibbonAchv); + expect(achvs._100_RIBBONS).toBeInstanceOf(RibbonAchv); + expect(achvs.TRANSFER_MAX_BATTLE_STAT).toBeInstanceOf(Achv); + expect(achvs.MAX_FRIENDSHIP).toBeInstanceOf(Achv); + expect(achvs.MEGA_EVOLVE).toBeInstanceOf(Achv); + expect(achvs.GIGANTAMAX).toBeInstanceOf(Achv); + expect(achvs.TERASTALLIZE).toBeInstanceOf(Achv); + expect(achvs.STELLAR_TERASTALLIZE).toBeInstanceOf(Achv); + expect(achvs.SPLICE).toBeInstanceOf(Achv); + expect(achvs.MINI_BLACK_HOLE).toBeInstanceOf(ModifierAchv); + expect(achvs.CATCH_MYTHICAL).toBeInstanceOf(Achv); + expect(achvs.CATCH_SUB_LEGENDARY).toBeInstanceOf(Achv); + expect(achvs.CATCH_LEGENDARY).toBeInstanceOf(Achv); + expect(achvs.SEE_SHINY).toBeInstanceOf(Achv); + expect(achvs.SHINY_PARTY).toBeInstanceOf(Achv); + expect(achvs.HATCH_MYTHICAL).toBeInstanceOf(Achv); + expect(achvs.HATCH_SUB_LEGENDARY).toBeInstanceOf(Achv); + expect(achvs.HATCH_LEGENDARY).toBeInstanceOf(Achv); + expect(achvs.HATCH_SHINY).toBeInstanceOf(Achv); + expect(achvs.HIDDEN_ABILITY).toBeInstanceOf(Achv); + expect(achvs.PERFECT_IVS).toBeInstanceOf(Achv); + expect(achvs.CLASSIC_VICTORY).toBeInstanceOf(Achv); + }); + + it("should initialize the achievements with IDs and parent IDs", () => { + + expect(achvs._10K_MONEY.id).toBe("_10K_MONEY"); + expect(achvs._10K_MONEY.hasParent).toBe(undefined); + expect(achvs._100K_MONEY.id).toBe("_100K_MONEY"); + expect(achvs._100K_MONEY.hasParent).toBe(true); + expect(achvs._100K_MONEY.parentId).toBe("_10K_MONEY"); + expect(achvs._1M_MONEY.id).toBe("_1M_MONEY"); + expect(achvs._1M_MONEY.hasParent).toBe(true); + expect(achvs._1M_MONEY.parentId).toBe("_100K_MONEY"); + expect(achvs._10M_MONEY.id).toBe("_10M_MONEY"); + expect(achvs._10M_MONEY.hasParent).toBe(true); + expect(achvs._10M_MONEY.parentId).toBe("_1M_MONEY"); + expect(achvs.LV_100.id).toBe("LV_100"); + expect(achvs.LV_100.hasParent).toBe(false); + expect(achvs.LV_250.id).toBe("LV_250"); + expect(achvs.LV_250.hasParent).toBe(true); + expect(achvs.LV_250.parentId).toBe("LV_100"); + expect(achvs.LV_1000.id).toBe("LV_1000"); + expect(achvs.LV_1000.hasParent).toBe(true); + expect(achvs.LV_1000.parentId).toBe("LV_250"); + }); +}); diff --git a/src/test/battle/battle-order.test.ts b/src/test/battle/battle-order.test.ts new file mode 100644 index 00000000000..e481150fafe --- /dev/null +++ b/src/test/battle/battle-order.test.ts @@ -0,0 +1,220 @@ +import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest"; +import Phaser from "phaser"; +import GameManager from "#app/test/utils/gameManager"; +import * as overrides from "#app/overrides"; +import {Abilities} from "#app/data/enums/abilities"; +import {Species} from "#app/data/enums/species"; +import { + CommandPhase, EnemyCommandPhase, + TurnStartPhase +} from "#app/phases"; +import {Mode} from "#app/ui/ui"; +import {getMovePosition} from "#app/test/utils/gameManagerUtils"; +import {Moves} from "#app/data/enums/moves"; +import {Command} from "#app/ui/command-ui-handler"; +import {Stat} from "#app/data/pokemon-stat"; +import TargetSelectUiHandler from "#app/ui/target-select-ui-handler"; +import {Button} from "#app/enums/buttons"; + + +describe("Battle order", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MEWTWO); + vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INSOMNIA); + vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INSOMNIA); + vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]); + }); + + it("opponent faster than player 50 vs 150", async() => { + await game.startBattle([ + Species.BULBASAUR, + ]); + game.scene.getParty()[0].stats[Stat.SPD] = 50; + game.scene.currentBattle.enemyParty[0].stats[Stat.SPD] = 150; + + game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); + }); + game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { + const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE); + (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); + }); + await game.phaseInterceptor.run(EnemyCommandPhase); + await game.phaseInterceptor.whenAboutToRun(TurnStartPhase); + const phase = game.scene.getCurrentPhase() as TurnStartPhase; + const order = phase.getOrder(); + expect(order[0]).toBe(2); + expect(order[1]).toBe(0); + }, 20000); + + it("Player faster than opponent 150 vs 50", async() => { + await game.startBattle([ + Species.BULBASAUR, + ]); + game.scene.getParty()[0].stats[Stat.SPD] = 150; + game.scene.currentBattle.enemyParty[0].stats[Stat.SPD] = 50; + + game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); + }); + game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { + const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE); + (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); + }); + await game.phaseInterceptor.run(EnemyCommandPhase); + await game.phaseInterceptor.whenAboutToRun(TurnStartPhase); + const phase = game.scene.getCurrentPhase() as TurnStartPhase; + const order = phase.getOrder(); + expect(order[0]).toBe(0); + expect(order[1]).toBe(2); + }, 20000); + + it("double - both opponents faster than player 50/50 vs 150/150", async() => { + vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(false); + vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + await game.startBattle([ + Species.BULBASAUR, + Species.BLASTOISE, + ]); + game.scene.getParty()[0].stats[Stat.SPD] = 50; + game.scene.getParty()[1].stats[Stat.SPD] = 50; + game.scene.currentBattle.enemyParty[0].stats[Stat.SPD] = 150; + game.scene.currentBattle.enemyParty[1].stats[Stat.SPD] = 150; + + game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); + }); + game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { + const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE); + (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); + }); + game.onNextPrompt("SelectTargetPhase", Mode.TARGET_SELECT, () => { + const handler = game.scene.ui.getHandler() as TargetSelectUiHandler; + handler.processInput(Button.ACTION); + }); + game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); + }); + game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { + const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE); + (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); + }); + game.onNextPrompt("SelectTargetPhase", Mode.TARGET_SELECT, () => { + const handler = game.scene.ui.getHandler() as TargetSelectUiHandler; + handler.processInput(Button.ACTION); + }); + await game.phaseInterceptor.runFrom(CommandPhase).to(EnemyCommandPhase); + await game.phaseInterceptor.run(EnemyCommandPhase); + await game.phaseInterceptor.whenAboutToRun(TurnStartPhase); + const phase = game.scene.getCurrentPhase() as TurnStartPhase; + const order = phase.getOrder(); + expect(order.indexOf(0)).toBeGreaterThan(order.indexOf(2)); + expect(order.indexOf(0)).toBeGreaterThan(order.indexOf(3)); + expect(order.indexOf(1)).toBeGreaterThan(order.indexOf(2)); + expect(order.indexOf(1)).toBeGreaterThan(order.indexOf(3)); + }, 20000); + + it("double - speed tie except 1 - 100/100 vs 100/150", async() => { + vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(false); + vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + await game.startBattle([ + Species.BULBASAUR, + Species.BLASTOISE, + ]); + game.scene.getParty()[0].stats[Stat.SPD] = 100; + game.scene.getParty()[1].stats[Stat.SPD] = 100; + game.scene.currentBattle.enemyParty[0].stats[Stat.SPD] = 100; + game.scene.currentBattle.enemyParty[1].stats[Stat.SPD] = 150; + + game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); + }); + game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { + const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE); + (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); + }); + game.onNextPrompt("SelectTargetPhase", Mode.TARGET_SELECT, () => { + const handler = game.scene.ui.getHandler() as TargetSelectUiHandler; + handler.processInput(Button.ACTION); + }); + game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); + }); + game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { + const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE); + (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); + }); + game.onNextPrompt("SelectTargetPhase", Mode.TARGET_SELECT, () => { + const handler = game.scene.ui.getHandler() as TargetSelectUiHandler; + handler.processInput(Button.ACTION); + }); + await game.phaseInterceptor.runFrom(CommandPhase).to(EnemyCommandPhase); + await game.phaseInterceptor.run(EnemyCommandPhase); + await game.phaseInterceptor.whenAboutToRun(TurnStartPhase); + const phase = game.scene.getCurrentPhase() as TurnStartPhase; + const order = phase.getOrder(); + expect(order.indexOf(3)).toBeLessThan(order.indexOf(0)); + expect(order.indexOf(3)).toBeLessThan(order.indexOf(1)); + expect(order.indexOf(3)).toBeLessThan(order.indexOf(2)); + }, 20000); + + it("double - speed tie 100/150 vs 100/150", async() => { + vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(false); + vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + await game.startBattle([ + Species.BULBASAUR, + Species.BLASTOISE, + ]); + game.scene.getParty()[0].stats[Stat.SPD] = 100; + game.scene.getParty()[1].stats[Stat.SPD] = 150; + game.scene.currentBattle.enemyParty[0].stats[Stat.SPD] = 100; + game.scene.currentBattle.enemyParty[1].stats[Stat.SPD] = 150; + + game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); + }); + game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { + const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE); + (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); + }); + game.onNextPrompt("SelectTargetPhase", Mode.TARGET_SELECT, () => { + const handler = game.scene.ui.getHandler() as TargetSelectUiHandler; + handler.processInput(Button.ACTION); + }); + game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); + }); + game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { + const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE); + (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); + }); + game.onNextPrompt("SelectTargetPhase", Mode.TARGET_SELECT, () => { + const handler = game.scene.ui.getHandler() as TargetSelectUiHandler; + handler.processInput(Button.ACTION); + }); + await game.phaseInterceptor.runFrom(CommandPhase).to(EnemyCommandPhase); + await game.phaseInterceptor.run(EnemyCommandPhase); + await game.phaseInterceptor.whenAboutToRun(TurnStartPhase); + const phase = game.scene.getCurrentPhase() as TurnStartPhase; + const order = phase.getOrder(); + expect(order.indexOf(1)).toBeLessThan(order.indexOf(0)); + expect(order.indexOf(1)).toBeLessThan(order.indexOf(2)); + expect(order.indexOf(3)).toBeLessThan(order.indexOf(0)); + expect(order.indexOf(3)).toBeLessThan(order.indexOf(2)); + }, 20000); +}); diff --git a/src/test/battle/battle.test.ts b/src/test/battle/battle.test.ts new file mode 100644 index 00000000000..521b56a8db2 --- /dev/null +++ b/src/test/battle/battle.test.ts @@ -0,0 +1,248 @@ +import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest"; +import {generateStarter, getMovePosition, waitUntil,} from "#app/test/utils/gameManagerUtils"; +import {Mode} from "#app/ui/ui"; +import {GameModes} from "#app/game-mode"; +import {Species} from "#app/data/enums/species"; +import * as overrides from "../../overrides"; +import {Command} from "#app/ui/command-ui-handler"; +import { + BattleEndPhase, + BerryPhase, + CommandPhase, + DamagePhase, + EggLapsePhase, + EncounterPhase, + EnemyCommandPhase, + FaintPhase, + LoginPhase, + MessagePhase, + MoveEffectPhase, + MoveEndPhase, + MovePhase, + PostSummonPhase, + SelectGenderPhase, + SelectModifierPhase, + SelectStarterPhase, + StatChangePhase, + TitlePhase, + TurnEndPhase, + TurnInitPhase, + TurnStartPhase, + VictoryPhase, +} from "#app/phases"; +import {Moves} from "#app/data/enums/moves"; +import GameManager from "#app/test/utils/gameManager"; +import Phaser from "phaser"; +import {allSpecies} from "#app/data/pokemon-species"; +import {PlayerGender} from "#app/data/enums/player-gender"; + +describe("Test Battle Phase", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + }); + + it("test phase interceptor with remove", async() => { + await game.phaseInterceptor.run(LoginPhase); + + await game.phaseInterceptor.run(LoginPhase, () => { + return game.phaseInterceptor.log.includes("LoginPhase"); + }); + + game.scene.gameData.gender = PlayerGender.MALE; + await game.phaseInterceptor.remove(SelectGenderPhase, () => game.isCurrentPhase(TitlePhase)); + + await game.phaseInterceptor.run(TitlePhase); + await waitUntil(() => game.scene.ui?.getMode() === Mode.TITLE); + + expect(game.scene.ui?.getMode()).toBe(Mode.TITLE); + }, 100000); + + it("test phase interceptor with prompt", async() => { + await game.phaseInterceptor.run(LoginPhase); + + game.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => { + game.scene.gameData.gender = PlayerGender.MALE; + game.endPhase(); + }); + + await game.phaseInterceptor.run(SelectGenderPhase); + + await game.phaseInterceptor.run(TitlePhase); + await game.waitMode(Mode.TITLE); + + + expect(game.scene.ui?.getMode()).toBe(Mode.TITLE); + expect(game.scene.gameData.gender).toBe(PlayerGender.MALE); + }, 100000); + + it("test phase interceptor with prompt with preparation for a future prompt", async() => { + await game.phaseInterceptor.run(LoginPhase); + + game.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => { + game.scene.gameData.gender = PlayerGender.MALE; + game.endPhase(); + }); + + game.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => { + game.setMode(Mode.MESSAGE); + game.endPhase(); + }); + await game.phaseInterceptor.run(SelectGenderPhase); + + await game.phaseInterceptor.run(TitlePhase); + await game.waitMode(Mode.TITLE); + + + expect(game.scene.ui?.getMode()).toBe(Mode.TITLE); + expect(game.scene.gameData.gender).toBe(PlayerGender.MALE); + }, 100000); + + it("newGame one-liner", async() => { + await game.startBattle(); + expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND); + expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); + }, 100000); + + it("do attack wave 3 - single battle - regular - OHKO", async() => { + vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MEWTWO); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA); + vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000); + vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3); + vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]); + vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + await game.startBattle(); + game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); + }); + game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { + const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE); + (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); + }); + await game.phaseInterceptor.run(EnemyCommandPhase); + await game.phaseInterceptor.run(TurnStartPhase); + + await game.phaseInterceptor.run(MovePhase); + await game.phaseInterceptor.run(MessagePhase); + await game.phaseInterceptor.run(MoveEffectPhase); + await game.phaseInterceptor.run(DamagePhase); + await game.phaseInterceptor.run(MessagePhase, () => game.isCurrentPhase(FaintPhase)); + await game.phaseInterceptor.run(FaintPhase); + await game.phaseInterceptor.run(MessagePhase); + + await game.phaseInterceptor.run(VictoryPhase); + await game.phaseInterceptor.run(MoveEndPhase); + await game.phaseInterceptor.run(MovePhase); + await game.phaseInterceptor.run(BerryPhase); + await game.phaseInterceptor.run(TurnEndPhase); + await game.phaseInterceptor.run(BattleEndPhase); + await game.phaseInterceptor.run(EggLapsePhase); + await game.phaseInterceptor.run(SelectModifierPhase); + expect(game.scene.ui?.getMode()).toBe(Mode.MODIFIER_SELECT); + expect(game.scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name); + }, 100000); + + it("do attack wave 3 - single battle - regular - NO OHKO with opponent using non damage attack", async() => { + vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MEWTWO); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA); + vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(5); + vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3); + vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]); + vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TAIL_WHIP]); + vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + await game.startBattle(); + game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); + }); + game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { + const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE); + (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); + }); + await game.phaseInterceptor.run(EnemyCommandPhase); + await game.phaseInterceptor.run(TurnStartPhase); + + await game.phaseInterceptor.run(MovePhase); + await game.phaseInterceptor.run(MessagePhase); + await game.phaseInterceptor.run(MoveEffectPhase); + await game.phaseInterceptor.run(DamagePhase); + await game.phaseInterceptor.run(MessagePhase, () => game.isCurrentPhase(MoveEndPhase)); + await game.phaseInterceptor.run(MoveEndPhase); + + await game.phaseInterceptor.run(MovePhase); + await game.phaseInterceptor.run(MessagePhase, () => game.isCurrentPhase(MoveEffectPhase)); + await game.phaseInterceptor.run(MoveEffectPhase); + game.scene.moveAnimations = null; // Mandatory to avoid the crash + await game.phaseInterceptor.run(StatChangePhase, () => game.isCurrentPhase(MessagePhase) || game.isCurrentPhase(MoveEndPhase) || game.isCurrentPhase(DamagePhase)); + await game.phaseInterceptor.run(DamagePhase, () => game.isCurrentPhase(MessagePhase) || game.isCurrentPhase(MoveEndPhase)); + await game.phaseInterceptor.run(MessagePhase, () => game.isCurrentPhase(MoveEndPhase)); + await game.phaseInterceptor.run(MoveEndPhase); + + await game.phaseInterceptor.run(BerryPhase); + await game.phaseInterceptor.run(MessagePhase, () => game.isCurrentPhase(TurnEndPhase)); + await game.phaseInterceptor.run(TurnEndPhase); + + await game.phaseInterceptor.run(TurnInitPhase); + await game.phaseInterceptor.run(CommandPhase); + await waitUntil(() => game.scene.ui?.getMode() === Mode.COMMAND); + expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND); + expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); + }, 100000); + + it("load 100% data file", async() => { + await game.importData("src/test/utils/saves/everything.prsv"); + const caughtCount = Object.keys(game.scene.gameData.dexData).filter((key) => { + const species = game.scene.gameData.dexData[key]; + return species.caughtAttr !== 0n; + }).length; + expect(caughtCount).toBe(Object.keys(allSpecies).length); + }, 50000); + + it("start battle with selected team", async() => { + await game.startBattle([ + Species.CHARIZARD, + Species.CHANSEY, + Species.MEW + ]); + expect(game.scene.getParty()[0].species.speciesId).toBe(Species.CHARIZARD); + expect(game.scene.getParty()[1].species.speciesId).toBe(Species.CHANSEY); + expect(game.scene.getParty()[2].species.speciesId).toBe(Species.MEW); + }, 50000); + + it("assert next phase", async() => { + await game.phaseInterceptor.run(LoginPhase); + game.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => { + game.scene.gameData.gender = PlayerGender.MALE; + game.endPhase(); + }, () => game.isCurrentPhase(TitlePhase)); + await game.phaseInterceptor.mustRun(SelectGenderPhase).catch((error) => expect(error).toBe(SelectGenderPhase)); + await game.phaseInterceptor.mustRun(TitlePhase).catch((error) => expect(error).toBe(TitlePhase)); + game.onNextPrompt("TitlePhase", Mode.TITLE, () => { + const starters = generateStarter(game.scene); + const selectStarterPhase = new SelectStarterPhase(game.scene, GameModes.CLASSIC); + game.scene.pushPhase(new EncounterPhase(game.scene, false)); + selectStarterPhase.initBattle(starters); + }); + await game.phaseInterceptor.mustRun(EncounterPhase).catch((error) => expect(error).toBe(EncounterPhase)); + await game.phaseInterceptor.mustRun(PostSummonPhase).catch((error) => expect(error).toBe(PostSummonPhase)); + }, 50000); + + it("test remove random battle seed int", async() => { + for (let i=0; i<10; i++) { + const rand = game.scene.randBattleSeedInt(15); + expect(rand).toBe(14); + } + }); +}); + diff --git a/src/test/eggs/egg.test.ts b/src/test/eggs/egg.test.ts new file mode 100644 index 00000000000..7cfe7fda651 --- /dev/null +++ b/src/test/eggs/egg.test.ts @@ -0,0 +1,33 @@ +import {beforeAll, describe, expect, it} from "vitest"; +import BattleScene from "../../battle-scene"; +import { getLegendaryGachaSpeciesForTimestamp } from "#app/data/egg.js"; +import { Species } from "#app/data/enums/species.js"; +import Phaser from "phaser"; + +describe("getLegendaryGachaSpeciesForTimestamp", () => { + + beforeAll(() => { + new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + it("should return Arceus for the 10th of June", () => { + const scene = new BattleScene(); + const timestamp = new Date(2024, 5, 10, 15, 0, 0, 0).getTime(); + const expectedSpecies = Species.ARCEUS; + + const result = getLegendaryGachaSpeciesForTimestamp(scene, timestamp); + + expect(result).toBe(expectedSpecies); + }); + it("should return Arceus for the 10th of July", () => { + const scene = new BattleScene(); + const timestamp = new Date(2024, 6, 10, 15, 0, 0, 0).getTime(); + const expectedSpecies = Species.ARCEUS; + + const result = getLegendaryGachaSpeciesForTimestamp(scene, timestamp); + + expect(result).toBe(expectedSpecies); + }); +}); diff --git a/src/test/debugImports.test.ts b/src/test/imports.test.ts similarity index 100% rename from src/test/debugImports.test.ts rename to src/test/imports.test.ts diff --git a/src/test/inputs/inputs.test.ts b/src/test/inputs/inputs.test.ts new file mode 100644 index 00000000000..4924ade0fc4 --- /dev/null +++ b/src/test/inputs/inputs.test.ts @@ -0,0 +1,105 @@ +import {afterEach, beforeAll, beforeEach, describe, expect, it} from "vitest"; +import Phaser from "phaser"; +import GameManager from "#app/test/utils/gameManager"; +import pad_xbox360 from "#app/configs/inputs/pad_xbox360"; +import cfg_keyboard_qwerty from "#app/configs/inputs/cfg_keyboard_qwerty"; +import InputsHandler from "#app/test/utils/inputsHandler"; + + +describe("Inputs", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + let originalDocument: Document; + + beforeAll(() => { + originalDocument = window.document; + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + Object.defineProperty(window, "document", { + value: originalDocument, + configurable: true, + writable: true, + }); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.inputsHandler = new InputsHandler(game.scene); + }); + + it("Mobile - test touch holding for 1ms - 1 input", async () => { + await game.inputsHandler.pressTouch("dpadUp", 1); + expect(game.inputsHandler.log.length).toBe(1); + }); + + it("Mobile - test touch holding for 200ms - 1 input", async () => { + await game.inputsHandler.pressTouch("dpadUp", 200); + expect(game.inputsHandler.log.length).toBe(1); + }); + + it("Mobile - test touch holding for 300ms - 2 input", async () => { + await game.inputsHandler.pressTouch("dpadUp", 300); + expect(game.inputsHandler.log.length).toBe(2); + }); + + it("Mobile - test touch holding for 1000ms - 4 input", async () => { + await game.inputsHandler.pressTouch("dpadUp", 1000); + expect(game.inputsHandler.log.length).toBe(4); + }); + + it("keyboard - test input holding for 1ms - 1 input", async() => { + await game.inputsHandler.pressKeyboardKey(cfg_keyboard_qwerty.deviceMapping.KEY_ARROW_UP, 1); + expect(game.inputsHandler.log.length).toBe(1); + }); + + it("keyboard - test input holding for 200ms - 1 input", async() => { + await game.inputsHandler.pressKeyboardKey(cfg_keyboard_qwerty.deviceMapping.KEY_ARROW_UP, 200); + expect(game.inputsHandler.log.length).toBe(1); + }); + + it("keyboard - test input holding for 300ms - 2 input", async() => { + await game.inputsHandler.pressKeyboardKey(cfg_keyboard_qwerty.deviceMapping.KEY_ARROW_UP, 300); + expect(game.inputsHandler.log.length).toBe(2); + }); + + it("keyboard - test input holding for 1000ms - 4 input", async() => { + await game.inputsHandler.pressKeyboardKey(cfg_keyboard_qwerty.deviceMapping.KEY_ARROW_UP, 1000); + expect(game.inputsHandler.log.length).toBe(4); + }); + + it("keyboard - test input holding for 2000ms - 8 input", async() => { + await game.inputsHandler.pressKeyboardKey(cfg_keyboard_qwerty.deviceMapping.KEY_ARROW_UP, 2000); + expect(game.inputsHandler.log.length).toBe(8); + }); + + it("gamepad - test input holding for 1ms - 1 input", async() => { + await game.inputsHandler.pressGamepadButton(pad_xbox360.deviceMapping.RC_S, 1); + expect(game.inputsHandler.log.length).toBe(1); + }); + + it("gamepad - test input holding for 200ms - 1 input", async() => { + await game.inputsHandler.pressGamepadButton(pad_xbox360.deviceMapping.RC_S, 200); + expect(game.inputsHandler.log.length).toBe(1); + }); + + it("gamepad - test input holding for 300ms - 2 input", async() => { + await game.inputsHandler.pressGamepadButton(pad_xbox360.deviceMapping.RC_S, 300); + expect(game.inputsHandler.log.length).toBe(2); + }); + + it("gamepad - test input holding for 1000ms - 4 input", async() => { + await game.inputsHandler.pressGamepadButton(pad_xbox360.deviceMapping.RC_S, 1000); + expect(game.inputsHandler.log.length).toBe(4); + }); + + it("gamepad - test input holding for 2000ms - 8 input", async() => { + await game.inputsHandler.pressGamepadButton(pad_xbox360.deviceMapping.RC_S, 2000); + expect(game.inputsHandler.log.length).toBe(8); + }); +}); + diff --git a/src/test/items/toxic_orb.test.ts b/src/test/items/toxic_orb.test.ts new file mode 100644 index 00000000000..7064bcd13a1 --- /dev/null +++ b/src/test/items/toxic_orb.test.ts @@ -0,0 +1,80 @@ +import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest"; +import Phaser from "phaser"; +import GameManager from "#app/test/utils/gameManager"; +import * as overrides from "#app/overrides"; +import {Abilities} from "#app/data/enums/abilities"; +import {Species} from "#app/data/enums/species"; +import { + CommandPhase, + EnemyCommandPhase, + MessagePhase, + TurnEndPhase, +} from "#app/phases"; +import {Mode} from "#app/ui/ui"; +import {Moves} from "#app/data/enums/moves"; +import {getMovePosition} from "#app/test/utils/gameManagerUtils"; +import {Command} from "#app/ui/command-ui-handler"; +import {StatusEffect} from "#app/data/status-effect"; + + +describe("Items - Toxic orb", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + const moveToUse = Moves.GROWTH; + const oppMoveToUse = Moves.TACKLE; + vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA); + vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INSOMNIA); + vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INSOMNIA); + vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000); + vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]); + vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([oppMoveToUse, oppMoveToUse, oppMoveToUse, oppMoveToUse]); + vi.spyOn(overrides, "STARTING_HELD_ITEMS_OVERRIDE", "get").mockReturnValue([{ + name: "TOXIC_ORB", + }]); + }); + + it("TOXIC ORB", async() => { + const moveToUse = Moves.GROWTH; + await game.startBattle([ + Species.MIGHTYENA, + Species.MIGHTYENA, + ]); + expect(game.scene.modifiers[0].type.id).toBe("TOXIC_ORB"); + + game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + // Select Attack + game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); + }); + game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { + // Select Move Growth + const movePosition = getMovePosition(game.scene, 0, moveToUse); + (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); + }); + + // will run the 13 phase from enemyCommandPhase to TurnEndPhase + await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase); + // Toxic orb should trigger here + await game.phaseInterceptor.run(MessagePhase); + const message = game.textInterceptor.getLatestMessage(); + expect(message).toContain("was badly poisoned by Toxic Orb"); + await game.phaseInterceptor.run(MessagePhase); + const message2 = game.textInterceptor.getLatestMessage(); + expect(message2).toContain("is hurt"); + expect(message2).toContain("by poison"); + expect(game.scene.getParty()[0].status.effect).toBe(StatusEffect.TOXIC); + }, 20000); +}); diff --git a/src/test/lokalisation/french.test.ts b/src/test/lokalisation/french.test.ts new file mode 100644 index 00000000000..25b7d7d9803 --- /dev/null +++ b/src/test/lokalisation/french.test.ts @@ -0,0 +1,42 @@ +import {afterEach, beforeAll, describe, expect, it} from "vitest"; +import Phaser from "phaser"; +import GameManager from "#app/test/utils/gameManager"; +import {Species} from "#app/data/enums/species"; +import i18next from "i18next"; +import {initI18n} from "#app/plugins/i18n"; + +describe("Lokalization - french", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + initI18n(); + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + it("test bulbasaur name english", async () => { + game = new GameManager(phaserGame); + await game.startBattle([ + Species.BULBASAUR, + ]); + expect(game.scene.getParty()[0].name).toBe("Bulbasaur"); + }, 20000); + + it("test bulbasaure name french", async () => { + const locale = "fr"; + i18next.changeLanguage(locale); + localStorage.setItem("prLang", locale); + game = new GameManager(phaserGame); + + await game.startBattle([ + Species.BULBASAUR, + ]); + expect(game.scene.getParty()[0].name).toBe("Bulbizarre"); + }, 20000); +}); diff --git a/src/test/moves/growth.test.ts b/src/test/moves/growth.test.ts new file mode 100644 index 00000000000..9a6e13f06a3 --- /dev/null +++ b/src/test/moves/growth.test.ts @@ -0,0 +1,69 @@ +import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest"; +import Phaser from "phaser"; +import GameManager from "#app/test/utils/gameManager"; +import * as overrides from "#app/overrides"; +import {Abilities} from "#app/data/enums/abilities"; +import {Species} from "#app/data/enums/species"; +import { + CommandPhase, + EnemyCommandPhase, + TurnInitPhase, +} from "#app/phases"; +import {Mode} from "#app/ui/ui"; +import {Stat} from "#app/data/pokemon-stat"; +import {Moves} from "#app/data/enums/moves"; +import {getMovePosition} from "#app/test/utils/gameManagerUtils"; +import {Command} from "#app/ui/command-ui-handler"; +import {BattleStat} from "#app/data/battle-stat"; + + +describe("Moves - Growth", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + const moveToUse = Moves.GROWTH; + vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA); + vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.MOXIE); + vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INSOMNIA); + vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000); + vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]); + vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]); + }); + + it("GROWTH", async() => { + const moveToUse = Moves.GROWTH; + await game.startBattle([ + Species.MIGHTYENA, + Species.MIGHTYENA, + ]); + let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats; + expect(battleStatsPokemon[Stat.SPATK]).toBe(0); + + const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats; + expect(battleStatsOpponent[BattleStat.SPATK]).toBe(0); + + game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); + }); + game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { + const movePosition = getMovePosition(game.scene, 0, moveToUse); + (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); + }); + await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnInitPhase); + battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats; + expect(battleStatsPokemon[BattleStat.SPATK]).toBe(1); + }, 20000); +}); diff --git a/src/test/moves/tackle.test.ts b/src/test/moves/tackle.test.ts new file mode 100644 index 00000000000..ca3f95731f2 --- /dev/null +++ b/src/test/moves/tackle.test.ts @@ -0,0 +1,85 @@ +import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest"; +import Phaser from "phaser"; +import GameManager from "#app/test/utils/gameManager"; +import * as overrides from "#app/overrides"; +import {Species} from "#app/data/enums/species"; +import { + CommandPhase, + EnemyCommandPhase, TurnEndPhase, +} from "#app/phases"; +import {Mode} from "#app/ui/ui"; +import {Moves} from "#app/data/enums/moves"; +import {getMovePosition} from "#app/test/utils/gameManagerUtils"; +import {Command} from "#app/ui/command-ui-handler"; +import {Stat} from "#app/data/pokemon-stat"; + + +describe("Moves - Tackle", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + const moveToUse = Moves.TACKLE; + vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MAGIKARP); + vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(1); + vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(97); + vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]); + vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.GROWTH,Moves.GROWTH,Moves.GROWTH,Moves.GROWTH]); + vi.spyOn(overrides, "NEVER_CRIT_OVERRIDE", "get").mockReturnValue(true); + }); + + it("TACKLE against ghost", async() => { + const moveToUse = Moves.TACKLE; + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.GENGAR); + await game.startBattle([ + Species.MIGHTYENA, + ]); + const hpOpponent = game.scene.currentBattle.enemyParty[0].hp; + game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); + }); + game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { + const movePosition = getMovePosition(game.scene, 0, moveToUse); + (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); + }); + await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase); + const hpLost = hpOpponent - game.scene.currentBattle.enemyParty[0].hp; + expect(hpLost).toBe(0); + }, 20000); + + it("TACKLE against not resistant", async() => { + const moveToUse = Moves.TACKLE; + await game.startBattle([ + Species.MIGHTYENA, + ]); + game.scene.currentBattle.enemyParty[0].stats[Stat.DEF] = 50; + game.scene.getParty()[0].stats[Stat.ATK] = 50; + + + const hpOpponent = game.scene.currentBattle.enemyParty[0].hp; + + game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); + }); + game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { + const movePosition = getMovePosition(game.scene, 0, moveToUse); + (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); + }); + await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase); + const hpLost = hpOpponent - game.scene.currentBattle.enemyParty[0].hp; + expect(hpLost).toBeGreaterThan(0); + expect(hpLost).toBe(4); + }, 20000); +}); diff --git a/src/test/moves/tail_whip.test.ts b/src/test/moves/tail_whip.test.ts new file mode 100644 index 00000000000..2d6789102d1 --- /dev/null +++ b/src/test/moves/tail_whip.test.ts @@ -0,0 +1,66 @@ +import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest"; +import Phaser from "phaser"; +import GameManager from "#app/test/utils/gameManager"; +import * as overrides from "#app/overrides"; +import {Abilities} from "#app/data/enums/abilities"; +import {Species} from "#app/data/enums/species"; +import { + CommandPhase, + EnemyCommandPhase, + TurnInitPhase, +} from "#app/phases"; +import {Mode} from "#app/ui/ui"; +import {Moves} from "#app/data/enums/moves"; +import {getMovePosition} from "#app/test/utils/gameManagerUtils"; +import {Command} from "#app/ui/command-ui-handler"; +import {BattleStat} from "#app/data/battle-stat"; + + +describe("Moves - Tail whip", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + const moveToUse = Moves.TAIL_WHIP; + vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA); + vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INSOMNIA); + vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INSOMNIA); + vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000); + vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]); + vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]); + }); + + it("TAIL_WHIP", async() => { + const moveToUse = Moves.TAIL_WHIP; + await game.startBattle([ + Species.MIGHTYENA, + Species.MIGHTYENA, + ]); + + let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats; + expect(battleStatsOpponent[BattleStat.DEF]).toBe(0); + + game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); + }); + game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { + const movePosition = getMovePosition(game.scene, 0, moveToUse); + (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); + }); + await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnInitPhase); + battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats; + expect(battleStatsOpponent[BattleStat.DEF]).toBe(-1); + }, 20000); +}); diff --git a/src/test/phaser.setup.ts b/src/test/phaser.setup.ts deleted file mode 100644 index 1776e8134e2..00000000000 --- a/src/test/phaser.setup.ts +++ /dev/null @@ -1,5 +0,0 @@ -import Phaser from "phaser"; - -export default new Phaser.Game({ - type: Phaser.HEADLESS, -}); diff --git a/src/test/phases/phases.test.ts b/src/test/phases/phases.test.ts new file mode 100644 index 00000000000..abb7ac2c975 --- /dev/null +++ b/src/test/phases/phases.test.ts @@ -0,0 +1,51 @@ +import BattleScene from "#app/battle-scene.js"; +import { LoginPhase, TitlePhase, UnavailablePhase } from "#app/phases.js"; +import { Mode } from "#app/ui/ui.js"; +import {afterEach, beforeAll, beforeEach, describe, expect, it} from "vitest"; +import Phaser from "phaser"; +import GameManager from "#app/test/utils/gameManager"; + +describe("Phases", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + let scene: BattleScene; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + scene = game.scene; + }); + + describe("LoginPhase", () => { + it("should start the login phase", async () => { + const loginPhase = new LoginPhase(scene); + loginPhase.start(); + expect(scene.ui.getMode()).to.equal(Mode.MESSAGE); + }); + }); + + describe("TitlePhase", () => { + it("should start the title phase", async () => { + const titlePhase = new TitlePhase(scene); + titlePhase.start(); + expect(scene.ui.getMode()).to.equal(Mode.MESSAGE); + }); + }); + + describe("UnavailablePhase", () => { + it("should start the unavailable phase", async () => { + const unavailablePhase = new UnavailablePhase(scene); + unavailablePhase.start(); + expect(scene.ui.getMode()).to.equal(Mode.UNAVAILABLE); + }); + }); +}); diff --git a/src/test/pokemon.test.ts b/src/test/pokemon.test.ts deleted file mode 100644 index d1f7da45256..00000000000 --- a/src/test/pokemon.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import {describe, expect, it} from "vitest"; -import {getPokemonSpecies} from "#app/data/pokemon-species"; -import {PokemonMove} from "#app/field/pokemon"; -import {Species} from "#app/data/enums/species"; -import {Moves} from "#app/data/enums/moves"; -import PokemonData from "#app/system/pokemon-data"; - -describe("some tests related to PokemonData and Species", () => { - it("should create a species", () => { - const species = getPokemonSpecies(Species.MEW); - expect(species).not.toBeNull(); - }); - - it("should create a pokemon", () => { - const pokemon = new PokemonData({ - species: Species.MEW, - level: 1, - }); - expect(pokemon).not.toBeNull(); - expect(pokemon.level).toEqual(1); - expect(pokemon.species).toEqual(Species.MEW); - }); - - it("should generate a moveset", () => { - const pokemon = new PokemonData({ - species: Species.MEW, - level: 1, - }); - expect(pokemon.moveset[0].moveId).toBe(Moves.TACKLE); - expect(pokemon.moveset[1].moveId).toBe(Moves.GROWL); - }); - - it("should create an ennemypokemon", () => { - const ennemyPokemon = new PokemonData({ - species: Species.MEWTWO, - level: 100, - }); - expect(ennemyPokemon).not.toBeNull(); - expect(ennemyPokemon.level).toEqual(100); - expect(ennemyPokemon.species).toEqual(Species.MEWTWO); - }); - - it("should create an ennemypokemon with specified moveset", () => { - const ennemyPokemon = new PokemonData({ - species: Species.MEWTWO, - level: 100, - moveset: [ - new PokemonMove(Moves.ACID), - new PokemonMove(Moves.ACROBATICS), - new PokemonMove(Moves.FOCUS_ENERGY), - ] - }); - expect(ennemyPokemon.moveset[0].moveId).toBe(Moves.ACID); - expect(ennemyPokemon.moveset[1].moveId).toBe(Moves.ACROBATICS); - expect(ennemyPokemon.moveset[2].moveId).toBe(Moves.FOCUS_ENERGY); - }); -}); diff --git a/src/test/helpers/inGameManip.ts b/src/test/settingMenu/helpers/inGameManip.ts similarity index 100% rename from src/test/helpers/inGameManip.ts rename to src/test/settingMenu/helpers/inGameManip.ts diff --git a/src/test/helpers/menuManip.ts b/src/test/settingMenu/helpers/menuManip.ts similarity index 100% rename from src/test/helpers/menuManip.ts rename to src/test/settingMenu/helpers/menuManip.ts diff --git a/src/test/rebinding_setting.test.ts b/src/test/settingMenu/rebinding_setting.test.ts similarity index 99% rename from src/test/rebinding_setting.test.ts rename to src/test/settingMenu/rebinding_setting.test.ts index 03e8cbb51c4..dee29cd42a2 100644 --- a/src/test/rebinding_setting.test.ts +++ b/src/test/settingMenu/rebinding_setting.test.ts @@ -5,8 +5,8 @@ import { getKeyWithKeycode, getKeyWithSettingName, } from "#app/configs/inputs/configHandler"; -import {MenuManip} from "#app/test/helpers/menuManip"; -import {InGameManip} from "#app/test/helpers/inGameManip"; +import {MenuManip} from "#app/test/settingMenu/helpers/menuManip"; +import {InGameManip} from "#app/test/settingMenu/helpers/inGameManip"; import {Device} from "#app/enums/devices"; import {InterfaceConfig} from "#app/inputs-controller"; import cfg_keyboard_qwerty from "#app/configs/inputs/cfg_keyboard_qwerty"; diff --git a/src/test/pokemonSprite.test.ts b/src/test/sprites/pokemonSprite.test.ts similarity index 98% rename from src/test/pokemonSprite.test.ts rename to src/test/sprites/pokemonSprite.test.ts index 07b3cd01cc7..5c1db85fa6f 100644 --- a/src/test/pokemonSprite.test.ts +++ b/src/test/sprites/pokemonSprite.test.ts @@ -1,8 +1,8 @@ import {beforeAll, describe, expect, it} from "vitest"; -import _masterlist from "../../public/images/pokemon/variant/_masterlist.json"; +import _masterlist from "../../../public/images/pokemon/variant/_masterlist.json"; import fs from "fs"; import path from "path"; -import {getAppRootDir} from "#app/test/testUtils"; +import {getAppRootDir} from "#app/test/sprites/spritesUtils"; const deepCopy = (data) => { return JSON.parse(JSON.stringify(data)); diff --git a/src/test/testUtils.ts b/src/test/sprites/spritesUtils.ts similarity index 100% rename from src/test/testUtils.ts rename to src/test/sprites/spritesUtils.ts diff --git a/src/test/ui/starter-select.test.ts b/src/test/ui/starter-select.test.ts new file mode 100644 index 00000000000..802542da259 --- /dev/null +++ b/src/test/ui/starter-select.test.ts @@ -0,0 +1,612 @@ +import {afterEach, beforeAll, beforeEach, describe, expect, it} from "vitest"; +import Phaser from "phaser"; +import GameManager from "#app/test/utils/gameManager"; +import {Species} from "#app/data/enums/species"; +import { + EncounterPhase, + SelectStarterPhase, + TitlePhase, +} from "#app/phases"; +import {Mode} from "#app/ui/ui"; +import {GameModes} from "#app/game-mode"; +import StarterSelectUiHandler from "#app/ui/starter-select-ui-handler"; +import {Button} from "#app/enums/buttons"; +import OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler"; +import SaveSlotSelectUiHandler from "#app/ui/save-slot-select-ui-handler"; +import {OptionSelectItem} from "#app/ui/abstact-option-select-ui-handler"; +import {Gender} from "#app/data/gender"; +import {Nature} from "#app/data/nature"; +import {Abilities} from "#app/data/enums/abilities"; +import {allSpecies} from "#app/data/pokemon-species"; + + +describe("UI - Starter select", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + }); + + it("Bulbasaur - shiny - variant 2 male", async() => { + await game.importData("src/test/utils/saves/everything.prsv"); + const caughtCount = Object.keys(game.scene.gameData.dexData).filter((key) => { + const species = game.scene.gameData.dexData[key]; + return species.caughtAttr !== 0n; + }).length; + expect(caughtCount).toBe(Object.keys(allSpecies).length); + await game.runToTitle(); + game.onNextPrompt("TitlePhase", Mode.TITLE, () => { + const currentPhase = game.scene.getCurrentPhase() as TitlePhase; + currentPhase.gameMode = GameModes.CLASSIC; + currentPhase.end(); + }); + await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase)); + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.RIGHT); + handler.processInput(Button.ACTION); + }); + let options: OptionSelectItem[]; + let optionSelectUiHandler: OptionSelectUiHandler; + await new Promise((resolve) => { + game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => { + optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler; + options = optionSelectUiHandler.getOptionsWithScroll(); + resolve(); + }); + }); + expect(options.some(option => option.label === "Add to Party")).toBe(true); + expect(options.some(option => option.label === "Toggle IVs")).toBe(true); + expect(options.some(option => option.label === "Manage Moves")).toBe(true); + expect(options.some(option => option.label === "Use Candies")).toBe(true); + expect(options.some(option => option.label === "Cancel")).toBe(true); + optionSelectUiHandler.processInput(Button.ACTION); + + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.SUBMIT); + }); + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { + const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; + saveSlotSelectUiHandler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + await game.phaseInterceptor.whenAboutToRun(EncounterPhase); + + expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR); + expect(game.scene.getParty()[0].shiny).toBe(true); + expect(game.scene.getParty()[0].variant).toBe(2); + expect(game.scene.getParty()[0].gender).toBe(Gender.MALE); + }, 20000); + + it("Bulbasaur - shiny - variant 2 female hardy overgrow", async() => { + await game.importData("src/test/utils/saves/everything.prsv"); + await game.runToTitle(); + game.onNextPrompt("TitlePhase", Mode.TITLE, () => { + const currentPhase = game.scene.getCurrentPhase() as TitlePhase; + currentPhase.gameMode = GameModes.CLASSIC; + currentPhase.end(); + }); + await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase)); + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.RIGHT); + handler.processInput(Button.CYCLE_GENDER); + handler.processInput(Button.ACTION); + }); + let options: OptionSelectItem[]; + let optionSelectUiHandler: OptionSelectUiHandler; + await new Promise((resolve) => { + game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => { + optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler; + options = optionSelectUiHandler.getOptionsWithScroll(); + resolve(); + }); + }); + expect(options.some(option => option.label === "Add to Party")).toBe(true); + expect(options.some(option => option.label === "Toggle IVs")).toBe(true); + expect(options.some(option => option.label === "Manage Moves")).toBe(true); + expect(options.some(option => option.label === "Use Candies")).toBe(true); + expect(options.some(option => option.label === "Cancel")).toBe(true); + optionSelectUiHandler.processInput(Button.ACTION); + + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.SUBMIT); + }); + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { + const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; + saveSlotSelectUiHandler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + await game.phaseInterceptor.whenAboutToRun(EncounterPhase); + + expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR); + expect(game.scene.getParty()[0].shiny).toBe(true); + expect(game.scene.getParty()[0].variant).toBe(2); + expect(game.scene.getParty()[0].nature).toBe(Nature.HARDY); + expect(game.scene.getParty()[0].getAbility().id).toBe(Abilities.OVERGROW); + }, 20000); + + it("Bulbasaur - shiny - variant 2 female lonely cholorophyl", async() => { + await game.importData("src/test/utils/saves/everything.prsv"); + await game.runToTitle(); + game.onNextPrompt("TitlePhase", Mode.TITLE, () => { + const currentPhase = game.scene.getCurrentPhase() as TitlePhase; + currentPhase.gameMode = GameModes.CLASSIC; + currentPhase.end(); + }); + await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase)); + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.RIGHT); + handler.processInput(Button.CYCLE_GENDER); + handler.processInput(Button.CYCLE_NATURE); + handler.processInput(Button.CYCLE_ABILITY); + handler.processInput(Button.ACTION); + }); + let options: OptionSelectItem[]; + let optionSelectUiHandler: OptionSelectUiHandler; + await new Promise((resolve) => { + game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => { + optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler; + options = optionSelectUiHandler.getOptionsWithScroll(); + resolve(); + }); + }); + expect(options.some(option => option.label === "Add to Party")).toBe(true); + expect(options.some(option => option.label === "Toggle IVs")).toBe(true); + expect(options.some(option => option.label === "Manage Moves")).toBe(true); + expect(options.some(option => option.label === "Use Candies")).toBe(true); + expect(options.some(option => option.label === "Cancel")).toBe(true); + optionSelectUiHandler.processInput(Button.ACTION); + + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.SUBMIT); + }); + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { + const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; + saveSlotSelectUiHandler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + await game.phaseInterceptor.whenAboutToRun(EncounterPhase); + + expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR); + expect(game.scene.getParty()[0].shiny).toBe(true); + expect(game.scene.getParty()[0].variant).toBe(2); + expect(game.scene.getParty()[0].nature).toBe(Nature.LONELY); + expect(game.scene.getParty()[0].getAbility().id).toBe(Abilities.CHLOROPHYLL); + }, 20000); + + it("Bulbasaur - shiny - variant 2 female", async() => { + await game.importData("src/test/utils/saves/everything.prsv"); + await game.runToTitle(); + game.onNextPrompt("TitlePhase", Mode.TITLE, () => { + const currentPhase = game.scene.getCurrentPhase() as TitlePhase; + currentPhase.gameMode = GameModes.CLASSIC; + currentPhase.end(); + }); + await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase)); + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.RIGHT); + handler.processInput(Button.CYCLE_GENDER); + handler.processInput(Button.ACTION); + }); + let options: OptionSelectItem[]; + let optionSelectUiHandler: OptionSelectUiHandler; + await new Promise((resolve) => { + game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => { + optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler; + options = optionSelectUiHandler.getOptionsWithScroll(); + resolve(); + }); + }); + expect(options.some(option => option.label === "Add to Party")).toBe(true); + expect(options.some(option => option.label === "Toggle IVs")).toBe(true); + expect(options.some(option => option.label === "Manage Moves")).toBe(true); + expect(options.some(option => option.label === "Use Candies")).toBe(true); + expect(options.some(option => option.label === "Cancel")).toBe(true); + optionSelectUiHandler.processInput(Button.ACTION); + + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.SUBMIT); + }); + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { + const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; + saveSlotSelectUiHandler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + await game.phaseInterceptor.whenAboutToRun(EncounterPhase); + + expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR); + expect(game.scene.getParty()[0].shiny).toBe(true); + expect(game.scene.getParty()[0].variant).toBe(2); + expect(game.scene.getParty()[0].gender).toBe(Gender.FEMALE); + }, 20000); + + it("Bulbasaur - not shiny", async() => { + await game.importData("src/test/utils/saves/everything.prsv"); + await game.runToTitle(); + game.onNextPrompt("TitlePhase", Mode.TITLE, () => { + const currentPhase = game.scene.getCurrentPhase() as TitlePhase; + currentPhase.gameMode = GameModes.CLASSIC; + currentPhase.end(); + }); + await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase)); + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.RIGHT); + handler.processInput(Button.CYCLE_SHINY); + handler.processInput(Button.ACTION); + }); + let options: OptionSelectItem[]; + let optionSelectUiHandler: OptionSelectUiHandler; + await new Promise((resolve) => { + game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => { + optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler; + options = optionSelectUiHandler.getOptionsWithScroll(); + resolve(); + }); + }); + expect(options.some(option => option.label === "Add to Party")).toBe(true); + expect(options.some(option => option.label === "Toggle IVs")).toBe(true); + expect(options.some(option => option.label === "Manage Moves")).toBe(true); + expect(options.some(option => option.label === "Use Candies")).toBe(true); + expect(options.some(option => option.label === "Cancel")).toBe(true); + optionSelectUiHandler.processInput(Button.ACTION); + + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.SUBMIT); + }); + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { + const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; + saveSlotSelectUiHandler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + await game.phaseInterceptor.whenAboutToRun(EncounterPhase); + + expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR); + expect(game.scene.getParty()[0].shiny).toBe(false); + expect(game.scene.getParty()[0].variant).toBe(0); + }, 20000); + + it("Bulbasaur - shiny - variant 0", async() => { + await game.importData("src/test/utils/saves/everything.prsv"); + await game.runToTitle(); + game.onNextPrompt("TitlePhase", Mode.TITLE, () => { + const currentPhase = game.scene.getCurrentPhase() as TitlePhase; + currentPhase.gameMode = GameModes.CLASSIC; + currentPhase.end(); + }); + await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase)); + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.RIGHT); + handler.processInput(Button.V); + handler.processInput(Button.ACTION); + }); + let options: OptionSelectItem[]; + let optionSelectUiHandler: OptionSelectUiHandler; + await new Promise((resolve) => { + game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => { + optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler; + options = optionSelectUiHandler.getOptionsWithScroll(); + resolve(); + }); + }); + expect(options.some(option => option.label === "Add to Party")).toBe(true); + expect(options.some(option => option.label === "Toggle IVs")).toBe(true); + expect(options.some(option => option.label === "Manage Moves")).toBe(true); + expect(options.some(option => option.label === "Use Candies")).toBe(true); + expect(options.some(option => option.label === "Cancel")).toBe(true); + optionSelectUiHandler.processInput(Button.ACTION); + + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.SUBMIT); + }); + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { + const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; + saveSlotSelectUiHandler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + await game.phaseInterceptor.whenAboutToRun(EncounterPhase); + + expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR); + expect(game.scene.getParty()[0].shiny).toBe(true); + expect(game.scene.getParty()[0].variant).toBe(0); + }, 20000); + + it("Bulbasaur - shiny - variant 1", async() => { + await game.importData("src/test/utils/saves/everything.prsv"); + await game.runToTitle(); + game.onNextPrompt("TitlePhase", Mode.TITLE, () => { + const currentPhase = game.scene.getCurrentPhase() as TitlePhase; + currentPhase.gameMode = GameModes.CLASSIC; + currentPhase.end(); + }); + await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase)); + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.RIGHT); + handler.processInput(Button.V); + handler.processInput(Button.V); + handler.processInput(Button.ACTION); + }); + let options: OptionSelectItem[]; + let optionSelectUiHandler: OptionSelectUiHandler; + await new Promise((resolve) => { + game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => { + optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler; + options = optionSelectUiHandler.getOptionsWithScroll(); + resolve(); + }); + }); + expect(options.some(option => option.label === "Add to Party")).toBe(true); + expect(options.some(option => option.label === "Toggle IVs")).toBe(true); + expect(options.some(option => option.label === "Manage Moves")).toBe(true); + expect(options.some(option => option.label === "Use Candies")).toBe(true); + expect(options.some(option => option.label === "Cancel")).toBe(true); + optionSelectUiHandler.processInput(Button.ACTION); + + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.SUBMIT); + }); + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { + const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; + saveSlotSelectUiHandler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + await game.phaseInterceptor.whenAboutToRun(EncounterPhase); + + expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR); + expect(game.scene.getParty()[0].shiny).toBe(true); + expect(game.scene.getParty()[0].variant).toBe(1); + }, 20000); + + it("Bulbasaur - shiny - variant 1", async() => { + await game.importData("src/test/utils/saves/everything.prsv"); + await game.runToTitle(); + game.onNextPrompt("TitlePhase", Mode.TITLE, () => { + const currentPhase = game.scene.getCurrentPhase() as TitlePhase; + currentPhase.gameMode = GameModes.CLASSIC; + currentPhase.end(); + }); + await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase)); + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.RIGHT); + handler.processInput(Button.V); + handler.processInput(Button.V); + handler.processInput(Button.V); + handler.processInput(Button.ACTION); + }); + let options: OptionSelectItem[]; + let optionSelectUiHandler: OptionSelectUiHandler; + await new Promise((resolve) => { + game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => { + optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler; + options = optionSelectUiHandler.getOptionsWithScroll(); + resolve(); + }); + }); + expect(options.some(option => option.label === "Add to Party")).toBe(true); + expect(options.some(option => option.label === "Toggle IVs")).toBe(true); + expect(options.some(option => option.label === "Manage Moves")).toBe(true); + expect(options.some(option => option.label === "Use Candies")).toBe(true); + expect(options.some(option => option.label === "Cancel")).toBe(true); + optionSelectUiHandler.processInput(Button.ACTION); + + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.SUBMIT); + }); + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { + const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; + saveSlotSelectUiHandler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + await game.phaseInterceptor.whenAboutToRun(EncounterPhase); + + expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR); + expect(game.scene.getParty()[0].shiny).toBe(true); + expect(game.scene.getParty()[0].variant).toBe(2); + }, 20000); + + it("Check if first pokemon in party is caterpie from gen 1 and 1rd row, 3rd column ", async() => { + await game.importData("src/test/utils/saves/everything.prsv"); + await game.runToTitle(); + game.onNextPrompt("TitlePhase", Mode.TITLE, () => { + const currentPhase = game.scene.getCurrentPhase() as TitlePhase; + currentPhase.gameMode = GameModes.CLASSIC; + currentPhase.end(); + }); + await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase)); + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.RIGHT); + handler.processInput(Button.RIGHT); + handler.processInput(Button.RIGHT); + handler.processInput(Button.RIGHT); + handler.processInput(Button.ACTION); + }); + let options: OptionSelectItem[]; + let optionSelectUiHandler: OptionSelectUiHandler; + await new Promise((resolve) => { + game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => { + optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler; + options = optionSelectUiHandler.getOptionsWithScroll(); + resolve(); + }); + }); + expect(options.some(option => option.label === "Add to Party")).toBe(true); + expect(options.some(option => option.label === "Toggle IVs")).toBe(true); + expect(options.some(option => option.label === "Manage Moves")).toBe(true); + expect(options.some(option => option.label === "Use Candies")).toBe(true); + expect(options.some(option => option.label === "Cancel")).toBe(true); + optionSelectUiHandler.processInput(Button.ACTION); + + let starterSelectUiHandler: StarterSelectUiHandler; + await new Promise((resolve) => { + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + starterSelectUiHandler = game.scene.ui.getHandler() as StarterSelectUiHandler; + starterSelectUiHandler.processInput(Button.SUBMIT); + resolve(); + }); + }); + expect(starterSelectUiHandler.starterGens[0]).toBe(0); + expect(starterSelectUiHandler.starterCursors[0]).toBe(3); + expect(starterSelectUiHandler.cursorObj.x).toBe(132 + 4 * 18); + expect(starterSelectUiHandler.cursorObj.y).toBe(10); + + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { + const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; + saveSlotSelectUiHandler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + await game.phaseInterceptor.whenAboutToRun(EncounterPhase); + expect(game.scene.getParty()[0].species.speciesId).toBe(Species.CATERPIE); + }, 20000); + + it("Check if first pokemon in party is nidoran_m from gen 1 and 2nd row, 4th column (cursor (9+4)-1) ", async() => { + await game.importData("src/test/utils/saves/everything.prsv"); + await game.runToTitle(); + game.onNextPrompt("TitlePhase", Mode.TITLE, () => { + const currentPhase = game.scene.getCurrentPhase() as TitlePhase; + currentPhase.gameMode = GameModes.CLASSIC; + currentPhase.end(); + }); + await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase)); + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.RIGHT); + handler.processInput(Button.RIGHT); + handler.processInput(Button.RIGHT); + handler.processInput(Button.RIGHT); + handler.processInput(Button.DOWN); + handler.processInput(Button.ACTION); + }); + let options: OptionSelectItem[]; + let optionSelectUiHandler: OptionSelectUiHandler; + await new Promise((resolve) => { + game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => { + optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler; + options = optionSelectUiHandler.getOptionsWithScroll(); + resolve(); + }); + }); + expect(options.some(option => option.label === "Add to Party")).toBe(true); + expect(options.some(option => option.label === "Toggle IVs")).toBe(true); + expect(options.some(option => option.label === "Manage Moves")).toBe(true); + expect(options.some(option => option.label === "Use Candies")).toBe(true); + expect(options.some(option => option.label === "Cancel")).toBe(true); + optionSelectUiHandler.processInput(Button.ACTION); + + let starterSelectUiHandler: StarterSelectUiHandler; + await new Promise((resolve) => { + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + starterSelectUiHandler = game.scene.ui.getHandler() as StarterSelectUiHandler; + starterSelectUiHandler.processInput(Button.SUBMIT); + resolve(); + }); + }); + expect(starterSelectUiHandler.starterGens[0]).toBe(0); + expect(starterSelectUiHandler.starterCursors[0]).toBe(12); + expect(starterSelectUiHandler.cursorObj.x).toBe(132 + 4 * 18); + expect(starterSelectUiHandler.cursorObj.y).toBe(28); + + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { + const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; + saveSlotSelectUiHandler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + await game.phaseInterceptor.whenAboutToRun(EncounterPhase); + expect(game.scene.getParty()[0].species.speciesId).toBe(Species.NIDORAN_M); + }, 20000); +}); diff --git a/src/test/utils/TextInterceptor.ts b/src/test/utils/TextInterceptor.ts new file mode 100644 index 00000000000..d3048f23f74 --- /dev/null +++ b/src/test/utils/TextInterceptor.ts @@ -0,0 +1,16 @@ +export default class TextInterceptor { + private scene; + private logs = []; + constructor(scene) { + this.scene = scene; + scene.messageWrapper = this; + } + + showText(text: string, delay?: integer, callback?: Function, callbackDelay?: integer, prompt?: boolean, promptDelay?: integer): void { + this.logs.push(text); + } + + getLatestMessage(): string { + return this.logs[this.logs.length - 1]; + } +} diff --git a/src/test/utils/fakeMobile.html b/src/test/utils/fakeMobile.html new file mode 100644 index 00000000000..6fa0406f361 --- /dev/null +++ b/src/test/utils/fakeMobile.html @@ -0,0 +1,50 @@ + +

+ \ No newline at end of file diff --git a/src/test/utils/gameManager.ts b/src/test/utils/gameManager.ts new file mode 100644 index 00000000000..cc759fd1563 --- /dev/null +++ b/src/test/utils/gameManager.ts @@ -0,0 +1,223 @@ +import GameWrapper from "#app/test/utils/gameWrapper"; +import {Mode} from "#app/ui/ui"; +import {generateStarter, waitUntil} from "#app/test/utils/gameManagerUtils"; +import { + CheckSwitchPhase, + CommandPhase, + EncounterPhase, + LoginPhase, + PostSummonPhase, + SelectGenderPhase, + SelectStarterPhase, + SummonPhase, + TitlePhase, + ToggleDoublePositionPhase, +} from "#app/phases"; +import BattleScene from "#app/battle-scene.js"; +import PhaseInterceptor from "#app/test/utils/phaseInterceptor"; +import TextInterceptor from "#app/test/utils/TextInterceptor"; +import {expect} from "vitest"; +import {GameModes} from "#app/game-mode"; +import fs from "fs"; +import { AES, enc } from "crypto-js"; +import {updateUserInfo} from "#app/account"; +import {Species} from "#app/data/enums/species"; +import {PlayerGender} from "#app/data/enums/player-gender"; +import {GameDataType} from "#app/data/enums/game-data-type"; +import InputsHandler from "#app/test/utils/inputsHandler"; +import {ExpNotification} from "#app/enums/exp-notification"; + +/** + * Class to manage the game state and transitions between phases. + */ +export default class GameManager { + public gameWrapper: GameWrapper; + public scene: BattleScene; + public phaseInterceptor: PhaseInterceptor; + public textInterceptor: TextInterceptor; + public inputsHandler: InputsHandler; + + /** + * Creates an instance of GameManager. + * @param phaserGame - The Phaser game instance. + * @param bypassLogin - Whether to bypass the login phase. + */ + constructor(phaserGame: Phaser.Game, bypassLogin: boolean = true) { + BattleScene.prototype.randBattleSeedInt = (arg) => arg-1; + this.gameWrapper = new GameWrapper(phaserGame, bypassLogin); + this.scene = new BattleScene(); + this.phaseInterceptor = new PhaseInterceptor(this.scene); + this.textInterceptor = new TextInterceptor(this.scene); + this.gameWrapper.setScene(this.scene); + } + + /** + * Sets the game mode. + * @param mode - The mode to set. + */ + setMode(mode: Mode) { + this.scene.ui?.setMode(mode); + } + + /** + * Waits until the specified mode is set. + * @param mode - The mode to wait for. + * @returns A promise that resolves when the mode is set. + */ + waitMode(mode: Mode): Promise { + return new Promise(async (resolve) => { + await waitUntil(() => this.scene.ui?.getMode() === mode); + return resolve(); + }); + } + + /** + * Ends the current phase. + */ + endPhase() { + this.scene.getCurrentPhase().end(); + } + + /** + * Adds an action to be executed on the next prompt. + * @param phaseTarget - The target phase. + * @param mode - The mode to wait for. + * @param callback - The callback to execute. + * @param expireFn - Optional function to determine if the prompt has expired. + */ + onNextPrompt(phaseTarget: string, mode: Mode, callback: () => void, expireFn?: () => void) { + this.phaseInterceptor.addToNextPrompt(phaseTarget, mode, callback, expireFn); + } + + /** + * Runs the game to the title phase. + * @returns A promise that resolves when the title phase is reached. + */ + runToTitle(): Promise { + return new Promise(async(resolve) => { + await this.phaseInterceptor.run(LoginPhase); + this.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => { + this.scene.gameData.gender = PlayerGender.MALE; + this.endPhase(); + }, () => this.isCurrentPhase(TitlePhase)); + await this.phaseInterceptor.run(SelectGenderPhase, () => this.isCurrentPhase(TitlePhase)); + await this.phaseInterceptor.run(TitlePhase); + this.scene.gameSpeed = 5; + this.scene.moveAnimations = false; + this.scene.showLevelUpStats = false; + this.scene.expGainsSpeed = 3; + this.scene.expParty = ExpNotification.SKIP; + this.scene.hpBarSpeed = 3; + resolve(); + }); + } + + /** + * Runs the game to the summon phase. + * @param species - Optional array of species to summon. + * @returns A promise that resolves when the summon phase is reached. + */ + runToSummon(species?: Species[]): Promise { + return new Promise(async(resolve) => { + await this.runToTitle(); + this.onNextPrompt("TitlePhase", Mode.TITLE, () => { + const starters = generateStarter(this.scene, species); + const selectStarterPhase = new SelectStarterPhase(this.scene, GameModes.CLASSIC); + this.scene.pushPhase(new EncounterPhase(this.scene, false)); + selectStarterPhase.initBattle(starters); + }); + await this.phaseInterceptor.run(EncounterPhase); + resolve(); + }); + } + + /** + * Starts a battle. + * @param species - Optional array of species to start the battle with. + * @returns A promise that resolves when the battle is started. + */ + startBattle(species?: Species[]): Promise { + return new Promise(async(resolve) => { + await this.runToSummon(species); + await this.phaseInterceptor.runFrom(PostSummonPhase).to(ToggleDoublePositionPhase); + await this.phaseInterceptor.run(SummonPhase, () => this.isCurrentPhase(CheckSwitchPhase) || this.isCurrentPhase(PostSummonPhase)); + this.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => { + this.setMode(Mode.MESSAGE); + this.endPhase(); + }, () => this.isCurrentPhase(PostSummonPhase)); + this.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => { + this.setMode(Mode.MESSAGE); + this.endPhase(); + }, () => this.isCurrentPhase(PostSummonPhase)); + await this.phaseInterceptor.run(CheckSwitchPhase, () => this.isCurrentPhase(PostSummonPhase)); + await this.phaseInterceptor.run(CheckSwitchPhase, () => this.isCurrentPhase(PostSummonPhase)); + await this.phaseInterceptor.runFrom(PostSummonPhase).to(CommandPhase); + await waitUntil(() => this.scene.ui?.getMode() === Mode.COMMAND); + console.log("==================[New Turn]=================="); + expect(this.scene.ui?.getMode()).toBe(Mode.COMMAND); + expect(this.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); + return resolve(); + }); + } + + /** + * Checks if the player has won the battle. + * @returns True if the player has won, otherwise false. + */ + isVictory() { + return this.scene.currentBattle.enemyParty.every(pokemon => pokemon.isFainted()); + } + + /** + * Checks if the current phase matches the target phase. + * @param phaseTarget - The target phase. + * @returns True if the current phase matches the target phase, otherwise false. + */ + isCurrentPhase(phaseTarget) { + const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name; + return this.scene.getCurrentPhase().constructor.name === targetName; + } + + /** + * Checks if the current mode matches the target mode. + * @param mode - The target mode. + * @returns True if the current mode matches the target mode, otherwise false. + */ + isCurrentMode(mode: Mode) { + return this.scene.ui?.getMode() === mode; + } + + /** + * Exports the save data to import it in a test game. + * @returns A promise that resolves with the exported save data. + */ + exportSaveToTest(): Promise { + return new Promise(async (resolve) => { + await this.scene.gameData.saveAll(this.scene, true, true, true, true); + this.scene.reset(true); + await waitUntil(() => this.scene.ui?.getMode() === Mode.TITLE); + await this.scene.gameData.tryExportData(GameDataType.SESSION, 0); + await waitUntil(() => localStorage.hasOwnProperty("toExport")); + return resolve(localStorage.getItem("toExport")); + }); + } + + /** + * Imports game data from a file. + * @param path - The path to the data file. + * @returns A promise that resolves with a tuple containing a boolean indicating success and an integer status code. + */ + async importData(path): Promise<[boolean, integer]> { + const saveKey = "x0i2O7WRiANTqPmZ"; + const dataRaw = fs.readFileSync(path, {encoding: "utf8", flag: "r"}); + let dataStr = AES.decrypt(dataRaw, saveKey).toString(enc.Utf8); + dataStr = this.scene.gameData.convertSystemDataStr(dataStr); + const systemData = this.scene.gameData.parseSystemData(dataStr); + const valid = !!systemData.dexData && !!systemData.timestamp; + if (valid) { + await updateUserInfo(); + await this.scene.gameData.initSystem(dataStr); + } + return updateUserInfo(); + } +} diff --git a/src/test/utils/gameManagerUtils.ts b/src/test/utils/gameManagerUtils.ts new file mode 100644 index 00000000000..4a72006faa6 --- /dev/null +++ b/src/test/utils/gameManagerUtils.ts @@ -0,0 +1,87 @@ +// Function to convert Blob to string +import {getDailyRunStarters} from "#app/data/daily-run"; +import {Gender} from "#app/data/gender"; +import {Species} from "#app/data/enums/species"; +import {Starter} from "#app/ui/starter-select-ui-handler"; +import {GameModes, gameModes} from "#app/game-mode"; +import {getPokemonSpecies, getPokemonSpeciesForm} from "#app/data/pokemon-species"; +import {PlayerPokemon} from "#app/field/pokemon"; + +export function blobToString(blob) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + + reader.onloadend = () => { + resolve(reader.result); + }; + + reader.onerror = () => { + reject(new Error("Error reading Blob as string")); + }; + + reader.readAsText(blob); + }); +} + + +export function holdOn(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +export function generateStarter(scene, species?: Species[]) { + const seed = "test"; + const starters = getTestRunStarters(scene, seed, species); + const startingLevel = scene.gameMode.getStartingLevel(); + for (const starter of starters) { + const starterProps = scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); + const starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0)); + const starterGender = starter.species.malePercent !== null + ? !starterProps.female ? Gender.MALE : Gender.FEMALE + : Gender.GENDERLESS; + const starterPokemon = scene.addPlayerPokemon(starter.species, startingLevel, starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, undefined, starter.nature); + starter.moveset = starterPokemon.moveset; + } + return starters; +} + +function getTestRunStarters(scene, seed, species) { + if (!species) { + return getDailyRunStarters(scene, seed); + } + const starters: Starter[] = []; + const startingLevel = gameModes[GameModes.CLASSIC].getStartingLevel(); + + for (const specie of species) { + const starterSpeciesForm = getPokemonSpeciesForm(specie, 0); + const starterSpecies = getPokemonSpecies(starterSpeciesForm.speciesId); + const pokemon = new PlayerPokemon(scene, starterSpecies, startingLevel, undefined, 0, undefined, undefined, undefined, undefined, undefined, undefined); + const starter: Starter = { + species: starterSpecies, + dexAttr: pokemon.getDexAttr(), + abilityIndex: pokemon.abilityIndex, + passive: false, + nature: pokemon.getNature(), + pokerus: pokemon.pokerus + }; + starters.push(starter); + } + return starters; +} + +export function waitUntil(truth) { + return new Promise(resolve => { + const interval = setInterval(() => { + if (truth()) { + clearInterval(interval); + resolve(true); + } + }, 1000); + }); +} + +export function getMovePosition(scene, pokemonIndex, moveIndex) { + const playerPokemon = scene.getPlayerField()[pokemonIndex]; + const moveSet = playerPokemon.getMoveset(); + const index = moveSet.findIndex((move) => move.moveId === moveIndex); + return index; +} diff --git a/src/test/utils/gameWrapper.ts b/src/test/utils/gameWrapper.ts new file mode 100644 index 00000000000..c9aed9865db --- /dev/null +++ b/src/test/utils/gameWrapper.ts @@ -0,0 +1,252 @@ +/* eslint-disable */ +// @ts-nocheck +import * as main from "#app/main"; +import fs from "fs"; +import InputManager = Phaser.Input.InputManager; +import KeyboardManager = Phaser.Input.Keyboard.KeyboardManager; +import KeyboardPlugin = Phaser.Input.Keyboard.KeyboardPlugin; +import GamepadPlugin = Phaser.Input.Gamepad.GamepadPlugin; +import EventEmitter = Phaser.Events.EventEmitter; +import UpdateList = Phaser.GameObjects.UpdateList; +import MockGraphics from "#app/test/utils/mocks/mocksContainer/mockGraphics"; +import MockTextureManager from "#app/test/utils/mocks/mockTextureManager"; +import Phaser from "phaser"; +import {blobToString} from "#app/test/utils/gameManagerUtils"; +import {vi} from "vitest"; +import mockLocalStorage from "#app/test/utils/mocks/mockLocalStorage"; +import mockConsoleLog from "#app/test/utils/mocks/mockConsoleLog"; +import MockLoader from "#app/test/utils/mocks/mockLoader"; +import {MockFetch} from "#app/test/utils/mocks/mockFetch"; +import * as Utils from "#app/utils"; +import InputText from "phaser3-rex-plugins/plugins/inputtext"; +import {MockClock} from "#app/test/utils/mocks/mockClock"; +import BattleScene from "#app/battle-scene.js"; +import {MoveAnim} from "#app/data/battle-anims"; +import Pokemon from "#app/field/pokemon"; +import * as battleScene from "#app/battle-scene"; + +Object.defineProperty(window, "localStorage", { + value: mockLocalStorage(), +}); +Object.defineProperty(window, "console", { + value: mockConsoleLog(false), +}); + + +InputText.prototype.setElement = () => null; +InputText.prototype.resize = () => null; +window.URL.createObjectURL = (blob: Blob) => { + blobToString(blob).then((data: string) => { + localStorage.setItem("toExport", data); + }) + return null; +}; +navigator.getGamepads = vi.fn().mockReturnValue([]); +global.fetch = vi.fn(MockFetch); +Utils.setCookie(Utils.sessionIdKey, 'fake_token'); + + +window.matchMedia = () => ({ + matches: false, +}); + + +/** + * Sets this object's position relative to another object with a given offset + * @param guideObject {@linkcode Phaser.GameObjects.GameObject} to base the position off of + * @param x The relative x position + * @param y The relative y position + */ +const setPositionRelative = function (guideObject: any, x: number, y: number) { + const offsetX = guideObject.width * (-0.5 + (0.5 - guideObject.originX)); + const offsetY = guideObject.height * (-0.5 + (0.5 - guideObject.originY)); + this.setPosition(guideObject.x + offsetX + x, guideObject.y + offsetY + y); +}; + +Phaser.GameObjects.Container.prototype.setPositionRelative = setPositionRelative; +Phaser.GameObjects.Sprite.prototype.setPositionRelative = setPositionRelative; +Phaser.GameObjects.Image.prototype.setPositionRelative = setPositionRelative; +Phaser.GameObjects.NineSlice.prototype.setPositionRelative = setPositionRelative; +Phaser.GameObjects.Text.prototype.setPositionRelative = setPositionRelative; +Phaser.GameObjects.Rectangle.prototype.setPositionRelative = setPositionRelative; + + +export default class GameWrapper { + public game: Phaser.Game; + public scene: BattleScene; + + constructor(phaserGame: Phaser.Game, bypassLogin: boolean) { + Phaser.Math.RND.sow([ 'test' ]); + vi.spyOn(Utils, "apiFetch", "get").mockReturnValue(fetch); + if (bypassLogin) { + vi.spyOn(battleScene, "bypassLogin", "get").mockReturnValue(true); + } + this.game = phaserGame; + MoveAnim.prototype.getAnim = () => ({ + frames: {}, + }); + Pokemon.prototype.enableMask = () => null; + localStorage.clear(); + } + + setScene(scene: BattleScene) { + this.scene = scene; + this.injectMandatory(); + this.scene.preload && this.scene.preload(); + this.scene.create(); + } + + injectMandatory() { + this.game.config = { + seed: ["test"], + } + this.scene.game = this.game; + this.game.renderer = { + maxTextures: -1, + gl: {}, + deleteTexture: () => null, + canvasToTexture: () => ({}), + createCanvasTexture: () => ({}), + pipelines: { + add: () => null, + }, + }; + this.scene.renderer = this.game.renderer; + this.scene.children = { + removeAll: () => null, + }; + + this.scene.sound = { + play: () => null, + pause: () => null, + setRate: () => null, + add: () => this.scene.sound, + get: () => this.scene.sound, + getAllPlaying: () => [], + manager: { + game: this.game, + }, + setVolume: () => null, + stopByKey: () => null, + on: (evt, callback) => callback(), + key: "", + }; + + this.scene.tweens = { + add: (data) => { + if (data.onComplete) { + data.onComplete(); + } + }, + getTweensOf: () => ([]), + killTweensOf: () => ([]), + chain: () => null, + addCounter: (data) => { + if (data.onComplete) { + data.onComplete(); + } + }, + }; + + this.scene.anims = this.game.anims; + this.scene.cache = this.game.cache; + this.scene.plugins = this.game.plugins; + this.scene.registry = this.game.registry; + this.scene.scale = this.game.scale; + this.scene.textures = this.game.textures; + this.scene.events = this.game.events; + this.scene.manager = new InputManager(this.game, {}); + this.scene.manager.keyboard = new KeyboardManager(this.scene); + this.scene.pluginEvents = new EventEmitter(); + this.scene.domContainer = {} as HTMLDivElement; + this.scene.spritePipeline = {}; + this.scene.fieldSpritePipeline = {}; + this.scene.load = new MockLoader(this.scene); + this.scene.sys = { + queueDepthSort: () => null, + anims: this.game.anims, + game: this.game, + textures: { + addCanvas: () => ({ + get: () => ({ // this.frame in Text.js + source: {}, + setSize: () => null, + glTexture: () => ({ + spectorMetadata: {}, + }), + }), + }) + }, + cache: this.scene.load.cacheManager, + scale: this.game.scale, + // _scene.sys.scale = new ScaleManager(_scene); + // events: { + // on: () => null, + // }, + events: new EventEmitter(), + settings: { + loader: { + key: 'battle', + } + }, + input: this.game.input, + }; + const mockTextureManager = new MockTextureManager(this.scene); + this.scene.add = mockTextureManager.add; + this.scene.sys.displayList = this.scene.add.displayList; + this.scene.sys.updateList = new UpdateList(this.scene); + this.scene.systems = this.scene.sys; + this.scene.input = this.game.input; + this.scene.scene = this.scene; + this.scene.input.keyboard = new KeyboardPlugin(this.scene); + this.scene.input.gamepad = new GamepadPlugin(this.scene); + this.scene.cachedFetch = (url, init) => { + return new Promise((resolve) => { + // need to remove that if later we want to test battle-anims + const newUrl = url.includes('./battle-anims/') ? prependPath('./battle-anims/tackle.json') : prependPath(url); + let raw; + try { + raw = fs.readFileSync(newUrl, {encoding: "utf8", flag: "r"}); + } catch(e) { + return resolve(createFetchBadResponse({})); + } + const data = JSON.parse(raw); + const response = createFetchResponse(data); + return resolve(response); + }); + }; + this.scene.make = { + graphics: (config) => new MockGraphics(mockTextureManager, config), + rexTransitionImagePack: () => ({ + transit: () => null, + }), + }; + this.scene.time = new MockClock(this.scene); + } +} + +function prependPath(originalPath) { + const prefix = "public"; + if (originalPath.startsWith("./")) { + return originalPath.replace("./", `${prefix}/`); + } + return originalPath; +} +// Simulate fetch response +function createFetchResponse(data) { + return { + ok: true, + status: 200, + json: () => Promise.resolve(data), + text: () => Promise.resolve(JSON.stringify(data)), + }; +} +// Simulate fetch response +function createFetchBadResponse(data) { + return { + ok: false, + status: 404, + json: () => Promise.resolve(data), + text: () => Promise.resolve(JSON.stringify(data)), + }; +} \ No newline at end of file diff --git a/src/test/utils/inputsHandler.ts b/src/test/utils/inputsHandler.ts new file mode 100644 index 00000000000..fd961ed3ef6 --- /dev/null +++ b/src/test/utils/inputsHandler.ts @@ -0,0 +1,115 @@ +import BattleScene from "#app/battle-scene"; +import Phaser from "phaser"; +import {InputsController} from "#app/inputs-controller"; +import pad_xbox360 from "#app/configs/inputs/pad_xbox360"; +import {holdOn} from "#app/test/utils/gameManagerUtils"; +import {initTouchControls} from "#app/touch-controls"; +import { JSDOM } from "jsdom"; +import fs from "fs"; + + +export default class InputsHandler { + private scene: BattleScene; + private events: Phaser.Events.EventEmitter; + private inputController: InputsController; + public log = []; + public logUp = []; + private fakePad: Fakepad; + private fakeMobile: FakeMobile; + + constructor(scene: BattleScene) { + this.scene = scene; + this.inputController = this.scene.inputController; + this.fakePad = new Fakepad(pad_xbox360); + this.fakeMobile = new FakeMobile(); + this.scene.input.gamepad.gamepads.push(this.fakePad); + this.init(); + } + + pressTouch(button: string, duration: integer): Promise { + return new Promise(async (resolve) => { + this.fakeMobile.touchDown(button); + await holdOn(duration); + this.fakeMobile.touchUp(button); + resolve(); + }); + } + + pressGamepadButton(button: integer, duration: integer): Promise { + return new Promise(async (resolve) => { + this.scene.input.gamepad.emit("down", this.fakePad, {index: button}); + await holdOn(duration); + this.scene.input.gamepad.emit("up", this.fakePad, {index: button}); + resolve(); + }); + } + + pressKeyboardKey(key: integer, duration: integer): Promise { + return new Promise(async (resolve) => { + this.scene.input.keyboard.emit("keydown", {keyCode: key}); + await holdOn(duration); + this.scene.input.keyboard.emit("keyup", {keyCode: key}); + resolve(); + }); + } + + init(): void { + setInterval(() => { + this.inputController.update(); + }); + initTouchControls(this.inputController.events); + this.events = this.inputController.events; + this.scene.input.gamepad.emit("connected", this.fakePad); + this.listenInputs(); + } + + listenInputs(): void { + this.events.on("input_down", (event) => { + this.log.push({type: "input_down", button: event.button}); + }, this); + + this.events.on("input_up", (event) => { + this.logUp.push({type: "input_up", button: event.button}); + }, this); + } +} + +class Fakepad extends Phaser.Input.Gamepad.Gamepad { + public id: string; + public index: number; + + constructor(pad) { + super(undefined, {...pad, buttons: pad.deviceMapping, axes: []}); + this.id = "xbox_360_fakepad"; + this.index = 0; + } +} + +class FakeMobile { + constructor() { + const fakeMobilePage = fs.readFileSync("./src/test/utils/fakeMobile.html", {encoding: "utf8", flag: "r"}); + const dom = new JSDOM(fakeMobilePage); + Object.defineProperty(window, "document", { + value: dom.window.document, + configurable: true, + }); + } + + touchDown(button: string) { + const node = document.querySelector(`[data-key][id='${button}']`); + if (!node) { + return; + } + const event = new Event("touchstart"); + node.dispatchEvent(event); + } + + touchUp(button: string) { + const node = document.querySelector(`[data-key][id='${button}']`); + if (!node) { + return; + } + const event = new Event("touchend"); + node.dispatchEvent(event); + } +} diff --git a/src/test/utils/misc.test.ts b/src/test/utils/misc.test.ts new file mode 100644 index 00000000000..da67fb48192 --- /dev/null +++ b/src/test/utils/misc.test.ts @@ -0,0 +1,79 @@ +import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest"; +import Phaser from "phaser"; +import GameManager from "#app/test/utils/gameManager"; +import {apiFetch} from "#app/utils"; +import {waitUntil} from "#app/test/utils/gameManagerUtils"; + +describe("Test misc", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + }); + + it("test fetch mock async", async () => { + const spy = vi.fn(); + await fetch("https://localhost:8080/account/info").then(response => { + expect(response.status).toBe(200); + expect(response.ok).toBe(true); + return response.json(); + }).then(data => { + spy(); // Call the spy function + expect(data).toEqual({"username":"greenlamp","lastSessionSlot":0}); + }); + expect(spy).toHaveBeenCalled(); + }); + + it("test apifetch mock async", async () => { + const spy = vi.fn(); + await apiFetch("https://localhost:8080/account/info").then(response => { + expect(response.status).toBe(200); + expect(response.ok).toBe(true); + return response.json(); + }).then(data => { + spy(); // Call the spy function + expect(data).toEqual({"username":"greenlamp","lastSessionSlot":0}); + }); + expect(spy).toHaveBeenCalled(); + }); + + it("test fetch mock sync", async () => { + const response = await fetch("https://localhost:8080/account/info"); + const data = await response.json(); + + expect(response.ok).toBe(true); + expect(response.status).toBe(200); + expect(data).toEqual({"username":"greenlamp","lastSessionSlot":0}); + }); + + it("test apifetch mock sync", async () => { + const data = await game.scene.cachedFetch("./battle-anims/splishy-splash.json"); + expect(data).not.toBeUndefined(); + }); + + it("testing wait phase queue", async () => { + const fakeScene = { + phaseQueue: [1, 2, 3] // Initially not empty + }; + setTimeout(() => { + fakeScene.phaseQueue = []; + }, 500); + const spy = vi.fn(); + await waitUntil(() => fakeScene.phaseQueue.length === 0).then(result => { + expect(result).toBe(true); + spy(); // Call the spy function + }); + expect(spy).toHaveBeenCalled(); + }); +}); diff --git a/src/test/utils/mocks/mockClock.ts b/src/test/utils/mocks/mockClock.ts new file mode 100644 index 00000000000..0d5ea68ed59 --- /dev/null +++ b/src/test/utils/mocks/mockClock.ts @@ -0,0 +1,17 @@ +import Clock = Phaser.Time.Clock; + + +export class MockClock extends Clock { + constructor(scene) { + super(scene); + setInterval(() => { + /* + To simulate frame update + eventEmitter.on(SceneEvents.PRE_UPDATE, this.preUpdate, this); + eventEmitter.on(SceneEvents.UPDATE, this.update, this); + */ + this.preUpdate(this.systems.game.loop.time, 100); + this.update(this.systems.game.loop.time, 100); + }, 100); + } +} diff --git a/src/test/utils/mocks/mockConsoleLog.ts b/src/test/utils/mocks/mockConsoleLog.ts new file mode 100644 index 00000000000..61c6c14b989 --- /dev/null +++ b/src/test/utils/mocks/mockConsoleLog.ts @@ -0,0 +1,82 @@ +const MockConsoleLog = (_logDisabled= false, _phaseText=false) => { + let logs = []; + const logDisabled: boolean = _logDisabled; + const phaseText: boolean = _phaseText; + const originalLog = console.log; + const originalError = console.error; + const originalDebug = console.debug; + const originalWarn = console.warn; + const notified = []; + + const blacklist = ["Phaser", "variant icon does not exist", "Texture \"%s\" not found"]; + const whitelist = ["Phase"]; + + return ({ + log(...args) { + const argsStr = this.getStr(args); + logs.push(argsStr); + if (logDisabled && (!phaseText)) { + return; + } + if ((phaseText && !whitelist.some((b) => argsStr.includes(b))) || blacklist.some((b) => argsStr.includes(b))) { + return; + } + originalLog(args); + }, + error(...args) { + const argsStr = this.getStr(args); + logs.push(argsStr); + originalError(args); // Appelle le console.error originel + }, + debug(...args) { + const argsStr = this.getStr(args); + logs.push(argsStr); + if (logDisabled && (!phaseText)) { + return; + } + if (!whitelist.some((b) => argsStr.includes(b)) || blacklist.some((b) => argsStr.includes(b))) { + return; + } + originalDebug(args); + }, + warn(...args) { + const argsStr = this.getStr(args); + logs.push(args); + if (logDisabled && (!phaseText)) { + return; + } + if (!whitelist.some((b) => argsStr.includes(b)) || blacklist.some((b) => argsStr.includes(b))) { + return; + } + originalWarn(args); + }, + notify(msg) { + originalLog(msg); + notified.push(msg); + }, + getLogs() { + return logs; + }, + clearLogs() { + logs = []; + }, + getStr(...args) { + return args.map(arg => { + if (typeof arg === "object" && arg !== null) { + // Handle objects including arrays + return JSON.stringify(arg, (key, value) => + typeof value === "bigint" ? value.toString() : value + ); + } else if (typeof arg === "bigint") { + // Handle BigInt values + return arg.toString(); + } else { + // Handle all other types + return arg.toString(); + } + }).join(";"); + }, + }); +}; + +export default MockConsoleLog; diff --git a/src/test/utils/mocks/mockFetch.ts b/src/test/utils/mocks/mockFetch.ts new file mode 100644 index 00000000000..ad364a95885 --- /dev/null +++ b/src/test/utils/mocks/mockFetch.ts @@ -0,0 +1,32 @@ +export const MockFetch = (input, init) => { + const url = typeof input === "string" ? input : input.url; + + let responseHandler; + let responseText; + + const handlers = { + "account/info": {"username":"greenlamp","lastSessionSlot":0}, + "savedata/session": {}, + "savedata/system": {}, + "savedata/updateall": "", + "daily/rankingpagecount": { data: 0 }, + "game/titlestats": {"playerCount":0,"battleCount":5}, + "daily/rankings": [], + }; + + + for (const key of Object.keys(handlers)) { + if (url.includes(key)) { + responseHandler = async() => handlers[key]; + responseText = async() => handlers[key] ? JSON.stringify(handlers[key]) : handlers[key]; + break; + } + } + + return Promise.resolve({ + ok: true, + status: 200, + json: responseHandler, + text: responseText, + }); +}; diff --git a/src/test/utils/mocks/mockLoader.ts b/src/test/utils/mocks/mockLoader.ts new file mode 100644 index 00000000000..661eb72f2c2 --- /dev/null +++ b/src/test/utils/mocks/mockLoader.ts @@ -0,0 +1,42 @@ +import CacheManager = Phaser.Cache.CacheManager; + + +export default class MockLoader { + public cacheManager; + constructor(scene) { + this.cacheManager = new CacheManager(scene); + } + + once(event, callback) { + callback(); + } + + setBaseURL(url) { + return null; + } + + video() { + return null; + } + + spritesheet(key, url, frameConfig) { + } + + audio(key, url) { + + } + + isLoading() { + return false; + } + + start() { + } + + image() { + + } + + atlas(key, textureUrl, atlasUrl) { + } +} diff --git a/src/test/utils/mocks/mockLocalStorage.ts b/src/test/utils/mocks/mockLocalStorage.ts new file mode 100644 index 00000000000..bb4ea4e890d --- /dev/null +++ b/src/test/utils/mocks/mockLocalStorage.ts @@ -0,0 +1,27 @@ +const mockLocalStorage = (() => { + let store = {} as Storage; + + return { + getItem(key: string) { + return store[key]; + }, + + setItem(key: string, value: string) { + store[key] = value; + }, + + hasOwnProperty(key: string) { + return store.hasOwnProperty(key); + }, + + removeItem(key: string) { + delete store[key]; + }, + + clear() { + store = {} as Storage; + }, + }; +}); + +export default mockLocalStorage; diff --git a/src/test/utils/mocks/mockTextureManager.ts b/src/test/utils/mocks/mockTextureManager.ts new file mode 100644 index 00000000000..e207f96b722 --- /dev/null +++ b/src/test/utils/mocks/mockTextureManager.ts @@ -0,0 +1,85 @@ +import MockContainer from "#app/test/utils/mocks/mocksContainer/mockContainer"; +import MockSprite from "#app/test/utils/mocks/mocksContainer/mockSprite"; +import MockRectangle from "#app/test/utils/mocks/mocksContainer/mockRectangle"; +import MockNineslice from "#app/test/utils/mocks/mocksContainer/mockNineslice"; +import MockImage from "#app/test/utils/mocks/mocksContainer/mockImage"; +import MockText from "#app/test/utils/mocks/mocksContainer/mockText"; +import MockPolygon from "#app/test/utils/mocks/mocksContainer/mockPolygon"; + + +export default class MockTextureManager { + private textures: Map; + private scene; + public add; + public displayList; + public list = []; + + constructor(scene) { + this.scene = scene; + this.textures = new Map(); + this.displayList = new Phaser.GameObjects.DisplayList(scene); + this.add = { + container: this.container.bind(this), + sprite: this.sprite.bind(this), + tileSprite: this.sprite.bind(this), + existing: this.existing.bind(this), + rectangle: this.rectangle.bind(this), + nineslice: this.nineslice.bind(this), + image: this.image.bind(this), + polygon: this.polygon.bind(this), + text: this.text.bind(this), + bitmapText: this.text.bind(this), + displayList: this.displayList, + }; + } + + container(x, y) { + const container = new MockContainer(this, x, y); + this.list.push(container); + return container; + } + + sprite(x,y, texture) { + const sprite = new MockSprite(this, x, y, texture); + this.list.push(sprite); + return sprite; + } + + existing(obj) { + // const whitelist = ["ArenaBase", "PlayerPokemon", "EnemyPokemon"]; + // const key = obj.constructor.name; + // if (whitelist.includes(key) || obj.texture?.key?.includes("trainer_")) { + // this.containers.push(obj); + // } + } + + rectangle(x, y, width, height, fillColor) { + const rectangle = new MockRectangle(this, x, y, width, height, fillColor); + this.list.push(rectangle); + return rectangle; + } + + nineslice(x, y, texture, frame, width, height, leftWidth, rightWidth, topHeight, bottomHeight) { + const nineSlice = new MockNineslice(this, x, y, texture, frame, width, height, leftWidth, rightWidth, topHeight, bottomHeight); + this.list.push(nineSlice); + return nineSlice; + } + + image(x, y, texture) { + const image = new MockImage(this, x, y, texture); + this.list.push(image); + return image; + } + + text(x, y, content, styleOptions) { + const text = new MockText(this, x, y, content, styleOptions); + this.list.push(text); + return text; + } + + polygon(x, y, content, fillColor, fillAlpha) { + const polygon = new MockPolygon(this, x, y, content, fillColor, fillAlpha); + this.list.push(polygon); + return polygon; + } +} diff --git a/src/test/utils/mocks/mocksContainer/mockContainer.ts b/src/test/utils/mocks/mocksContainer/mockContainer.ts new file mode 100644 index 00000000000..2e2b9567796 --- /dev/null +++ b/src/test/utils/mocks/mocksContainer/mockContainer.ts @@ -0,0 +1,207 @@ +import MockTextureManager from "#app/test/utils/mocks/mockTextureManager"; + +export default class MockContainer { + protected x; + protected y; + protected scene; + protected width; + protected height; + protected visible; + private alpha; + private style; + public frame; + protected textureManager; + public list = []; + + constructor(textureManager: MockTextureManager, x, y) { + this.x = x; + this.y = y; + this.frame = {}; + this.textureManager = textureManager; + } + setVisible(visible) { + this.visible = visible; + } + + once(event, callback, source) { + } + + off(event, callback, source) { + } + + removeFromDisplayList() { + // same as remove or destroy + } + + addedToScene() { + // This callback is invoked when this Game Object is added to a Scene. + } + + setSize(width, height) { + // Sets the size of this Game Object. + } + + setMask() { + /// Sets the mask that this Game Object will use to render with. + } + + setPositionRelative(source, x, y) { + /// Sets the position of this Game Object to be a relative position from the source Game Object. + } + + setInteractive(hitArea?, callback?, dropZone?) { + /// Sets the InteractiveObject to be a drop zone for a drag and drop operation. + } + setOrigin(x, y) { + this.x = x; + this.y = y; + } + + setAlpha(alpha) { + this.alpha = alpha; + } + + setFrame(frame, updateSize?: boolean, updateOrigin?: boolean) { + // Sets the frame this Game Object will use to render with. + } + + setScale(scale) { + // Sets the scale of this Game Object. + } + + setPosition(x, y) { + this.x = x; + this.y = y; + } + + setX(x) { + this.x = x; + } + + setY(y) { + this.y = y; + } + + destroy() { + this.list = []; + } + + setShadow(shadowXpos, shadowYpos, shadowColor) { + // Sets the shadow settings for this Game Object. + } + + setLineSpacing(lineSpacing) { + // Sets the line spacing value of this Game Object. + } + + setText(text) { + // Sets the text this Game Object will display. + } + + setAngle(angle) { + // Sets the angle of this Game Object. + } + + setShadowOffset(offsetX, offsetY) { + // Sets the shadow offset values. + } + + setWordWrapWidth(width) { + // Sets the width (in pixels) to use for wrapping lines. + } + + setFontSize(fontSize) { + // Sets the font size of this Game Object. + } + getBounds() { + return { width: this.width, height: this.height }; + } + + setColor(color) { + // Sets the tint of this Game Object. + } + + setShadowColor(color) { + // Sets the shadow color. + } + + setTint(color) { + // Sets the tint of this Game Object. + } + + setStrokeStyle(thickness, color) { + // Sets the stroke style for the graphics. + return this; + } + + setDepth(depth) { + // Sets the depth of this Game Object. + } + + setTexture(texture) { + // Sets the texture this Game Object will use to render with. + } + + clearTint() { + // Clears any previously set tint. + } + + sendToBack() { + // Sends this Game Object to the back of its parent's display list. + } + + moveAbove(obj) { + // Moves this Game Object to be above the given Game Object in the display list. + } + + moveBelow(obj) { + // Moves this Game Object to be below the given Game Object in the display list. + } + + setName(name) { + // return this.phaserSprite.setName(name); + } + + bringToTop(obj) { + // Brings this Game Object to the top of its parents display list. + } + + on(event, callback, source) { + } + + add(obj) { + // Adds a child to this Game Object. + this.list.push(obj); + } + + removeAll() { + // Removes all Game Objects from this Container. + this.list = []; + } + + addAt(obj, index) { + // Adds a Game Object to this Container at the given index. + this.list.splice(index, 0, obj); + } + + remove(obj) { + const index = this.list.indexOf(obj); + if (index !== -1) { + this.list.splice(index, 1); + } + } + + getIndex(obj) { + const index = this.list.indexOf(obj); + return index || -1; + } + + getAt(index) { + return this.list[index]; + } + + getAll() { + return this.list; + } + +} diff --git a/src/test/utils/mocks/mocksContainer/mockGraphics.ts b/src/test/utils/mocks/mocksContainer/mockGraphics.ts new file mode 100644 index 00000000000..a08f150fe61 --- /dev/null +++ b/src/test/utils/mocks/mocksContainer/mockGraphics.ts @@ -0,0 +1,96 @@ +export default class MockGraphics { + private scene; + public list = []; + constructor(textureManager, config) { + this.scene = textureManager.scene; + } + + fillStyle(color) { + // Sets the fill style to be used by the fill methods. + } + + beginPath() { + // Starts a new path by emptying the list of sub-paths. Call this method when you want to create a new path. + } + + fillRect(x, y, width, height) { + // Adds a rectangle shape to the path which is filled when you call fill(). + } + + createGeometryMask() { + // Creates a geometry mask. + } + + setOrigin(x, y) { + } + + setAlpha(alpha) { + } + + setVisible(visible) { + } + + setName(name) { + } + + once(event, callback, source) { + } + + removeFromDisplayList() { + // same as remove or destroy + } + + addedToScene() { + // This callback is invoked when this Game Object is added to a Scene. + } + + setPositionRelative(source, x, y) { + /// Sets the position of this Game Object to be a relative position from the source Game Object. + } + + destroy() { + this.list = []; + } + + setScale(scale) { + // Sets the scale of this Game Object. + } + + off(event, callback, source) { + } + + add(obj) { + // Adds a child to this Game Object. + this.list.push(obj); + } + + removeAll() { + // Removes all Game Objects from this Container. + this.list = []; + } + + addAt(obj, index) { + // Adds a Game Object to this Container at the given index. + this.list.splice(index, 0, obj); + } + + remove(obj) { + const index = this.list.indexOf(obj); + if (index !== -1) { + this.list.splice(index, 1); + } + } + + getIndex(obj) { + const index = this.list.indexOf(obj); + return index || -1; + } + + getAt(index) { + return this.list[index]; + } + + getAll() { + return this.list; + } +} diff --git a/src/test/utils/mocks/mocksContainer/mockImage.ts b/src/test/utils/mocks/mocksContainer/mockImage.ts new file mode 100644 index 00000000000..601dfd22811 --- /dev/null +++ b/src/test/utils/mocks/mocksContainer/mockImage.ts @@ -0,0 +1,11 @@ +import MockContainer from "#app/test/utils/mocks/mocksContainer/mockContainer"; + + +export default class MockImage extends MockContainer { + private texture; + + constructor(textureManager, x, y, texture) { + super(textureManager, x, y); + this.texture = texture; + } +} diff --git a/src/test/utils/mocks/mocksContainer/mockNineslice.ts b/src/test/utils/mocks/mocksContainer/mockNineslice.ts new file mode 100644 index 00000000000..3edbbb18c31 --- /dev/null +++ b/src/test/utils/mocks/mocksContainer/mockNineslice.ts @@ -0,0 +1,20 @@ +import MockContainer from "#app/test/utils/mocks/mocksContainer/mockContainer"; + + +export default class MockNineslice extends MockContainer { + private texture; + private leftWidth; + private rightWidth; + private topHeight; + private bottomHeight; + + constructor(textureManager, x, y, texture, frame, width, height, leftWidth, rightWidth, topHeight, bottomHeight) { + super(textureManager, x, y); + this.texture = texture; + this.frame = frame; + this.leftWidth = leftWidth; + this.rightWidth = rightWidth; + this.topHeight = topHeight; + this.bottomHeight = bottomHeight; + } +} diff --git a/src/test/utils/mocks/mocksContainer/mockPolygon.ts b/src/test/utils/mocks/mocksContainer/mockPolygon.ts new file mode 100644 index 00000000000..cf448883ce0 --- /dev/null +++ b/src/test/utils/mocks/mocksContainer/mockPolygon.ts @@ -0,0 +1,9 @@ +import MockContainer from "#app/test/utils/mocks/mocksContainer/mockContainer"; + + +export default class MockPolygon extends MockContainer { + constructor(textureManager, x, y, content, fillColor, fillAlpha) { + super(textureManager, x, y); + } +} + diff --git a/src/test/utils/mocks/mocksContainer/mockRectangle.ts b/src/test/utils/mocks/mocksContainer/mockRectangle.ts new file mode 100644 index 00000000000..f4ecd2af9d8 --- /dev/null +++ b/src/test/utils/mocks/mocksContainer/mockRectangle.ts @@ -0,0 +1,74 @@ +export default class MockRectangle { + private fillColor; + private scene; + public list = []; + + constructor(textureManager, x, y, width, height, fillColor) { + this.fillColor = fillColor; + this.scene = textureManager.scene; + } + setOrigin(x, y) { + } + + setAlpha(alpha) { + } + setVisible(visible) { + } + + setName(name) { + } + + once(event, callback, source) { + } + + removeFromDisplayList() { + // same as remove or destroy + } + + addedToScene() { + // This callback is invoked when this Game Object is added to a Scene. + } + + setPositionRelative(source, x, y) { + /// Sets the position of this Game Object to be a relative position from the source Game Object. + } + + destroy() { + this.list = []; + } + + add(obj) { + // Adds a child to this Game Object. + this.list.push(obj); + } + + removeAll() { + // Removes all Game Objects from this Container. + this.list = []; + } + + addAt(obj, index) { + // Adds a Game Object to this Container at the given index. + this.list.splice(index, 0, obj); + } + + remove(obj) { + const index = this.list.indexOf(obj); + if (index !== -1) { + this.list.splice(index, 1); + } + } + + getIndex(obj) { + const index = this.list.indexOf(obj); + return index || -1; + } + + getAt(index) { + return this.list[index]; + } + + getAll() { + return this.list; + } +} diff --git a/src/test/utils/mocks/mocksContainer/mockSprite.ts b/src/test/utils/mocks/mocksContainer/mockSprite.ts new file mode 100644 index 00000000000..30effe185ad --- /dev/null +++ b/src/test/utils/mocks/mocksContainer/mockSprite.ts @@ -0,0 +1,205 @@ +import Sprite = Phaser.GameObjects.Sprite; +import Frame = Phaser.Textures.Frame; +import Phaser from "phaser"; + + +export default class MockSprite { + private phaserSprite; + public pipelineData; + public texture; + public key; + public frame; + public textureManager; + public scene; + public anims; + public list = []; + constructor(textureManager, x, y, texture) { + this.textureManager = textureManager; + this.scene = textureManager.scene; + Phaser.GameObjects.Sprite.prototype.setInteractive = this.setInteractive; + // @ts-ignore + Phaser.GameObjects.Sprite.prototype.setTexture = this.setTexture; + Phaser.GameObjects.Sprite.prototype.setSizeToFrame = this.setSizeToFrame; + Phaser.GameObjects.Sprite.prototype.setFrame = this.setFrame; + // Phaser.GameObjects.Sprite.prototype.disable = this.disable; + + // Phaser.GameObjects.Sprite.prototype.texture = { frameTotal: 1, get: () => null }; + this.phaserSprite = new Phaser.GameObjects.Sprite(textureManager.scene, x, y, texture); + this.pipelineData = {}; + this.texture = { + key: texture || "", + }; + this.anims = { + pause: () => null, + }; + } + + setTexture(key: string, frame?: string | number) { + return this; + } + + setSizeToFrame(frame?: boolean | Frame): Sprite { + return {} as Sprite; + } + + setPipeline(obj) { + // Sets the pipeline of this Game Object. + return this.phaserSprite.setPipeline(obj); + } + + off(event, callback, source) { + } + + setTintFill(color) { + // Sets the tint fill color. + return this.phaserSprite.setTintFill(color); + } + + setScale(scale) { + return this.phaserSprite.setScale(scale); + } + + setOrigin(x, y) { + return this.phaserSprite.setOrigin(x, y); + } + + setSize(width, height) { + // Sets the size of this Game Object. + return this.phaserSprite.setSize(width, height); + } + + once(event, callback, source) { + return this.phaserSprite.once(event, callback, source); + } + + removeFromDisplayList() { + // same as remove or destroy + return this.phaserSprite.removeFromDisplayList(); + } + + addedToScene() { + // This callback is invoked when this Game Object is added to a Scene. + return this.phaserSprite.addedToScene(); + } + + setVisible(visible) { + return this.phaserSprite.setVisible(visible); + } + + setPosition(x, y) { + return this.phaserSprite.setPosition(x, y); + } + + stop() { + return this.phaserSprite.stop(); + } + + setInteractive(hitArea, hitAreaCallback, dropZone) { + return null; + } + + on(event, callback, source) { + return this.phaserSprite.on(event, callback, source); + } + + setAlpha(alpha) { + return this.phaserSprite.setAlpha(alpha); + } + + setTint(color) { + // Sets the tint of this Game Object. + return this.phaserSprite.setTint(color); + } + + setFrame(frame, updateSize?: boolean, updateOrigin?: boolean) { + // Sets the frame this Game Object will use to render with. + this.frame = frame; + return frame; + } + + setPositionRelative(source, x, y) { + /// Sets the position of this Game Object to be a relative position from the source Game Object. + return this.phaserSprite.setPositionRelative(source, x, y); + } + + setCrop(x, y, width, height) { + // Sets the crop size of this Game Object. + return this.phaserSprite.setCrop(x, y, width, height); + } + + clearTint() { + // Clears any previously set tint. + return this.phaserSprite.clearTint(); + } + + disableInteractive() { + // Disables Interactive features of this Game Object. + return null; + } + + apply() { + return this.phaserSprite.apply(); + } + + play() { + // return this.phaserSprite.play(); + } + + setPipelineData(key, value) { + this.pipelineData[key] = value; + } + + destroy() { + return this.phaserSprite.destroy(); + } + + setName(name) { + return this.phaserSprite.setName(name); + } + + setAngle(angle) { + return this.phaserSprite.setAngle(angle); + } + + setMask() { + + } + + add(obj) { + // Adds a child to this Game Object. + this.list.push(obj); + } + + removeAll() { + // Removes all Game Objects from this Container. + this.list = []; + } + + addAt(obj, index) { + // Adds a Game Object to this Container at the given index. + this.list.splice(index, 0, obj); + } + + remove(obj) { + const index = this.list.indexOf(obj); + if (index !== -1) { + this.list.splice(index, 1); + } + } + + getIndex(obj) { + const index = this.list.indexOf(obj); + return index || -1; + } + + getAt(index) { + return this.list[index]; + } + + getAll() { + return this.list; + } + + + +} diff --git a/src/test/utils/mocks/mocksContainer/mockText.ts b/src/test/utils/mocks/mocksContainer/mockText.ts new file mode 100644 index 00000000000..f219a6d1bad --- /dev/null +++ b/src/test/utils/mocks/mocksContainer/mockText.ts @@ -0,0 +1,265 @@ +import UI from "#app/ui/ui"; + +export default class MockText { + private phaserText; + private wordWrapWidth; + private splitRegExp; + private scene; + private textureManager; + public list = []; + constructor(textureManager, x, y, content, styleOptions) { + this.scene = textureManager.scene; + this.textureManager = textureManager; + // Phaser.GameObjects.TextStyle.prototype.setStyle = () => null; + // Phaser.GameObjects.Text.prototype.updateText = () => null; + // Phaser.Textures.TextureManager.prototype.addCanvas = () => {}; + UI.prototype.showText = this.showText; + // super(scene, x, y); + // this.phaserText = new Phaser.GameObjects.Text(scene, x, y, content, styleOptions); + } + + runWordWrap(text) { + if (!text) { + return ""; + } + let result = ""; + this.splitRegExp = /(?:\r\n|\r|\n)/; + const lines = text.split(this.splitRegExp); + const lastLineIndex = lines.length - 1; + const whiteSpaceWidth = 2; + + for (let i = 0; i <= lastLineIndex; i++) { + let spaceLeft = this.wordWrapWidth; + const words = lines[i].split(" "); + const lastWordIndex = words.length - 1; + + for (let j = 0; j <= lastWordIndex; j++) { + const word = words[j]; + const wordWidth = word.length * 2; + let wordWidthWithSpace = wordWidth; + + if (j < lastWordIndex) { + wordWidthWithSpace += whiteSpaceWidth; + } + + if (wordWidthWithSpace > spaceLeft) { + // Skip printing the newline if it's the first word of the line that is greater + // than the word wrap width. + if (j > 0) { + result += "\n"; + spaceLeft = this.wordWrapWidth; + } + } + + result += word; + + if (j < lastWordIndex) { + result += " "; + spaceLeft -= wordWidthWithSpace; + } else { + spaceLeft -= wordWidth; + } + } + + if (i < lastLineIndex) { + result += "\n"; + } + } + + return result; + } + + showText(text, delay, callback, callbackDelay, prompt, promptDelay) { + this.scene.messageWrapper.showText(text, delay, callback, callbackDelay, prompt, promptDelay); + if (callback) { + callback(); + } + } + + setScale(scale) { + // return this.phaserText.setScale(scale); + } + + setShadow(shadowXpos, shadowYpos, shadowColor) { + // Sets the shadow settings for this Game Object. + // return this.phaserText.setShadow(shadowXpos, shadowYpos, shadowColor); + } + + setLineSpacing(lineSpacing) { + // Sets the line spacing value of this Game Object. + // return this.phaserText.setLineSpacing(lineSpacing); + } + + setOrigin(x, y) { + // return this.phaserText.setOrigin(x, y); + } + + once(event, callback, source) { + // return this.phaserText.once(event, callback, source); + } + + off(event, callback, obj) { + } + + removedFromScene() { + + } + + addToDisplayList() { + + } + + setStroke(color, thickness) { + // Sets the stroke color and thickness. + // return this.phaserText.setStroke(color, thickness); + } + + removeFromDisplayList() { + // same as remove or destroy + // return this.phaserText.removeFromDisplayList(); + } + + addedToScene() { + // This callback is invoked when this Game Object is added to a Scene. + // return this.phaserText.addedToScene(); + } + + setVisible(visible) { + // return this.phaserText.setVisible(visible); + } + + setY(y) { + // return this.phaserText.setY(y); + } + + setX(x) { + // return this.phaserText.setX(x); + } + + setText(text) { + // Sets the text this Game Object will display. + // return this.phaserText.setText(text); + } + + setAngle(angle) { + // Sets the angle of this Game Object. + // return this.phaserText.setAngle(angle); + } + + setPositionRelative(source, x, y) { + /// Sets the position of this Game Object to be a relative position from the source Game Object. + // return this.phaserText.setPositionRelative(source, x, y); + } + + setShadowOffset(offsetX, offsetY) { + // Sets the shadow offset values. + // return this.phaserText.setShadowOffset(offsetX, offsetY); + } + + setWordWrapWidth(width) { + // Sets the width (in pixels) to use for wrapping lines. + this.wordWrapWidth = width; + } + + setFontSize(fontSize) { + // Sets the font size of this Game Object. + // return this.phaserText.setFontSize(fontSize); + } + + getBounds() { + // return this.phaserText.getBounds(); + return { + width: 1, + }; + } + + setColor(color) { + // Sets the tint of this Game Object. + // return this.phaserText.setColor(color); + } + + setShadowColor(color) { + // Sets the shadow color. + // return this.phaserText.setShadowColor(color); + } + + setTint(color) { + // Sets the tint of this Game Object. + // return this.phaserText.setTint(color); + } + + setStrokeStyle(thickness, color) { + // Sets the stroke style for the graphics. + // return this.phaserText.setStrokeStyle(thickness, color); + } + + destroy() { + // return this.phaserText.destroy(); + this.list = []; + } + + setAlpha(alpha) { + // return this.phaserText.setAlpha(alpha); + } + + setName(name) { + // return this.phaserText.setName(name); + } + + setAlign(align) { + // return this.phaserText.setAlign(align); + } + + setMask() { + /// Sets the mask that this Game Object will use to render with. + } + + getBottomLeft() { + return { + x: 0, + y: 0, + }; + } + + getTopLeft() { + return { + x: 0, + y: 0, + }; + } + + add(obj) { + // Adds a child to this Game Object. + this.list.push(obj); + } + + removeAll() { + // Removes all Game Objects from this Container. + this.list = []; + } + + addAt(obj, index) { + // Adds a Game Object to this Container at the given index. + this.list.splice(index, 0, obj); + } + + remove(obj) { + const index = this.list.indexOf(obj); + if (index !== -1) { + this.list.splice(index, 1); + } + } + + getIndex(obj) { + const index = this.list.indexOf(obj); + return index || -1; + } + + getAt(index) { + return this.list[index]; + } + + getAll() { + return this.list; + } +} diff --git a/src/test/utils/phaseInterceptor.ts b/src/test/utils/phaseInterceptor.ts new file mode 100644 index 00000000000..2915f2e614b --- /dev/null +++ b/src/test/utils/phaseInterceptor.ts @@ -0,0 +1,279 @@ +import { + BattleEndPhase, + BerryPhase, + CheckSwitchPhase, CommandPhase, DamagePhase, EggLapsePhase, + EncounterPhase, EnemyCommandPhase, FaintPhase, + LoginPhase, MessagePhase, MoveEffectPhase, MoveEndPhase, MovePhase, NewBattlePhase, NextEncounterPhase, + PostSummonPhase, + SelectGenderPhase, SelectModifierPhase, + SelectStarterPhase, ShinySparklePhase, ShowAbilityPhase, StatChangePhase, SummonPhase, + TitlePhase, ToggleDoublePositionPhase, TurnEndPhase, TurnInitPhase, TurnStartPhase, VictoryPhase +} from "#app/phases"; +import {Mode} from "#app/ui/ui"; + +export default class PhaseInterceptor { + public scene; + public phases = {}; + public log; + private onHold; + private interval; + private promptInterval; + private intervalRun; + private prompts; + private phaseFrom; + + /** + * List of phases with their corresponding start methods. + */ + private PHASES = [ + [LoginPhase, this.startPhase], + [TitlePhase, this.startPhase], + [SelectGenderPhase, this.startPhase], + [EncounterPhase, this.startPhase], + [SelectStarterPhase, this.startPhase], + [PostSummonPhase, this.startPhase], + [SummonPhase, this.startPhase], + [ToggleDoublePositionPhase, this.startPhase], + [CheckSwitchPhase, this.startPhase], + [ShowAbilityPhase, this.startPhase], + [MessagePhase, this.startPhase], + [TurnInitPhase, this.startPhase], + [CommandPhase, this.startPhase], + [EnemyCommandPhase, this.startPhase], + [TurnStartPhase, this.startPhase], + [MovePhase, this.startPhase], + [MoveEffectPhase, this.startPhase], + [DamagePhase, this.startPhase], + [FaintPhase, this.startPhase], + [BerryPhase, this.startPhase], + [TurnEndPhase, this.startPhase], + [BattleEndPhase, this.startPhase], + [EggLapsePhase, this.startPhase], + [SelectModifierPhase, this.startPhase], + [NextEncounterPhase, this.startPhase], + [NewBattlePhase, this.startPhase], + [VictoryPhase, this.startPhase], + [MoveEndPhase, this.startPhase], + [StatChangePhase, this.startPhase], + [ShinySparklePhase, this.startPhase], + ]; + + /** + * Constructor to initialize the scene and properties, and to start the phase handling. + * @param scene - The scene to be managed. + */ + constructor(scene) { + this.scene = scene; + this.log = []; + this.onHold = []; + this.prompts = []; + this.initPhases(); + this.startPromptHander(); + } + + /** + * Method to set the starting phase. + * @param phaseFrom - The phase to start from. + * @returns The instance of the PhaseInterceptor. + */ + runFrom(phaseFrom) { + this.phaseFrom = phaseFrom; + return this; + } + + /** + * Method to transition to a target phase. + * @param phaseTo - The phase to transition to. + * @returns A promise that resolves when the transition is complete. + */ + async to(phaseTo): Promise { + return new Promise(async (resolve) => { + await this.run(this.phaseFrom); + this.phaseFrom = null; + const targetName = typeof phaseTo === "string" ? phaseTo : phaseTo.name; + this.intervalRun = setInterval(async () => { + const currentPhase = this.onHold?.length && this.onHold[0]; + if (currentPhase && currentPhase.name !== targetName) { + await this.run(currentPhase.name); + } else if (currentPhase.name === targetName) { + await this.run(currentPhase.name); + clearInterval(this.intervalRun); + return resolve(); + } + }); + }); + } + + /** + * Method to run a phase with an optional skip function. + * @param phaseTarget - The phase to run. + * @param skipFn - Optional skip function. + * @returns A promise that resolves when the phase is run. + */ + run(phaseTarget, skipFn?): Promise { + this.scene.moveAnimations = null; // Mandatory to avoid crash + return new Promise(async (resolve) => { + this.waitUntil(phaseTarget, skipFn).then(() => { + const currentPhase = this.onHold.shift(); + currentPhase.call(); + resolve(); + }).catch(() => { + resolve(); + }); + }); + } + + /** + * Method to ensure a phase is run, to throw error on test if not. + * @param phaseTarget - The phase to run. + * @returns A promise that resolves when the phase is run. + */ + mustRun(phaseTarget): Promise { + const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name; + this.scene.moveAnimations = null; // Mandatory to avoid crash + return new Promise(async (resolve, reject) => { + const interval = setInterval(async () => { + const currentPhase = this.onHold?.length && this.onHold[0]; + if (currentPhase && currentPhase.name !== targetName) { + reject(currentPhase); + } else if (currentPhase && currentPhase.name === targetName) { + clearInterval(interval); + await this.run(phaseTarget); + resolve(); + } + }); + }); + } + + /** + * Method to execute actions when about to run a phase. Does not run the phase, stop right before. + * @param phaseTarget - The phase to run. + * @param skipFn - Optional skip function. + * @returns A promise that resolves when the phase is about to run. + */ + whenAboutToRun(phaseTarget, skipFn?): Promise { + return new Promise(async (resolve) => { + this.waitUntil(phaseTarget, skipFn).then(() => { + resolve(); + }).catch(() => { + resolve(); + }); + }); + } + + /** + * Method to remove a phase from the list. + * @param phaseTarget - The phase to remove. + * @param skipFn - Optional skip function. + * @returns A promise that resolves when the phase is removed. + */ + remove(phaseTarget, skipFn?): Promise { + return new Promise(async (resolve) => { + this.waitUntil(phaseTarget, skipFn).then(() => { + this.onHold.shift(); + this.scene.getCurrentPhase().end(); + resolve(); + }).catch(() => { + resolve(); + }); + }); + } + + /** + * Method to wait until a specific phase is reached. + * @param phaseTarget - The phase to wait for. + * @param skipFn - Optional skip function. + * @returns A promise that resolves when the phase is reached. + */ + waitUntil(phaseTarget, skipFn?): Promise { + const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name; + return new Promise((resolve, reject) => { + this.interval = setInterval(() => { + const currentPhase = this.onHold?.length && this.onHold[0] && this.onHold[0].name; + // if the currentPhase here is not filled, it means it's a phase we haven't added to the list + if (currentPhase === targetName) { + clearInterval(this.interval); + return resolve(); + } else if (skipFn && skipFn()) { + clearInterval(this.interval); + return reject("Skipped phase"); + } + }); + }); + } + + /** + * Method to initialize phases and their corresponding methods. + */ + initPhases() { + for (const [phase, method] of this.PHASES) { + const originalStart = phase.prototype.start; + this.phases[phase.name] = originalStart; + phase.prototype.start = () => method.call(this, phase); + } + } + + /** + * Method to start a phase and log it. + * @param phase - The phase to start. + */ + startPhase(phase) { + this.log.push(phase.name); + const instance = this.scene.getCurrentPhase(); + this.onHold.push({ + name: phase.name, + call: () => { + this.phases[phase.name].apply(instance); + } + }); + } + + /** + * Method to start the prompt handler. + */ + startPromptHander() { + this.promptInterval = setInterval(() => { + if (this.prompts.length) { + const actionForNextPrompt = this.prompts[0]; + const expireFn = actionForNextPrompt.expireFn && actionForNextPrompt.expireFn(); + const currentMode = this.scene.ui.getMode(); + const currentPhase = this.scene.getCurrentPhase().constructor.name; + if (expireFn) { + this.prompts.shift(); + } else if (currentMode === actionForNextPrompt.mode && currentPhase === actionForNextPrompt.phaseTarget) { + this.prompts.shift().callback(); + } + } + }); + } + + /** + * Method to add an action to the next prompt. + * @param phaseTarget - The target phase for the prompt. + * @param mode - The mode of the UI. + * @param callback - The callback function to execute. + * @param expireFn - The function to determine if the prompt has expired. + */ + addToNextPrompt(phaseTarget: string, mode: Mode, callback: () => void, expireFn: () => void) { + this.prompts.push({ + phaseTarget, + mode, + callback, + expireFn + }); + } + + /** + * Restores the original state of phases and clears intervals. + * + * This function iterates through all phases and resets their `start` method to the original + * function stored in `this.phases`. Additionally, it clears the `promptInterval` and `interval`. + */ + restoreOg() { + for (const [phase] of this.PHASES) { + phase.prototype.start = this.phases[phase.name]; + } + clearInterval(this.promptInterval); + clearInterval(this.interval); + } +} diff --git a/src/test/utils/saves/everything.prsv b/src/test/utils/saves/everything.prsv new file mode 100644 index 00000000000..2e4a851eb8f --- /dev/null +++ b/src/test/utils/saves/everything.prsv @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/src/test/vitest.setup.ts b/src/test/vitest.setup.ts index 5777cc00611..8729e7796f0 100644 --- a/src/test/vitest.setup.ts +++ b/src/test/vitest.setup.ts @@ -1,5 +1,4 @@ import "vitest-canvas-mock"; -import "#app/test/phaser.setup"; import "#app/test/fontFace.setup"; import {initStatsKeys} from "#app/ui/game-stats-ui-handler"; import {initPokemonPrevolutions} from "#app/data/pokemon-evolutions"; @@ -9,7 +8,9 @@ import {initPokemonForms} from "#app/data/pokemon-forms"; import {initSpecies} from "#app/data/pokemon-species"; import {initMoves} from "#app/data/move"; import {initAbilities} from "#app/data/ability"; +import {initAchievements} from "#app/system/achv.js"; +initAchievements(); initStatsKeys(); initPokemonPrevolutions(); initBiomes(); diff --git a/src/ui/game-stats-ui-handler.ts b/src/ui/game-stats-ui-handler.ts index 86b4e71f72d..eda36919d3b 100644 --- a/src/ui/game-stats-ui-handler.ts +++ b/src/ui/game-stats-ui-handler.ts @@ -1,3 +1,4 @@ +import Phaser from "phaser"; import BattleScene from "../battle-scene"; import { TextStyle, addTextObject } from "./text"; import { Mode } from "./ui"; diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 29926568d80..16406fad675 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -207,8 +207,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { private genSpecies: PokemonSpecies[][] = []; private lastSpecies: PokemonSpecies; private speciesLoaded: Map = new Map(); - private starterGens: integer[] = []; - private starterCursors: integer[] = []; + public starterGens: integer[] = []; + public starterCursors: integer[] = []; private pokerusGens: integer[] = []; private pokerusCursors: integer[] = []; private starterAttr: bigint[] = []; @@ -227,7 +227,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { private canAddParty: boolean; private assetLoadCancelled: Utils.BooleanHolder; - private cursorObj: Phaser.GameObjects.Image; + public cursorObj: Phaser.GameObjects.Image; private starterCursorObjs: Phaser.GameObjects.Image[]; private pokerusCursorObjs: Phaser.GameObjects.Image[]; private starterIcons: Phaser.GameObjects.Sprite[]; diff --git a/src/utils.test.ts b/src/utils.test.ts index 01bdd5db542..93f2a96ec4c 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -1,9 +1,15 @@ -import { expect, describe, it } from "vitest"; +import {expect, describe, it, beforeAll} from "vitest"; import { randomString, padInt } from "./utils"; import Phaser from "phaser"; describe("utils", () => { + + beforeAll(() => { + new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); describe("randomString", () => { it("should return a string of the specified length", () => { const str = randomString(10); diff --git a/vitest.config.js b/vitest.config.js index c73476431dd..5dcb326e1ea 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -15,6 +15,7 @@ export default defineConfig(({ mode }) => { }, threads: false, trace: true, + restoreMocks: true, environmentOptions: { jsdom: { resources: 'usable',