diff --git a/gulp/sounds.js b/gulp/sounds.js index 24e45b27..dfd88237 100644 --- a/gulp/sounds.js +++ b/gulp/sounds.js @@ -9,7 +9,7 @@ function gulptasksSounds($, gulp, buildFolder) { return gulp.src(builtSoundsDir).pipe($.clean({ force: true })); }); - const filters = ["loudnorm", "volume=0.2"]; + const filters = ["volume=0.2"]; const fileCache = new $.cache.Cache({ cacheDirName: "shapezio-precompiled-sounds", @@ -27,8 +27,8 @@ function gulptasksSounds($, gulp, buildFolder) { .audioBitrate(48) .audioChannels(1) .audioFrequency(22050) - .audioCodec("libmp3lame"); - // .audioFilters(["volume=0.25"]) + .audioCodec("libmp3lame") + .audioFilters(["volume=0.3"]); }), { name: "music", diff --git a/res_raw/sounds/music/menu.mp3 b/res_raw/sounds/music/menu.mp3 new file mode 100644 index 00000000..8fdd13eb Binary files /dev/null and b/res_raw/sounds/music/menu.mp3 differ diff --git a/res_raw/sounds/music/theme.mp3 b/res_raw/sounds/music/theme.mp3 index 4b61dd27..485a553a 100644 Binary files a/res_raw/sounds/music/theme.mp3 and b/res_raw/sounds/music/theme.mp3 differ diff --git a/res_raw/sounds/ui/badge_notification.wav b/res_raw/sounds/ui/badge_notification.wav new file mode 100644 index 00000000..0af30494 --- /dev/null +++ b/res_raw/sounds/ui/badge_notification.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dba7e859fb98713943890281a543e209d57c6633496090bd70d23dc58bed4405 +size 743136 diff --git a/src/js/core/config.js b/src/js/core/config.js index 7b09443e..1d76ce6d 100644 --- a/src/js/core/config.js +++ b/src/js/core/config.js @@ -37,7 +37,7 @@ export const globalConfig = { // Belt speeds // NOTICE: Update webpack.production.config too! - beltSpeedItemsPerSecond: 1, + beltSpeedItemsPerSecond: 10, itemSpacingOnBelts: 0.63, minerSpeedItemsPerSecond: 0, // COMPUTED @@ -71,7 +71,7 @@ export const globalConfig = { debug: { /* dev:start */ - // fastGameEnter: true, + fastGameEnter: true, noArtificialDelays: true, // disableSavegameWrite: true, showEntityBounds: false, diff --git a/src/js/core/game_state.js b/src/js/core/game_state.js index 7454dcc8..cd3cb677 100644 --- a/src/js/core/game_state.js +++ b/src/js/core/game_state.js @@ -252,7 +252,7 @@ export class GameState { * @returns {string|null} */ getThemeMusic() { - return null; + return MUSIC.menu; } //////////////////// diff --git a/src/js/game/base_item.js b/src/js/game/base_item.js index a4bd5e68..c974315d 100644 --- a/src/js/game/base_item.js +++ b/src/js/game/base_item.js @@ -1,5 +1,6 @@ import { DrawParameters } from "../core/draw_parameters"; import { BasicSerializableObject, types } from "../savegame/serialization"; +import { THEME } from "./theme"; /** * Class for items on belts etc. Not an entity for performance reasons @@ -28,6 +29,6 @@ export class BaseItem extends BasicSerializableObject { draw(x, y, parameters, size) {} getBackgroundColorAsResource() { - return "#eaebec"; + abstract; } } diff --git a/src/js/game/hud/parts/game_menu.js b/src/js/game/hud/parts/game_menu.js index 74c4343e..d00a22e4 100644 --- a/src/js/game/hud/parts/game_menu.js +++ b/src/js/game/hud/parts/game_menu.js @@ -1,5 +1,6 @@ import { BaseHUDPart } from "../base_hud_part"; import { makeDiv, randomInt } from "../../../core/utils"; +import { SOUNDS } from "../../../platform/sound"; export class HUDGameMenu extends BaseHUDPart { initialize() {} @@ -60,12 +61,13 @@ export class HUDGameMenu extends BaseHUDPart { this.trackClicks(this.saveButton, this.startSave); this.musicButton.classList.toggle("muted", this.root.app.settings.getAllSettings().musicMuted); - this.sfxButton.classList.toggle("muted", this.root.app.settings.getAllSettings().musicMuted); + this.sfxButton.classList.toggle("muted", this.root.app.settings.getAllSettings().soundsMuted); this.root.signals.gameSaved.add(this.onGameSaved, this); } update() { + let playSound = false; for (let i = 0; i < this.badgesToUpdate.length; ++i) { const { badge, button, badgeElement, lastRenderAmount } = this.badgesToUpdate[i]; const amount = badge(); @@ -73,10 +75,18 @@ export class HUDGameMenu extends BaseHUDPart { if (amount > 0) { badgeElement.innerText = amount; } + // Check if the badge increased + if (amount > lastRenderAmount) { + playSound = true; + } this.badgesToUpdate[i].lastRenderAmount = amount; button.classList.toggle("hasBadge", amount > 0); } } + + if (playSound) { + this.root.soundProxy.playUi(SOUNDS.badgeNotification); + } } onGameSaved() { diff --git a/src/js/game/items/color_item.js b/src/js/game/items/color_item.js index f2b97298..ec555135 100644 --- a/src/js/game/items/color_item.js +++ b/src/js/game/items/color_item.js @@ -4,13 +4,7 @@ import { DrawParameters } from "../../core/draw_parameters"; import { types } from "../../savegame/serialization"; import { BaseItem } from "../base_item"; import { enumColors, enumColorsToHexCode } from "../colors"; - -/** @enum {string} */ -const enumColorToMapBackground = { - [enumColors.red]: "#ffbfc1", - [enumColors.green]: "#cbffc4", - [enumColors.blue]: "#bfdaff", -}; +import { THEME } from "../theme"; export class ColorItem extends BaseItem { static getId() { @@ -39,7 +33,7 @@ export class ColorItem extends BaseItem { } getBackgroundColorAsResource() { - return enumColorToMapBackground[this.color]; + return THEME.map.resources[this.color]; } /** @@ -75,8 +69,8 @@ export class ColorItem extends BaseItem { context.scale((dpi * w) / 12, (dpi * h) / 12); context.fillStyle = enumColorsToHexCode[this.color]; - context.strokeStyle = "rgba(100,102, 110, 1)"; - context.lineWidth = 2; + context.strokeStyle = THEME.items.outline; + context.lineWidth = 2 * THEME.items.outlineWidth; context.beginCircle(2, -1, 3); context.stroke(); context.fill(); diff --git a/src/js/game/items/shape_item.js b/src/js/game/items/shape_item.js index 3773ae84..cfdb9830 100644 --- a/src/js/game/items/shape_item.js +++ b/src/js/game/items/shape_item.js @@ -2,6 +2,7 @@ import { DrawParameters } from "../../core/draw_parameters"; import { types } from "../../savegame/serialization"; import { BaseItem } from "../base_item"; import { ShapeDefinition } from "../shape_definition"; +import { THEME } from "../theme"; export class ShapeItem extends BaseItem { static getId() { @@ -33,6 +34,10 @@ export class ShapeItem extends BaseItem { this.definition = definition; } + getBackgroundColorAsResource() { + return THEME.map.resources.shape; + } + /** * @param {number} x * @param {number} y diff --git a/src/js/game/map_view.js b/src/js/game/map_view.js index 6c42b266..90919a2a 100644 --- a/src/js/game/map_view.js +++ b/src/js/game/map_view.js @@ -4,6 +4,7 @@ import { DrawParameters } from "../core/draw_parameters"; import { BaseMap } from "./map"; import { freeCanvas, makeOffscreenBuffer } from "../core/buffer_utils"; import { Entity } from "./entity"; +import { THEME } from "./theme"; /** * This is the view of the map, it extends the map which is the raw model and allows @@ -16,7 +17,7 @@ export class MapView extends BaseMap { /** * DPI of the background cache images, required in some places */ - this.backgroundCacheDPI = 4; + this.backgroundCacheDPI = 2; /** * The cached background sprite, containing the flat background @@ -109,14 +110,16 @@ export class MapView extends BaseMap { }); context.scale(dpi, dpi); - context.fillStyle = "#fff"; + context.fillStyle = THEME.map.background; context.fillRect(0, 0, dims, dims); - context.fillStyle = "#fafafa"; - context.fillRect(0, 0, dims, 1); - context.fillRect(0, 0, 1, dims); - context.fillRect(dims - 1, 0, 1, dims); - context.fillRect(0, dims - 1, dims, 1); + const borderWidth = THEME.map.gridLineWidth; + context.fillStyle = THEME.map.grid; + context.fillRect(0, 0, dims, borderWidth); + context.fillRect(0, borderWidth, borderWidth, dims); + + context.fillRect(dims - borderWidth, borderWidth, borderWidth, dims - 2 * borderWidth); + context.fillRect(borderWidth, dims - borderWidth, dims, borderWidth); this.cachedBackgroundCanvas = canvas; this.cachedBackgroundContext = context; diff --git a/src/js/game/shape_definition.js b/src/js/game/shape_definition.js index 714bd2b2..bce1a18b 100644 --- a/src/js/game/shape_definition.js +++ b/src/js/game/shape_definition.js @@ -7,6 +7,7 @@ import { createLogger } from "../core/logging"; import { Vector } from "../core/vector"; import { BasicSerializableObject, types } from "../savegame/serialization"; import { enumColors, enumColorsToHexCode, enumColorToShortcode, enumShortcodeToColor } from "./colors"; +import { THEME } from "./theme"; const rusha = require("rusha"); @@ -274,8 +275,8 @@ export class ShapeDefinition extends BasicSerializableObject { context.rotate(rotation); context.fillStyle = enumColorsToHexCode[color]; - context.strokeStyle = "#555"; - context.lineWidth = 1; + context.strokeStyle = THEME.items.outline; + context.lineWidth = THEME.items.outlineWidth; const insetPadding = 0.0; diff --git a/src/js/game/theme.js b/src/js/game/theme.js new file mode 100644 index 00000000..198f9797 --- /dev/null +++ b/src/js/game/theme.js @@ -0,0 +1,7 @@ +export const THEMES = { + dark: require("./themes/dark.json"), + light: require("./themes/light.json"), +}; + +// TODO: Make themes customizable +export const THEME = THEMES.light; diff --git a/src/js/game/themes/dark.json b/src/js/game/themes/dark.json new file mode 100644 index 00000000..6d0c1b22 --- /dev/null +++ b/src/js/game/themes/dark.json @@ -0,0 +1,20 @@ +{ + "uiStyle": "dark", + "map": { + "background": "#2e2f37", + "grid": "rgba(255, 255, 255, 0.02)", + "gridLineWidth": 0.5, + + "resources": { + "shape": "#3d3f4a", + "red": "#4a3d3f", + "green": "#3e4a3d", + "blue": "#35384a" + } + }, + + "items": { + "outline": "#111418", + "outlineWidth": 0.75 + } +} diff --git a/src/js/game/themes/light.json b/src/js/game/themes/light.json new file mode 100644 index 00000000..c3431928 --- /dev/null +++ b/src/js/game/themes/light.json @@ -0,0 +1,20 @@ +{ + "uiStyle": "light", + "map": { + "background": "#fff", + "grid": "#fafafa", + "gridLineWidth": 1, + + "resources": { + "shape": "#eaebec", + "red": "#ffbfc1", + "green": "#cbffc4", + "blue": "#bfdaff" + } + }, + + "items": { + "outline": "#55575a", + "outlineWidth": 0.75 + } +} diff --git a/src/js/platform/browser/sound.js b/src/js/platform/browser/sound.js index 985e5db7..5a5d4c4c 100644 --- a/src/js/platform/browser/sound.js +++ b/src/js/platform/browser/sound.js @@ -74,10 +74,17 @@ class MusicInstance extends MusicInstanceInterface { autoplay: false, loop: true, html5: true, - volume: 0.3, + volume: 1, preload: true, pool: 2, + onunlock: () => { + if (this.playing) { + logger.log("Playing music after manual unlock"); + this.play(); + } + }, + onload: () => { resolve(); }, diff --git a/src/js/platform/sound.js b/src/js/platform/sound.js index f326e0dc..bdfa2474 100644 --- a/src/js/platform/sound.js +++ b/src/js/platform/sound.js @@ -18,6 +18,7 @@ export const SOUNDS = { dialogOk: "ui/dialog_ok.mp3", swishHide: "ui/ui_swish_hide.mp3", swishShow: "ui/ui_swish_show.mp3", + badgeNotification: "ui/badge_notification.mp3", levelComplete: "ui/level_complete.mp3", @@ -27,6 +28,7 @@ export const SOUNDS = { export const MUSIC = { theme: "theme.mp3", + menu: "menu.mp3", }; export class SoundInstanceInterface { diff --git a/src/js/profile/application_settings.js b/src/js/profile/application_settings.js index a4edf745..b86ba45d 100644 --- a/src/js/profile/application_settings.js +++ b/src/js/profile/application_settings.js @@ -6,6 +6,7 @@ import { ReadWriteProxy } from "../core/read_write_proxy"; import { BoolSetting, EnumSetting, BaseSetting } from "./setting_types"; import { createLogger } from "../core/logging"; import { ExplainedResult } from "../core/explained_result"; +import { THEMES } from "../game/theme"; const logger = createLogger("application_settings"); @@ -67,6 +68,18 @@ export const allApplicationSettings = [ }, G_IS_STANDALONE ), + new EnumSetting("theme", { + options: Object.keys(THEMES), + valueGetter: theme => theme, + textGetter: theme => theme.substr(0, 1).toUpperCase() + theme.substr(1), + category: categoryApp, + restartRequired: false, + changeCb: + /** + * @param {Application} app + */ + (app, id) => document.body.setAttribute("data-theme", id), + }), new BoolSetting( "soundsMuted", categoryApp, @@ -100,6 +113,7 @@ class SettingsStorage { this.soundsMuted = false; this.musicMuted = false; + this.theme = "light"; } } @@ -110,7 +124,17 @@ export class ApplicationSettings extends ReadWriteProxy { initialize() { // Read and directly write latest data back - return this.readAsync().then(() => this.writeAsync()); + return this.readAsync() + .then(() => { + // Apply default setting callbacks + const settings = this.getAllSettings(); + for (let i = 0; i < allApplicationSettings.length; ++i) { + const handle = allApplicationSettings[i]; + handle.apply(this.app, settings[handle.id]); + } + }) + + .then(() => this.writeAsync()); } save() { @@ -208,7 +232,7 @@ export class ApplicationSettings extends ReadWriteProxy { } getCurrentVersion() { - return 2; + return 3; } migrate(data) { diff --git a/src/js/profile/setting_types.js b/src/js/profile/setting_types.js index 04ed4e57..9e11d793 100644 --- a/src/js/profile/setting_types.js +++ b/src/js/profile/setting_types.js @@ -27,6 +27,16 @@ export class BaseSetting { this.dialogs = null; } + /** + * @param {Application} app + * @param {any} value + */ + apply(app, value) { + if (this.changeCb) { + this.changeCb(app, value); + } + } + /** * @param {Application} app * @param {Element} element diff --git a/src/js/states/preload.js b/src/js/states/preload.js index ba3b515f..6e885b15 100644 --- a/src/js/states/preload.js +++ b/src/js/states/preload.js @@ -99,17 +99,6 @@ export class PreloadState extends GameState { .then(() => { return this.app.settings.initialize(); }) - .then(() => { - // Make sure the app pickups the right size - this.app.updateAfterUiScaleChanged(); - }) - - .then(() => { - // Initialize fullscreen - if (this.app.platformWrapper.getSupportsFullscreen()) { - this.app.platformWrapper.setFullscreen(this.app.settings.getIsFullScreen()); - } - }) .then(() => this.setStatus("Initializing sounds")) .then(() => {