diff --git a/index.css b/index.css index 8be2653415f..bd0075020c0 100644 --- a/index.css +++ b/index.css @@ -89,11 +89,6 @@ body { line-height: 0.9; } -#apadLabelMenu { - margin-left: 10%; - line-height: 1.1; -} - #apad > :nth-child(2) { position: relative; right: var(--controls-size); @@ -101,12 +96,12 @@ body { #apad .apadRectBtn { position: relative; + text-align: center; + padding-right: 10%; border-radius: 10%; - margin-top: calc(var(--controls-size) * -0.4); bottom: calc(var(--controls-size) * 0.05); - left: calc(var(--controls-size) * 0.21); width: calc(var(--controls-size) * 0.6); - height: calc(var(--controls-size) * 0.4); + height: calc(var(--controls-size) * 0.3); } #apad .apadSqBtn { @@ -118,21 +113,34 @@ body { #apad .apadBtnContainer { position: relative; display: flex; +} + +#apad .apadRectBtnContainer { + flex-wrap: wrap; + margin-top: calc(var(--controls-size) * -0.8); + left: calc(var(--controls-size) * 0.175); + height: calc(var(--controls-size) * 0.8); +} + +#apad .apadSqBtnContainer { flex-wrap: wrap; justify-content: space-evenly; align-items: center; margin-bottom: calc(var(--controls-size) * -0.8); top: calc(var(--controls-size) * -0.9); - left: calc(var(--controls-size) * 0.1); width: calc(var(--controls-size) * 0.8); height: calc(var(--controls-size) * 0.8); } -#touchControls:not([data-ui-mode='STARTER_SELECT']) #apad .apadBtnContainer { +#apad .apadRectBtnContainer > #apadMenu { + align-self: flex-end; +} + +#touchControls:not([data-ui-mode='STARTER_SELECT']) #apad .apadRectBtnContainer > #apadCycleNature, #touchControls:not([data-ui-mode='STARTER_SELECT']) #apad .apadSqBtnContainer { display: none; } -#apad .apadRectBtn + .apadBtnContainer { +#apad .apadRectBtnContainer + .apadSqBtnContainer { top: calc(var(--controls-size) * -1.9); left: calc(var(--controls-size) * -0.9); } diff --git a/index.html b/index.html index db494294697..0667ad151f3 100644 --- a/index.html +++ b/index.html @@ -41,10 +41,15 @@
B
-
- Menu +
+
+ N +
+
+ Menu +
-
+
R
diff --git a/public/images/ui/starter_select_bg.png b/public/images/ui/starter_select_bg.png index 547ccfb7316..870354f1f1c 100644 Binary files a/public/images/ui/starter_select_bg.png and b/public/images/ui/starter_select_bg.png differ diff --git a/src/battle-phases.ts b/src/battle-phases.ts index 4ae1addf7c1..e23f0acd668 100644 --- a/src/battle-phases.ts +++ b/src/battle-phases.ts @@ -25,7 +25,7 @@ import { Gender } from "./data/gender"; import { Weather, WeatherType, getRandomWeatherType, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather"; import { TempBattleStat } from "./data/temp-battle-stat"; import { ArenaTagType, ArenaTrapTag, TrickRoomTag } from "./data/arena-tag"; -import { Abilities, CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, PostAttackAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreWeatherEffectAbAttrs } from "./data/ability"; +import { Abilities, CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, PostAttackAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreWeatherEffectAbAttrs } from "./data/ability"; import { Unlockables, getUnlockableName } from "./system/unlockables"; import { getBiomeKey } from "./arena"; import { BattleType, BattlerIndex, TurnCommand } from "./battle"; @@ -39,6 +39,7 @@ import { vouchers } from "./system/voucher"; import { loggedInUser, updateUserInfo } from "./account"; import { GameDataType } from "./system/game-data"; import { addPokeballCaptureStars, addPokeballOpenParticles } from "./anims"; +import { Nature } from "./data/nature"; export class LoginPhase extends BattlePhase { private showText: boolean; @@ -228,7 +229,7 @@ export class SelectStarterPhase extends BattlePhase { ? !starterProps.female ? Gender.MALE : Gender.FEMALE : Gender.GENDERLESS; const starterIvs = this.scene.gameData.dexData[starter.species.speciesId].ivs.slice(0); - const starterPokemon = new PlayerPokemon(this.scene, starter.species, startingLevel, starterProps.abilityIndex, starterProps.formIndex, starterGender, starterProps.shiny, starterIvs); + const starterPokemon = new PlayerPokemon(this.scene, starter.species, startingLevel, starterProps.abilityIndex, starterProps.formIndex, starterGender, starterProps.shiny, starterIvs, starter.nature); if (starter.pokerus) starterPokemon.pokerus = true; if (this.scene.gameMode === GameMode.SPLICED_ENDLESS) @@ -372,6 +373,9 @@ export class EncounterPhase extends BattlePhase { else { const enemySpecies = this.scene.randomSpecies(battle.waveIndex, level, true); battle.enemyParty[e] = new EnemyPokemon(this.scene, enemySpecies, level, false); + this.scene.getParty().slice(0, !battle.double ? 1 : 2).reverse().forEach(playerPokemon => { + applyAbAttrs(SyncEncounterNatureAbAttr, playerPokemon, null, battle.enemyParty[e]); + }); } } const enemyPokemon = this.scene.getEnemyParty()[e]; diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 5d6fde410a6..7f22d400a93 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -63,6 +63,7 @@ export enum Button { CYCLE_FORM, CYCLE_GENDER, CYCLE_ABILITY, + CYCLE_NATURE, QUICK_START, AUTO, SPEED_UP, @@ -575,6 +576,7 @@ export default class BattleScene extends Phaser.Scene { [Button.CYCLE_FORM]: [keyCodes.F], [Button.CYCLE_GENDER]: [keyCodes.G], [Button.CYCLE_ABILITY]: [keyCodes.E], + [Button.CYCLE_NATURE]: [keyCodes.N], [Button.QUICK_START]: [keyCodes.Q], [Button.AUTO]: [keyCodes.F2], [Button.SPEED_UP]: [keyCodes.PLUS], @@ -1032,6 +1034,8 @@ export default class BattleScene extends Phaser.Scene { inputSuccess = this.ui.processInput(Button.CYCLE_GENDER); else if (this.isButtonPressed(Button.CYCLE_ABILITY)) inputSuccess = this.ui.processInput(Button.CYCLE_ABILITY); + else if (this.isButtonPressed(Button.CYCLE_NATURE)) + inputSuccess = this.ui.processInput(Button.CYCLE_NATURE); else return; } else if (this.isButtonPressed(Button.SPEED_UP)) { diff --git a/src/data/ability.ts b/src/data/ability.ts index 265297093f0..463a1ffced2 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -1005,6 +1005,18 @@ export class WeightMultiplierAbAttr extends AbAttr { } } +export class SyncEncounterNatureAbAttr extends AbAttr { + constructor() { + super(false); + } + + apply(pokemon: Pokemon, cancelled: Utils.BooleanHolder, args: any[]): boolean { + (args[0] as Pokemon).setNature(pokemon.nature); + + return true; + } +} + function applyAbAttrsInternal(attrType: { new(...args: any[]): TAttr }, pokemon: Pokemon, applyFunc: AbAttrApplyFunc, isAsync?: boolean, showAbilityInstant?: boolean): Promise { return new Promise(resolve => { @@ -1505,7 +1517,8 @@ export function initAbilities() { .attr(TypeImmunityAbAttr, Type.GROUND, (pokemon: Pokemon) => !pokemon.getTag(BattlerTagType.IGNORE_FLYING) && !pokemon.scene.arena.getTag(ArenaTagType.GRAVITY)), new Ability(Abilities.EFFECT_SPORE, "Effect Spore", "Contact with the Pokémon may inflict poison, sleep, or paralysis on its attacker.", 3) .attr(PostDefendContactApplyStatusEffectAbAttr, 10, StatusEffect.POISON, StatusEffect.PARALYSIS, StatusEffect.SLEEP), - new Ability(Abilities.SYNCHRONIZE, "Synchronize (N)", "The attacker will receive the same status condition if it inflicts a burn, poison, or paralysis to the Pokémon.", 3), + new Ability(Abilities.SYNCHRONIZE, "Synchronize (N)", "The attacker will receive the same status condition if it inflicts a burn, poison, or paralysis to the Pokémon.", 3) + .attr(SyncEncounterNatureAbAttr), new Ability(Abilities.CLEAR_BODY, "Clear Body", "Prevents other Pokémon's moves or Abilities from lowering the Pokémon's stats.", 3) .attr(ProtectStatAbAttr), new Ability(Abilities.NATURAL_CURE, "Natural Cure (N)", "All status conditions heal when the Pokémon switches out.", 3), diff --git a/src/data/nature.ts b/src/data/nature.ts new file mode 100644 index 00000000000..27f6361f55d --- /dev/null +++ b/src/data/nature.ts @@ -0,0 +1,130 @@ +import { Stat, getStatName } from "./pokemon-stat"; +import * as Utils from "../utils"; +import { TextStyle, getBBCodeFrag } from "../ui/text"; + +export enum Nature { + HARDY, + LONELY, + BRAVE, + ADAMANT, + NAUGHTY, + BOLD, + DOCILE, + RELAXED, + IMPISH, + LAX, + TIMID, + HASTY, + SERIOUS, + JOLLY, + NAIVE, + MODEST, + MILD, + QUIET, + BASHFUL, + RASH, + CALM, + GENTLE, + SASSY, + CAREFUL, + QUIRKY +} + +export function getNatureName(nature: Nature, includeStatEffects: boolean = false, forStarterSelect: boolean = false): string { + let ret = Utils.toReadableString(Nature[nature]); + if (includeStatEffects) { + const stats = Utils.getEnumValues(Stat).slice(1); + let increasedStat: Stat = null; + let decreasedStat: Stat = null; + for (let stat of stats) { + const multiplier = getNatureStatMultiplier(nature, stat); + if (multiplier > 1) + increasedStat = stat; + else + decreasedStat = stat; + } + const textStyle = forStarterSelect ? TextStyle.SUMMARY : TextStyle.WINDOW; + if (increasedStat && decreasedStat) + ret = `${getBBCodeFrag(`${ret}${!forStarterSelect ? '\n' : ' '}(`, textStyle)}${getBBCodeFrag(`+${getStatName(increasedStat, true)}`, TextStyle.SUMMARY_BLUE)}${getBBCodeFrag('/', textStyle)}${getBBCodeFrag(`-${getStatName(decreasedStat, true)}`, TextStyle.SUMMARY_PINK)}${getBBCodeFrag(')', textStyle)}`; + else + ret = getBBCodeFrag(`${ret}${!forStarterSelect ? '\n' : ' '}(-)`, textStyle); + } + return ret; +} + +export function getNatureStatMultiplier(nature: Nature, stat: Stat): number { + switch (stat) { + case Stat.ATK: + switch (nature) { + case Nature.LONELY: + case Nature.BRAVE: + case Nature.ADAMANT: + case Nature.NAUGHTY: + return 1.1; + case Nature.BOLD: + case Nature.TIMID: + case Nature.MODEST: + case Nature.CALM: + return 0.9; + } + break; + case Stat.DEF: + switch (nature) { + case Nature.BOLD: + case Nature.RELAXED: + case Nature.IMPISH: + case Nature.LAX: + return 1.1; + case Nature.LONELY: + case Nature.HASTY: + case Nature.MILD: + case Nature.GENTLE: + return 0.9; + } + break; + case Stat.SPATK: + switch (nature) { + case Nature.MODEST: + case Nature.MILD: + case Nature.QUIET: + case Nature.RASH: + return 1.1; + case Nature.ADAMANT: + case Nature.IMPISH: + case Nature.JOLLY: + case Nature.CAREFUL: + return 0.9; + } + break; + case Stat.SPDEF: + switch (nature) { + case Nature.CALM: + case Nature.GENTLE: + case Nature.SASSY: + case Nature.CAREFUL: + return 1.1; + case Nature.NAUGHTY: + case Nature.LAX: + case Nature.NAIVE: + case Nature.RASH: + return 0.9; + } + break; + case Stat.SPD: + switch (nature) { + case Nature.TIMID: + case Nature.HASTY: + case Nature.JOLLY: + case Nature.NAIVE: + return 1.1; + case Nature.BRAVE: + case Nature.RELAXED: + case Nature.QUIET: + case Nature.SASSY: + return 0.9; + } + break; + } + + return 1; +} \ No newline at end of file diff --git a/src/data/pokemon-stat.ts b/src/data/pokemon-stat.ts index d7ca92b43c7..228d25a74ea 100644 --- a/src/data/pokemon-stat.ts +++ b/src/data/pokemon-stat.ts @@ -7,26 +7,26 @@ export enum Stat { SPD }; -export function getStatName(stat: Stat) { +export function getStatName(stat: Stat, shorten: boolean = false) { let ret: string; switch (stat) { case Stat.HP: - ret = 'Max. HP'; + ret = !shorten ? 'Max. HP' : 'MaxHP'; break; case Stat.ATK: - ret = 'Attack'; + ret = !shorten ? 'Attack' : 'Atk'; break; case Stat.DEF: - ret = 'Defense'; + ret = !shorten ? 'Defense' : 'Def'; break; case Stat.SPATK: - ret = 'Sp. Atk'; + ret = !shorten ? 'Sp. Atk' : 'SpAtk'; break; case Stat.SPDEF: - ret = 'Sp. Def'; + ret = !shorten ? 'Sp. Def' : 'SpDef'; break; case Stat.SPD: - ret = 'Speed'; + ret = !shorten ? 'Speed' : 'Spd'; break; } return ret; diff --git a/src/egg-hatch-phase.ts b/src/egg-hatch-phase.ts index bd163ad1339..cbd9e100d90 100644 --- a/src/egg-hatch-phase.ts +++ b/src/egg-hatch-phase.ts @@ -7,13 +7,14 @@ import { EGG_SEED, Egg, GachaType, getLegendaryGachaSpeciesForTimestamp, getType import EggHatchSceneHandler from "./ui/egg-hatch-scene-handler"; import { ModifierTier } from "./modifier/modifier-type"; import { Species } from "./data/species"; -import Pokemon, { PlayerPokemon } from "./pokemon"; +import { PlayerPokemon } from "./pokemon"; import { getPokemonSpecies, speciesStarters } from "./data/pokemon-species"; import { StatsContainer } from "./ui/stats-container"; -import { TextStyle, addTextObject } from "./ui/text"; +import { TextStyle, addBBCodeTextObject, addTextObject } from "./ui/text"; import { Gender, getGenderColor, getGenderSymbol } from "./data/gender"; import { achvs } from "./system/achv"; import { addWindow } from "./ui/window"; +import { getNatureName } from "./data/nature"; export class EggHatchPhase extends BattlePhase { private egg: Egg; @@ -91,55 +92,58 @@ export class EggHatchPhase extends BattlePhase { this.eggHatchOverlay.setAlpha(0); this.scene.fieldUI.add(this.eggHatchOverlay); - const infoBg = addWindow(this.scene, 0, 0, 96, 116); + const infoBg = addWindow(this.scene, 0, 0, 104, 132); infoBg.setOrigin(0.5, 0.5); this.infoContainer = this.scene.add.container(this.eggHatchBg.displayWidth + infoBg.width / 2, this.eggHatchBg.displayHeight / 2); - this.statsContainer = new StatsContainer(this.scene, -48, -54, true); + this.statsContainer = new StatsContainer(this.scene, -48, -64, true); this.infoContainer.add(infoBg); this.infoContainer.add(this.statsContainer); - const pokemonGenderLabelText = addTextObject(this.scene, -16, 32, 'Gender:', TextStyle.WINDOW, { fontSize: '64px' }); + const pokemonGenderLabelText = addTextObject(this.scene, -18, 20, 'Gender:', TextStyle.WINDOW, { fontSize: '64px' }); pokemonGenderLabelText.setOrigin(1, 0); pokemonGenderLabelText.setVisible(false); this.infoContainer.add(pokemonGenderLabelText); - const pokemonGenderText = addTextObject(this.scene, -12, 32, '', TextStyle.WINDOW, { fontSize: '64px' }); + const pokemonGenderText = addTextObject(this.scene, -14, 20, '', TextStyle.WINDOW, { fontSize: '64px' }); pokemonGenderText.setOrigin(0, 0); pokemonGenderText.setVisible(false); this.infoContainer.add(pokemonGenderText); - const pokemonAbilityLabelText = addTextObject(this.scene, -16, 32, 'Ability:', TextStyle.WINDOW, { fontSize: '64px' }); + const pokemonAbilityLabelText = addTextObject(this.scene, -18, 20, 'Ability:', TextStyle.WINDOW, { fontSize: '64px' }); pokemonAbilityLabelText.setOrigin(1, 0); this.infoContainer.add(pokemonAbilityLabelText); - const pokemonAbilityText = addTextObject(this.scene, -12, 32, '', TextStyle.WINDOW, { fontSize: '64px' }); + const pokemonAbilityText = addTextObject(this.scene, -14, 20, '', TextStyle.WINDOW, { fontSize: '64px' }); pokemonAbilityText.setOrigin(0, 0); this.infoContainer.add(pokemonAbilityText); + const pokemonNatureLabelText = addTextObject(this.scene, -18, 30, 'Nature:', TextStyle.WINDOW, { fontSize: '64px' }); + pokemonNatureLabelText.setOrigin(1, 0); + this.infoContainer.add(pokemonNatureLabelText); + + const pokemonNatureText = addBBCodeTextObject(this.scene, -14, 30, '', TextStyle.WINDOW, { fontSize: '64px', lineSpacing: 3, maxLines: 2 }); + pokemonNatureText.setOrigin(0, 0); + this.infoContainer.add(pokemonNatureText); + this.eggHatchContainer.add(this.infoContainer); const pokemon = this.generatePokemon(); if (pokemon.fusionSpecies) pokemon.clearFusionSpecies(); - let abilityYOffset = 5; - if (pokemon.gender > Gender.GENDERLESS) { pokemonGenderText.setText(getGenderSymbol(pokemon.gender)); pokemonGenderText.setColor(getGenderColor(pokemon.gender)); pokemonGenderText.setShadowColor(getGenderColor(pokemon.gender, true)); pokemonGenderLabelText.setVisible(true); pokemonGenderText.setVisible(true); - - abilityYOffset = 10; } - [ pokemonAbilityLabelText, pokemonAbilityText ].map(t => t.y += abilityYOffset); - pokemonAbilityText.setText(pokemon.getAbility().name); + pokemonNatureText.setText(getNatureName(pokemon.nature, true)); const originalIvs: integer[] = this.scene.gameData.dexData[pokemon.species.speciesId].caughtAttr ? this.scene.gameData.dexData[pokemon.species.speciesId].ivs @@ -205,7 +209,7 @@ export class EggHatchPhase extends BattlePhase { targets: this.infoContainer, duration: Utils.fixedInt(750), ease: 'Cubic.easeInOut', - x: this.eggHatchBg.displayWidth - 48 + x: this.eggHatchBg.displayWidth - 52 }); this.scene.playSoundWithoutBgm('evolution_fanfare'); diff --git a/src/pokemon.ts b/src/pokemon.ts index 4397e54abfc..a824d1baf51 100644 --- a/src/pokemon.ts +++ b/src/pokemon.ts @@ -33,6 +33,7 @@ import { LevelMoves } from './data/pokemon-level-moves'; import { DamageAchv, achvs } from './system/achv'; import { DexAttr } from './system/game-data'; import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from '@material/material-color-utilities'; +import { Nature, getNatureStatMultiplier } from './data/nature'; export enum FieldPosition { CENTER, @@ -62,6 +63,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public hp: integer; public stats: integer[]; public ivs: integer[]; + public nature: Nature; public moveset: PokemonMove[]; public status: Status; public friendship: integer; @@ -88,7 +90,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, ivs?: integer[], dataSource?: Pokemon | PokemonData) { + constructor(scene: BattleScene, x: number, y: number, species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData) { super(scene, x, y); if (!species.isObtainable() && this.isPlayer()) @@ -123,6 +125,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.hp = dataSource.hp; this.stats = dataSource.stats; this.ivs = dataSource.ivs; + this.nature = dataSource.nature || 0 as Nature; this.moveset = dataSource.moveset; this.status = dataSource.status; this.friendship = dataSource.friendship !== undefined ? dataSource.friendship : this.species.baseFriendship; @@ -145,6 +148,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { Utils.binToDec(Utils.decToBin(this.id).substring(20, 25)), Utils.binToDec(Utils.decToBin(this.id).substring(25, 30)) ]; + + this.nature = nature !== undefined + ? nature + : Utils.randSeedInt(25) as Nature; if (this.gender === undefined) { if (this.species.malePercent === null) @@ -519,12 +526,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (lastMaxHp && value > lastMaxHp) this.hp += value - lastMaxHp; } - } else - value = Math.min(value + 5, 99999); + } else { + value += 5; + const natureStatMultiplier = getNatureStatMultiplier(this.nature, s); + if (natureStatMultiplier !== 1) + value = Math[natureStatMultiplier > 1 ? 'ceil' : 'floor'](value * natureStatMultiplier); + value = Math.min(value, 99999); + } this.stats[s] = value; } } + setNature(nature: Nature): void { + this.nature = nature; + this.calculateStats(); + } + getMaxHp(): integer { return this.getStat(Stat.HP); } @@ -1725,8 +1742,8 @@ export default interface Pokemon { export class PlayerPokemon extends Pokemon { public compatibleTms: Moves[]; - constructor(scene: BattleScene, species: PokemonSpecies, level: integer, abilityIndex: integer, formIndex: integer, gender?: Gender, shiny?: boolean, ivs?: integer[], dataSource?: Pokemon | PokemonData) { - super(scene, 106, 148, species, level, abilityIndex, formIndex, gender, shiny, ivs, dataSource); + constructor(scene: BattleScene, species: PokemonSpecies, level: integer, abilityIndex: integer, formIndex: integer, gender?: Gender, shiny?: boolean, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData) { + super(scene, 106, 148, species, level, abilityIndex, formIndex, gender, shiny, ivs, nature, dataSource); this.generateCompatibleTms(); } @@ -1791,7 +1808,7 @@ export class PlayerPokemon extends Pokemon { return new Promise(resolve => { const species = getPokemonSpecies(evolution.speciesId); const formIndex = Math.max(this.species.forms.findIndex(f => f.formKey === evolution.evoFormKey), 0); - const ret = new PlayerPokemon(this.scene, species, this.level, this.abilityIndex, formIndex, this.gender, this.shiny, this.ivs, this); + const ret = new PlayerPokemon(this.scene, species, this.level, this.abilityIndex, formIndex, this.gender, this.shiny, this.ivs, this.nature, this); ret.loadAssets().then(() => resolve(ret)); }); } @@ -1822,7 +1839,7 @@ export class PlayerPokemon extends Pokemon { if (this.species.speciesId === Species.NINCADA && evolution.speciesId === Species.NINJASK) { const newEvolution = pokemonEvolutions[this.species.speciesId][1]; if (newEvolution.condition.predicate(this)) { - const newPokemon = new PlayerPokemon(this.scene, this.species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny); + const newPokemon = new PlayerPokemon(this.scene, this.species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this.ivs, this.nature); this.scene.getParty().push(newPokemon); newPokemon.evolve(newEvolution); const modifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier @@ -1897,7 +1914,7 @@ export class EnemyPokemon extends Pokemon { constructor(scene: BattleScene, species: PokemonSpecies, level: integer, trainer: boolean, dataSource?: PokemonData) { super(scene, 236, 84, species, level, dataSource?.abilityIndex, dataSource?.formIndex, - dataSource?.gender, dataSource ? dataSource.shiny : false, null, dataSource); + dataSource?.gender, dataSource ? dataSource.shiny : false, null, dataSource ? dataSource.nature : undefined, dataSource); this.trainer = trainer; @@ -2096,7 +2113,7 @@ export class EnemyPokemon extends Pokemon { this.pokeball = pokeballType; this.metLevel = this.level; this.metBiome = this.scene.arena.biomeType; - const newPokemon = new PlayerPokemon(this.scene, this.species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, null, this); + const newPokemon = new PlayerPokemon(this.scene, this.species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this.ivs, this.nature, this); party.push(newPokemon); ret = newPokemon; } diff --git a/src/system/game-data.ts b/src/system/game-data.ts index ca9b8d456d3..13485b17f3f 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -20,6 +20,7 @@ import { VoucherType, vouchers } from "./voucher"; import { AES, enc } from "crypto-js"; import { Mode } from "../ui/ui"; import { loggedInUser, updateUserInfo } from "../account"; +import { Nature } from "../data/nature"; const saveKey = 'x0i2O7WRiANTqPmZ'; // Temporary; secure encryption is not yet necessary @@ -94,6 +95,7 @@ export interface DexData { export interface DexEntry { seenAttr: bigint; caughtAttr: bigint; + natureAttr: integer, seenCount: integer; caughtCount: integer; hatchedCount: integer; @@ -121,6 +123,7 @@ export interface DexAttrProps { const systemShortKeys = { seenAttr: '$sa', caughtAttr: '$ca', + natureAttr: '$na', seenCount: '$s' , caughtCount: '$c', ivs: '$i' @@ -133,6 +136,7 @@ export class GameData { public secretId: integer; public dexData: DexData; + private defaultDexData: DexData; public unlocks: Unlocks; @@ -261,6 +265,7 @@ export class GameData { this.dexData = Object.assign(this.dexData, systemData.dexData); this.consolidateDexData(this.dexData); + this.defaultDexData = null; resolve(true); } @@ -292,7 +297,7 @@ export class GameData { return ret; } - return k.endsWith('Attr') ? BigInt(v) : v; + return k.endsWith('Attr') && k !== 'natureAttr' ? BigInt(v) : v; }) as SystemSaveData; } @@ -657,7 +662,7 @@ export class GameData { for (let species of allSpecies) { data[species.speciesId] = { - seenAttr: 0n, caughtAttr: 0n, seenCount: 0, caughtCount: 0, hatchedCount: 0, ivs: [ 0, 0, 0, 0, 0, 0 ] + seenAttr: 0n, caughtAttr: 0n, natureAttr: 0, seenCount: 0, caughtCount: 0, hatchedCount: 0, ivs: [ 0, 0, 0, 0, 0, 0 ] }; } @@ -675,14 +680,24 @@ export class GameData { const defaultStarterAttr = DexAttr.NON_SHINY | DexAttr.MALE | DexAttr.ABILITY_1 | DexAttr.DEFAULT_FORM; - for (let ds of defaultStarters) { - let entry = data[ds] as DexEntry; + const defaultStarterNatures: Nature[] = []; + + this.scene.executeWithSeedOffset(() => { + const neutralNatures = [ Nature.HARDY, Nature.DOCILE, Nature.SERIOUS, Nature.BASHFUL, Nature.QUIRKY ]; + for (let s = 0; s < defaultStarters.length; s++) + defaultStarterNatures.push(Phaser.Math.RND.pick(neutralNatures)); + }, 0, 'default'); + + for (let ds = 0; ds < defaultStarters.length; ds++) { + let entry = data[defaultStarters[ds]] as DexEntry; entry.seenAttr = defaultStarterAttr; entry.caughtAttr = defaultStarterAttr; + entry.natureAttr = Math.pow(2, defaultStarterNatures[ds] + 1); for (let i in entry.ivs) entry.ivs[i] = 10; } + this.defaultDexData = Object.assign({}, data); this.dexData = data; } @@ -702,6 +717,7 @@ export class GameData { const dexEntry = this.dexData[species.speciesId]; const caughtAttr = dexEntry.caughtAttr; dexEntry.caughtAttr |= pokemon.getDexAttr(); + dexEntry.natureAttr |= Math.pow(2, pokemon.nature + 1); if (incrementCount) { if (!fromEgg) dexEntry.caughtCount++; @@ -767,6 +783,28 @@ export class GameData { }; } + getSpeciesDefaultNature(species: PokemonSpecies): Nature { + const dexEntry = this.dexData[species.speciesId]; + for (let n = 0; n < 25; n++) { + if (dexEntry.natureAttr & Math.pow(2, n + 1)) + return n as Nature; + } + return 0 as Nature; + } + + getSpeciesDefaultNatureAttr(species: PokemonSpecies): integer { + return Math.pow(2, this.getSpeciesDefaultNature(species)); + } + + getNaturesForAttr(natureAttr: integer): Nature[] { + let ret: Nature[] = []; + for (let n = 0; n < 25; n++) { + if (natureAttr & Math.pow(2, n + 1)) + ret.push(n); + } + return ret; + } + getFormIndex(attr: bigint): integer { if (!attr || attr < DexAttr.DEFAULT_FORM) return 0; @@ -785,6 +823,8 @@ export class GameData { const entry = dexData[k] as DexEntry; if (!entry.hasOwnProperty('hatchedCount')) entry.hatchedCount = 0; + if (!entry.hasOwnProperty('natureAttr') || (entry.caughtAttr && !entry.natureAttr)) + entry.natureAttr = this.defaultDexData[k].natureAttr || Math.pow(2, Utils.randInt(25, 1)); } } } \ No newline at end of file diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index 9e8ca0db31f..02fbf286681 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -2,6 +2,7 @@ import { BattleType } from "../battle"; import BattleScene from "../battle-scene"; import { Biome } from "../data/biome"; import { Gender } from "../data/gender"; +import { Nature } from "../data/nature"; import { PokeballType } from "../data/pokeball"; import { getPokemonSpecies } from "../data/pokemon-species"; import { Species } from "../data/species"; @@ -23,6 +24,7 @@ export default class PokemonData { public hp: integer; public stats: integer[]; public ivs: integer[]; + public nature: Nature; public moveset: PokemonMove[]; public status: Status; public friendship: integer; @@ -55,6 +57,7 @@ export default class PokemonData { this.hp = source.hp; this.stats = source.stats; this.ivs = source.ivs; + this.nature = source.nature !== undefined ? source.nature : 0 as Nature; this.friendship = source.friendship !== undefined ? source.friendship : getPokemonSpecies(this.species).baseFriendship; this.metLevel = source.metLevel || 5; this.metBiome = source.metBiome !== undefined ? source.metBiome : -1; @@ -92,7 +95,7 @@ export default class PokemonData { toPokemon(scene: BattleScene, battleType?: BattleType): Pokemon { const species = getPokemonSpecies(this.species); if (this.player) - return new PlayerPokemon(scene, species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, null, this); + return new PlayerPokemon(scene, species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this.ivs, this.nature, this); return new EnemyPokemon(scene, species, this.level, battleType === BattleType.TRAINER, this); } } \ No newline at end of file diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 934e3cebf2e..edb1fd09a41 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -1,7 +1,7 @@ import BattleScene, { Button } from "../battle-scene"; import PokemonSpecies, { SpeciesFormKey, allSpecies, getPokemonSpecies, speciesStarters as speciesStarterValues } from "../data/pokemon-species"; import { Species } from "../data/species"; -import { TextStyle, addTextObject, getTextColor } from "./text"; +import { TextStyle, addBBCodeTextObject, addTextObject, getTextColor } from "./text"; import { Mode } from "./ui"; import MessageUiHandler from "./message-ui-handler"; import { Gender, getGenderColor, getGenderSymbol } from "../data/gender"; @@ -14,12 +14,15 @@ import * as Utils from "../utils"; import PokemonIconAnimHandler, { PokemonIconAnimMode } from "./pokemon-icon-anim-handler"; import { StatsContainer } from "./stats-container"; import { addWindow } from "./window"; +import { Nature, getNatureName } from "../data/nature"; +import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; export type StarterSelectCallback = (starters: Starter[]) => void; export interface Starter { species: PokemonSpecies; dexAttr: bigint; + nature: Nature; pokerus: boolean; } @@ -36,6 +39,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { private pokemonGenderText: Phaser.GameObjects.Text; private pokemonAbilityLabelText: Phaser.GameObjects.Text; private pokemonAbilityText: Phaser.GameObjects.Text; + private pokemonNatureLabelText: Phaser.GameObjects.Text; + private pokemonNatureText: BBCodeText; private genOptionsText: Phaser.GameObjects.Text; private instructionsText: Phaser.GameObjects.Text; private starterSelectMessageBoxContainer: Phaser.GameObjects.Container; @@ -44,6 +49,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { private genMode: boolean; private statsMode: boolean; private dexAttrCursor: bigint = 0n; + private natureCursor: integer = 0; private genCursor: integer = 0; private genScrollCursor: integer = 0; @@ -55,11 +61,13 @@ export default class StarterSelectUiHandler extends MessageUiHandler { private pokerusGens: integer[] = []; private pokerusCursors: integer[] = []; private starterAttr: bigint[] = []; + private starterNatures: Nature[] = []; private speciesStarterDexEntry: DexEntry; private canCycleShiny: boolean; private canCycleForm: boolean; private canCycleGender: boolean; private canCycleAbility: boolean; + private canCycleNature: boolean; private value: integer = 0; private assetLoadCancelled: Utils.BooleanHolder; @@ -131,10 +139,19 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.pokemonAbilityLabelText.setVisible(false); this.starterSelectContainer.add(this.pokemonAbilityLabelText); - this.pokemonAbilityText = addTextObject(this.scene, 38, 126, '', TextStyle.SUMMARY, { fontSize: '64px' }); + this.pokemonAbilityText = addTextObject(this.scene, 34, 126, '', TextStyle.SUMMARY, { fontSize: '64px' }); this.pokemonAbilityText.setOrigin(0, 0); this.starterSelectContainer.add(this.pokemonAbilityText); + this.pokemonNatureLabelText = addTextObject(this.scene, 6, 135, 'Nature:', TextStyle.SUMMARY, { fontSize: '64px' }); + this.pokemonNatureLabelText.setOrigin(0, 0); + this.pokemonNatureLabelText.setVisible(false); + this.starterSelectContainer.add(this.pokemonNatureLabelText); + + this.pokemonNatureText = addBBCodeTextObject(this.scene, 34, 135, '', TextStyle.SUMMARY, { fontSize: '64px' }); + this.pokemonNatureText.setOrigin(0, 0); + this.starterSelectContainer.add(this.pokemonNatureText); + this.genOptionsText = addTextObject(this.scene, 124, 7, '', TextStyle.WINDOW, { fontSize: 72, lineSpacing: 39, align: 'center' }); this.genOptionsText.setShadowOffset(4.5, 4.5); this.genOptionsText.setOrigin(0.5, 0); @@ -254,7 +271,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.pokemonSprite = this.scene.add.sprite(53, 63, `pkmn__sub`); this.starterSelectContainer.add(this.pokemonSprite); - this.instructionsText = addTextObject(this.scene, 4, 140, '', TextStyle.PARTY, { fontSize: '42px' }); + this.instructionsText = addTextObject(this.scene, 4, 148, '', TextStyle.PARTY, { fontSize: '42px' }); this.starterSelectContainer.add(this.instructionsText); this.starterSelectMessageBoxContainer = this.scene.add.container(0, this.scene.game.canvas.height / 6); @@ -422,6 +439,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.starterGens.push(this.getGenCursorWithScroll()); this.starterCursors.push(this.cursor); this.starterAttr.push(this.dexAttrCursor); + this.starterNatures.push(this.natureCursor as unknown as Nature); if (this.speciesLoaded.get(species.speciesId)) species.cry(this.scene); if (this.starterCursors.length === 6 || this.value === 10) @@ -453,7 +471,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { switch (button) { case Button.CYCLE_SHINY: if (this.canCycleShiny) { - this.setSpeciesDetails(this.lastSpecies, !props.shiny, undefined, undefined, undefined); + this.setSpeciesDetails(this.lastSpecies, !props.shiny, undefined, undefined, undefined, undefined); if (this.dexAttrCursor & DexAttr.SHINY) this.scene.playSound('sparkle'); else @@ -469,13 +487,13 @@ export default class StarterSelectUiHandler extends MessageUiHandler { if (this.speciesStarterDexEntry.caughtAttr & this.scene.gameData.getFormAttr(newFormIndex)) break; } while (newFormIndex !== props.formIndex); - this.setSpeciesDetails(this.lastSpecies, undefined, newFormIndex, undefined, undefined); + this.setSpeciesDetails(this.lastSpecies, undefined, newFormIndex, undefined, undefined, undefined); success = true; } break; case Button.CYCLE_GENDER: if (this.canCycleGender) { - this.setSpeciesDetails(this.lastSpecies, undefined, undefined, !props.female, undefined); + this.setSpeciesDetails(this.lastSpecies, undefined, undefined, !props.female, undefined, undefined); success = true; } break; @@ -496,7 +514,16 @@ export default class StarterSelectUiHandler extends MessageUiHandler { break; } } while (newAbilityIndex !== props.abilityIndex); - this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, newAbilityIndex); + this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, newAbilityIndex, undefined); + success = true; + } + break; + case Button.CYCLE_NATURE: + if (this.canCycleNature) { + const natures = this.scene.gameData.getNaturesForAttr(this.speciesStarterDexEntry.natureAttr); + const natureIndex = natures.indexOf(this.natureCursor); + const newNature = natures[natureIndex < natures.length - 1 ? natureIndex + 1 : 0]; + this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, newNature); success = true; } break; @@ -534,14 +561,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } updateInstructions(): void { - let instructionLines = [ - 'Arrow Keys/WASD: Move' - ]; + let instructionLines = [ ]; let cycleInstructionLines = []; - if (!this.genMode) - instructionLines.push('A/Space/Enter: Select'); - if (this.starterCursors.length) - instructionLines.push('X/Backspace/Esc: Undo'); if (this.speciesStarterDexEntry?.caughtAttr) { if (this.canCycleShiny) cycleInstructionLines.push('R: Cycle Shiny'); @@ -551,6 +572,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { cycleInstructionLines.push('G: Cycle Gender'); if (this.canCycleAbility) cycleInstructionLines.push('E: Cycle Ability'); + if (this.canCycleNature) + cycleInstructionLines.push('N: Cycle Nature'); } if (cycleInstructionLines.length > 2) { @@ -656,6 +679,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { setSpecies(species: PokemonSpecies) { this.speciesStarterDexEntry = species ? this.scene.gameData.dexData[species.speciesId] : null; this.dexAttrCursor = species ? this.scene.gameData.getSpeciesDefaultDexAttr(species) : 0n; + this.natureCursor = species ? this.scene.gameData.getSpeciesDefaultNature(species) : 0; if (this.statsMode) { if (this.speciesStarterDexEntry?.caughtAttr) { @@ -685,24 +709,43 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.pokemonGrowthRateText.setShadowColor(getGrowthRateColor(species.growthRate, true)); this.pokemonGrowthRateLabelText.setVisible(true); this.pokemonAbilityLabelText.setVisible(true); + this.pokemonNatureLabelText.setVisible(true); this.iconAnimHandler.addOrUpdate(this.starterSelectGenIconContainers[species.generation - 1].getAt(this.genSpecies[species.generation - 1].indexOf(species)) as Phaser.GameObjects.Sprite, PokemonIconAnimMode.PASSIVE); - const defaultDexAttr = this.scene.gameData.getSpeciesDefaultDexAttr(species); - const props = this.scene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); - - this.setSpeciesDetails(species, props.shiny, props.formIndex, props.female, props.abilityIndex); + let starterIndex = -1; + + this.starterGens.every((g, i) => { + const starterSpecies = this.genSpecies[g][this.starterCursors[i]]; + if (starterSpecies.speciesId === species.speciesId) { + starterIndex = i; + return false; + } + return true; + }); + + if (starterIndex > -1) { + const props = this.scene.gameData.getSpeciesDexAttrProps(species, this.starterAttr[starterIndex]); + this.setSpeciesDetails(species, props.shiny, props.formIndex, props.female, props.abilityIndex, this.starterNatures[starterIndex]); + } else { + const defaultDexAttr = this.scene.gameData.getSpeciesDefaultDexAttr(species); + const defaultNature = this.scene.gameData.getSpeciesDefaultNature(species); + const props = this.scene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); + + this.setSpeciesDetails(species, props.shiny, props.formIndex, props.female, props.abilityIndex, defaultNature); + } } else { this.pokemonNumberText.setText(Utils.padInt(0, 3)); this.pokemonNameText.setText(species ? '???' : ''); this.pokemonGrowthRateText.setText(''); this.pokemonGrowthRateLabelText.setVisible(false); this.pokemonAbilityLabelText.setVisible(false); + this.pokemonNatureLabelText.setVisible(false); - this.setSpeciesDetails(species, false, 0, false, 0); + this.setSpeciesDetails(species, false, 0, false, 0, 0); } } - setSpeciesDetails(species: PokemonSpecies, shiny: boolean, formIndex: integer, female: boolean, abilityIndex: integer): void { + setSpeciesDetails(species: PokemonSpecies, shiny: boolean, formIndex: integer, female: boolean, abilityIndex: integer, natureIndex: integer): void { const oldProps = species ? this.scene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor) : null; this.dexAttrCursor = 0n; @@ -711,8 +754,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.dexAttrCursor |= (female !== undefined ? !female : !(female = oldProps.female)) ? DexAttr.MALE : DexAttr.FEMALE; this.dexAttrCursor |= (abilityIndex !== undefined ? !abilityIndex : !(abilityIndex = oldProps.abilityIndex)) ? DexAttr.ABILITY_1 : species.ability2 && abilityIndex === 1 ? DexAttr.ABILITY_2 : DexAttr.ABILITY_HIDDEN; this.dexAttrCursor |= this.scene.gameData.getFormAttr(formIndex !== undefined ? formIndex : (formIndex = oldProps.formIndex)); + if (natureIndex === undefined) + natureIndex = this.scene.gameData.getSpeciesDefaultNature(species); } + this.natureCursor = natureIndex; + this.pokemonSprite.setVisible(false); if (this.assetLoadCancelled) { @@ -724,6 +771,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const dexEntry = this.scene.gameData.dexData[species.speciesId]; if (!dexEntry.caughtAttr) { const props = this.scene.gameData.getSpeciesDexAttrProps(species, this.scene.gameData.getSpeciesDefaultDexAttr(species)); + const defaultNature = this.scene.gameData.getSpeciesDefaultNature(species); if (shiny === undefined || shiny !== props.shiny) shiny = props.shiny; if (formIndex === undefined || formIndex !== props.formIndex) @@ -732,9 +780,27 @@ export default class StarterSelectUiHandler extends MessageUiHandler { female = props.female; if (abilityIndex === undefined || abilityIndex !== props.abilityIndex) abilityIndex = props.abilityIndex; + if (natureIndex === undefined || natureIndex !== defaultNature) + natureIndex = defaultNature; } if (this.speciesStarterDexEntry?.caughtAttr) { + let starterIndex = -1; + + this.starterGens.every((g, i) => { + const starterSpecies = this.genSpecies[g][this.starterCursors[i]]; + if (starterSpecies.speciesId === species.speciesId) { + starterIndex = i; + return false; + } + return true; + }); + + if (starterIndex > -1) { + this.starterAttr[starterIndex] = this.dexAttrCursor; + this.starterNatures[starterIndex] = this.natureCursor; + } + const assetLoadCancelled = new Utils.BooleanHolder(false); this.assetLoadCancelled = assetLoadCancelled; @@ -754,6 +820,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.canCycleAbility = [ dexEntry.caughtAttr & DexAttr.ABILITY_1, dexEntry.caughtAttr & DexAttr.ABILITY_2, dexEntry.caughtAttr & DexAttr.ABILITY_HIDDEN ].filter(a => a).length > 1; this.canCycleForm = species.forms.filter(f => !f.formKey || f.formKey.indexOf(SpeciesFormKey.MEGA) === -1) .map((_, f) => dexEntry.caughtAttr & this.scene.gameData.getFormAttr(f)).filter(a => a).length > 1; + this.canCycleNature = this.scene.gameData.getNaturesForAttr(dexEntry.natureAttr).length > 1; } if (dexEntry.caughtAttr && species.malePercent !== null) { @@ -771,11 +838,16 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const isHidden = ability === this.lastSpecies.abilityHidden; this.pokemonAbilityText.setColor(getTextColor(!isHidden ? TextStyle.SUMMARY : TextStyle.SUMMARY_GOLD)); this.pokemonAbilityText.setShadowColor(getTextColor(!isHidden ? TextStyle.SUMMARY : TextStyle.SUMMARY_GOLD, true)); - } else + + this.pokemonNatureText.setText(getNatureName(natureIndex as unknown as Nature, true, true)); + } else { this.pokemonAbilityText.setText(''); + this.pokemonNatureText.setText(''); + } } else { this.pokemonGenderText.setText(''); this.pokemonAbilityText.setText(''); + this.pokemonNatureText.setText(''); } this.updateInstructions(); @@ -785,6 +857,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.starterGens.pop(); this.starterCursors.pop(); this.starterAttr.pop(); + this.starterNatures.pop(); this.starterCursorObjs[this.starterCursors.length].setVisible(false); this.starterIcons[this.starterCursors.length].setTexture('pokemon_icons_0'); this.starterIcons[this.starterCursors.length].setFrame('unknown'); @@ -832,6 +905,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { return { species: starterSpecies, dexAttr: thisObj.starterAttr[i], + nature: thisObj.starterNatures[i] as Nature, pokerus: !![ 0, 1, 2 ].filter(n => thisObj.pokerusGens[n] === starterSpecies.generation - 1 && thisObj.pokerusCursors[n] === thisObj.genSpecies[starterSpecies.generation - 1].indexOf(starterSpecies)).length }; })); diff --git a/src/ui/stats-container.ts b/src/ui/stats-container.ts index 1e496b9d7df..e89284f0cdf 100644 --- a/src/ui/stats-container.ts +++ b/src/ui/stats-container.ts @@ -71,7 +71,7 @@ export class StatsContainer extends Phaser.GameObjects.Container { let label = ivs[i].toString(); if (this.showDiff && originalIvs) { if (originalIvs[i] < ivs[i]) - label += ` ([color=${getGenderColor(Gender.MALE)}][shadow=${getGenderColor(Gender.MALE, true)}]+${ivs[i] - originalIvs[i]}[/shadow][/color])`; + label += ` ([color=${getTextColor(TextStyle.SUMMARY_BLUE)}][shadow=${getTextColor(TextStyle.SUMMARY_BLUE, true)}]+${ivs[i] - originalIvs[i]}[/shadow][/color])`; else label += ' (-)'; } diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index 8a24a340bed..e23e4c781df 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -7,12 +7,13 @@ import { Type } from "../data/type"; import { TextStyle, addBBCodeTextObject, addTextObject, getBBCodeFrag, getTextColor } from "./text"; import Move, { MoveCategory } from "../data/move"; import { getPokeballAtlasKey } from "../data/pokeball"; -import { getGenderColor, getGenderSymbol } from "../data/gender"; +import { Gender, getGenderColor, getGenderSymbol } from "../data/gender"; import { getLevelTotalExp } from "../data/exp"; import { Stat, getStatName } from "../data/pokemon-stat"; import { PokemonHeldItemModifier } from "../modifier/modifier"; import { StatusEffect } from "../data/status-effect"; import { getBiomeName } from "../data/biome"; +import { Nature, getNatureStatMultiplier } from "../data/nature"; enum Page { PROFILE, @@ -531,7 +532,7 @@ export default class SummaryUiHandler extends UiHandler { }); } - let memoString = `${getBBCodeFrag(`${this.pokemon.metBiome === -1 ? 'apparently ' : ''}met at Lv`, TextStyle.WINDOW)}${getBBCodeFrag(this.pokemon.metLevel.toString(), TextStyle.SUMMARY_RED)}${getBBCodeFrag(',', TextStyle.WINDOW)}\n${getBBCodeFrag(getBiomeName(this.pokemon.metBiome), TextStyle.SUMMARY_RED)}${getBBCodeFrag('.', TextStyle.WINDOW)}`; + let memoString = `${getBBCodeFrag(Utils.toReadableString(Nature[this.pokemon.nature]), TextStyle.SUMMARY_RED)} nature,\n${getBBCodeFrag(`${this.pokemon.metBiome === -1 ? 'apparently ' : ''}met at Lv`, TextStyle.WINDOW)}${getBBCodeFrag(this.pokemon.metLevel.toString(), TextStyle.SUMMARY_RED)}${getBBCodeFrag(',', TextStyle.WINDOW)}\n${getBBCodeFrag(getBiomeName(this.pokemon.metBiome), TextStyle.SUMMARY_RED)}${getBBCodeFrag('.', TextStyle.WINDOW)}`; const memoText = addBBCodeTextObject(this.scene, 7, 113, memoString, TextStyle.WINDOW); memoText.setOrigin(0, 0); @@ -550,7 +551,9 @@ export default class SummaryUiHandler extends UiHandler { const rowIndex = s % 3; const colIndex = Math.floor(s / 3); - const statLabel = addTextObject(this.scene, 27 + 115 * colIndex, 56 + 16 * rowIndex, statName, TextStyle.SUMMARY); + const natureStatMultiplier = getNatureStatMultiplier(this.pokemon.nature, s); + + const statLabel = addTextObject(this.scene, 27 + 115 * colIndex, 56 + 16 * rowIndex, statName, natureStatMultiplier === 1 ? TextStyle.SUMMARY : natureStatMultiplier > 1 ? TextStyle.SUMMARY_BLUE : TextStyle.SUMMARY_PINK); statLabel.setOrigin(0.5, 0); statsContainer.add(statLabel); diff --git a/src/ui/text.ts b/src/ui/text.ts index b844d3dda51..29a9c4cf23d 100644 --- a/src/ui/text.ts +++ b/src/ui/text.ts @@ -9,6 +9,7 @@ export enum TextStyle { PARTY_RED, SUMMARY, SUMMARY_RED, + SUMMARY_BLUE, SUMMARY_PINK, SUMMARY_GOLD, MONEY, @@ -129,6 +130,8 @@ export function getTextColor(textStyle: TextStyle, shadow?: boolean): string { case TextStyle.SUMMARY_RED: case TextStyle.TOOLTIP_TITLE: return !shadow ? '#e70808' : '#ffbd73'; + case TextStyle.SUMMARY_BLUE: + return !shadow ? '#40c8f8' : '#006090'; case TextStyle.SUMMARY_PINK: return !shadow ? '#f89890' : '#984038'; case TextStyle.SUMMARY_GOLD: