Compare commits

...

18 Commits

Author SHA1 Message Date
Benjamin Odom
d34e0dd2c6
Fix Errors in inputs-controller.ts (#1523) 2024-05-29 00:43:38 +01:00
Amani H
47d39388ac
Implement Triple Kick/Triple Axel/Population Bomb (#1341)
* Implement Triple Kick/Triple Axel/Population Bomb

* Add maximum allowed base power increments

* Add documentation

* Fix Population Bomb, Account for Skill Link

* Change multi-hit power increment behavior

* Fix Typo
2024-05-29 00:07:35 +01:00
Benjamin Odom
4d15269eec
Fix Error with Typecasting (#1519) 2024-05-28 23:45:18 +01:00
chaosgrimmon
899a2bf96e
Match Shiny Grafaiai animation frames with Non-Shiny counterparts (#1477)
A user reported [here](https://discord.com/channels/1125469663833370665/1238339524778790922/1244817919985061978) that "Shiny grafaiai keeps fading in and out of existence".

The presumed culprit is frame 17 being set to have a width and height of 3 pixels.
To fix this, the code from the non-shiny .json was used instead, which used dimensions of 66x55, somewhat more reasonable for the targeted slice of spritesheet.

Not tested, with due concern given to the abysmal odds of specifically encountering a shiny Grafaiai.
... but the change shouldn't break anything, I hope?
2024-05-28 17:48:56 -04:00
Greenlamp2
0385a90f08
put the preventElementZoom call into a method instead to call it from anywhere/anytime we import it (#1515) 2024-05-28 16:22:32 -05:00
Benjamin Odom
a163a420bd
Fixes etPositionRelative Errors (#1514) 2024-05-28 17:16:14 -04:00
GoldTra
d7146bb1c1
Updated spanish translations (#1500) 2024-05-28 17:14:44 -04:00
Adrian T
fccd546415
Add Settings On Title Screen, Update Starter-Select Tutorial Dialogue (EN, DE, FR, CN, BR) (#1367)
* add settings on title screen, update starter select tutorial (en)

* French "Settings" transaltion in menu.ts

* Update French tutorial.ts

* [LOCALIZATION] add DE

* fix players online position

* [LOCALIZATION] add zh_CN

* [LOCALIZATION] add pt_BR

---------

Co-authored-by: Lugiad <adrien.grivel@hotmail.fr>
2024-05-28 16:51:33 -04:00
AJ Fontaine
5afe32a1e2
[QOL] Removed King's Rock requirement for Politoed/Slowking evos (#1501)
* Removed King's Rock requirement for Politoed/Slowking evos

* Remove unused header
2024-05-28 15:57:46 -04:00
Benjamin Odom
1420c26f22
Fix issue with shiny star placement in starter-select-ui-handler.ts (#1512) 2024-05-28 15:50:40 -04:00
Ethan
4695d0617d
Download Ability Fix (#1119)
* Fixes Issue #993 by changing from base stats to include summonBattleStats

* Added TODO with Substitute

* Fix conflicts

* Unsure why true was put through for passive, probably just a mistake on my end

* !== fix

* Conflict fix

* Eslint Fix

* Implement Double Battle average to take in stats from both Pkmn
2024-05-28 15:32:52 -04:00
Benjamin Odom
91013cf8c4
Candy Icon Display Bugfix (#1507)
Fixes a small visual issue where scrolling through a list of Pokémon would not hide a candy if the Pokémon was not the root species.
2024-05-28 20:22:59 +01:00
flx-sta
8824082ceb
Fix for the zoom issue (#1326) (#1329)
* #1326: add touchstart event to dpadCenter to prevent zoom behavior

* #1326: improve variable naming for preventElementZoom

* #1326: change selector to "#dpad"

instead of just "#dpadCenter"
2024-05-28 14:02:49 -05:00
Frederico Santos
f4acf8bfc3
Removed trello reference. (#1508) 2024-05-28 13:58:01 -05:00
Matthew Olker
dc8672b2bd indicator hotfix 2024-05-28 14:05:00 -04:00
José Ricardo Fleury Oliveira
7e003d68a9
Indicator when a candy upgrade is available (#1083)
* initial implementation

* updated logic

* reverse retTint

* added candy overlays and colors

* added settings and minor fixes

* german changes

* logic fix

* german changes pt2

* german changes pt3

* setting name changed

* Update battle-scene.ts

* initial animation implementation

* minor fixes

* main compatibility

* minor fix

* logic for animations

* eslint fixes

* final generation logic

* Pause Animation when Selected or Purchased

* Disable Indicator if not Root Species

* Add to Reload and Add Anchor

* Fix Animation on Change

* Fix Icon on Change

* Code Cleanup

* fix

---------

Co-authored-by: Benjamin Odom <bennybroseph@gmail.com>
2024-05-28 13:49:15 -04:00
Dmitriy K
9893c21a7e
implement Cheek Pouch ability (#987)
* Partially implement Cheek Pouch ability

* add cheek pouch trigger to EatBerryAttr class

* Partially implement Cheek Pouch ability

* add cheek pouch trigger to EatBerryAttr class

* add cheek pouch trigger to new berry phase, remove partial tag

* run auto formatter because I forgot

* hotfix berries triggering before status effects

* Revert "hotfix berries triggering before status effects"

This reverts commit 3fbbc9a165.

* add partial tags to abilities that would be blocked by Heal Block
2024-05-28 13:19:03 -04:00
Matthew Olker
e7cef039c3 fix dialogue interface structure 2024-05-28 12:32:17 -04:00
42 changed files with 773 additions and 278 deletions

View File

@ -26,7 +26,7 @@ We're using ESLint as our common linter and formatter. It will run automatically
## 🪧 To Do
Check out our [Trello Board](https://trello.com/b/z10B703R/pokerogue-board) to see what we're working on
Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to see how can you help us!
# 📝 Credits
> If this project contains assets you have produced and you do not see your name here, **please** reach out.

2
package-lock.json generated
View File

@ -38,7 +38,7 @@
"vitest-canvas-mock": "^0.3.3"
},
"engines": {
"node": ">=18.0.0"
"node": ">=20.0.0"
}
},
"node_modules/@ampproject/remapping": {

View File

@ -146,14 +146,14 @@
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 3,
"h": 3
"w": 66,
"h": 55
},
"frame": {
"x": 132,
"y": 0,
"w": 3,
"h": 3
"x": 0,
"y": 112,
"w": 66,
"h": 55
}
},
{

View File

@ -2,6 +2,12 @@ import Move from "./data/move";
/** Alias for all {@linkcode BattleScene} events */
export enum BattleSceneEventType {
/**
* Triggers when the corresponding setting is changed
* @see {@linkcode CandyUpgradeNotificationChangedEvent}
*/
CANDY_UPGRADE_NOTIFICATION_CHANGED = "onCandyUpgradeDisplayChanged",
/**
* Triggers when a move is successfully used
* @see {@linkcode MoveUsedEvent}
@ -24,6 +30,20 @@ export enum BattleSceneEventType {
NEW_ARENA = "onNewArena",
}
/**
* Container class for {@linkcode BattleSceneEventType.CANDY_UPGRADE_NOTIFICATION_CHANGED} events
* @extends Event
*/
export class CandyUpgradeNotificationChangedEvent extends Event {
/** The new value the setting was changed to */
public newValue: number;
constructor(newValue: number) {
super(BattleSceneEventType.CANDY_UPGRADE_NOTIFICATION_CHANGED);
this.newValue = newValue;
}
}
/**
* Container class for {@linkcode BattleSceneEventType.MOVE_USED} events
* @extends Event

View File

@ -93,6 +93,19 @@ export default class BattleScene extends SceneBase {
public showLevelUpStats: boolean = true;
public enableTutorials: boolean = import.meta.env.VITE_BYPASS_TUTORIAL === "1";
public enableRetries: boolean = false;
/**
* Determines the condition for a notification should be shown for Candy Upgrades
* - 0 = 'Off'
* - 1 = 'Passives Only'
* - 2 = 'On'
*/
public candyUpgradeNotification: integer = 0;
/**
* Determines what type of notification is used for Candy Upgrades
* - 0 = 'Icon'
* - 1 = 'Animation'
*/
public candyUpgradeDisplay: integer = 0;
public moneyFormat: MoneyFormat = MoneyFormat.NORMAL;
public uiTheme: UiTheme = UiTheme.DEFAULT;
public windowType: integer = 0;

View File

@ -1573,18 +1573,41 @@ export class PostSummonClearAllyStatsAbAttr extends PostSummonAbAttr {
}
}
/**
* Download raises either the Attack stat or Special Attack stat by one stage depending on the foe's currently lowest defensive stat:
* it will raise Attack if the foe's current Defense is lower than its current Special Defense stat;
* otherwise, it will raise Special Attack.
* @extends PostSummonAbAttr
* @see {applyPostSummon}
*/
export class DownloadAbAttr extends PostSummonAbAttr {
private enemyDef: integer;
private enemySpDef: integer;
private enemyCountTally: integer;
private stats: BattleStat[];
// TODO: Implement the Substitute feature(s) once move is implemented.
/**
* Checks to see if it is the opening turn (starting a new game), if so, Download won't work. This is because Download takes into account
* vitamins and items, so it needs to use the BattleStat and the stat alone.
* @param {Pokemon} pokemon Pokemon that is using the move, as well as seeing the opposing pokemon.
* @param {boolean} passive N/A
* @param {any[]} args N/A
* @returns Returns true if ability is used successful, false if not.
*/
applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean {
this.enemyDef = 0;
this.enemySpDef = 0;
this.enemyCountTally = 0;
for (const opponent of pokemon.getOpponents()) {
this.enemyDef += opponent.stats[BattleStat.DEF];
this.enemySpDef += opponent.stats[BattleStat.SPDEF];
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);
}
if (this.enemyDef < this.enemySpDef) {
@ -2662,6 +2685,38 @@ export class PreventBerryUseAbAttr extends AbAttr {
}
}
/**
* A Pokemon with this ability heals by a percentage of their maximum hp after eating a berry
* @param healPercent - Percent of Max HP to heal
* @see {@linkcode apply()} for implementation
*/
export class HealFromBerryUseAbAttr extends AbAttr {
/** Percent of Max HP to heal */
private healPercent: number;
constructor(healPercent: number) {
super();
// Clamp healPercent so its between [0,1].
this.healPercent = Math.max(Math.min(healPercent, 1), 0);
}
apply(pokemon: Pokemon, passive: boolean, ...args: [Utils.BooleanHolder, any[]]): boolean {
const { name: abilityName } = passive ? pokemon.getPassiveAbility() : pokemon.getAbility();
pokemon.scene.unshiftPhase(
new PokemonHealPhase(
pokemon.scene,
pokemon.getBattlerIndex(),
Math.max(Math.floor(pokemon.getMaxHp() * this.healPercent), 1),
getPokemonMessage(pokemon, `'s ${abilityName}\nrestored its HP!`),
true
)
);
return true;
}
}
export class RunSuccessAbAttr extends AbAttr {
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
(args[0] as Utils.IntegerHolder).value = 256;
@ -3308,9 +3363,11 @@ export function initAbilities() {
.bypassFaint(),
new Ability(Abilities.VOLT_ABSORB, 3)
.attr(TypeImmunityHealAbAttr, Type.ELECTRIC)
.partial() // Healing not blocked by Heal Block
.ignorable(),
new Ability(Abilities.WATER_ABSORB, 3)
.attr(TypeImmunityHealAbAttr, Type.WATER)
.partial() // Healing not blocked by Heal Block
.ignorable(),
new Ability(Abilities.OBLIVIOUS, 3)
.attr(BattlerTagImmunityAbAttr, BattlerTagType.INFATUATED)
@ -3409,7 +3466,8 @@ export function initAbilities() {
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.getMove().hasFlag(MoveFlags.SOUND_BASED))
.ignorable(),
new Ability(Abilities.RAIN_DISH, 3)
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.RAIN, WeatherType.HEAVY_RAIN),
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.RAIN, WeatherType.HEAVY_RAIN)
.partial(), // Healing not blocked by Heal Block
new Ability(Abilities.SAND_STREAM, 3)
.attr(PostSummonWeatherChangeAbAttr, WeatherType.SANDSTORM)
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.SANDSTORM),
@ -3530,6 +3588,7 @@ export function initAbilities() {
.attr(PostWeatherLapseHealAbAttr, 2, WeatherType.RAIN, WeatherType.HEAVY_RAIN)
.attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 1.25)
.attr(TypeImmunityHealAbAttr, Type.WATER)
.partial() // Healing not blocked by Heal Block
.ignorable(),
new Ability(Abilities.DOWNLOAD, 4)
.attr(DownloadAbAttr),
@ -3607,7 +3666,8 @@ export function initAbilities() {
.ignorable(),
new Ability(Abilities.ICE_BODY, 4)
.attr(BlockWeatherDamageAttr, WeatherType.HAIL)
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.HAIL, WeatherType.SNOW),
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.HAIL, WeatherType.SNOW)
.partial(), // Healing not blocked by Heal Block
new Ability(Abilities.SOLID_ROCK, 4)
.attr(ReceivedMoveDamageMultiplierAbAttr,(target, user, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 0.75)
.ignorable(),
@ -3768,7 +3828,8 @@ export function initAbilities() {
.ignorable()
.unimplemented(),
new Ability(Abilities.CHEEK_POUCH, 6)
.unimplemented(),
.attr(HealFromBerryUseAbAttr, 1/3)
.partial(), // Healing not blocked by Heal Block
new Ability(Abilities.PROTEAN, 6)
.unimplemented(),
new Ability(Abilities.FUR_COAT, 6)
@ -4201,6 +4262,7 @@ export function initAbilities() {
.ignorable(),
new Ability(Abilities.EARTH_EATER, 9)
.attr(TypeImmunityHealAbAttr, Type.GROUND)
.partial() // Healing not blocked by Heal Block
.ignorable(),
new Ability(Abilities.MYCELIUM_MIGHT, 9)
.attr(MoveAbilityBypassAbAttr, (pokemon, move: Move) => move.category === MoveCategory.STATUS)
@ -4214,7 +4276,8 @@ export function initAbilities() {
.attr(PostSummonStatChangeAbAttr, BattleStat.EVA, -1)
.condition(getOncePerBattleCondition(Abilities.SUPERSWEET_SYRUP)),
new Ability(Abilities.HOSPITALITY, 9)
.attr(PostSummonAllyHealAbAttr, 4, true),
.attr(PostSummonAllyHealAbAttr, 4, true)
.partial(), // Healing not blocked by Heal Block
new Ability(Abilities.TOXIC_CHAIN, 9)
.attr(PostAttackApplyStatusEffectAbAttr, false, 30, StatusEffect.TOXIC),
new Ability(Abilities.EMBODY_ASPECT_TEAL, 9)

View File

@ -9,14 +9,14 @@ export interface TrainerTypeMessages {
}
export interface TrainerTypeDialogue {
[key: integer]: TrainerTypeMessages | [TrainerTypeMessages, TrainerTypeMessages]
[key: integer]: TrainerTypeMessages | Array<TrainerTypeMessages>
}
export function getTrainerTypeDialogue(): TrainerTypeDialogue {
return trainerTypeDialogue;
}
export const trainerTypeDialogue = {
export const trainerTypeDialogue: TrainerTypeDialogue = {
[TrainerType.YOUNGSTER]: [
{
encounter: [

View File

@ -12,7 +12,7 @@ import * as Utils from "../utils";
import { WeatherType } from "./weather";
import { ArenaTagSide, ArenaTrapTag } from "./arena-tag";
import { ArenaTagType } from "./enums/arena-tag-type";
import { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr, applyPostDefendAbAttrs, PostDefendContactApplyStatusEffectAbAttr, MoveAbilityBypassAbAttr, ReverseDrainAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, BlockItemTheftAbAttr, applyPostAttackAbAttrs, ConfusionOnStatusEffectAbAttr } from "./ability";
import { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr, applyPostDefendAbAttrs, PostDefendContactApplyStatusEffectAbAttr, MoveAbilityBypassAbAttr, ReverseDrainAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, BlockItemTheftAbAttr, applyPostAttackAbAttrs, ConfusionOnStatusEffectAbAttr, HealFromBerryUseAbAttr } from "./ability";
import { Abilities } from "./enums/abilities";
import { allAbilities } from "./ability";
import { PokemonHeldItemModifier, BerryModifier, PreserveBerryModifier } from "../modifier/modifier";
@ -86,6 +86,10 @@ export enum MoveFlags {
WIND_MOVE = 1 << 14,
TRIAGE_MOVE = 1 << 15,
IGNORE_ABILITIES = 1 << 16,
/**
* Enables all hits of a multi-hit move to be accuracy checked individually
*/
CHECK_ALL_HITS = 1 << 17,
}
type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean;
@ -346,6 +350,11 @@ export default class Move implements Localizable {
return this;
}
checkAllHits(checkAllHits?: boolean): this {
this.setFlag(MoveFlags.CHECK_ALL_HITS, checkAllHits);
return this;
}
checkFlag(flag: MoveFlags, user: Pokemon, target: Pokemon): boolean {
switch (flag) {
case MoveFlags.MAKES_CONTACT:
@ -955,8 +964,7 @@ export enum MultiHitType {
_2,
_2_TO_5,
_3,
_3_INCR,
_1_TO_10,
_10,
BEAT_UP,
}
@ -1287,37 +1295,8 @@ export class MultiHitAttr extends MoveAttr {
case MultiHitType._3:
hitTimes = 3;
break;
case MultiHitType._3_INCR:
hitTimes = 3;
// TODO: Add power increase for every hit
break;
case MultiHitType._1_TO_10:
{
const rand = user.randSeedInt(90);
const hitValue = new Utils.IntegerHolder(rand);
applyAbAttrs(MaxMultiHitAbAttr, user, null, hitValue);
if (hitValue.value >= 81) {
hitTimes = 1;
} else if (hitValue.value >= 73) {
hitTimes = 2;
} else if (hitValue.value >= 66) {
hitTimes = 3;
} else if (hitValue.value >= 60) {
hitTimes = 4;
} else if (hitValue.value >= 54) {
hitTimes = 5;
} else if (hitValue.value >= 49) {
hitTimes = 6;
} else if (hitValue.value >= 44) {
hitTimes = 7;
} else if (hitValue.value >= 40) {
hitTimes = 8;
} else if (hitValue.value >= 36) {
hitTimes = 9;
} else {
hitTimes = 10;
}
}
case MultiHitType._10:
hitTimes = 10;
break;
case MultiHitType.BEAT_UP:
const party = user.isPlayer() ? user.scene.getParty() : user.scene.getEnemyParty();
@ -1577,8 +1556,11 @@ export class EatBerryAttr extends MoveEffectAttr {
}
target.scene.updateModifiers(target.isPlayer());
}
this.chosenBerry = undefined;
applyAbAttrs(HealFromBerryUseAbAttr, target, new Utils.BooleanHolder(false));
return true;
}
@ -2748,6 +2730,43 @@ export class WaterShurikenPowerAttr extends VariablePowerAttr {
}
}
/**
* Attribute used for multi-hit moves that increase power in increments of the
* move's base power for each hit, namely Triple Kick and Triple Axel.
* @extends VariablePowerAttr
* @see {@linkcode apply}
*/
export class MultiHitPowerIncrementAttr extends VariablePowerAttr {
/** The max number of base power increments allowed for this move */
private maxHits: integer;
constructor(maxHits: integer) {
super();
this.maxHits = maxHits;
}
/**
* Increases power of move in increments of the base power for the amount of times
* the move hit. In the case that the move is extended, it will circle back to the
* original base power of the move after incrementing past the maximum amount of
* hits.
* @param user {@linkcode Pokemon} that used the move
* @param target {@linkcode Pokemon} that the move was used on
* @param move {@linkcode Move} with this attribute
* @param args [0] {@linkcode Utils.NumberHolder} for final calculated power of move
* @returns true if attribute application succeeds
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const hitsTotal = user.turnData.hitCount - Math.max(user.turnData.hitsLeft, 0);
const power = args[0] as Utils.NumberHolder;
power.value = move.power * (1 + hitsTotal % this.maxHits);
return true;
}
}
export class VariableAtkAttr extends MoveAttr {
constructor() {
super();
@ -5408,12 +5427,9 @@ export function initMoves() {
.attr(SketchAttr)
.ignoresVirtual(),
new AttackMove(Moves.TRIPLE_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 10, 90, 10, -1, 0, 2)
.attr(MultiHitAttr, MultiHitType._3_INCR)
.attr(MissEffectAttr, (user: Pokemon, move: Move) => {
user.turnData.hitsLeft = 1;
return true;
})
.partial(),
.attr(MultiHitAttr, MultiHitType._3)
.attr(MultiHitPowerIncrementAttr, 3)
.checkAllHits(),
new AttackMove(Moves.THIEF, Type.DARK, MoveCategory.PHYSICAL, 60, 100, 25, -1, 0, 2)
.attr(StealHeldItemChanceAttr, 0.3),
new StatusMove(Moves.SPIDER_WEB, Type.BUG, -1, 10, -1, 0, 2)
@ -7265,12 +7281,9 @@ export function initMoves() {
new AttackMove(Moves.FLIP_TURN, Type.WATER, MoveCategory.PHYSICAL, 60, 100, 20, -1, 0, 8)
.attr(ForceSwitchOutAttr, true, false),
new AttackMove(Moves.TRIPLE_AXEL, Type.ICE, MoveCategory.PHYSICAL, 20, 90, 10, -1, 0, 8)
.attr(MultiHitAttr, MultiHitType._3_INCR)
.attr(MissEffectAttr, (user: Pokemon, move: Move) => {
user.turnData.hitsLeft = 1;
return true;
})
.partial(),
.attr(MultiHitAttr, MultiHitType._3)
.attr(MultiHitPowerIncrementAttr, 3)
.checkAllHits(),
new AttackMove(Moves.DUAL_WINGBEAT, Type.FLYING, MoveCategory.PHYSICAL, 40, 90, 10, -1, 0, 8)
.attr(MultiHitAttr, MultiHitType._2),
new AttackMove(Moves.SCORCHING_SANDS, Type.GROUND, MoveCategory.SPECIAL, 70, 100, 10, 30, 0, 8)
@ -7513,9 +7526,9 @@ export function initMoves() {
new AttackMove(Moves.SPIN_OUT, Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, 100, 0, 9)
.attr(StatChangeAttr, BattleStat.SPD, -2, true),
new AttackMove(Moves.POPULATION_BOMB, Type.NORMAL, MoveCategory.PHYSICAL, 20, 90, 10, -1, 0, 9)
.attr(MultiHitAttr, MultiHitType._1_TO_10)
.attr(MultiHitAttr, MultiHitType._10)
.slicingMove()
.partial(),
.checkAllHits(),
new AttackMove(Moves.ICE_SPINNER, Type.ICE, MoveCategory.PHYSICAL, 80, 100, 15, -1, 0, 9)
.attr(ClearTerrainAttr),
new AttackMove(Moves.GLAIVE_RUSH, Type.DRAGON, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 9)

View File

@ -1,5 +1,4 @@
import { Gender } from "./gender";
import { FlinchChanceModifier } from "../modifier/modifier";
import { Moves } from "./enums/moves";
import { PokeballType } from "./pokeball";
import Pokemon from "../field/pokemon";
@ -216,7 +215,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
],
[Species.SLOWPOKE]: [
new SpeciesEvolution(Species.SLOWBRO, 37, null, null),
new SpeciesEvolution(Species.SLOWKING, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => !!p.scene.findModifier(m => (m instanceof FlinchChanceModifier) && (m as FlinchChanceModifier).pokemonId === p.id, true)), SpeciesWildEvolutionDelay.VERY_LONG)
new SpeciesEvolution(Species.SLOWKING, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => true /* King's Rock */), SpeciesWildEvolutionDelay.VERY_LONG)
],
[Species.MAGNEMITE]: [
new SpeciesEvolution(Species.MAGNETON, 30, null, null)
@ -1244,7 +1243,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
],
[Species.POLIWHIRL]: [
new SpeciesEvolution(Species.POLIWRATH, 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG),
new SpeciesEvolution(Species.POLITOED, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => !!p.scene.findModifier(m => (m instanceof FlinchChanceModifier) && (m as FlinchChanceModifier).pokemonId === p.id, true)), SpeciesWildEvolutionDelay.VERY_LONG)
new SpeciesEvolution(Species.POLITOED, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => true /* King's Rock */), SpeciesWildEvolutionDelay.VERY_LONG)
],
[Species.WEEPINBELL]: [
new SpeciesEvolution(Species.VICTREEBEL, 1, EvolutionItem.LEAF_STONE, null, SpeciesWildEvolutionDelay.LONG)

View File

@ -1,4 +1,4 @@
import Phaser, {Time} from "phaser";
import Phaser from "phaser";
import * as Utils from "./utils";
import {initTouchControls} from "./touch-controls";
import pad_generic from "./configs/pad_generic";
@ -6,6 +6,7 @@ import pad_unlicensedSNES from "./configs/pad_unlicensedSNES";
import pad_xbox360 from "./configs/pad_xbox360";
import pad_dualshock from "./configs/pad_dualshock";
import {Button} from "./enums/buttons";
import BattleScene from "./battle-scene";
export interface GamepadMapping {
[key: string]: number;
@ -47,16 +48,17 @@ const repeatInputDelayMillis = 250;
*/
export class InputsController {
private buttonKeys: Phaser.Input.Keyboard.Key[][];
private gamepads: Array<string> = new Array();
private scene: Phaser.Scene;
private gamepads: Phaser.Input.Gamepad.Gamepad[] = new Array();
private scene: BattleScene;
private buttonLock: Button;
private buttonLock2: Button;
private interactions: Map<Button, Map<string, boolean>> = new Map();
private time: Time;
private player: Map<String, GamepadMapping> = new Map();
private time: Phaser.Time.Clock;
private player: GamepadMapping;
private gamepadSupport: boolean = true;
public events: Phaser.Events.EventEmitter;
/**
* Initializes a new instance of the game control system, setting up initial state and configurations.
@ -69,7 +71,7 @@ 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: Phaser.Scene) {
constructor(scene: BattleScene) {
this.scene = scene;
this.time = this.scene.time;
this.buttonKeys = [];
@ -108,7 +110,6 @@ export class InputsController {
}, 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) {
@ -201,7 +202,7 @@ export class InputsController {
setupGamepad(thisGamepad: Phaser.Input.Gamepad.Gamepad): void {
const gamepadID = thisGamepad.id.toLowerCase();
const mappedPad = this.mapGamepad(gamepadID);
this.player["mapping"] = mappedPad.gamepadMapping;
this.player = mappedPad.gamepadMapping;
}
/**
@ -236,26 +237,26 @@ export class InputsController {
*/
getActionGamepadMapping(): ActionGamepadMapping {
const gamepadMapping = {};
if (!this.player?.mapping) {
if (!this?.player) {
return gamepadMapping;
}
gamepadMapping[this.player.mapping.LC_N] = Button.UP;
gamepadMapping[this.player.mapping.LC_S] = Button.DOWN;
gamepadMapping[this.player.mapping.LC_W] = Button.LEFT;
gamepadMapping[this.player.mapping.LC_E] = Button.RIGHT;
gamepadMapping[this.player.mapping.TOUCH] = Button.SUBMIT;
gamepadMapping[this.player.mapping.RC_S] = this.scene.abSwapped ? Button.CANCEL : Button.ACTION;
gamepadMapping[this.player.mapping.RC_E] = this.scene.abSwapped ? Button.ACTION : Button.CANCEL;
gamepadMapping[this.player.mapping.SELECT] = Button.STATS;
gamepadMapping[this.player.mapping.START] = Button.MENU;
gamepadMapping[this.player.mapping.RB] = Button.CYCLE_SHINY;
gamepadMapping[this.player.mapping.LB] = Button.CYCLE_FORM;
gamepadMapping[this.player.mapping.LT] = Button.CYCLE_GENDER;
gamepadMapping[this.player.mapping.RT] = Button.CYCLE_ABILITY;
gamepadMapping[this.player.mapping.RC_W] = Button.CYCLE_NATURE;
gamepadMapping[this.player.mapping.RC_N] = Button.CYCLE_VARIANT;
gamepadMapping[this.player.mapping.LS] = Button.SPEED_UP;
gamepadMapping[this.player.mapping.RS] = Button.SLOW_DOWN;
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.CYCLE_VARIANT;
gamepadMapping[this.player.LS] = Button.SPEED_UP;
gamepadMapping[this.player.RS] = Button.SLOW_DOWN;
return gamepadMapping;
}

View File

@ -1635,13 +1635,13 @@ export const PGMdialogue: DialogueTranslationEntries = {
"encounter": {
1: `Ich habe mich entschieden, erneut meinen Hut in den Ring zu werfen.
$Komm jetzt... Zeig mir die Früchte deines Trainings.`,
"victory": {
1: "Ich freue mich auf Neuigkeiten über all deine Erfolge!"
},
"defeat": {
1: "Was ist los? Das ist doch nicht alles, oder?"
}
},
"victory": {
1: "Ich freue mich auf Neuigkeiten über all deine Erfolge!"
},
"defeat": {
1: "Was ist los? Das ist doch nicht alles, oder?"
}
},
"nemona": {
"encounter": {
@ -2337,7 +2337,7 @@ export const PGMdialogue: DialogueTranslationEntries = {
};
// 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.
export const PGFdialogue: SimpleTranslationEntries = PGMdialogue;
export const PGFdialogue: DialogueTranslationEntries = PGMdialogue;
// Dialogue of the endboss of the game when the player character is male (Or unset)
export const PGMbattleSpecDialogue: SimpleTranslationEntries = {

View File

@ -11,6 +11,7 @@ export const menu: SimpleTranslationEntries = {
"dailyRun": "Täglicher Run (Beta)",
"loadGame": "Spiel laden",
"newGame": "Neues Spiel",
"settings": "Einstellungen",
"selectGameMode": "Wähle einen Spielmodus",
"logInOrCreateAccount": "Melde dich an oder erstelle einen Account zum starten. Keine Email nötig!",
"username": "Benutzername",

View File

@ -14,7 +14,8 @@ export const tutorial: SimpleTranslationEntries = {
$Dort kannst du u. A. die Spielgeschwin-\ndigkeit und das Fensterdesign ändern.
$Das Menü verbirgt noch andere Funktionen - probier' sie gerne aus!`,
"starterSelect": `Hier kannst du deine Starter-Pokémon auswählen.\nSie begleiten dich am Anfang deines Abenteuers.
"starterSelect": `In diesem Bildschirm kannst du mit Z oder Leertaste deine\nStarter auswählen.
$Sie begleiten dich am Anfang deines Abenteuers.
$Jeder Starter hat einen Preis. Dein Team kann bis zu sechs\nMitglieder haben, solange der Gesamtpreis max. 10 beträgt.
$Du kannst Geschlecht, Fähigkeit und Form beliebig auswählen,\nsobald du sie mindestens einmal gefangen hast.
$Die DVs ergeben sich aus den Höchstwerten aller Pokémon,\ndie du bereits gefangen hast.

View File

@ -1595,13 +1595,13 @@ export const PGMdialogue: DialogueTranslationEntries = {
"encounter": {
1: `I decided to throw my hat in the ring once more.
$Come now Show me the fruits of your training.`,
"victory": {
1: "I eagerly await news of all your achievements!"
},
"defeat": {
1: "What's the matter? This isn't all, is it?"
}
},
"victory": {
1: "I eagerly await news of all your achievements!"
},
"defeat": {
1: "What's the matter? This isn't all, is it?"
}
},
"nemona": {
"encounter": {
@ -2278,7 +2278,7 @@ export const PGMdialogue: DialogueTranslationEntries = {
// 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.
export const PGFdialogue: SimpleTranslationEntries = PGMdialogue;
export const PGFdialogue: DialogueTranslationEntries = PGMdialogue;
// Dialogue of the endboss of the game when the player character is male (Or unset)
export const PGMbattleSpecDialogue: SimpleTranslationEntries = {

View File

@ -11,6 +11,7 @@ export const menu: SimpleTranslationEntries = {
"dailyRun": "Daily Run (Beta)",
"loadGame": "Load Game",
"newGame": "New Game",
"settings": "Settings",
"selectGameMode": "Select a game mode.",
"logInOrCreateAccount": "Log in or create an account to start. No email required!",
"username": "Username",

View File

@ -12,7 +12,7 @@ export const tutorial: SimpleTranslationEntries = {
$From the settings you can change game speed, window style, and other options.
$There are also various other features here, so be sure to check them all!`,
"starterSelect": `From this screen, you can select your starters.\nThese are your initial party members.
"starterSelect": `From this screen, you can select your starters by pressing\nZ or the Space bar. These are your initial party members.
$Each starter has a value. Your party can have up to\n6 members as long as the total does not exceed 10.
$You can also select gender, ability, and form depending on\nthe variants you've caught or hatched.
$The IVs for a species are also the best of every one you've\ncaught or hatched, so try to get lots of the same species!`,

View File

@ -3,8 +3,8 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n";
export const battle: SimpleTranslationEntries = {
"bossAppeared": "¡{{bossName}} te corta el paso!",
"trainerAppeared": "¡{{trainerName}}\nte desafía!",
"trainerAppearedDouble": "{{trainerName}}\nwould like to battle!",
"trainerSendOut": "{{trainerName}} sent out\n{{pokemonName}}!",
"trainerAppearedDouble": "¡{{trainerName}}\nwould te desafían!",
"trainerSendOut": "¡{{trainerName}} saca a\n{{pokemonName}}!",
"singleWildAppeared": "¡Un {{pokemonName}} salvaje te corta el paso!",
"multiWildAppeared": "¡Un {{pokemonName1}} y un {{pokemonName2}} salvajes\nte cortan el paso!",
"playerComeBack": "¡{{pokemonName}}, ven aquí!",
@ -13,9 +13,9 @@ export const battle: SimpleTranslationEntries = {
"trainerGo": "¡{{trainerName}} saca a {{pokemonName}}!",
"switchQuestion": "¿Quieres cambiar a\n{{pokemonName}}?",
"trainerDefeated": "¡Has derrotado a\n{{trainerName}}!",
"moneyWon": "You got\n₽{{moneyAmount}} for winning!",
"moneyWon": "¡Has ganado\n₽{{moneyAmount}} por vencer!",
"pokemonCaught": "¡{{pokemonName}} atrapado!",
"partyFull": "Your party is full.\nRelease a Pokémon to make room for {{pokemonName}}?",
"partyFull": "Tu equipo esta completo.\n¿Quieres liberar un Pokémon para meter a {{pokemonName}}?",
"pokemon": "Pokémon",
"sendOutPokemon": "¡Adelante, {{pokemonName}}!",
"hitResultCriticalHit": "!Un golpe crítico!",
@ -39,7 +39,7 @@ export const battle: SimpleTranslationEntries = {
"learnMoveAnd": "Y…",
"levelCapUp": "¡Se ha incrementado el\nnivel máximo a {{levelCap}}!",
"moveNotImplemented": "{{moveName}} aún no está implementado y no se puede seleccionar.",
"moveNoPP": "There's no PP left for\nthis move!",
"moveNoPP": "¡No hay suficientes PP\npara este movimiento!",
"moveDisabled": "!No puede usar {{moveName}} porque ha sido anulado!",
"noPokeballForce": "Una fuerza misteriosa\nte impide usar Poké Balls.",
"noPokeballTrainer": "¡No puedes atrapar a los\nPokémon de los demás!",

View File

@ -1,40 +1,40 @@
import { SimpleTranslationEntries } from "#app/plugins/i18n";
export const biome: SimpleTranslationEntries = {
"unknownLocation": "Somewhere you can\'t remember",
"TOWN": "Town",
"PLAINS": "Plains",
"GRASS": "Grassy Field",
"TALL_GRASS": "Tall Grass",
"METROPOLIS": "Metropolis",
"FOREST": "Forest",
"SEA": "Sea",
"SWAMP": "Swamp",
"BEACH": "Beach",
"LAKE": "Lake",
"SEABED": "Seabed",
"MOUNTAIN": "Mountain",
"unknownLocation": "En algún lugar que no puedes recordar",
"TOWN": "Ciudad",
"PLAINS": "Valle",
"GRASS": "Campo",
"TALL_GRASS": "Pradera de Hierba Alta",
"METROPOLIS": "Metrópolis",
"FOREST": "Bosque",
"SEA": "Mar",
"SWAMP": "Pantano",
"BEACH": "Playa",
"LAKE": "Lago",
"SEABED": "Fondo del mar",
"MOUNTAIN": "Montaña",
"BADLANDS": "Badlands",
"CAVE": "Cave",
"DESERT": "Desert",
"ICE_CAVE": "Ice Cave",
"MEADOW": "Meadow",
"POWER_PLANT": "Power Plant",
"VOLCANO": "Volcano",
"GRAVEYARD": "Graveyard",
"CAVE": "Cueva",
"DESERT": "Desierto",
"ICE_CAVE": "Cueva Helada",
"MEADOW": "Prado",
"POWER_PLANT": "Central Eléctrica",
"VOLCANO": "Volcán",
"GRAVEYARD": "Cementerio",
"DOJO": "Dojo",
"FACTORY": "Factory",
"RUINS": "Ancient Ruins",
"WASTELAND": "Wasteland",
"ABYSS": "Abyss",
"SPACE": "Space",
"CONSTRUCTION_SITE": "Construction Site",
"JUNGLE": "Jungle",
"FAIRY_CAVE": "Fairy Cave",
"TEMPLE": "Temple",
"SLUM": "Slum",
"SNOWY_FOREST": "Snowy Forest",
"ISLAND": "Island",
"LABORATORY": "Laboratory",
"FACTORY": "Fábrica",
"RUINS": "Ruinas Antiguas",
"WASTELAND": "Páramo",
"ABYSS": "Abismo",
"SPACE": "Espacio",
"CONSTRUCTION_SITE": "Obra",
"JUNGLE": "Jungla",
"FAIRY_CAVE": "Cueva de Hadas",
"TEMPLE": "Templo",
"SLUM": "Suburbio",
"SNOWY_FOREST": "Bosque nevado",
"ISLAND": "Isla",
"LABORATORY": "Laboratorio",
"END": "???",
} as const;

View File

@ -1595,13 +1595,13 @@ export const PGMdialogue: DialogueTranslationEntries = {
"encounter": {
1: `I decided to throw my hat in the ring once more.
$Come now Show me the fruits of your training.`,
"victory": {
1: "I eagerly await news of all your achievements!"
},
"defeat": {
1: "What's the matter? This isn't all, is it?"
}
},
"victory": {
1: "I eagerly await news of all your achievements!"
},
"defeat": {
1: "What's the matter? This isn't all, is it?"
}
},
"nemona": {
"encounter": {
@ -2278,7 +2278,7 @@ export const PGMdialogue: DialogueTranslationEntries = {
// 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.
export const PGFdialogue: SimpleTranslationEntries = PGMdialogue;
export const PGFdialogue: DialogueTranslationEntries = PGMdialogue;
// Dialogue of the endboss of the game when the player character is male (Or unset)
export const PGMbattleSpecDialogue: SimpleTranslationEntries = {

View File

@ -11,6 +11,7 @@ export const menu: SimpleTranslationEntries = {
"dailyRun": "Reto diario (Beta)",
"loadGame": "Cargar partida",
"newGame": "Nueva partida",
"settings": "Settings",
"selectGameMode": "Elige un modo de juego.",
"logInOrCreateAccount": "Inicia sesión o crea una cuenta para empezar. ¡No se requiere correo electrónico!",
"username": "Usuario",

View File

@ -2,17 +2,17 @@ import {SimpleTranslationEntries} from "#app/plugins/i18n";
// Titles of special trainers like gym leaders, elite four, and the champion
export const titles: SimpleTranslationEntries = {
"elite_four": "Elite Four",
"elite_four_female": "Elite Four",
"gym_leader": "Gym Leader",
"gym_leader_female": "Gym Leader",
"gym_leader_double": "Gym Leader Duo",
"champion": "Champion",
"champion_female": "Champion",
"champion_double": "Champion Duo",
"elite_four": "Alto Mando",
"elite_four_female": "Alto Mando",
"gym_leader": "Líder de gimnasio",
"gym_leader_female": "Líder de gimnasio",
"gym_leader_double": "Líderes de Gimnasio",
"champion": "Campeón",
"champion_female": "Campeona",
"champion_double": "Campeones",
"rival": "Rival",
"professor": "Professor",
"frontier_brain": "Frontier Brain",
"professor": "Profesor",
"frontier_brain": "As del Frente Batalla",
// Maybe if we add the evil teams we can add "Team Rocket" and "Team Aqua" etc. here as well as "Team Rocket Boss" and "Team Aqua Admin" etc.
} as const;

View File

@ -1595,13 +1595,13 @@ export const PGMdialogue: DialogueTranslationEntries = {
"encounter": {
1: `I decided to throw my hat in the ring once more.
$Come now Show me the fruits of your training.`,
"victory": {
1: "I eagerly await news of all your achievements!"
},
"defeat": {
1: "What's the matter? This isn't all, is it?"
}
},
"victory": {
1: "I eagerly await news of all your achievements!"
},
"defeat": {
1: "What's the matter? This isn't all, is it?"
}
},
"nemona": {
"encounter": {
@ -3873,13 +3873,13 @@ export const PGFdialogue: DialogueTranslationEntries = {
"encounter": {
1: `I decided to throw my hat in the ring once more.
$Come now Show me the fruits of your training.`,
"victory": {
1: "I eagerly await news of all your achievements!"
},
"defeat": {
1: "What's the matter? This isn't all, is it?"
}
},
"victory": {
1: "I eagerly await news of all your achievements!"
},
"defeat": {
1: "What's the matter? This isn't all, is it?"
}
},
"nemona": {
"encounter": {

View File

@ -6,6 +6,7 @@ export const menu: SimpleTranslationEntries = {
"dailyRun": "Défi du jour (Bêta)",
"loadGame": "Charger la partie",
"newGame": "Nouvelle partie",
"settings": "Paramètres",
"selectGameMode": "Sélectionnez un mode de jeu.",
"logInOrCreateAccount": "Connectez-vous ou créez un compte pour commencer. Aucun e-mail requis !",
"username": "Nom dutilisateur",

View File

@ -16,7 +16,7 @@ export const tutorial: SimpleTranslationEntries = {
$Il y a également toute une variété dautres fonctionnalités,
$jetez-y un œil !`,
"starterSelect": `Choisissez vos starters depuis cet écran.\nIls formeront votre équipe de départ.
"starterSelect": `Choisissez vos starters depuis cet écran avec Z ou Espace.\nIls formeront votre équipe de départ.
$Chacun possède une valeur. Votre équipe peut avoir jusquà\n6 membres, tant que vous ne dépassez pas un cout de 10.
$Vous pouvez aussi choisir le sexe, le talent et la forme en\nfonction des variants déjà capturés ou éclos.
$Les IVs dun starter sont les meilleurs de tous ceux de son\nespèce déjà obtenus. Essayez donc den obtenir plusieurs !`,

View File

@ -1595,13 +1595,13 @@ export const PGMdialogue: DialogueTranslationEntries = {
"encounter": {
1: `I decided to throw my hat in the ring once more.
$Come now Show me the fruits of your training.`,
"victory": {
1: "I eagerly await news of all your achievements!"
},
"defeat": {
1: "What's the matter? This isn't all, is it?"
}
},
"victory": {
1: "I eagerly await news of all your achievements!"
},
"defeat": {
1: "What's the matter? This isn't all, is it?"
}
},
"nemona": {
"encounter": {
@ -2278,7 +2278,7 @@ export const PGMdialogue: DialogueTranslationEntries = {
// 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.
export const PGFdialogue: SimpleTranslationEntries = PGMdialogue;
export const PGFdialogue: DialogueTranslationEntries = PGMdialogue;
// Dialogue of the endboss of the game when the player character is male (Or unset)
export const PGMbattleSpecDialogue: SimpleTranslationEntries = {

View File

@ -10,6 +10,7 @@ export const menu: SimpleTranslationEntries = {
"continue": "Continua",
"newGame": "Nuova Partita",
"loadGame": "Carica Partita",
"settings": "Settings",
"dailyRun": "Corsa Giornaliera (Beta)",
"selectGameMode": "Seleziona una modalità di gioco.",
"logInOrCreateAccount": "Accedi o crea un nuovo account per iniziare. Non è richiesta un'email!",

View File

@ -1595,13 +1595,13 @@ export const PGMdialogue: DialogueTranslationEntries = {
"encounter": {
1: `I decided to throw my hat in the ring once more.
$Come now Show me the fruits of your training.`,
"victory": {
1: "I eagerly await news of all your achievements!"
},
"defeat": {
1: "What's the matter? This isn't all, is it?"
}
},
"victory": {
1: "I eagerly await news of all your achievements!"
},
"defeat": {
1: "What's the matter? This isn't all, is it?"
}
},
"nemona": {
"encounter": {
@ -2278,7 +2278,7 @@ export const PGMdialogue: DialogueTranslationEntries = {
// 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.
export const PGFdialogue: SimpleTranslationEntries = PGMdialogue;
export const PGFdialogue: DialogueTranslationEntries = PGMdialogue;
// Dialogue of the endboss of the game when the player character is male (Or unset)
export const PGMbattleSpecDialogue: SimpleTranslationEntries = {

View File

@ -11,6 +11,7 @@ export const menu: SimpleTranslationEntries = {
"dailyRun": "Desafio Diário (Beta)",
"loadGame": "Carregar Jogo",
"newGame": "Novo Jogo",
"settings": "Configurações",
"selectGameMode": "Escolha um modo de jogo.",
"logInOrCreateAccount": "Inicie uma sessão ou crie uma conta para começar. Não é necessário email!",
"username": "Nome de Usuário",

View File

@ -18,7 +18,8 @@ export const tutorial: SimpleTranslationEntries = {
$Existem também vários outros recursos disponíveis aqui.
$Não deixe de conferir todos eles!`,
"starterSelect": `Aqui você pode escolher seus iniciais.\nEsses serão os primeiro Pokémon da sua equipe.
"starterSelect": `Aqui você pode escolher seus iniciais apertando a tecla Z ou\na Barra de Espaço.
$Esses serão os primeiro Pokémon da sua equipe.
$Cada inicial tem seu custo. Sua equipe pode ter até 6\nmembros, desde que a soma dos custos não ultrapasse 10.
$Você pode escolher o gênero, a habilidade\ne até a forma do seu inicial.
$Essas opções dependem das variantes dessa\nespécie que você capturou ou chocou.

View File

@ -1595,13 +1595,13 @@ export const PGMdialogue: DialogueTranslationEntries = {
"encounter": {
1: `I decided to throw my hat in the ring once more.
$Come now Show me the fruits of your training.`,
"victory": {
1: "I eagerly await news of all your achievements!"
},
"defeat": {
1: "What's the matter? This isn't all, is it?"
}
},
"victory": {
1: "I eagerly await news of all your achievements!"
},
"defeat": {
1: "What's the matter? This isn't all, is it?"
}
},
"nemona": {
"encounter": {
@ -2278,7 +2278,7 @@ export const PGMdialogue: DialogueTranslationEntries = {
// 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.
export const PGFdialogue: SimpleTranslationEntries = PGMdialogue;
export const PGFdialogue: DialogueTranslationEntries = PGMdialogue;
// Dialogue of the endboss of the game when the player character is male (Or unset)
export const PGMbattleSpecDialogue: SimpleTranslationEntries = {

View File

@ -11,6 +11,7 @@ export const menu: SimpleTranslationEntries = {
"dailyRun": "每日挑战 (Beta)",
"loadGame": "加载游戏",
"newGame": "新游戏",
"settings": "设置",
"selectGameMode": "选择一个游戏模式",
"logInOrCreateAccount": "登录或创建账户以开始游戏。无需邮箱!",
"username": "用户名",

View File

@ -13,7 +13,7 @@ export const tutorial: SimpleTranslationEntries = {
$在设置中\n和其他选项
$这里还有各种其他功能`,
"starterSelect": `在此页面中,您可以选择您的初始宝可梦。\n这些是您最初的队伍成员。
"starterSelect": `在此页面中,您可以通过按Z或空格键选择\n您的初始宝可梦。这些是您最初的队伍成员。
$每个初始宝可梦都有一个费用值\n最多可以拥有6名成员10
$您还可以根据您捕获或孵化的变种选择性别\n
$一个物种个体值是您捕获或孵化的所有宝可\n梦中最好的`,

View File

@ -1595,13 +1595,13 @@ export const PGMdialogue: DialogueTranslationEntries = {
"encounter": {
1: `I decided to throw my hat in the ring once more.
$Come now Show me the fruits of your training.`,
"victory": {
1: "I eagerly await news of all your achievements!"
},
"defeat": {
1: "What's the matter? This isn't all, is it?"
}
},
"victory": {
1: "I eagerly await news of all your achievements!"
},
"defeat": {
1: "What's the matter? This isn't all, is it?"
}
},
"nemona": {
"encounter": {
@ -2278,7 +2278,7 @@ export const PGMdialogue: DialogueTranslationEntries = {
// 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.
export const PGFdialogue: SimpleTranslationEntries = PGMdialogue;
export const PGFdialogue: DialogueTranslationEntries = PGMdialogue;
// Dialogue of the endboss of the game when the player character is male (Or unset)
export const PGMbattleSpecDialogue: SimpleTranslationEntries = {

View File

@ -11,6 +11,7 @@ export const menu: SimpleTranslationEntries = {
"dailyRun": "每日挑戰 (Beta)",
"loadGame": "加載遊戲",
"newGame": "新遊戲",
"settings": "Settings",
"selectGameMode": "選擇遊戲模式",
"logInOrCreateAccount": "登入或註冊即可開始遊戲,無需郵箱!",
"username": "用戶名",

View File

@ -5,7 +5,6 @@ import { version } from "../package.json";
import UIPlugin from "phaser3-rex-plugins/templates/ui/ui-plugin";
import BBCodeTextPlugin from "phaser3-rex-plugins/plugins/bbcodetext-plugin";
import InputTextPlugin from "phaser3-rex-plugins/plugins/inputtext-plugin.js";
import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
import TransitionImagePackPlugin from "phaser3-rex-plugins/templates/transitionimagepack/transitionimagepack-plugin.js";
import { LoadingScene } from "./loading-scene";
@ -72,23 +71,82 @@ const config: Phaser.Types.Core.GameConfig = {
version: version
};
/**
* 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) {
if (guideObject && guideObject instanceof Phaser.GameObjects.GameObject) {
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);
return;
}
this.setPosition(x, y);
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);
};
declare module "phaser" {
namespace GameObjects {
interface Container {
/**
* 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
*/
setPositionRelative(guideObject: any, x: number, y: number): void;
}
interface Sprite {
/**
* 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
*/
setPositionRelative(guideObject: any, x: number, y: number): void;
}
interface Image {
/**
* 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
*/
setPositionRelative(guideObject: any, x: number, y: number): void;
}
interface NineSlice {
/**
* 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
*/
setPositionRelative(guideObject: any, x: number, y: number): void;
}
interface Text {
/**
* 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
*/
setPositionRelative(guideObject: any, x: number, y: number): void;
}
interface Rectangle {
/**
* 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
*/
setPositionRelative(guideObject: any, x: number, y: number): void;
}
}
}
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;
BBCodeText.prototype.setPositionRelative = setPositionRelative;
Phaser.GameObjects.Rectangle.prototype.setPositionRelative = setPositionRelative;
document.fonts.load("16px emerald").then(() => document.fonts.load("10px pkmnems"));

View File

@ -30,7 +30,7 @@ import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, get
import { TempBattleStat } from "./data/temp-battle-stat";
import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag";
import { ArenaTagType } from "./data/enums/arena-tag-type";
import { CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, IgnoreOpponentEvasionAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, BlockRedirectAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, BattleStatMultiplierAbAttr, applyBattleStatMultiplierAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr } from "./data/ability";
import { CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, IgnoreOpponentEvasionAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, BlockRedirectAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, BattleStatMultiplierAbAttr, applyBattleStatMultiplierAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, MaxMultiHitAbAttr, HealFromBerryUseAbAttr } from "./data/ability";
import { Unlockables, getUnlockableName } from "./system/unlockables";
import { getBiomeKey } from "./field/arena";
import { BattleType, BattlerIndex, TurnCommand } from "./battle";
@ -262,6 +262,14 @@ export class TitlePhase extends Phase {
return true;
},
keepOpen: true
},
{
label: i18next.t("menu:settings"),
handler: () => {
this.scene.ui.setOverlayMode(Mode.SETTINGS);
return true;
},
keepOpen: true
});
const config: OptionSelectConfig = {
options: options,
@ -1015,7 +1023,6 @@ export class EncounterPhase extends BattlePhase {
}
}
}
handleTutorial(this.scene, Tutorial.Access_Menu).then(() => super.end());
}
@ -2221,7 +2228,9 @@ export class BerryPhase extends FieldPhase {
if (cancelled.value) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " is too\nnervous to eat berries!"));
} else {
this.scene.unshiftPhase(new CommonAnimPhase(this.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.USE_ITEM));
this.scene.unshiftPhase(
new CommonAnimPhase(this.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.USE_ITEM)
);
for (const berryModifier of this.scene.applyModifiers(BerryModifier, pokemon.isPlayer(), pokemon) as BerryModifier[]) {
if (berryModifier.consumed) {
@ -2234,6 +2243,8 @@ export class BerryPhase extends FieldPhase {
}
this.scene.updateModifiers(pokemon.isPlayer());
applyAbAttrs(HealFromBerryUseAbAttr, pokemon, new Utils.BooleanHolder(false));
}
}
});
@ -2822,9 +2833,13 @@ export class MoveEffectPhase extends PokemonPhase {
const user = this.getUserPokemon();
// Hit check only calculated on first hit for multi-hit moves
// Hit check only calculated on first hit for multi-hit moves unless flag is set to check all hits.
// However, if an ability with the MaxMultiHitAbAttr, namely Skill Link, is present, act as a normal
// multi-hit move and proceed with all hits
if (user.turnData.hitsLeft < user.turnData.hitCount) {
return true;
if (!this.move.getMove().hasFlag(MoveFlags.CHECK_ALL_HITS) || user.hasAbilityWithAttr(MaxMultiHitAbAttr)) {
return true;
}
}
if (user.hasAbilityWithAttr(AlwaysHitAbAttr) || target.hasAbilityWithAttr(AlwaysHitAbAttr)) {

View File

@ -65,19 +65,14 @@ export interface DialogueTranslationEntry {
}
export interface DialogueTranslationCategory {
encounter: DialogueTranslationEntry;
victory: DialogueTranslationEntry;
defeat?: DialogueTranslationEntry;
}
export interface DialogueTranslationTrainerClass {
[key: string]: DialogueTranslationCategory;
[category: string]: DialogueTranslationEntry;
}
export interface DialogueTranslationEntries {
[key: string]: DialogueTranslationTrainerClass;
[trainertype: string]: DialogueTranslationCategory;
}
export interface Localizable {
localize(): void;
}

View File

@ -19,27 +19,33 @@ export function initGameSpeed() {
const originalAddEvent = this.time.addEvent;
this.time.addEvent = function (config: Phaser.Time.TimerEvent | Phaser.Types.Time.TimerEventConfig) {
if (config.delay) {
if (!(config instanceof Phaser.Time.TimerEvent) && config.delay) {
config.delay = transformValue(config.delay);
}
return originalAddEvent.apply(this, [ config ]);
};
const originalTweensAdd = this.tweens.add;
this.tweens.add = function (config: Phaser.Types.Tweens.TweenBuilderConfig | Phaser.Types.Tweens.TweenChainBuilderConfig | Phaser.Tweens.Tween | Phaser.Tweens.TweenChain) {
if (config.duration) {
config.duration = transformValue(config.duration);
}
if (config.delay) {
config.delay = transformValue(config.delay);
}
if (config.repeatDelay) {
config.repeatDelay = transformValue(config.repeatDelay);
}
if (config.loopDelay) {
config.loopDelay = transformValue(config.loopDelay);
config.loopDelay = transformValue(config.loopDelay as number);
}
if (config.hold) {
config.hold = transformValue(config.hold);
if (!(config instanceof Phaser.Tweens.TweenChain) ) {
if (config.duration) {
config.duration = transformValue(config.duration);
}
if (!(config instanceof Phaser.Tweens.Tween)) {
if (config.delay) {
config.delay = transformValue(config.delay as number);
}
if (config.repeatDelay) {
config.repeatDelay = transformValue(config.repeatDelay);
}
if (config.hold) {
config.hold = transformValue(config.hold);
}
}
}
return originalTweensAdd.apply(this, [ config ]);
};
@ -51,13 +57,13 @@ export function initGameSpeed() {
t.duration = transformValue(t.duration);
}
if (t.delay) {
t.delay = transformValue(t.delay);
t.delay = transformValue(t.delay as number);
}
if (t.repeatDelay) {
t.repeatDelay = transformValue(t.repeatDelay);
}
if (t.loopDelay) {
t.loopDelay = transformValue(t.loopDelay);
t.loopDelay = transformValue(t.loopDelay as number);
}
if (t.hold) {
t.hold = transformValue(t.hold);
@ -78,7 +84,7 @@ export function initGameSpeed() {
config.repeatDelay = transformValue(config.repeatDelay);
}
if (config.loopDelay) {
config.loopDelay = transformValue(config.loopDelay);
config.loopDelay = transformValue(config.loopDelay as number);
}
if (config.hold) {
config.hold = transformValue(config.hold);

View File

@ -5,6 +5,7 @@ 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";
export enum Setting {
@ -18,6 +19,8 @@ export enum Setting {
Window_Type = "WINDOW_TYPE",
Tutorials = "TUTORIALS",
Enable_Retries = "ENABLE_RETRIES",
Candy_Upgrade_Notification = "CANDY_UPGRADE_NOTIFICATION",
Candy_Upgrade_Display = "CANDY_UPGRADE_DISPLAY",
Money_Format = "MONEY_FORMAT",
Sprite_Set = "SPRITE_SET",
Move_Animations = "MOVE_ANIMATIONS",
@ -52,6 +55,8 @@ 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.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"],
@ -78,6 +83,8 @@ export const settingDefaults: SettingDefaults = {
[Setting.Window_Type]: 0,
[Setting.Tutorials]: 1,
[Setting.Enable_Retries]: 0,
[Setting.Candy_Upgrade_Notification]: 0,
[Setting.Candy_Upgrade_Display]: 0,
[Setting.Money_Format]: 0,
[Setting.Sprite_Set]: 0,
[Setting.Move_Animations]: 1,
@ -93,7 +100,7 @@ export const settingDefaults: SettingDefaults = {
[Setting.Vibration]: 0
};
export const reloadSettings: Setting[] = [Setting.UI_Theme, Setting.Language, Setting.Sprite_Set];
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) {
@ -127,6 +134,16 @@ export function setSetting(scene: BattleScene, setting: Setting, value: integer)
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":

View File

@ -3,6 +3,8 @@ export const keysDown = new Map();
let lastTouchedId;
export function initTouchControls(buttonMap) {
const dpadDiv = document.querySelector("#dpad");
preventElementZoom(dpadDiv);
for (const button of document.querySelectorAll("[data-key]")) {
// @ts-ignore
bindKey(button, button.dataset.key, buttonMap);
@ -115,3 +117,26 @@ function bindKey(node, key, buttonMap) {
}
});
}
/**
* {@link https://stackoverflow.com/a/39778831/4622620|Source}
*
* Prevent zoom on specified element
* @param {HTMLElement} element
*/
function preventElementZoom(element) {
element.addEventListener("touchstart", (event) => {
const currentTouchTimeStamp = event.timeStamp;
const previousTouchTimeStamp = event.currentTarget.dataset.lastTouchTimeStamp || currentTouchTimeStamp;
const timeStampDifference = currentTouchTimeStamp - previousTouchTimeStamp;
const fingers = event.touches.length;
event.currentTarget.dataset.lastTouchTimeStamp = currentTouchTimeStamp;
if (!timeStampDifference || timeStampDifference > 500 || fingers > 1) {
return;
} // not double-tap
event.preventDefault();
event.target.click();
});
}

View File

@ -1,3 +1,4 @@
import { BattleSceneEventType, CandyUpgradeNotificationChangedEvent } from "#app/battle-scene-events.js";
import { pokemonPrevolutions } from "#app/data/pokemon-evolutions";
import { Variant, getVariantTint } from "#app/data/variant";
import { argbFromRgba } from "@material/material-color-utilities";
@ -103,6 +104,29 @@ function getValueReductionCandyCounts(baseValue: integer): [integer, integer] {
return starterCandyCosts[baseValue - 1].costReduction;
}
/**
* Calculates the icon position for a Pokemon of a given UI index
* @param index UI index to calculate the icon position of
* @returns An interface with an x and y property
*/
function calcIconPosition(index: number): {x: number, y: number} {
const x = (index % 9) * 18;
const y = Math.floor(index / 9) * 18;
return {x: x, y: y};
}
/**
* Calculates the {@linkcode Phaser.GameObjects.Sprite} position for a Pokemon of a given UI index
* @param index UI index to calculate the icon position of
* @returns An interface with an x and y property
*/
function calcSpritePosition(index: number): {x: number, y: number} {
const position = calcIconPosition(index);
return {x: position.x - 2, y: position.y + 2};
}
const gens = [
i18next.t("starterSelectUiHandler:gen1"),
i18next.t("starterSelectUiHandler:gen2"),
@ -202,6 +226,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
private shinyIcons: Phaser.GameObjects.Image[][];
private hiddenAbilityIcons: Phaser.GameObjects.Image[];
private classicWinIcons: Phaser.GameObjects.Image[];
private candyUpgradeIcon: Phaser.GameObjects.Image[];
private candyUpgradeOverlayIcon: Phaser.GameObjects.Image[];
private iconAnimHandler: PokemonIconAnimHandler;
@ -392,9 +418,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.genSpecies[g].push(species);
const defaultDexAttr = this.scene.gameData.getSpeciesDefaultDexAttr(species, false, true);
const defaultProps = this.scene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
const x = (s % 9) * 18;
const y = Math.floor(s / 9) * 18;
const icon = this.scene.add.sprite(x - 2, y + 2, species.getIconAtlasKey(defaultProps.formIndex, defaultProps.shiny, defaultProps.variant));
const position = calcIconPosition(s);
const icon = this.scene.add.sprite(position.x - 2, position.y + 2, species.getIconAtlasKey(defaultProps.formIndex, defaultProps.shiny, defaultProps.variant));
icon.setScale(0.5);
icon.setOrigin(0, 0);
icon.setFrame(species.getIconId(defaultProps.female, defaultProps.formIndex, defaultProps.shiny, defaultProps.variant));
@ -417,9 +442,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
});
this.starterValueLabels = new Array(81).fill(null).map((_, i) => {
const x = (i % 9) * 18;
const y = Math.floor(i / 9) * 18;
const ret = addTextObject(this.scene, x + 152, y + 11, "0", TextStyle.WINDOW, { fontSize: "32px" });
const position = calcIconPosition(i);
const ret = addTextObject(this.scene, position.x + 152, position.y + 11, "0", TextStyle.WINDOW, { fontSize: "32px" });
ret.setShadowOffset(2, 2);
ret.setOrigin(0, 0);
ret.setVisible(false);
@ -428,9 +452,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
});
const getShinyStar = (i: integer, v: integer): Phaser.GameObjects.Image => {
const x = (i % 9) * 18 - v * 3;
const y = Math.floor(i / 9) * 18;
const ret = this.scene.add.image(x + 163, y + 11, "shiny_star_small");
const position = calcIconPosition(i);
const ret = this.scene.add.image((position.x - v * 3) + 163, position.y + 11, "shiny_star_small");
ret.setOrigin(0, 0);
ret.setScale(0.5);
ret.setVisible(false);
@ -443,9 +466,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
});
this.hiddenAbilityIcons = new Array(81).fill(null).map((_, i) => {
const x = (i % 9) * 18;
const y = Math.floor(i / 9) * 18;
const ret = this.scene.add.image(x + 163, y + 16, "ha_capsule");
const position = calcIconPosition(i);
const ret = this.scene.add.image(position.x + 163, position.y + 16, "ha_capsule");
ret.setOrigin(0, 0);
ret.setScale(0.5);
ret.setVisible(false);
@ -454,9 +476,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
});
this.classicWinIcons = new Array(81).fill(null).map((_, i) => {
const x = (i % 9) * 18;
const y = Math.floor(i / 9) * 18;
const ret = this.scene.add.image(x + 153, y + 21, "champion_ribbon");
const position = calcIconPosition(i);
const ret = this.scene.add.image(position.x + 153, position.y + 21, "champion_ribbon");
ret.setOrigin(0, 0);
ret.setScale(0.5);
ret.setVisible(false);
@ -464,6 +485,26 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
return ret;
});
this.candyUpgradeIcon = new Array(81).fill(null).map((_, i) => {
const position = calcIconPosition(i);
const ret = this.scene.add.image(position.x + 163, position.y + 21, "candy");
ret.setOrigin(0, 0);
ret.setScale(0.25);
ret.setVisible(false);
this.starterSelectContainer.add(ret);
return ret;
});
this.candyUpgradeOverlayIcon = new Array(81).fill(null).map((_, i) => {
const position = calcIconPosition(i);
const ret = this.scene.add.image(position.x + 163, position.y + 21, "candy_overlay");
ret.setOrigin(0, 0);
ret.setScale(0.25);
ret.setVisible(false);
this.starterSelectContainer.add(ret);
return ret;
});
this.pokemonSprite = this.scene.add.sprite(53, 63, "pkmn__sub");
this.pokemonSprite.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true });
this.starterSelectContainer.add(this.pokemonSprite);
@ -653,25 +694,14 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.starterSelectContainer.add(this.statsContainer);
this.scene.eventTarget.addEventListener(BattleSceneEventType.CANDY_UPGRADE_NOTIFICATION_CHANGED, (e) => this.onCandyUpgradeDisplayChanged(e));
this.updateInstructions();
}
show(args: any[]): boolean {
if (args.length >= 2 && args[0] instanceof Function && typeof args[1] === "number") {
super.show(args);
for (let g = 0; g < this.genSpecies.length; g++) {
this.genSpecies[g].forEach((species, s) => {
const dexEntry = this.scene.gameData.dexData[species.speciesId];
const icon = this.starterSelectGenIconContainers[g].getAt(s) as Phaser.GameObjects.Sprite;
if (dexEntry.caughtAttr) {
icon.clearTint();
} else if (dexEntry.seenAttr) {
icon.setTint(0x808080);
}
});
}
this.starterSelectCallback = args[0] as StarterSelectCallback;
this.starterSelectContainer.setVisible(true);
@ -684,6 +714,21 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.setCursor(0);
this.tryUpdateValue(0);
for (let g = 0; g < this.genSpecies.length; g++) {
this.genSpecies[g].forEach((species, s) => {
const icon = this.starterSelectGenIconContainers[g].getAt(s) as Phaser.GameObjects.Sprite;
const dexEntry = this.scene.gameData.dexData[species.speciesId];
if (dexEntry.caughtAttr) {
icon.clearTint();
} else if (dexEntry.seenAttr) {
icon.setTint(0x808080);
}
this.setUpgradeAnimation(icon, species);
});
}
handleTutorial(this.scene, Tutorial.Starter_Select);
return true;
@ -706,6 +751,161 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.starterSelectMessageBoxContainer.setVisible(!!text?.length);
}
/**
* Determines if 'Icon' based upgrade notifications should be shown
* @returns true if upgrade notifications are enabled and set to display an 'Icon'
*/
isUpgradeIconEnabled(): boolean {
return this.scene.candyUpgradeNotification !== 0 && this.scene.candyUpgradeDisplay === 0;
}
/**
* Determines if 'Animation' based upgrade notifications should be shown
* @returns true if upgrade notifications are enabled and set to display an 'Animation'
*/
isUpgradeAnimationEnabled(): boolean {
return this.scene.candyUpgradeNotification !== 0 && this.scene.candyUpgradeDisplay === 1;
}
/**
* Determines if a passive upgrade is available for the given species ID
* @param speciesId The ID of the species to check the passive of
* @returns true if the user has enough candies and a passive has not been unlocked already
*/
isPassiveAvailable(speciesId: number): boolean {
// Get this species ID's starter data
const starterData = this.scene.gameData.starterData[speciesId];
return starterData.candyCount >= getPassiveCandyCount(speciesStarters[speciesId])
&& !(starterData.passiveAttr & PassiveAttr.UNLOCKED);
}
/**
* Determines if a value reduction upgrade is available for the given species ID
* @param speciesId The ID of the species to check the value reduction of
* @returns true if the user has enough candies and all value reductions have not been unlocked already
*/
isValueReductionAvailable(speciesId: number): boolean {
// Get this species ID's starter data
const starterData = this.scene.gameData.starterData[speciesId];
return starterData.candyCount >= getValueReductionCandyCounts(speciesStarters[speciesId])[starterData.valueReduction]
&& starterData.valueReduction < 2;
}
/**
* Sets a bounce animation if enabled and the Pokemon has an upgrade
* @param icon {@linkcode Phaser.GameObjects.GameObject} to animate
* @param species {@linkcode PokemonSpecies} of the icon used to check for upgrades
* @param startPaused Should this animation be paused after it is added?
*/
setUpgradeAnimation(icon: Phaser.GameObjects.Sprite, species: PokemonSpecies, startPaused: boolean = false): void {
this.scene.tweens.killTweensOf(icon);
// Skip animations if they are disabled
if (this.scene.candyUpgradeDisplay === 0 || species.speciesId !== species.getRootSpeciesId(false)) {
return;
}
const position = calcSpritePosition(this.genSpecies[species.generation - 1].indexOf(species));
icon.y = position.y;
const tweenChain: Phaser.Types.Tweens.TweenChainBuilderConfig = {
targets: icon,
loop: -1,
// Make the initial bounce a little randomly delayed
delay: Utils.randIntRange(0, 50) * 5,
loopDelay: 1000,
tweens: [
{
targets: icon,
y: position.y - 5,
duration: Utils.fixedInt(125),
ease: "Cubic.easeOut",
yoyo: true
},
{
targets: icon,
y: position.y - 3,
duration: Utils.fixedInt(150),
ease: "Cubic.easeOut",
yoyo: true
}
],};
const passiveAvailable = this.isPassiveAvailable(species.speciesId);
// 'Only Passives' mode
if (this.scene.candyUpgradeNotification === 1) {
if (passiveAvailable) {
this.scene.tweens.chain(tweenChain).paused = startPaused;
}
// 'On' mode
} else if (this.scene.candyUpgradeNotification === 2) {
if (passiveAvailable || this.isValueReductionAvailable(species.speciesId)) {
this.scene.tweens.chain(tweenChain).paused = startPaused;
}
}
}
/**
* Sets the visibility of a Candy Upgrade Icon given an index
* @param index The UI index of the icon within this generation container
*/
setUpgradeIcon(index: number): void {
const species = this.genSpecies[this.getGenCursorWithScroll()][index];
const slotVisible = !!species?.speciesId;
if (!species // No Pokemon exists at that UI index
|| this.scene.candyUpgradeNotification === 0 // Notification setting is 'Off'
|| species.speciesId !== species.getRootSpeciesId(false)) { // Pokemon is not the base evolution and can't use candy
// Set all icons as hidden and exit early
this.candyUpgradeIcon[index].setVisible(false);
this.candyUpgradeOverlayIcon[index].setVisible(false);
return;
}
const passiveAvailable = this.isPassiveAvailable(species.speciesId);
// 'Only Passive Unlocks' mode
if (this.scene.candyUpgradeNotification === 1) {
this.candyUpgradeIcon[index].setVisible(slotVisible && passiveAvailable);
this.candyUpgradeOverlayIcon[index].setVisible(slotVisible && this.candyUpgradeIcon[index].visible);
// 'On' mode
} else if (this.scene.candyUpgradeNotification === 2) {
this.candyUpgradeIcon[index].setVisible(
slotVisible && ( passiveAvailable || this.isValueReductionAvailable(species.speciesId)));
this.candyUpgradeOverlayIcon[index].setVisible(slotVisible && this.candyUpgradeIcon[index].visible);
}
}
/**
* Processes an {@linkcode CandyUpgradeNotificationChangedEvent} sent when the corresponding setting changes
* @param event {@linkcode Event} sent by the callback
*/
onCandyUpgradeDisplayChanged(event: Event): void {
const candyUpgradeDisplayEvent = event as CandyUpgradeNotificationChangedEvent;
if (!candyUpgradeDisplayEvent) {
return;
}
// Loop through all visible candy icons when set to 'Icon' mode
if (this.scene.candyUpgradeDisplay === 0) {
this.genSpecies[this.getGenCursorWithScroll()].forEach((_species, s) => {
this.setUpgradeIcon(s);
});
return;
}
// Loop through all animations when set to 'Animation' mode
for (let g = 0; g < this.genSpecies.length; g++) {
this.genSpecies[g].forEach((species, s) => {
const icon = this.starterSelectGenIconContainers[g].getAt(s) as Phaser.GameObjects.Sprite;
this.setUpgradeAnimation(icon, species);
});
}
}
processInput(button: Button): boolean {
if (this.blockInput) {
return false;
@ -948,6 +1148,16 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
});
ui.setMode(Mode.STARTER_SELECT);
this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, undefined, undefined);
// Update the candy upgrade display
if (this.isUpgradeIconEnabled() ) {
this.setUpgradeIcon(this.cursor);
}
if (this.isUpgradeAnimationEnabled()) {
const genSpecies = this.genSpecies[this.lastSpecies.generation - 1];
this.setUpgradeAnimation(this.starterSelectGenIconContainers[this.lastSpecies.generation - 1].getAt(genSpecies.indexOf(this.lastSpecies)), this.lastSpecies, true);
}
return true;
}
return false;
@ -975,6 +1185,18 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.tryUpdateValue(0);
ui.setMode(Mode.STARTER_SELECT);
this.scene.playSound("buy");
// If the notification setting is set to 'On', update the candy upgrade display
if (this.scene.candyUpgradeNotification === 2) {
if (this.isUpgradeIconEnabled() ) {
this.setUpgradeIcon(this.cursor);
}
if (this.isUpgradeAnimationEnabled()) {
const genSpecies = this.genSpecies[this.lastSpecies.generation - 1];
this.setUpgradeAnimation(this.starterSelectGenIconContainers[this.lastSpecies.generation - 1].getAt(genSpecies.indexOf(this.lastSpecies)), this.lastSpecies, true);
}
}
return true;
}
return false;
@ -1293,6 +1515,24 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
}
this.hiddenAbilityIcons[s].setVisible(slotVisible && !!this.scene.gameData.dexData[speciesId].caughtAttr && !!(this.scene.gameData.starterData[speciesId].abilityAttr & 4));
this.classicWinIcons[s].setVisible(slotVisible && this.scene.gameData.starterData[speciesId].classicWinCount > 0);
// 'Candy Icon' mode
if (this.scene.candyUpgradeDisplay === 0) {
if (!starterColors[speciesId]) {
// Default to white if no colors are found
starterColors[speciesId] = [ "ffffff", "ffffff" ];
}
// Set the candy colors
this.candyUpgradeIcon[s].setTint(argbFromRgba(Utils.rgbHexToRgba(starterColors[speciesId][0])));
this.candyUpgradeOverlayIcon[s].setTint(argbFromRgba(Utils.rgbHexToRgba(starterColors[speciesId][1])));
this.setUpgradeIcon(s);
} else if (this.scene.candyUpgradeDisplay === 1) {
this.candyUpgradeIcon[s].setVisible(false);
this.candyUpgradeOverlayIcon[s].setVisible(false);
}
}
} else {
changed = super.setCursor(cursor);
@ -1370,6 +1610,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
lastSpeciesIcon.setTexture(this.lastSpecies.getIconAtlasKey(props.formIndex, props.shiny, props.variant), this.lastSpecies.getIconId(props.female, props.formIndex, props.shiny, props.variant));
this.checkIconId(lastSpeciesIcon, this.lastSpecies, props.female, props.formIndex, props.shiny, props.variant);
this.iconAnimHandler.addOrUpdate(lastSpeciesIcon, PokemonIconAnimMode.NONE);
// Resume the animation for the previously selected species
const speciesIndex = this.genSpecies[this.lastSpecies.generation - 1].indexOf(this.lastSpecies);
const icon = this.starterSelectGenIconContainers[this.lastSpecies.generation - 1].getAt(speciesIndex) as Phaser.GameObjects.Sprite;
this.scene.tweens.getTweensOf(icon).forEach(tween => tween.resume());
}
this.lastSpecies = species;
@ -1434,7 +1679,22 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.pokemonCandyDarknessOverlay.setCrop(0,0,16, candyCropY);
}
this.iconAnimHandler.addOrUpdate(this.starterSelectGenIconContainers[species.generation - 1].getAt(this.genSpecies[species.generation - 1].indexOf(species)) as Phaser.GameObjects.Sprite, PokemonIconAnimMode.PASSIVE);
// Pause the animation when the species is selected
const speciesIndex = this.genSpecies[species.generation - 1].indexOf(species);
const icon = this.starterSelectGenIconContainers[species.generation - 1].getAt(speciesIndex) as Phaser.GameObjects.Sprite;
if (this.isUpgradeAnimationEnabled()) {
this.scene.tweens.getTweensOf(icon).forEach(tween => tween.pause());
// Reset the position of the icon
const position = calcSpritePosition(speciesIndex);
icon.x = position.x;
icon.y = position.y;
}
// Initiates the small up and down idle animation
this.iconAnimHandler.addOrUpdate(icon, PokemonIconAnimMode.PASSIVE);
let starterIndex = -1;

View File

@ -38,7 +38,7 @@ export default class TitleUiHandler extends OptionSelectUiHandler {
this.titleContainer.add(this.dailyRunScoreboard);
this.playerCountLabel = addTextObject(this.scene, (this.scene.game.canvas.width / 6) - 2, (this.scene.game.canvas.height / 6) - 90, `? ${i18next.t("menu:playersOnline")}`, TextStyle.MESSAGE, { fontSize: "54px" });
this.playerCountLabel = addTextObject(this.scene, (this.scene.game.canvas.width / 6) - 2, (this.scene.game.canvas.height / 6) - 109, `? ${i18next.t("menu:playersOnline")}`, TextStyle.MESSAGE, { fontSize: "54px" });
this.playerCountLabel.setOrigin(1, 0);
this.titleContainer.add(this.playerCountLabel);