diff --git a/README.md b/README.md index 59210f859a9..97512f53a68 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@ - Title screen - Starter select screen - - UI - Get starters from save data caught Pokemon - Moves - Move logic diff --git a/public/audio/bgm/menu.mp3 b/public/audio/bgm/menu.mp3 new file mode 100644 index 00000000000..51ceb78ba39 Binary files /dev/null and b/public/audio/bgm/menu.mp3 differ diff --git a/public/images/ui/starter_select_bg.png b/public/images/ui/starter_select_bg.png new file mode 100644 index 00000000000..7dd9a7dac90 Binary files /dev/null and b/public/images/ui/starter_select_bg.png differ diff --git a/public/images/ui/starter_select_cursor.png b/public/images/ui/starter_select_cursor.png new file mode 100644 index 00000000000..f55e33fdb94 Binary files /dev/null and b/public/images/ui/starter_select_cursor.png differ diff --git a/public/images/ui/starter_select_cursor_highlight.png b/public/images/ui/starter_select_cursor_highlight.png new file mode 100644 index 00000000000..6819454bf64 Binary files /dev/null and b/public/images/ui/starter_select_cursor_highlight.png differ diff --git a/public/images/ui/starter_select_gen_cursor.png b/public/images/ui/starter_select_gen_cursor.png new file mode 100644 index 00000000000..e602d15316d Binary files /dev/null and b/public/images/ui/starter_select_gen_cursor.png differ diff --git a/public/images/ui/starter_select_gen_cursor_highlight.png b/public/images/ui/starter_select_gen_cursor_highlight.png new file mode 100644 index 00000000000..ce2f2265ffe Binary files /dev/null and b/public/images/ui/starter_select_gen_cursor_highlight.png differ diff --git a/public/images/ui/starter_select_message.png b/public/images/ui/starter_select_message.png new file mode 100644 index 00000000000..68ad3273a44 Binary files /dev/null and b/public/images/ui/starter_select_message.png differ diff --git a/src/battle-anims.ts b/src/battle-anims.ts index f2082ad6fb7..6a3cb96ff67 100644 --- a/src/battle-anims.ts +++ b/src/battle-anims.ts @@ -429,9 +429,12 @@ function loadAnimAssets(scene: BattleScene, anims: Anim[], startLoad?: boolean): scene.loadImage(bg, 'battle_anims'); for (let s of sounds) scene.loadSe(s, 'battle_anims', s); - scene.load.once(Phaser.Loader.Events.COMPLETE, () => resolve()); - if (startLoad && !scene.load.isLoading()) - scene.load.start(); + if (startLoad) { + scene.load.once(Phaser.Loader.Events.COMPLETE, () => resolve()); + if (!scene.load.isLoading()) + scene.load.start(); + } else + resolve(); }); } diff --git a/src/battle-phases.ts b/src/battle-phases.ts index b202cf18917..e08dd683a13 100644 --- a/src/battle-phases.ts +++ b/src/battle-phases.ts @@ -16,7 +16,9 @@ import { EvolutionPhase } from "./evolution-phase"; import { BattlePhase } from "./battle-phase"; import { BattleStat, getBattleStatLevelChangeDescription, getBattleStatName } from "./battle-stat"; import { Biome, biomeLinks } from "./biome"; -import { ModifierType, ModifierTypeOption, PokemonModifierType, PokemonMoveModifierType, getModifierTypeOptionsForWave, regenerateModifierPoolThresholds } from "./modifier-type"; +import { ModifierTypeOption, PokemonModifierType, PokemonMoveModifierType, getModifierTypeOptionsForWave, regenerateModifierPoolThresholds } from "./modifier-type"; +import PokemonSpecies from "./pokemon-species"; +import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; export class SelectStarterPhase extends BattlePhase { constructor(scene: BattleScene) { @@ -26,7 +28,26 @@ export class SelectStarterPhase extends BattlePhase { start() { super.start(); - this.scene.ui.setMode(Mode.STARTER_SELECT); + this.scene.sound.play('menu'); + + this.scene.ui.setMode(Mode.STARTER_SELECT, (starterSpecies: PokemonSpecies[]) => { + const party = this.scene.getParty(); + const loadPokemonAssets: Promise[] = []; + for (let species of starterSpecies) { + const starter = new PlayerPokemon(this.scene, species, 5); + starter.setVisible(false); + party.push(starter); + loadPokemonAssets.push(starter.loadAssets()); + } + Promise.all(loadPokemonAssets).then(() => { + this.scene.ui.clearText(); + this.scene.ui.setMode(Mode.MESSAGE).then(() => { + SoundFade.fadeOut(this.scene.sound.get('menu'), 500, true); + this.scene.time.delayedCall(500, () => this.scene.arena.playBgm()); + this.end(); + }); + }); + }); } } diff --git a/src/battle-scene.ts b/src/battle-scene.ts index a410a96f77b..8664969c7aa 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1,7 +1,7 @@ import Phaser from 'phaser'; import { Biome, BiomeArena } from './biome'; import UI from './ui/ui'; -import { EncounterPhase, SummonPhase, CommandPhase, NextEncounterPhase, SwitchBiomePhase, NewBiomeEncounterPhase, SelectBiomePhase } from './battle-phases'; +import { EncounterPhase, SummonPhase, CommandPhase, NextEncounterPhase, SwitchBiomePhase, NewBiomeEncounterPhase, SelectBiomePhase, SelectStarterPhase } from './battle-phases'; import { PlayerPokemon, EnemyPokemon } from './pokemon'; import PokemonSpecies, { allSpecies, getPokemonSpecies } from './pokemon-species'; import * as Utils from './utils'; @@ -23,6 +23,7 @@ export enum Button { RIGHT, ACTION, CANCEL, + QUICK_START, RANDOM, AUTO, SPEED_UP, @@ -32,6 +33,7 @@ export enum Button { export default class BattleScene extends Phaser.Scene { public auto: boolean; public gameSpeed: integer = 1; + public quickStart: boolean; private phaseQueue: BattlePhase[]; private phaseQueuePrepend: BattlePhase[]; @@ -157,6 +159,13 @@ export default class BattleScene extends Phaser.Scene { this.loadImage('biome_select_window_2', 'ui'); this.loadImage('biome_select_window_3', 'ui'); + this.loadImage('starter_select_bg', 'ui'); + this.loadImage('starter_select_message', 'ui'); + this.loadImage('starter_select_cursor', 'ui'); + this.loadImage('starter_select_cursor_highlight', 'ui'); + this.loadImage('starter_select_gen_cursor', 'ui'); + this.loadImage('starter_select_gen_cursor_highlight', 'ui'); + // Load arena images Utils.getEnumValues(Biome).map(at => { const atKey = Biome[at].toLowerCase(); @@ -215,6 +224,7 @@ export default class BattleScene extends Phaser.Scene { this.loadSe('pb_catch'); this.loadSe('pb_lock'); + this.loadBgm('menu'); this.loadBgm('level_up_fanfare'); this.loadBgm('evolution'); this.loadBgm('evolution_fanfare'); @@ -269,6 +279,7 @@ export default class BattleScene extends Phaser.Scene { let loadPokemonAssets = []; const isRandom = this.isButtonPressed(Button.RANDOM); // For testing purposes + this.quickStart = isRandom || this.isButtonPressed(Button.QUICK_START); if (isRandom) { const biomes = Utils.getEnumValues(Biome); @@ -291,18 +302,17 @@ export default class BattleScene extends Phaser.Scene { a.setOrigin(0, 0); field.add(a); }); - this.arena.playBgm(); - for (let s = 0; s < 3; s++) { - const playerSpecies = !isRandom ? getPokemonSpecies(s === 0 ? Species.TORCHIC : s === 1 ? Species.TREECKO : Species.MUDKIP) : this.randomSpecies(5); - const playerPokemon = new PlayerPokemon(this, playerSpecies, 5); - playerPokemon.setVisible(false); - loadPokemonAssets.push(playerPokemon.loadAssets()); + if (this.quickStart) { + for (let s = 0; s < 3; s++) { + const playerSpecies = !isRandom ? getPokemonSpecies(s === 0 ? Species.TORCHIC : s === 1 ? Species.TREECKO : Species.MUDKIP) : this.randomSpecies(5); + const playerPokemon = new PlayerPokemon(this, playerSpecies, 5); + playerPokemon.setVisible(false); + loadPokemonAssets.push(playerPokemon.loadAssets()); - this.party.push(playerPokemon); + this.party.push(playerPokemon); + } } - - console.log(this.getPlayerPokemon().species.name, this.getPlayerPokemon().species.speciesId, this.getPlayerPokemon().stats); const trainerPbFrameNames = this.anims.generateFrameNames('trainer_m_pb', { zeroPad: 2, start: 1, end: 12 }); this.anims.create({ @@ -354,6 +364,7 @@ export default class BattleScene extends Phaser.Scene { [Button.RIGHT]: [keyCodes.RIGHT, keyCodes.D], [Button.ACTION]: [keyCodes.ENTER, keyCodes.SPACE, keyCodes.Z], [Button.CANCEL]: [keyCodes.BACKSPACE, keyCodes.ESC, keyCodes.X], + [Button.QUICK_START]: [keyCodes.Q], [Button.RANDOM]: [keyCodes.R], [Button.AUTO]: [keyCodes.F2], [Button.SPEED_UP]: [keyCodes.PLUS], @@ -384,8 +395,6 @@ export default class BattleScene extends Phaser.Scene { newBattle(): Battle { if (this.currentBattle) { - console.log(this.getPlayerPokemon(), this.getParty().map(p => p.name), this.getPlayerPokemon().id) - this.getEnemyPokemon().destroy(); if (this.currentBattle.waveIndex % 10) this.unshiftPhase(new NextEncounterPhase(this)); @@ -394,7 +403,11 @@ export default class BattleScene extends Phaser.Scene { this.unshiftPhase(new NewBiomeEncounterPhase(this)); } } else { - //this.pushPhase(new SelectStarterPhase(this)); + if (!this.quickStart) { + this.arena.preloadBgm(); + this.pushPhase(new SelectStarterPhase(this)); + } else + this.arena.playBgm(); this.pushPhase(new EncounterPhase(this)); this.pushPhase(new SummonPhase(this)); } diff --git a/src/biome.ts b/src/biome.ts index 64aa741e96e..688b6738f33 100644 --- a/src/biome.ts +++ b/src/biome.ts @@ -187,12 +187,18 @@ export class BiomeArena { return Biome[this.biomeType].toLowerCase(); } - playBgm() { + preloadBgm(): void { this.scene.loadBgm(this.bgm); - this.scene.load.once(Phaser.Loader.Events.COMPLETE, () => this.scene.playBgm(this.bgm)); } - fadeOutBgm(duration: integer, destroy?: boolean) { + playBgm(): void { + this.scene.loadBgm(this.bgm); + this.scene.load.once(Phaser.Loader.Events.COMPLETE, () => this.scene.playBgm(this.bgm)); + if (!this.scene.load.isLoading()) + this.scene.load.start(); + } + + fadeOutBgm(duration: integer, destroy?: boolean): void { if (destroy === undefined) destroy = true; const bgm = this.scene.sound.get(this.bgm); diff --git a/src/pokemon-evolutions.ts b/src/pokemon-evolutions.ts index 1f47033b8f1..73c305a2a86 100644 --- a/src/pokemon-evolutions.ts +++ b/src/pokemon-evolutions.ts @@ -770,6 +770,12 @@ export const pokemonEvolutions: PokemonEvolutions = { [Species.EXEGGCUTE]: [ new SpeciesEvolution(Species.EXEGGUTOR, 1, "Leaf Stone", null, SpeciesWildEvolutionDelay.LONG) ], + [Species.TANGELA]: [ + new SpeciesEvolution(Species.TANGROWTH, 1, null, new SpeciesEvolutionCondition((p: Pokemon) => true /* Ancient power learned*/), SpeciesWildEvolutionDelay.LONG) + ], + [Species.LICKITUNG]: [ + new SpeciesEvolution(Species.LICKILICKY, 1, null, new SpeciesEvolutionCondition((p: Pokemon) => true /* Rollout learned*/), SpeciesWildEvolutionDelay.LONG) + ], [Species.STARYU]: [ new SpeciesEvolution(Species.STARMIE, 1, "Water Stone", null, SpeciesWildEvolutionDelay.MEDIUM) ], @@ -785,27 +791,54 @@ export const pokemonEvolutions: PokemonEvolutions = { [Species.TOGETIC]: [ new SpeciesEvolution(Species.TOGEKISS, 1, "Shiny Stone", null, SpeciesWildEvolutionDelay.VERY_LONG) ], + [Species.AIPOM]: [ + new SpeciesEvolution(Species.AMBIPOM, 1, null, new SpeciesEvolutionCondition((p: Pokemon) => true /* Double hit learned*/), SpeciesWildEvolutionDelay.LONG) + ], [Species.SUNKERN]: [ new SpeciesEvolution(Species.SUNFLORA, 1, "Sun Stone", null, SpeciesWildEvolutionDelay.MEDIUM) ], + [Species.YANMA]: [ + new SpeciesEvolution(Species.YANMEGA, 1, null, new SpeciesEvolutionCondition((p: Pokemon) => true /* Rollout learned*/), SpeciesWildEvolutionDelay.LONG) + ], [Species.MURKROW]: [ new SpeciesEvolution(Species.HONCHKROW, 1, "Dusk Stone", null, SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.MISDREAVUS]: [ new SpeciesEvolution(Species.MISMAGIUS, 1, "Dusk Stone", null, SpeciesWildEvolutionDelay.VERY_LONG) ], + [Species.GLIGAR]: [ + new SpeciesEvolution(Species.GLISCOR, 1, null, new SpeciesEvolutionCondition((p: Pokemon) => true /* Razor fang at night*/), SpeciesWildEvolutionDelay.LONG) + ], + [Species.SNEASEL]: [ + new SpeciesEvolution(Species.WEAVILE, 1, null, new SpeciesEvolutionCondition((p: Pokemon) => true /* Razor claw at night*/), SpeciesWildEvolutionDelay.LONG) + ], + [Species.PILOSWINE]: [ + new SpeciesEvolution(Species.MAMOSWINE, 1, null, new SpeciesEvolutionCondition((p: Pokemon) => true /* Ancient power learned*/), SpeciesWildEvolutionDelay.VERY_LONG) + ], [Species.LOMBRE]: [ new SpeciesEvolution(Species.LUDICOLO, 1, "Water Stone", null, SpeciesWildEvolutionDelay.LONG) ], [Species.NUZLEAF]: [ new SpeciesEvolution(Species.SHIFTRY, 1, "Leaf Stone", null, SpeciesWildEvolutionDelay.LONG) ], + [Species.NOSEPASS]: [ + new SpeciesEvolution(Species.PROBOPASS, 1, null, new SpeciesEvolutionCondition((p: Pokemon) => true /* Magnetic field??*/), SpeciesWildEvolutionDelay.LONG) + ], [Species.SKITTY]: [ new SpeciesEvolution(Species.DELCATTY, 1, "Moon Stone", null, SpeciesWildEvolutionDelay.MEDIUM) ], [Species.ROSELIA]: [ new SpeciesEvolution(Species.ROSERADE, 1, "Shiny Stone", null, SpeciesWildEvolutionDelay.VERY_LONG) ], + [Species.BONSLY]: [ + new SpeciesEvolution(Species.SUDOWOODO, 1, null, new SpeciesEvolutionCondition((p: Pokemon) => true /* Mimic learned */), SpeciesWildEvolutionDelay.MEDIUM) + ], + [Species.MIME_JR]: [ + new SpeciesEvolution(Species.MR_MIME, 1, null, new SpeciesEvolutionCondition((p: Pokemon) => true /* Mimic learned */), SpeciesWildEvolutionDelay.MEDIUM) + ], + [Species.MANTYKE]: [ + new SpeciesEvolution(Species.MANTINE, 1, null, new SpeciesEvolutionCondition((p: Pokemon) => true /* Remoraid in party */), SpeciesWildEvolutionDelay.MEDIUM) + ], [Species.PANSAGE]: [ new SpeciesEvolution(Species.SIMISAGE, 1, "Leaf Stone", null, SpeciesWildEvolutionDelay.MEDIUM) ], @@ -904,10 +937,10 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.CROBAT, 1, null, new SpeciesEvolutionCondition((p: Pokemon) => p.winCount >= 10), SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.CHANSEY]: [ - new SpeciesEvolution(Species.BLISSEY, 1, null, new SpeciesEvolutionCondition((p: Pokemon) => p.winCount >= 10), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(Species.BLISSEY, 1, null, new SpeciesEvolutionCondition((p: Pokemon) => p.winCount >= 10), SpeciesWildEvolutionDelay.MEDIUM) ], [Species.MUNCHLAX]: [ - new SpeciesEvolution(Species.SNORLAX, 1, null, new SpeciesEvolutionCondition((p: Pokemon) => p.winCount >= 10), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(Species.SNORLAX, 1, null, new SpeciesEvolutionCondition((p: Pokemon) => p.winCount >= 10), SpeciesWildEvolutionDelay.MEDIUM) ], [Species.TOGEPI]: [ new SpeciesEvolution(Species.TOGETIC, 1, null, new SpeciesEvolutionCondition((p: Pokemon) => p.winCount >= 10), SpeciesWildEvolutionDelay.SHORT) diff --git a/src/pokemon-level-moves.ts b/src/pokemon-level-moves.ts index 574656e4a2e..44efafd9da5 100644 --- a/src/pokemon-level-moves.ts +++ b/src/pokemon-level-moves.ts @@ -1,7 +1,11 @@ import { Moves } from "./move"; import { Species } from "./species"; -export const pokemonLevelMoves = { +interface PokemonLevelMoves { + [key: string]: Array> +} + +export const pokemonLevelMoves: PokemonLevelMoves = { [Species.BULBASAUR]: [ [ 1, Moves.TACKLE ], [ 3, Moves.GROWL ], diff --git a/src/pokemon-species.ts b/src/pokemon-species.ts index eb83395ac41..708f1a4a3e3 100644 --- a/src/pokemon-species.ts +++ b/src/pokemon-species.ts @@ -131,7 +131,7 @@ export default class PokemonSpecies { if (pokemonEvolutions.hasOwnProperty(this.speciesId)) { for (let e of pokemonEvolutions[this.speciesId]) { const condition = e.condition; - if (!condition || !condition.applyToWild || condition.predicate(this)) { + if (!condition || typeof(condition) === 'string' || !condition.applyToWild || condition.predicate(this)) { const speciesId = e.speciesId; const level = e.level; evolutionLevels.push([ speciesId, level ]); @@ -146,7 +146,7 @@ export default class PokemonSpecies { return evolutionLevels; } - getPrevolutionLevels() { + getPrevolutionLevels(ignoreConditions?: boolean) { const prevolutionLevels = []; const allEvolvingPokemon = Object.keys(pokemonEvolutions); @@ -154,7 +154,7 @@ export default class PokemonSpecies { for (let e of pokemonEvolutions[p]) { if (e.speciesId === this.speciesId) { const condition = e.condition; - if (!condition || !condition.applyToWild || condition.predicate(this)) { + if (ignoreConditions || !condition || typeof(condition) === 'string' || !condition.applyToWild || condition.predicate(this)) { const speciesId = parseInt(p) as Species; let level = e.level; prevolutionLevels.push([ speciesId, level ]); @@ -169,6 +169,18 @@ export default class PokemonSpecies { return prevolutionLevels; } + getSpriteAtlasPath(female: boolean, shiny?: boolean): string { + return this.getSpriteId(female, shiny).replace(/\_{2}/g, '/'); + } + + getSpriteId(female: boolean, shiny?: boolean): string { + return `${shiny ? 'shiny__' : ''}${this.genderDiffs && female ? 'female__' : ''}${this.speciesId}`; + } + + getSpriteKey(female: boolean, shiny?: boolean): string { + return `pkmn__${this.getSpriteId(female, shiny)}`; + } + getIconAtlasKey(): string { return `pokemon_icons_${this.generation}`; } @@ -225,6 +237,32 @@ export default class PokemonSpecies { return `pkmn_icon__${this.getIconId()}`; } + loadAssets(scene: BattleScene, female: boolean, shiny?: boolean, startLoad?: boolean): Promise { + return new Promise(resolve => { + scene.load.audio(this.speciesId.toString(), `audio/cry/${this.speciesId}.mp3`); + scene.loadAtlas(this.getSpriteKey(female, shiny), 'pokemon', this.getSpriteAtlasPath(female, shiny)); + scene.load.once(Phaser.Loader.Events.COMPLETE, () => { + const originalWarn = console.warn; + // Ignore warnings for missing frames, because there will be a lot + console.warn = () => {}; + const frameNames = scene.anims.generateFrameNames(this.getSpriteKey(female, shiny), { zeroPad: 4, suffix: ".png", start: 1, end: 256 }); + console.warn = originalWarn; + scene.anims.create({ + key: this.getSpriteKey(female, shiny), + frames: frameNames, + frameRate: 12, + repeat: -1 + }); + resolve(); + }); + if (startLoad) { + if (!scene.load.isLoading()) + scene.load.start(); + } else + resolve(); + }); + } + generateIconAnim(scene: BattleScene): void { const frameNames = scene.anims.generateFrameNames(this.getIconAtlasKey(), { prefix: `${this.getIconId()}_`, zeroPad: 2, suffix: '.png', start: 1, end: 34 }); scene.anims.create({ @@ -234,6 +272,11 @@ export default class PokemonSpecies { repeat: -1 }); } + + cry(scene: BattleScene, soundConfig?: Phaser.Types.Sound.SoundConfig): integer { + scene.sound.play(this.speciesId.toString(), soundConfig); + return scene.sound.get(this.speciesId.toString()).totalDuration * 1000; + } } class PokemonForm extends PokemonSpecies { @@ -750,14 +793,14 @@ export const allSpecies = [ [ Species.PROBOPASS, "Probopass", 4, 0, 0, 0, "Compass Pokémon", Type.ROCK, Type.STEEL, 1.4, 340, "Sturdy", "Magnet Pull", "Sand Force", 525, 60, 55, 145, 75, 150, 40, 60, 70, 184, GrowthRate.MEDIUM_FAST, "Mineral", null, 50, 20, 0 ], [ Species.DUSKNOIR, "Dusknoir", 4, 0, 0, 0, "Gripper Pokémon", Type.GHOST, -1, 2.2, 106.6, "Pressure", null, "Frisk", 525, 45, 100, 135, 65, 135, 45, 45, 35, 236, GrowthRate.FAST, "Amorphous", null, 50, 25, 0 ], [ Species.FROSLASS, "Froslass", 4, 0, 0, 0, "Snow Land Pokémon", Type.ICE, Type.GHOST, 1.3, 26.6, "Snow Cloak", null, "Cursed Body", 480, 70, 80, 70, 80, 70, 110, 75, 70, 168, GrowthRate.MEDIUM_FAST, "Fairy", "Mineral", 0, 20, 0 ], - [ Species.ROTOM, "Rotom", 4, 0, 0, 0, "Plasma Pokémon", Type.ELECTRIC, Type.GHOST, 0.3, 0.3, "Levitate", null, null, 440, 50, 50, 77, 95, 77, 91, 45, 70, 154, GrowthRate.MEDIUM_FAST, "Amorphous", null, null, 20, 1, + [ Species.ROTOM, "Rotom", 4, 0, 0, 0, "Plasma Pokémon", Type.ELECTRIC, Type.GHOST, 0.3, 0.3, "Levitate", null, null, 440, 50, 50, 77, 95, 77, 91, 45, 70, 154, GrowthRate.MEDIUM_FAST, "Amorphous", null, null, 20, 0, [ - [ "Normal", 4, 0, 0, 0, "Plasma Pokémon", Type.ELECTRIC, Type.GHOST, 0.3, 0.3, "Levitate", null, null, 440, 50, 50, 77, 95, 77, 91, 45, 70, 154, GrowthRate.MEDIUM_FAST, "Amorphous", null, null, 20, 1 ], - [ "Heat", 4, 0, 0, 0, "Plasma Pokémon", Type.ELECTRIC, Type.FIRE, 0.3, 0.3, "Levitate", null, null, 520, 50, 65, 107, 105, 107, 86, 45, 70, 182, GrowthRate.MEDIUM_FAST, "Amorphous", null, null, 20, 1 ], - [ "Wash", 4, 0, 0, 0, "Plasma Pokémon", Type.ELECTRIC, Type.WATER, 0.3, 0.3, "Levitate", null, null, 520, 50, 65, 107, 105, 107, 86, 45, 70, 182, GrowthRate.MEDIUM_FAST, "Amorphous", null, null, 20, 1 ], - [ "Frost", 4, 0, 0, 0, "Plasma Pokémon", Type.ELECTRIC, Type.ICE, 0.3, 0.3, "Levitate", null, null, 520, 50, 65, 107, 105, 107, 86, 45, 70, 182, GrowthRate.MEDIUM_FAST, "Amorphous", null, null, 20, 1 ], - [ "Fan", 4, 0, 0, 0, "Plasma Pokémon", Type.ELECTRIC, Type.FLYING, 0.3, 0.3, "Levitate", null, null, 520, 50, 65, 107, 105, 107, 86, 45, 70, 182, GrowthRate.MEDIUM_FAST, "Amorphous", null, null, 20, 1 ], - [ "Mow", 4, 0, 0, 0, "Plasma Pokémon", Type.ELECTRIC, Type.GRASS, 0.3, 0.3, "Levitate", null, null, 520, 50, 65, 107, 105, 107, 86, 45, 70, 182, GrowthRate.MEDIUM_FAST, "Amorphous", null, null, 20, 1 ] + [ "Normal", 4, 0, 0, 0, "Plasma Pokémon", Type.ELECTRIC, Type.GHOST, 0.3, 0.3, "Levitate", null, null, 440, 50, 50, 77, 95, 77, 91, 45, 70, 154, GrowthRate.MEDIUM_FAST, "Amorphous", null, null, 20, 0 ], + [ "Heat", 4, 0, 0, 0, "Plasma Pokémon", Type.ELECTRIC, Type.FIRE, 0.3, 0.3, "Levitate", null, null, 520, 50, 65, 107, 105, 107, 86, 45, 70, 182, GrowthRate.MEDIUM_FAST, "Amorphous", null, null, 20, 0 ], + [ "Wash", 4, 0, 0, 0, "Plasma Pokémon", Type.ELECTRIC, Type.WATER, 0.3, 0.3, "Levitate", null, null, 520, 50, 65, 107, 105, 107, 86, 45, 70, 182, GrowthRate.MEDIUM_FAST, "Amorphous", null, null, 20, 0 ], + [ "Frost", 4, 0, 0, 0, "Plasma Pokémon", Type.ELECTRIC, Type.ICE, 0.3, 0.3, "Levitate", null, null, 520, 50, 65, 107, 105, 107, 86, 45, 70, 182, GrowthRate.MEDIUM_FAST, "Amorphous", null, null, 20, 0 ], + [ "Fan", 4, 0, 0, 0, "Plasma Pokémon", Type.ELECTRIC, Type.FLYING, 0.3, 0.3, "Levitate", null, null, 520, 50, 65, 107, 105, 107, 86, 45, 70, 182, GrowthRate.MEDIUM_FAST, "Amorphous", null, null, 20, 0 ], + [ "Mow", 4, 0, 0, 0, "Plasma Pokémon", Type.ELECTRIC, Type.GRASS, 0.3, 0.3, "Levitate", null, null, 520, 50, 65, 107, 105, 107, 86, 45, 70, 182, GrowthRate.MEDIUM_FAST, "Amorphous", null, null, 20, 0 ] ] ], [ Species.UXIE, "Uxie", 4, 1, 0, 0, "Knowledge Pokémon", Type.PSYCHIC, -1, 0.3, 0.3, "Levitate", null, null, 580, 75, 75, 130, 75, 130, 95, 3, 140, 261, GrowthRate.SLOW, "Undiscovered", null, null, 80, 0 ], diff --git a/src/pokemon.ts b/src/pokemon.ts index 2871fd31a9e..909a7fbcf6f 100644 --- a/src/pokemon.ts +++ b/src/pokemon.ts @@ -163,32 +163,24 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { Promise.allSettled(moveIds.map(m => initMoveAnim(m))) .then(() => { loadMoveAnimAssets(this.scene as BattleScene, moveIds); - (this.scene as BattleScene).loadAtlas(this.getSpriteKey(), 'pokemon', this.getSpriteAtlasPath()); + this.species.loadAssets(this.scene as BattleScene, this.gender === Gender.FEMALE); if (this.isPlayer()) (this.scene as BattleScene).loadAtlas(this.getBattleSpriteKey(), 'pokemon', this.getBattleSpriteAtlasPath()); - this.scene.load.audio(this.species.speciesId.toString(), `audio/cry/${this.species.speciesId}.mp3`); this.scene.load.once(Phaser.Loader.Events.COMPLETE, () => { - const originalWarn = console.warn; - // Ignore warnings for missing frames, because there will be a lot - console.warn = () => {}; - const frameNames = this.scene.anims.generateFrameNames(this.getSpriteKey(), { zeroPad: 4, suffix: ".png", start: 1, end: 256 }); - const battleFrameNames = this.isPlayer() - ? this.scene.anims.generateFrameNames(this.getBattleSpriteKey(), { zeroPad: 4, suffix: ".png", start: 1, end: 256 }) - : null; - console.warn = originalWarn; - this.scene.anims.create({ - key: this.getSpriteKey(), - frames: frameNames, - frameRate: 12, - repeat: -1 - }); if (this.isPlayer()) { - this.scene.anims.create({ - key: this.getBattleSpriteKey(), - frames: battleFrameNames, - frameRate: 12, - repeat: -1 - }); + const originalWarn = console.warn; + // Ignore warnings for missing frames, because there will be a lot + console.warn = () => {}; + const battleFrameNames = this.scene.anims.generateFrameNames(this.getBattleSpriteKey(), { zeroPad: 4, suffix: ".png", start: 1, end: 256 }); + console.warn = originalWarn; + if (this.isPlayer()) { + this.scene.anims.create({ + key: this.getBattleSpriteKey(), + frames: battleFrameNames, + frameRate: 12, + repeat: -1 + }); + } } this.playAnim(); resolve(); @@ -208,7 +200,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getSpriteId(): string { - return `${this.shiny ? 'shiny__' : ''}${this.species.genderDiffs && !this.gender ? 'female__' : ''}${this.species.speciesId}`; + return this.species.getSpriteId(this.gender === Gender.FEMALE, this.shiny); } getBattleSpriteId(): string { @@ -216,7 +208,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getSpriteKey(): string { - return `pkmn__${this.getSpriteId()}`; + return this.species.getSpriteKey(this.gender === Gender.FEMALE, this.shiny); } getBattleSpriteKey(): string { @@ -507,8 +499,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } cry(soundConfig?: Phaser.Types.Sound.SoundConfig): integer { - this.scene.sound.play(this.species.speciesId.toString(), soundConfig); - return this.scene.sound.get(this.species.speciesId.toString()).totalDuration * 1000; + return this.species.cry(this.scene as BattleScene, soundConfig); } faintCry(callback: Function) { diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 9f197bcc508..79bb6bc9f40 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -1,10 +1,37 @@ import BattleScene, { Button } from "../battle-scene"; -import { allSpecies } from "../pokemon-species"; +import PokemonSpecies, { allSpecies } from "../pokemon-species"; +import { Species } from "../species"; +import { TextStyle, addTextObject } from "../text"; import { Mode } from "./ui"; -import UiHandler from "./uiHandler"; +import * as Utils from "../utils"; +import MessageUiHandler from "./message-ui-handler"; -export default class StarterSelectUiHandler extends UiHandler { +export type StarterSelectCallback = (starterSpecies: PokemonSpecies[]) => void; + +export default class StarterSelectUiHandler extends MessageUiHandler { private starterSelectContainer: Phaser.GameObjects.Container; + private starterSelectGenIconContainers: Phaser.GameObjects.Container[]; + private pokemonNumberText: Phaser.GameObjects.Text; + private pokemonSprite: Phaser.GameObjects.Sprite; + private pokemonNameText: Phaser.GameObjects.Text; + private starterSelectMessageBoxContainer: Phaser.GameObjects.Container; + + private genMode: boolean; + private genCursor: integer = 0; + private genSpecies: PokemonSpecies[][] = []; + private lastSpecies: PokemonSpecies; + private speciesLoaded: Map = new Map(); + private starterGens: integer[] = []; + private starterCursors: integer[] = []; + + private assetLoadCancelled: Utils.BooleanHolder; + private cursorObj: Phaser.GameObjects.Image; + private starterCursorObjs: Phaser.GameObjects.Image[]; + private starterIcons: Phaser.GameObjects.Sprite[]; + private genCursorObj: Phaser.GameObjects.Image; + private genCursorHighlightObj: Phaser.GameObjects.Image; + + private starterSelectCallback: StarterSelectCallback; constructor(scene: BattleScene) { super(scene, Mode.STARTER_SELECT); @@ -17,38 +44,217 @@ export default class StarterSelectUiHandler extends UiHandler { this.starterSelectContainer.setVisible(false); ui.add(this.starterSelectContainer); - let s = 0; + const bgColor = this.scene.add.rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6, 0x006860); + bgColor.setOrigin(0, 0); + this.starterSelectContainer.add(bgColor); - for (let species of allSpecies) { - if (species.getSpeciesForLevel(1) !== species.speciesId || species.generation >= 6) - continue; - species.generateIconAnim(this.scene); - const x = (s % 24) * 13; - const y = Math.floor(s / 24) * 13; - const icon = this.scene.add.sprite(x, y, species.getIconAtlasKey()); + const starterSelectBg = this.scene.add.image(1, 1, 'starter_select_bg'); + starterSelectBg.setOrigin(0, 0); + this.starterSelectContainer.add(starterSelectBg); + + this.pokemonNumberText = addTextObject(this.scene, 17, 1, '000', TextStyle.SUMMARY); + this.pokemonNumberText.setOrigin(0, 0); + this.starterSelectContainer.add(this.pokemonNumberText); + + this.pokemonSprite = this.scene.add.sprite(53, 63, `pkmn__sub`); + this.starterSelectContainer.add(this.pokemonSprite); + + this.pokemonNameText = addTextObject(this.scene, 6, 112, '', TextStyle.SUMMARY); + this.pokemonNameText.setOrigin(0, 0); + this.starterSelectContainer.add(this.pokemonNameText); + + const genText = addTextObject(this.scene, 115, 6, 'I\nII\nIII\nIV\nV', TextStyle.WINDOW); + genText.setLineSpacing(16); + this.starterSelectContainer.add(genText); + + this.starterSelectGenIconContainers = new Array(5).fill(null).map((_, i) => { + const container = this.scene.add.container(149, 9); + if (i) + container.setVisible(false); + this.starterSelectContainer.add(container); + return container; + }); + + this.starterCursorObjs = new Array(3).fill(null).map(() => { + const cursorObj = this.scene.add.image(0, 0, 'starter_select_cursor_highlight'); + cursorObj.setVisible(false); + cursorObj.setOrigin(0, 0); + this.starterSelectContainer.add(cursorObj); + return cursorObj; + }); + + this.cursorObj = this.scene.add.image(0, 0, 'starter_select_cursor'); + this.cursorObj.setOrigin(0, 0); + this.starterSelectContainer.add(this.cursorObj); + + this.genCursorHighlightObj = this.scene.add.image(111, 5, 'starter_select_gen_cursor_highlight'); + this.genCursorHighlightObj.setOrigin(0, 0); + this.starterSelectContainer.add(this.genCursorHighlightObj); + + this.genCursorObj = this.scene.add.image(111, 5, 'starter_select_gen_cursor'); + this.genCursorObj.setVisible(false); + this.genCursorObj.setOrigin(0, 0); + this.starterSelectContainer.add(this.genCursorObj); + + for (let g = 0; g < this.starterSelectGenIconContainers.length; g++) { + let s = 0; + this.genSpecies.push([]); + + for (let species of allSpecies) { + if (species.getPrevolutionLevels(true).length || species.generation !== g + 1) + continue; + this.speciesLoaded.set(species.speciesId, false); + this.genSpecies[g].push(species); + species.generateIconAnim(this.scene); + const x = (s % 9) * 18; + const y = Math.floor(s / 9) * 18; + const icon = this.scene.add.sprite(x, y, species.getIconAtlasKey()); + icon.setScale(0.5); + icon.setOrigin(0, 0); + icon.play(species.getIconKey()).stop(); + this.starterSelectGenIconContainers[g].add(icon); + s++; + } + } + + this.scene.anims.create({ + key: 'pkmn_icon__000', + frames: this.scene.anims.generateFrameNames('pokemon_icons_0', { prefix: `000_`, zeroPad: 2, suffix: '.png', start: 1, end: 34 }), + frameRate: 128, + repeat: -1 + }); + + this.starterIcons = new Array(3).fill(null).map((_, i) => { + const icon = this.scene.add.sprite(115, 95 + 16 * i, 'pokemon_icons_0'); icon.setScale(0.5); icon.setOrigin(0, 0); - icon.play(species.getIconKey()).stop(); + icon.play('pkmn_icon__000'); this.starterSelectContainer.add(icon); - s++; + return icon; + }); + + this.starterSelectMessageBoxContainer = this.scene.add.container(0, this.scene.game.canvas.height / 6); + this.starterSelectMessageBoxContainer.setVisible(false); + this.starterSelectContainer.add(this.starterSelectMessageBoxContainer); + + const starterSelectMessageBox = this.scene.add.image(0, 0, 'starter_select_message'); + starterSelectMessageBox.setOrigin(0, 1); + this.starterSelectMessageBoxContainer.add(starterSelectMessageBox); + + this.message = addTextObject(this.scene, 8, -8, '', TextStyle.WINDOW, { maxLines: 1 }); + this.message.setOrigin(0, 1); + this.starterSelectMessageBoxContainer.add(this.message); + } + + show(args: any[]): void { + if (args.length >= 1 && args[0] instanceof Function) { + super.show(args); + + this.starterSelectCallback = args[0] as StarterSelectCallback; + + this.starterSelectContainer.setVisible(true); + + this.setGenMode(false); + this.setCursor(0); + this.setGenMode(true); + this.setCursor(0); } } - - show(args: any[]) { - super.show(args); - this.starterSelectContainer.setVisible(true); - this.setCursor(0); + showText(text: string, delay?: integer, callback?: Function, callbackDelay?: integer, prompt?: boolean, promptDelay?: integer) { + super.showText(text, delay, callback, callbackDelay, prompt, promptDelay); + + this.starterSelectMessageBoxContainer.setVisible(true); } - processInput(button: Button) { + processInput(button: Button): void { const ui = this.getUi(); let success = false; - if (button === Button.ACTION) { - } else if (button === Button.CANCEL) { + if (this.genMode) { + switch (button) { + case Button.UP: + if (this.genCursor) + success = this.setCursor(this.genCursor - 1); + break; + case Button.DOWN: + if (this.genCursor < 4) + success = this.setCursor(this.genCursor + 1); + break; + case Button.RIGHT: + success = this.setGenMode(false); + break; + } } else { + if (button === Button.ACTION) { + if (this.starterCursors.length < 3) { + let isDupe = false; + for (let s = 0; s < this.starterCursors.length; s++) { + if (this.starterGens[s] === this.genCursor && this.starterCursors[s] === this.cursor) { + isDupe = true; + break; + } + } + if (!isDupe) { + const cursorObj = this.starterCursorObjs[this.starterCursors.length]; + cursorObj.setVisible(true); + cursorObj.setPosition(this.cursorObj.x, this.cursorObj.y); + const species = this.genSpecies[this.genCursor][this.cursor]; + this.starterIcons[this.starterCursors.length].play(species.getIconKey()); + this.starterGens.push(this.genCursor); + this.starterCursors.push(this.cursor); + if (this.speciesLoaded.get(species.speciesId)) + species.cry(this.scene); + if (this.starterCursors.length === 3) { + ui.showText('Begin with these POKéMON?', null, () => { + ui.setModeWithoutClear(Mode.CONFIRM, () => { + ui.setMode(Mode.STARTER_SELECT); + const originalStarterSelectCallback = this.starterSelectCallback; + this.starterSelectCallback = null; + originalStarterSelectCallback(new Array(3).fill(0).map((_, i) => this.genSpecies[this.starterGens[i]][this.starterCursors[i]])); + }, () => { + ui.setMode(Mode.STARTER_SELECT); + this.popStarter(); + this.clearText(); + }); + }); + } + success = true; + } else + ui.playError(); + } + } else if (button === Button.CANCEL) { + if (this.starterCursors.length) { + this.popStarter(); + success = true; + } else + ui.playError(); + } else { + const genStarters = this.starterSelectGenIconContainers[this.genCursor].getAll().length; + const rows = Math.ceil(genStarters / 9); + const row = Math.floor(this.cursor / 9); + switch (button) { + case Button.UP: + if (row) + success = this.setCursor(this.cursor - 9); + break; + case Button.DOWN: + if (row < rows - 2 || (row < rows - 1 && this.cursor % 9 <= (genStarters - 1) % 9)) + success = this.setCursor(this.cursor + 9); + break; + case Button.LEFT: + if (this.cursor % 9) + success = this.setCursor(this.cursor - 1); + else + success = this.setGenMode(true); + break; + case Button.RIGHT: + if (this.cursor % 9 < (row < rows - 1 ? 8 : (genStarters - 1) % 9)) + success = this.setCursor(this.cursor + 1); + break; + } + } } if (success) @@ -56,17 +262,99 @@ export default class StarterSelectUiHandler extends UiHandler { } setCursor(cursor: integer): boolean { - let changed: boolean = this.cursor !== cursor; + let changed = false; - if (changed) { - const forward = this.cursor < cursor; - this.cursor = cursor; + if (this.genMode) { + changed = this.genCursor !== cursor; + + if (this.genCursor !== undefined) + this.starterSelectGenIconContainers[this.genCursor].setVisible(false); + this.cursor = 0; + this.genCursor = cursor; + this.genCursorObj.setY(5 + 17 * this.genCursor); + this.genCursorHighlightObj.setY(this.genCursorObj.y); + this.starterSelectGenIconContainers[this.genCursor].setVisible(true); + + for (let s = 0; s < this.starterCursorObjs.length; s++) + this.starterCursorObjs[s].setVisible(this.starterGens[s] === cursor); + } else { + changed = super.setCursor(cursor); + + this.cursorObj.setPosition(148 + 18 * (cursor % 9), 10 + 18 * Math.floor(cursor / 9)); + + this.setSpecies(this.genSpecies[this.genCursor][cursor]); } return changed; } + + setGenMode(genMode: boolean): boolean { + if (genMode !== this.genMode) { + this.genMode = genMode; + + this.genCursorObj.setVisible(genMode); + this.cursorObj.setVisible(!genMode); + + this.setCursor(genMode ? this.genCursor : this.cursor); + if (genMode) + this.setSpecies(null); + + return true; + } + + return false; + } + + setSpecies(species: PokemonSpecies) { + this.pokemonSprite.setVisible(false); + + if (this.assetLoadCancelled) { + this.assetLoadCancelled.value = true; + this.assetLoadCancelled = null; + } + + if (this.lastSpecies) + (this.starterSelectGenIconContainers[this.lastSpecies.generation - 1].getAt(this.genSpecies[this.lastSpecies.generation - 1].indexOf(this.lastSpecies)) as Phaser.GameObjects.Sprite).stop(); + + if (species) { + this.pokemonNumberText.setText(Utils.padInt(species.speciesId, 3)); + this.pokemonNameText.setText(species.name.toUpperCase()); + + const assetLoadCancelled = new Utils.BooleanHolder(false); + this.assetLoadCancelled = assetLoadCancelled; + + const female = Utils.randInt(2) === 1; + species.loadAssets(this.scene, female, false, true).then(() => { + if (assetLoadCancelled.value) + return; + this.assetLoadCancelled = null; + this.speciesLoaded.set(species.speciesId, true); + this.pokemonSprite.play(species.getSpriteKey(female, false)); + this.pokemonSprite.setVisible(true); + }); + + (this.starterSelectGenIconContainers[this.genCursor].getAt(this.cursor) as Phaser.GameObjects.Sprite).play(species.getIconKey()); + } else { + this.pokemonNumberText.setText(Utils.padInt(0, 3)); + this.pokemonNameText.setText(''); + } + + this.lastSpecies = species; + } + + popStarter(): void { + this.starterGens.pop(); + this.starterCursors.pop(); + this.starterCursorObjs[this.starterCursors.length].setVisible(false); + this.starterIcons[this.starterCursors.length].play('pkmn_icon__000'); + } + + clearText() { + this.starterSelectMessageBoxContainer.setVisible(false); + super.clearText(); + } - clear() { + clear(): void { super.clear(); this.cursor = -1; this.starterSelectContainer.setVisible(false); diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 9c466c57a6b..ae43187fcb9 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -20,11 +20,11 @@ export enum Mode { BALL, MODIFIER_SELECT, PARTY, - CONFIRM, SUMMARY, BIOME_SELECT, STARTER_SELECT, - EVOLUTION_SCENE + EVOLUTION_SCENE, + CONFIRM }; const transitionModes = [ @@ -56,11 +56,11 @@ export default class UI extends Phaser.GameObjects.Container { new BallUiHandler(scene), new ModifierSelectUiHandler(scene), new PartyUiHandler(scene), - new ConfirmUiHandler(scene), new SummaryUiHandler(scene), new BiomeSelectUiHandler(scene), new StarterSelectUiHandler(scene), - new EvolutionSceneHandler(scene) + new EvolutionSceneHandler(scene), + new ConfirmUiHandler(scene) ]; } diff --git a/src/ui/uiHandler.ts b/src/ui/uiHandler.ts index 7a5b8270c4b..81c5ba92caa 100644 --- a/src/ui/uiHandler.ts +++ b/src/ui/uiHandler.ts @@ -18,7 +18,7 @@ export default abstract class UiHandler { this.active = true; } - abstract processInput(button: Button); + abstract processInput(button: Button): void; getUi() { return this.scene.ui; diff --git a/src/utils.ts b/src/utils.ts index 3dccaf031ff..996c980d08c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -62,6 +62,14 @@ export function getEnumValues(enumType): integer[] { return Object.values(enumType).filter(v => !isNaN(parseInt(v.toString()))).map(v => parseInt(v.toString())); } +export class BooleanHolder { + public value: boolean; + + constructor(value: boolean) { + this.value = value; + } +} + export class NumberHolder { public value: number;