Properly implement sound and music volumes, debounce writes

pull/655/head
tobspr 4 years ago
parent 50e40888fd
commit 6042fcba62

@ -36,6 +36,7 @@
"core-js": "3",
"crc": "^3.8.0",
"cssnano-preset-advanced": "^4.0.7",
"debounce-promise": "^3.1.2",
"email-validator": "^2.0.4",
"eslint": "7.1.0",
"fastdom": "^1.0.8",

@ -351,12 +351,12 @@ canvas {
box-sizing: border-box;
}
.pressed {
.pressed:not(.noPressEffect) {
transform: scale(0.98) !important;
animation: none !important;
}
.pressedSmallElement {
.pressedSmallElement:not(.noPressEffect) {
transform: scale(0.88) !important;
animation: none !important;
}
@ -570,36 +570,46 @@ canvas {
}
}
.range {
.rangeInputContainer {
display: flex;
align-items: center;
justify-content: center;
label {
@include S(margin-right, 5px);
&,
& * {
@include PlainText;
}
}
}
.range-input {
input.rangeInput {
cursor: pointer;
background-color: transparent;
width: 100px;
height: 10px;
transform: translate(7px, 2px);
@include S(width, 100px);
@include S(height, 16px);
&::-webkit-slider-runnable-track {
background-color: darken($mainBgColor, 3);
color: darken($mainBgColor, 3);
height: 16px;
border-radius: 8px;
// @include S(height, 16px);
@include S(border-radius, 8px);
}
&::-webkit-slider-thumb {
appearance: none;
-webkit-appearance: none;
box-shadow: inset 0 0 0 10px $themeColor;
background-color: transparent;
width: 20px;
height: 20px;
box-shadow: inset 0 0 0 D(10px) $themeColor;
border-radius: 50%;
transition: 0.3s;
transition: box-shadow 0.3s;
}
&:hover::-webkit-slider-thumb {
box-shadow: inset 0 0 0 10px lighten($themeColor, 15);
&:hover {
&::-webkit-slider-thumb {
box-shadow: inset 0 0 0 D(10px) lighten($themeColor, 15);
}
}
}

@ -34,19 +34,6 @@
&:hover {
opacity: 0.8;
}
&.music {
background-image: uiResource("icons/music_on.png");
&.muted {
background-image: uiResource("icons/music_off.png");
}
}
&.sfx {
background-image: uiResource("icons/sound_on.png");
&.muted {
background-image: uiResource("icons/sound_off.png");
}
}
&.save {
background-image: uiResource("icons/save.png");

@ -22,11 +22,6 @@
@include S(border-radius, $globalBorderRadius);
@include S(margin-bottom, 5px);
label {
text-transform: uppercase;
@include Text;
}
.desc {
@include S(margin-top, 5px);
@include SuperSmallText;
@ -37,6 +32,11 @@
display: grid;
align-items: center;
grid-template-columns: 1fr auto;
> label {
text-transform: uppercase;
@include Text;
}
}
&.disabled {

@ -18,6 +18,7 @@ export const CHANGELOG = [
"Tier 2 tunnels are now 9 tiles wide, so the gap between is 8 tiles (double the tier 1 range)",
"Updated and added new translations (Thanks to all contributors!)",
"Added setting to be able to delete buildings while placing (inspired by hexy)",
"You can now adjust the sound and music volumes! (inspired by Yoshie2000)",
"Mark pinned shapes in statistics dialog and show them first (inspired by davidburhans)",
"Added setting to show chunk borders",
"Quad painters have been reworked! They now are integrated with the wires, and only paint the shape when the value is 1 (inspired by dengr1605)",

@ -12,6 +12,8 @@ import { decompressX64, compressX64 } from "./lzstring";
import { asyncCompressor, compressionPrefix } from "./async_compression";
import { compressObject, decompressObject } from "../savegame/savegame_compressor";
const debounce = require("debounce-promise");
const logger = createLogger("read_write_proxy");
const salt = accessNestedPropertyReverse(globalConfig, ["file", "info"]);
@ -36,6 +38,11 @@ export class ReadWriteProxy {
);
});
}
/**
* Store a debounced handler to prevent double writes
*/
this.debouncedWrite = debounce(this.doWriteAsync.bind(this), 50);
}
// -- Methods to override
@ -122,7 +129,8 @@ export class ReadWriteProxy {
}
/**
* Writes the data asychronously, fails if verify() fails
* Writes the data asychronously, fails if verify() fails.
* Debounces the operation by up to 50ms
* @returns {Promise<void>}
*/
writeAsync() {
@ -133,6 +141,14 @@ export class ReadWriteProxy {
return Promise.reject(verifyResult.reason);
}
return this.debouncedWrite();
}
/**
* Actually writes the data asychronously
* @returns {Promise<void>}
*/
doWriteAsync() {
return asyncCompressor
.compressObjectAsync(this.currentData)
.then(compressed => {

@ -88,19 +88,13 @@ export class HUDGameMenu extends BaseHUDPart {
const menuButtons = makeDiv(this.element, null, ["menuButtons"]);
this.musicButton = makeDiv(menuButtons, null, ["button", "music"]);
this.sfxButton = makeDiv(menuButtons, null, ["button", "sfx"]);
this.saveButton = makeDiv(menuButtons, null, ["button", "save", "animEven"]);
this.settingsButton = makeDiv(menuButtons, null, ["button", "settings"]);
this.trackClicks(this.musicButton, this.toggleMusic);
this.trackClicks(this.sfxButton, this.toggleSfx);
this.trackClicks(this.saveButton, this.startSave);
this.trackClicks(this.settingsButton, this.openSettings);
this.musicButton.classList.toggle("muted", this.root.app.settings.getAllSettings().musicMuted);
this.sfxButton.classList.toggle("muted", this.root.app.settings.getAllSettings().soundsMuted);
}
initialize() {
this.root.signals.gameSaved.add(this.onGameSaved, this);
}
@ -111,7 +105,7 @@ export class HUDGameMenu extends BaseHUDPart {
// Update visibility of buttons
for (let i = 0; i < this.visibilityToUpdate.length; ++i) {
const { button, condition, domAttach } = this.visibilityToUpdate[i];
const { condition, domAttach } = this.visibilityToUpdate[i];
domAttach.update(condition());
}
@ -172,17 +166,4 @@ export class HUDGameMenu extends BaseHUDPart {
openSettings() {
this.root.hud.parts.settingsMenu.show();
}
toggleMusic() {
const newValue = !this.root.app.settings.getAllSettings().musicMuted;
this.root.app.settings.updateSetting("musicMuted", newValue);
this.musicButton.classList.toggle("muted", newValue);
}
toggleSfx() {
const newValue = !this.root.app.settings.getAllSettings().soundsMuted;
this.root.app.settings.updateSetting("soundsMuted", newValue);
this.sfxButton.classList.toggle("muted", newValue);
}
}

@ -185,6 +185,9 @@ export class SoundImplBrowser extends SoundInterface {
}
initialize() {
// NOTICE: We override the initialize() method here with custom logic because
// we have a sound sprites instance
this.sfxHandle = new SoundSpritesContainer();
// @ts-ignore
@ -198,11 +201,11 @@ export class SoundImplBrowser extends SoundInterface {
this.music[musicPath] = music;
}
this.musicMuted = this.app.settings.getAllSettings().musicMuted;
this.soundsMuted = this.app.settings.getAllSettings().soundsMuted;
this.musicVolume = this.app.settings.getAllSettings().musicVolume;
this.soundVolume = this.app.settings.getAllSettings().soundVolume;
if (G_IS_DEV && globalConfig.debug.disableMusic) {
this.musicMuted = true;
this.musicVolume = 0.0;
}
return Promise.resolve();

@ -103,9 +103,6 @@ export class SoundInterface {
this.pageIsVisible = true;
this.musicMuted = false;
this.soundsMuted = false;
this.musicVolume = 1.0;
this.soundVolume = 1.0;
}
@ -127,13 +124,11 @@ export class SoundInterface {
this.music[musicPath] = music;
}
this.musicMuted = this.app.settings.getAllSettings().musicMuted;
this.soundsMuted = this.app.settings.getAllSettings().soundsMuted;
this.musicVolume = this.app.settings.getAllSettings().musicVolume;
this.soundVolume = this.app.settings.getAllSettings().soundVolume;
if (G_IS_DEV && globalConfig.debug.disableMusic) {
this.musicMuted = true;
this.musicVolume = 0.0;
}
return Promise.resolve();
@ -170,47 +165,6 @@ export class SoundInterface {
return Promise.all(...promises);
}
/**
* Returns if the music is muted
* @returns {boolean}
*/
getMusicMuted() {
return this.musicMuted;
}
/**
* Returns if sounds are muted
* @returns {boolean}
*/
getSoundsMuted() {
return this.soundsMuted;
}
/**
* Sets if the music is muted
* @param {boolean} muted
*/
setMusicMuted(muted) {
this.musicMuted = muted;
if (this.musicMuted) {
if (this.currentMusic) {
this.currentMusic.stop();
}
} else {
if (this.currentMusic) {
this.currentMusic.play(this.musicVolume);
}
}
}
/**
* Sets if the sounds are muted
* @param {boolean} muted
*/
setSoundsMuted(muted) {
this.soundsMuted = muted;
}
/**
* Returns the music volume
* @returns {number}
@ -254,7 +208,7 @@ export class SoundInterface {
this.pageIsVisible = pageIsVisible;
if (this.currentMusic) {
if (pageIsVisible) {
if (!this.currentMusic.isPlaying() && !this.musicMuted) {
if (!this.currentMusic.isPlaying()) {
this.currentMusic.play(this.musicVolume);
}
} else {
@ -267,9 +221,6 @@ export class SoundInterface {
* @param {string} key
*/
playUiSound(key) {
if (this.soundsMuted) {
return;
}
if (!this.sounds[key]) {
logger.warn("Sound", key, "not found, probably not loaded yet");
return;
@ -288,7 +239,7 @@ export class SoundInterface {
logger.warn("Music", key, "not found, probably not loaded yet");
return;
}
if (!this.pageIsVisible || this.soundsMuted) {
if (!this.pageIsVisible) {
return;
}
@ -319,7 +270,7 @@ export class SoundInterface {
this.currentMusic.stop();
}
this.currentMusic = music;
if (music && this.pageIsVisible && !this.musicMuted) {
if (music && this.pageIsVisible) {
logger.log("Starting", this.currentMusic.key);
music.play(this.musicVolume);
}

@ -159,29 +159,13 @@ export const allApplicationSettings = [
(app, id) => app.updateAfterUiScaleChanged(),
}),
new BoolSetting(
"soundsMuted",
enumCategories.general,
/**
* @param {Application} app
*/
(app, value) => app.sound.setSoundsMuted(value)
),
new RangeSetting(
"soundVolume",
enumCategories.general,
/**
* @param {Application} app
*/
(app, value) => app.sound.setSoundVolume(value / 100.0)
),
new BoolSetting(
"musicMuted",
enumCategories.general,
/**
* @param {Application} app
*/
(app, value) => app.sound.setMusicMuted(value)
(app, value) => app.sound.setSoundVolume(value)
),
new RangeSetting(
"musicVolume",
@ -189,7 +173,7 @@ export const allApplicationSettings = [
/**
* @param {Application} app
*/
(app, value) => app.sound.setMusicVolume(value / 100.0)
(app, value) => app.sound.setMusicVolume(value)
),
new BoolSetting(
@ -302,8 +286,6 @@ class SettingsStorage {
this.uiScale = "regular";
this.fullscreen = G_IS_STANDALONE;
this.soundsMuted = false;
this.musicMuted = false;
this.soundVolume = 1.0;
this.musicVolume = 1.0;
@ -515,7 +497,17 @@ export class ApplicationSettings extends ReadWriteProxy {
const setting = allApplicationSettings[i];
const storedValue = settings[setting.id];
if (!setting.validate(storedValue)) {
return ExplainedResult.bad("Bad setting value for " + setting.id + ": " + storedValue);
return ExplainedResult.bad(
"Bad setting value for " +
setting.id +
": " +
storedValue +
" @ settings version " +
data.version +
" (latest is " +
this.getCurrentVersion() +
")"
);
}
}
return ExplainedResult.good();
@ -529,7 +521,7 @@ export class ApplicationSettings extends ReadWriteProxy {
}
getCurrentVersion() {
return 24;
return 25;
}
/** @param {{settings: SettingsStorage, version: number}} data */
@ -633,12 +625,21 @@ export class ApplicationSettings extends ReadWriteProxy {
}
if (data.version < 24) {
data.settings.musicVolume = 1.0;
data.settings.soundVolume = 1.0;
data.settings.refreshRate = "60";
data.version = 24;
}
if (data.version < 25) {
data.settings.musicVolume = 0.5;
data.settings.soundVolume = 0.5;
// @ts-ignore
delete data.settings.musicMuted;
// @ts-ignore
delete data.settings.soundsMuted;
data.version = 25;
}
return ExplainedResult.good();
}
}

@ -227,10 +227,10 @@ export class RangeSetting extends BaseSetting {
category,
changeCb = null,
enabled = true,
defaultValue = 100,
defaultValue = 1.0,
minValue = 0,
maxValue = 100,
stepSize = 1
maxValue = 1.0,
stepSize = 0.0001
) {
super(id, category, changeCb, enabled);
@ -247,9 +247,9 @@ export class RangeSetting extends BaseSetting {
<div class="row">
<label>${T.settings.labels[this.id].title}</label>
<div class="value range" data-setting="${this.id}">
<label class="range-label">${this.defaultValue}</label>
<input class="range-input" type="range" value="${this.defaultValue}" min="${
<div class="value rangeInputContainer noPressEffect" data-setting="${this.id}">
<label>${this.defaultValue}</label>
<input class="rangeInput" type="range" value="${this.defaultValue}" min="${
this.minValue
}" max="${this.maxValue}" step="${this.stepSize}">
</div>
@ -265,33 +265,58 @@ export class RangeSetting extends BaseSetting {
this.element = element;
this.dialogs = dialogs;
this.element.querySelector(".range-input").addEventListener("input", () => {
this.getRangeInputElement().addEventListener("input", () => {
this.updateLabels();
});
this.getRangeInputElement().addEventListener("change", () => {
this.modify();
});
}
syncValueToElement() {
const value = this.app.settings.getSetting(this.id);
/** @type {HTMLInputElement} */
const rangeInput = this.element.querySelector(".range-input"),
rangeLabel = this.element.querySelector(".range-label");
rangeInput.value = value;
rangeLabel.innerHTML = value;
this.setElementValue(value);
}
/**
* Sets the elements value to the given value
* @param {number} value
*/
setElementValue(value) {
const rangeInput = this.getRangeInputElement();
const rangeLabel = this.element.querySelector("label");
rangeInput.value = String(value);
rangeLabel.innerHTML = T.settings.rangeSliderPercentage.replace(
"<amount>",
String(Math.round(value * 100.0))
);
}
updateLabels() {
const value = Number(this.getRangeInputElement().value);
this.setElementValue(value);
}
/**
* @returns {HTMLInputElement}
*/
getRangeInputElement() {
return this.element.querySelector("input.rangeInput");
}
modify() {
/** @type {HTMLInputElement} */
const rangeInput = this.element.querySelector(".range-input");
const newValue = Number(rangeInput.value);
const rangeInput = this.getRangeInputElement();
const newValue = Math.round(Number(rangeInput.value) * 100.0) / 100.0;
this.app.settings.updateSetting(this.id, newValue);
this.syncValueToElement();
console.log("SET", newValue);
if (this.changeCb) {
this.changeCb(this.app, newValue);
}
}
validate(value) {
return typeof value === "number";
return typeof value === "number" && value >= this.minValue && value <= this.maxValue;
}
}

@ -728,6 +728,8 @@ settings:
prod: Production
buildDate: Built <at-date>
rangeSliderPercentage: <amount> %
labels:
uiScale:
title: Interface scale

@ -2740,6 +2740,11 @@ cyclist@^1.0.1:
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
debounce-promise@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/debounce-promise/-/debounce-promise-3.1.2.tgz#320fb8c7d15a344455cd33cee5ab63530b6dc7c5"
integrity sha512-rZHcgBkbYavBeD9ej6sP56XfG53d51CD4dnaw989YX/nZ/ZJfgRx/9ePKmTNiUiyQvh4mtrMoS3OAWW+yoYtpg==
debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"

Loading…
Cancel
Save