From d3f55ad4aea5a150430c95b4023c7ca0e362ea5d Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Thu, 26 Oct 2023 16:33:59 -0400 Subject: [PATCH] Add game settings --- src/battle-phases.ts | 7 +- src/battle-scene.ts | 120 ++++++++++++++++------- src/evolution-phase.ts | 6 +- src/pokemon.ts | 16 +--- src/system/game-data.ts | 35 ++++++- src/system/settings.ts | 52 ++++++++++ src/ui/message-ui-handler.ts | 2 +- src/ui/settings-ui-handler.ts | 174 ++++++++++++++++++++++++++++++++++ src/ui/text.ts | 10 +- src/ui/ui.ts | 25 ++++- 10 files changed, 388 insertions(+), 59 deletions(-) create mode 100644 src/system/settings.ts create mode 100644 src/ui/settings-ui-handler.ts diff --git a/src/battle-phases.ts b/src/battle-phases.ts index 9d62cda1e..660e6bb8e 100644 --- a/src/battle-phases.ts +++ b/src/battle-phases.ts @@ -99,7 +99,7 @@ export class SelectStarterPhase extends BattlePhase { start() { super.start(); - this.scene.playSound('menu', { loop: true }); + this.scene.playBgm('menu'); this.scene.ui.setMode(Mode.STARTER_SELECT, (starters: Starter[]) => { const party = this.scene.getParty(); @@ -2209,7 +2209,7 @@ export class UnlockPhase extends BattlePhase { this.scene.time.delayedCall(2000, () => { this.scene.gameData.unlocks[this.unlockable] = true; this.scene.gameData.saveSystem(); - this.scene.playSound('level_up_fanfare'); + this.scene.playSoundWithoutBgm('level_up_fanfare'); this.scene.ui.setMode(Mode.MESSAGE); this.scene.arenaBg.setVisible(false); this.scene.ui.fadeIn(250).then(() => { @@ -2857,8 +2857,7 @@ export class PartyHealPhase extends BattlePhase { move.ppUsed = 0; pokemon.updateInfo(true); } - const healSong = this.scene.sound.add('heal'); - healSong.play({ volume: this.scene.gameVolume }); + const healSong = this.scene.playSoundWithoutBgm('heal'); this.scene.time.delayedCall(healSong.totalDuration * 1000, () => { healSong.destroy(); if (this.resumeBgm && bgmPlaying) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index cd4efaaad..cf594568b 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1,6 +1,6 @@ import Phaser from 'phaser'; import { Biome } from './data/biome'; -import UI from './ui/ui'; +import UI, { Mode } from './ui/ui'; import { EncounterPhase, SummonPhase, NextEncounterPhase, NewBiomeEncounterPhase, SelectBiomePhase, MessagePhase, CheckLoadPhase, TurnInitPhase, ReturnPhase, ToggleDoublePositionPhase, CheckSwitchPhase, LevelCapPhase, TestMessagePhase, ShowTrainerPhase } from './battle-phases'; import Pokemon, { PlayerPokemon, EnemyPokemon } from './pokemon'; import PokemonSpecies, { PokemonSpeciesFilter, allSpecies, getPokemonSpecies, initSpecies } from './data/pokemon-species'; @@ -29,6 +29,9 @@ import TrainerData from './system/trainer-data'; import SoundFade from 'phaser3-rex-plugins/plugins/soundfade'; import { pokemonPrevolutions } from './data/pokemon-evolutions'; import PokeballTray from './ui/pokeball-tray'; +import { Setting, settingOptions } from './system/settings'; +import SettingsUiHandler from './ui/settings-ui-handler'; +import MessageUiHandler from './ui/message-ui-handler'; const enableAuto = true; const quickStart = false; @@ -44,6 +47,7 @@ export enum Button { RIGHT, ACTION, CANCEL, + MENU, CYCLE_SHINY, CYCLE_FORM, CYCLE_GENDER, @@ -58,9 +62,13 @@ export interface PokeballCounts { [pb: string]: integer; } +export type AnySound = Phaser.Sound.WebAudioSound | Phaser.Sound.HTML5AudioSound | Phaser.Sound.NoAudioSound; + export default class BattleScene extends Phaser.Scene { public auto: boolean; - public gameVolume: number = 0.5; + public masterVolume: number = 0.5; + public bgmVolume: number = 1; + public seVolume: number = 1; public gameSpeed: integer = 1; public quickStart: boolean = quickStart; public finalWave: integer = 200; @@ -105,8 +113,9 @@ export default class BattleScene extends Phaser.Scene { public spritePipeline: SpritePipeline; - private bgm: Phaser.Sound.BaseSound; + private bgm: AnySound; private bgmResumeTimer: Phaser.Time.TimerEvent; + private bgmCache: Set = new Set(); private buttonKeys: Phaser.Input.Keyboard.Key[][]; @@ -484,7 +493,8 @@ export default class BattleScene extends Phaser.Scene { [Button.LEFT]: [keyCodes.LEFT, keyCodes.A], [Button.RIGHT]: [keyCodes.RIGHT, keyCodes.D], [Button.ACTION]: [keyCodes.ENTER, keyCodes.SPACE, keyCodes.Z], - [Button.CANCEL]: [keyCodes.BACKSPACE, keyCodes.ESC, keyCodes.X], + [Button.CANCEL]: [keyCodes.BACKSPACE, keyCodes.X], + [Button.MENU]: [keyCodes.ESC, keyCodes.M], [Button.CYCLE_SHINY]: [keyCodes.R], [Button.CYCLE_FORM]: [keyCodes.F], [Button.CYCLE_GENDER]: [keyCodes.G], @@ -813,6 +823,32 @@ export default class BattleScene extends Phaser.Scene { this.ui.processInput(Button.ACTION); else if (this.isButtonPressed(Button.CANCEL)) this.ui.processInput(Button.CANCEL); + else if (this.isButtonPressed(Button.MENU)) { + switch (this.ui.getMode()) { + case Mode.MESSAGE: + if (!(this.ui.getHandler() as MessageUiHandler).pendingPrompt) + return; + case Mode.COMMAND: + case Mode.FIGHT: + case Mode.BALL: + case Mode.TARGET_SELECT: + case Mode.PARTY: + case Mode.SUMMARY: + case Mode.BIOME_SELECT: + case Mode.STARTER_SELECT: + case Mode.CONFIRM: + case Mode.GAME_MODE_SELECT: + this.ui.setModeWithoutClear(Mode.SETTINGS); + this.playSound('menu_open'); + break; + case Mode.SETTINGS: + this.ui.revertMode(); + this.playSound('select'); + break; + default: + return; + } + } else if (this.ui?.getHandler() instanceof StarterSelectUiHandler) { if (this.isButtonPressed(Button.CYCLE_SHINY)) this.ui.processInput(Button.CYCLE_SHINY); @@ -826,25 +862,24 @@ export default class BattleScene extends Phaser.Scene { return; } else if (this.isButtonPressed(Button.SPEED_UP)) { - if (!this.auto) { - if (this.gameSpeed < 2.5) - this.gameSpeed += 0.25; - } else if (this.gameSpeed < 20) - this.gameSpeed++; + if (this.gameSpeed < 5) { + this.gameData.saveSetting(Setting.Game_Speed, settingOptions[Setting.Game_Speed].indexOf(`${this.gameSpeed}x`) + 1); + if (this.ui.getMode() === Mode.SETTINGS) + (this.ui.getHandler() as SettingsUiHandler).show([]); + } } else if (this.isButtonPressed(Button.SLOW_DOWN)) { if (this.gameSpeed > 1) { - if (!this.auto) - this.gameSpeed -= 0.25; - else - this.gameSpeed--; + this.gameData.saveSetting(Setting.Game_Speed, Math.max(settingOptions[Setting.Game_Speed].indexOf(`${this.gameSpeed}x`) - 1, 0)); + if (this.ui.getMode() === Mode.SETTINGS) + (this.ui.getHandler() as SettingsUiHandler).show([]); } } else if (enableAuto) { if (this.isButtonPressed(Button.AUTO)) { this.auto = !this.auto; if (this.auto) this.gameSpeed = Math.floor(this.gameSpeed); - else if (this.gameSpeed > 2.5) - this.gameSpeed = 2.5; + else if (this.gameSpeed > 5) + this.gameSpeed = 5; } else return; } else @@ -867,13 +902,14 @@ export default class BattleScene extends Phaser.Scene { if (this.bgm && bgmName === this.bgm.key) { if (!this.bgm.isPlaying) { this.bgm.play({ - volume: this.gameVolume + volume: this.masterVolume * this.bgmVolume }); } return; } if (fadeOut && !this.bgm) fadeOut = false; + this.bgmCache.add(bgmName); this.loadBgm(bgmName); let loopPoint = 0; loopPoint = bgmName === this.arena.bgm @@ -883,7 +919,7 @@ export default class BattleScene extends Phaser.Scene { const playNewBgm = () => { if (bgmName === null && this.bgm && !this.bgm.pendingRemove) { this.bgm.play({ - volume: this.gameVolume + volume: this.masterVolume * this.bgmVolume }); return; } @@ -891,7 +927,7 @@ export default class BattleScene extends Phaser.Scene { this.bgm.stop(); this.bgm = this.sound.add(bgmName, { loop: true }); this.bgm.play({ - volume: this.gameVolume + volume: this.masterVolume * this.bgmVolume }); if (loopPoint) this.bgm.on('looped', () => this.bgm.play({ seek: loopPoint })); @@ -912,14 +948,27 @@ export default class BattleScene extends Phaser.Scene { this.load.start(); } - pauseBgm(): void { - if (this.bgm && this.bgm.isPlaying) + pauseBgm(): boolean { + if (this.bgm && this.bgm.isPlaying) { this.bgm.pause(); + return true; + } + return false; } - resumeBgm(): void { - if (this.bgm && this.bgm.isPaused) + resumeBgm(): boolean { + if (this.bgm && this.bgm.isPaused) { this.bgm.resume(); + return true; + } + return false; + } + + updateSoundVolume(): void { + if (this.sound) { + for (let sound of this.sound.getAllPlaying()) + (sound as AnySound).setVolume(this.masterVolume * (this.bgmCache.has(sound.key) ? this.bgmVolume : this.seVolume)); + } } fadeOutBgm(duration?: integer, destroy?: boolean): boolean { @@ -938,27 +987,32 @@ export default class BattleScene extends Phaser.Scene { return false; } - playSound(soundName: string, config?: object) { + playSound(soundName: string, config?: object): AnySound { if (config) { if (config.hasOwnProperty('volume')) - config['volume'] *= this.gameVolume; + config['volume'] *= this.masterVolume * this.seVolume; else - config['volume'] = this.gameVolume; + config['volume'] = this.masterVolume * this.seVolume; } else - config = { volume: this.gameVolume }; + config = { volume: this.masterVolume * this.seVolume }; this.sound.play(soundName, config); + return this.sound.get(soundName) as AnySound; } - playSoundWithoutBgm(soundName: string, pauseDuration?: integer): void { - this.pauseBgm(); + playSoundWithoutBgm(soundName: string, pauseDuration?: integer): AnySound { + this.bgmCache.add(soundName); + const resumeBgm = this.pauseBgm(); this.playSound(soundName); - const sound = this.sound.get(soundName); + const sound = this.sound.get(soundName) as AnySound; if (this.bgmResumeTimer) this.bgmResumeTimer.destroy(); - this.bgmResumeTimer = this.time.delayedCall((pauseDuration || Utils.fixedInt(sound.totalDuration * 1000)), () => { - this.resumeBgm(); - this.bgmResumeTimer = null; - }); + if (resumeBgm) { + this.bgmResumeTimer = this.time.delayedCall((pauseDuration || Utils.fixedInt(sound.totalDuration * 1000)), () => { + this.resumeBgm(); + this.bgmResumeTimer = null; + }); + } + return sound; } getBgmLoopPoint(bgmName: string): number { diff --git a/src/evolution-phase.ts b/src/evolution-phase.ts index f16c2a01a..9e53e06fa 100644 --- a/src/evolution-phase.ts +++ b/src/evolution-phase.ts @@ -101,8 +101,8 @@ export class EvolutionPhase extends BattlePhase { this.scene.unshiftPhase(new EndEvolutionPhase(this.scene)); this.scene.time.delayedCall(1000, () => { - const evolutionBgm = this.scene.sound.add('evolution'); - evolutionBgm.play({ volume: this.scene.gameVolume }); + const evolutionBgm = this.scene.playSoundWithoutBgm('evolution'); + evolutionBgm.play({ volume: this.scene.masterVolume }); this.scene.tweens.add({ targets: this.evolutionBgOverlay, alpha: 1, @@ -167,7 +167,7 @@ export class EvolutionPhase extends BattlePhase { this.scene.time.delayedCall(250, () => { pokemon.cry(); this.scene.time.delayedCall(1250, () => { - this.scene.playSound('evolution_fanfare'); + this.scene.playSoundWithoutBgm('evolution_fanfare'); this.scene.ui.showText(`Congratulations! Your ${preName}\nevolved into ${pokemon.name}!`, null, () => this.end(), null, true, 3000); this.scene.time.delayedCall(Utils.fixedInt(4250), () => this.scene.playBgm()); }); diff --git a/src/pokemon.ts b/src/pokemon.ts index 38de8d031..780ced83f 100644 --- a/src/pokemon.ts +++ b/src/pokemon.ts @@ -1,5 +1,5 @@ import Phaser from 'phaser'; -import BattleScene from './battle-scene'; +import BattleScene, { AnySound } from './battle-scene'; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from './ui/battle-info'; import Move, { StatChangeAttr, HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariablePowerAttr, Moves, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, AttackMove, AddBattlerTagAttr } from "./data/move"; import { pokemonLevelMoves } from './data/pokemon-level-moves'; @@ -885,7 +885,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const key = this.species.speciesId.toString(); let i = 0; let rate = 0.85; - this.scene.playSound(key, { rate: rate }); + const crySound = this.scene.playSound(key, { rate: rate }) as AnySound; const sprite = this.getSprite(); const tintSprite = this.getTintSprite(); const delay = Math.max(this.scene.sound.get(key).totalDuration * 50, 25); @@ -894,7 +894,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { sprite.anims.pause(); tintSprite.anims.pause(); let faintCryTimer = this.scene.time.addEvent({ - delay: delay, + delay: Utils.fixedInt(delay), repeat: -1, callback: () => { ++i; @@ -907,14 +907,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } frameProgress -= frameThreshold; } - const crySound = this.scene.sound.get(key); if (crySound && !crySound.pendingRemove) { rate *= 0.99; - crySound.play({ - rate: rate, - seek: (i * delay * 0.001) * rate, - volume: this.scene.gameVolume - }); + crySound.setRate(rate); } else { faintCryTimer.destroy(); @@ -925,10 +920,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } }); // Failsafe - this.scene.time.delayedCall(3000, () => { + this.scene.time.delayedCall(Utils.fixedInt(3000), () => { if (!faintCryTimer || !this.scene) return; - const crySound = this.scene.sound.get(key); if (crySound?.isPlaying) crySound.stop(); faintCryTimer.destroy(); diff --git a/src/system/game-data.ts b/src/system/game-data.ts index e4873e3dd..28ba02c4d 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -14,6 +14,7 @@ import { GameMode } from "../game-mode"; import { BattleType } from "../battle"; import TrainerData from "./trainer-data"; import { trainerConfigs } from "../data/trainer-type"; +import { Setting, setSetting, settingDefaults } from "./settings"; interface SystemSaveData { trainerId: integer; @@ -81,8 +82,9 @@ export class GameData { constructor(scene: BattleScene) { this.scene = scene; - this.trainerId = Utils.randInt(65536); - this.secretId = Utils.randInt(65536); + this.loadSettings(); + this.trainerId = Utils.randSeedInt(65536); + this.secretId = Utils.randSeedInt(65536); this.unlocks = { [Unlockables.ENDLESS_MODE]: false, [Unlockables.MINI_BLACK_HOLE]: false @@ -109,7 +111,7 @@ export class GameData { } private loadSystem(): boolean { - if (!localStorage.getItem('data')) + if (!localStorage.hasOwnProperty('data')) return false; const data = JSON.parse(atob(localStorage.getItem('data'))) as SystemSaveData; @@ -133,6 +135,33 @@ export class GameData { return true; } + public saveSetting(setting: Setting, valueIndex: integer): boolean { + let settings: object = {}; + if (localStorage.hasOwnProperty('settings')) + settings = JSON.parse(localStorage.getItem('settings')); + + setSetting(this.scene, setting as Setting, valueIndex); + + Object.keys(settingDefaults).forEach(s => { + if (s === setting) + settings[s] = valueIndex; + }); + + localStorage.setItem('settings', JSON.stringify(settings)); + + return true; + } + + private loadSettings(): boolean { + if (!localStorage.hasOwnProperty('settings')) + return false; + + const settings = JSON.parse(localStorage.getItem('settings')); + + for (let setting of Object.keys(settings)) + setSetting(this.scene, setting as Setting, settings[setting]); + } + saveSession(scene: BattleScene): boolean { const sessionData = { seed: scene.seed, diff --git a/src/system/settings.ts b/src/system/settings.ts new file mode 100644 index 000000000..a407abde6 --- /dev/null +++ b/src/system/settings.ts @@ -0,0 +1,52 @@ +import BattleScene from "../battle-scene"; + +export enum Setting { + Game_Speed = "GAME_SPEED", + Master_Volume = "MASTER_VOLUME", + BGM_Volume = "BGM_VOLUME", + SE_Volume = "SE_VOLUME" +} + +export interface SettingOptions { + [key: string]: string[] +} + +export interface SettingDefaults { + [key: string]: integer +} + +export const settingOptions: SettingOptions = { + [Setting.Game_Speed]: [ '1x', '1.25x', '1.5x', '2x', '2.5x', '3x', '4x', '5x' ], + [Setting.Master_Volume]: new Array(11).fill(null).map((_, i) => i ? (i * 10).toString() : 'Mute'), + [Setting.BGM_Volume]: new Array(11).fill(null).map((_, i) => i ? (i * 10).toString() : 'Mute'), + [Setting.SE_Volume]: new Array(11).fill(null).map((_, i) => i ? (i * 10).toString() : 'Mute') +}; + +export const settingDefaults: SettingDefaults = { + [Setting.Game_Speed]: 0, + [Setting.Master_Volume]: 5, + [Setting.BGM_Volume]: 10, + [Setting.SE_Volume]: 10 +}; + +export function setSetting(scene: BattleScene, setting: Setting, value: integer): boolean { + switch (setting) { + case Setting.Game_Speed: + scene.gameSpeed = parseFloat(settingOptions[setting][value].replace('x', '')); + break; + case Setting.Master_Volume: + scene.masterVolume = value ? parseInt(settingOptions[setting][value]) * 0.01 : 0; + scene.updateSoundVolume(); + break; + case Setting.BGM_Volume: + scene.bgmVolume = value ? parseInt(settingOptions[setting][value]) * 0.01 : 0; + scene.updateSoundVolume(); + break; + case Setting.SE_Volume: + scene.seVolume = value ? parseInt(settingOptions[setting][value]) * 0.01 : 0; + scene.updateSoundVolume(); + break; + } + + return true; +} \ No newline at end of file diff --git a/src/ui/message-ui-handler.ts b/src/ui/message-ui-handler.ts index bd183faaf..faeeb9079 100644 --- a/src/ui/message-ui-handler.ts +++ b/src/ui/message-ui-handler.ts @@ -6,7 +6,7 @@ import * as Utils from "../utils"; export default abstract class MessageUiHandler extends AwaitableUiHandler { protected textTimer: Phaser.Time.TimerEvent; protected textCallbackTimer: Phaser.Time.TimerEvent; - protected pendingPrompt: boolean; + public pendingPrompt: boolean; public message: Phaser.GameObjects.Text; public prompt: Phaser.GameObjects.Sprite; diff --git a/src/ui/settings-ui-handler.ts b/src/ui/settings-ui-handler.ts new file mode 100644 index 000000000..97f723a9a --- /dev/null +++ b/src/ui/settings-ui-handler.ts @@ -0,0 +1,174 @@ +import BattleScene, { Button } from "../battle-scene"; +import { Setting, settingDefaults, settingOptions } from "../system/settings"; +import { TextStyle, addTextObject, getTextColor } from "./text"; +import { Mode } from "./ui"; +import UiHandler from "./uiHandler"; + +export default class SettingsUiHandler extends UiHandler { + private settingsContainer: Phaser.GameObjects.Container; + private optionsContainer: Phaser.GameObjects.Container; + + private optionsBg: Phaser.GameObjects.NineSlice; + + private optionCursors: integer[]; + + private optionValueLabels: Phaser.GameObjects.Text[][]; + + private cursorObj: Phaser.GameObjects.NineSlice; + + constructor(scene: BattleScene, mode?: Mode) { + super(scene, mode); + } + + setup() { + const ui = this.getUi(); + + this.settingsContainer = this.scene.add.container(1, -(this.scene.game.canvas.height / 6) + 1); + + const headerBg = this.scene.add.nineslice(0, 0, 'window', null, (this.scene.game.canvas.width / 6) - 2, 24, 6, 6, 6, 6); + headerBg.setOrigin(0, 0); + + const headerText = addTextObject(this.scene, 0, 0, 'Options', TextStyle.SETTINGS_LABEL); + headerText.setOrigin(0, 0); + headerText.setPositionRelative(headerBg, 8, 4); + + this.optionsBg = this.scene.add.nineslice(0, headerBg.height, 'window', null, (this.scene.game.canvas.width / 6) - 2, (this.scene.game.canvas.height / 6) - headerBg.height - 2, 6, 6, 6, 6); + this.optionsBg.setOrigin(0, 0); + + this.optionsContainer = this.scene.add.container(0, 0); + + this.optionValueLabels = []; + + Object.keys(Setting).forEach((setting, s) => { + const settingLabel = addTextObject(this.scene, 8, 28 + s * 16, setting.replace(/\_/g, ' '), TextStyle.SETTINGS_LABEL); + settingLabel.setOrigin(0, 0); + + this.optionsContainer.add(settingLabel); + + this.optionValueLabels.push(settingOptions[Setting[setting]].map((option, o) => { + const valueLabel = addTextObject(this.scene, 0, 0, option, settingDefaults[Setting[setting]] === o ? TextStyle.SETTINGS_SELECTED : TextStyle.WINDOW); + valueLabel.setOrigin(0, 0); + + this.optionsContainer.add(valueLabel); + + return valueLabel; + })); + + const totalWidth = this.optionValueLabels[s].map(o => o.width).reduce((total, width) => total += width, 0); + + const totalSpace = 220 - totalWidth / 6; + const optionSpacing = Math.floor(totalSpace / (this.optionValueLabels[s].length - 1)); + + let xOffset = 0; + + for (let value of this.optionValueLabels[s]) { + value.setPositionRelative(settingLabel, 82 + xOffset, 0); + xOffset += value.width / 6 + optionSpacing; + } + }); + + this.optionCursors = Object.values(settingDefaults); + + this.settingsContainer.add(headerBg); + this.settingsContainer.add(headerText); + this.settingsContainer.add(this.optionsBg); + this.settingsContainer.add(this.optionsContainer); + + ui.add(this.settingsContainer); + + this.setCursor(0); + + this.settingsContainer.setVisible(false); + } + + show(args: any[]) { + super.show(args); + + const settings: object = localStorage.hasOwnProperty('settings') ? JSON.parse(localStorage.getItem('settings')) : {}; + + Object.keys(settingDefaults).forEach((setting, s) => { + this.setOptionCursor(s, settings.hasOwnProperty(setting) ? settings[setting] : settingDefaults[setting]); + }); + + this.settingsContainer.setVisible(true); + this.setCursor(0); + + this.getUi().moveTo(this.settingsContainer, this.getUi().length - 1); + } + + processInput(button: Button) { + const ui = this.getUi(); + + let success = false; + + if (button === Button.CANCEL) { + success = true; + this.scene.ui.revertMode(); + } else { + switch (button) { + case Button.UP: + if (this.cursor) + success = this.setCursor(this.cursor - 1); + break; + case Button.DOWN: + if (this.cursor < this.optionValueLabels.length - 1) + success = this.setCursor(this.cursor + 1); + break; + case Button.LEFT: + if (this.optionCursors[this.cursor]) + success = this.setOptionCursor(this.cursor, this.optionCursors[this.cursor] - 1, true); + break; + case Button.RIGHT: + if (this.optionCursors[this.cursor] < this.optionValueLabels[this.cursor].length - 1) + success = this.setOptionCursor(this.cursor, this.optionCursors[this.cursor] + 1, true); + break; + } + } + + if (success) + ui.playSelect(); + } + + setCursor(cursor: integer): boolean { + const ret = super.setCursor(cursor); + + if (!this.cursorObj) { + this.cursorObj = this.scene.add.nineslice(0, 0, 'starter_select_cursor_highlight', null, (this.scene.game.canvas.width / 6) - 10, 16, 1, 1, 1, 1); + this.cursorObj.setOrigin(0, 0); + this.optionsContainer.add(this.cursorObj); + } + + this.cursorObj.setPositionRelative(this.optionsBg, 4, 4 + this.cursor * 16); + + return ret; + } + + setOptionCursor(settingIndex: integer, cursor: integer, save?: boolean): boolean { + const lastValueLabel = this.optionValueLabels[settingIndex][this.optionCursors[settingIndex]]; + lastValueLabel.setColor(getTextColor(TextStyle.WINDOW)); + lastValueLabel.setShadowColor(getTextColor(TextStyle.WINDOW, true)); + + this.optionCursors[settingIndex] = cursor; + + const newValueLabel = this.optionValueLabels[settingIndex][cursor]; + newValueLabel.setColor(getTextColor(TextStyle.SETTINGS_SELECTED)); + newValueLabel.setShadowColor(getTextColor(TextStyle.SETTINGS_SELECTED, true)); + + if (save) + this.scene.gameData.saveSetting(Setting[Object.keys(Setting)[settingIndex]], cursor); + + return true; + } + + clear() { + super.clear(); + this.settingsContainer.setVisible(false); + this.eraseCursor(); + } + + eraseCursor() { + if (this.cursorObj) + this.cursorObj.destroy(); + this.cursorObj = null; + } +} \ No newline at end of file diff --git a/src/ui/text.ts b/src/ui/text.ts index 7ff32520e..c4778e970 100644 --- a/src/ui/text.ts +++ b/src/ui/text.ts @@ -7,7 +7,9 @@ export enum TextStyle { SUMMARY, SUMMARY_RED, SUMMARY_GOLD, - MONEY + MONEY, + SETTINGS_LABEL, + SETTINGS_SELECTED }; export function addTextObject(scene: Phaser.Scene, x: number, y: number, content: string, style: TextStyle, extraStyleOptions?: Phaser.Types.GameObjects.Text.TextStyle) { @@ -29,6 +31,8 @@ export function addTextObject(scene: Phaser.Scene, x: number, y: number, content case TextStyle.SUMMARY_GOLD: case TextStyle.WINDOW: case TextStyle.MESSAGE: + case TextStyle.SETTINGS_LABEL: + case TextStyle.SETTINGS_SELECTED: styleOptions.fontSize = '96px'; break; case TextStyle.BATTLE_INFO: @@ -80,5 +84,9 @@ export function getTextColor(textStyle: TextStyle, shadow?: boolean) { case TextStyle.SUMMARY_GOLD: case TextStyle.MONEY: return !shadow ? '#e8e8a8' : '#a0a060' + case TextStyle.SETTINGS_LABEL: + return !shadow ? '#f8b050' : '#c07800'; + case TextStyle.SETTINGS_SELECTED: + return !shadow ? '#f88880' : '#f83018'; } } \ No newline at end of file diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 4c155596d..e95a2c1f7 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -14,6 +14,7 @@ import EvolutionSceneHandler from './evolution-scene-handler'; import BiomeSelectUiHandler from './biome-select-ui-handler'; import TargetSelectUiHandler from './target-select-ui-handler'; import GameModeSelectUiHandler from './game-mode-select-ui-handler'; +import SettingsUiHandler from './settings-ui-handler'; export enum Mode { MESSAGE, @@ -28,7 +29,8 @@ export enum Mode { STARTER_SELECT, EVOLUTION_SCENE, CONFIRM, - GAME_MODE_SELECT + GAME_MODE_SELECT, + SETTINGS }; const transitionModes = [ @@ -40,11 +42,13 @@ const transitionModes = [ const noTransitionModes = [ Mode.CONFIRM, - Mode.GAME_MODE_SELECT + Mode.GAME_MODE_SELECT, + Mode.SETTINGS ]; export default class UI extends Phaser.GameObjects.Container { private mode: Mode; + private lastMode: Mode; private handlers: UiHandler[]; private overlay: Phaser.GameObjects.Rectangle; @@ -67,7 +71,8 @@ export default class UI extends Phaser.GameObjects.Container { new StarterSelectUiHandler(scene), new EvolutionSceneHandler(scene), new ConfirmUiHandler(scene), - new GameModeSelectUiHandler(scene) + new GameModeSelectUiHandler(scene), + new SettingsUiHandler(scene) ]; } @@ -181,6 +186,7 @@ export default class UI extends Phaser.GameObjects.Container { if (this.mode !== mode) { if (clear) this.getHandler().clear(); + this.lastMode = this.mode && !clear ? this.mode : undefined; this.mode = mode; this.getHandler().show(args); } @@ -199,6 +205,10 @@ export default class UI extends Phaser.GameObjects.Container { }); } + getMode(): Mode { + return this.mode; + } + setMode(mode: Mode, ...args: any[]): Promise { return this.setModeInternal(mode, true, false, args); } @@ -210,4 +220,13 @@ export default class UI extends Phaser.GameObjects.Container { setModeWithoutClear(mode: Mode, ...args: any[]): Promise { return this.setModeInternal(mode, false, false, args); } + + revertMode(): void { + if (!this.lastMode) + return; + + this.getHandler().clear(); + this.mode = this.lastMode; + this.lastMode = undefined; + } } \ No newline at end of file