diff --git a/frontend/src/custom.d.ts b/frontend/src/custom.d.ts new file mode 100644 index 0000000..3f6ebdd --- /dev/null +++ b/frontend/src/custom.d.ts @@ -0,0 +1,4 @@ +declare module '*.mp3' { + const content: string; + export default content; +} diff --git a/frontend/src/index.html b/frontend/src/index.html index 7492db1..81e35e0 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -89,6 +89,11 @@ /> + + Renderer | undefined) => { ); }; -const toggleSettings = () => - (settings.className = settings.className === 'open' ? '' : 'open'); +const toggleSettings = () => { + settings.className = settings.className === 'open' ? '' : 'open'; + SoundHandler.play(Sounds.click); +}; const applyServerContainerShadows = () => { const { scrollHeight, clientHeight, scrollTop } = serverContainer; @@ -104,6 +108,7 @@ const applyServerContainerShadows = () => { const main = async () => { try { let game: Game; + SoundHandler.initialize(); handleFullScreen(minimize, maximize); toggleSettingsButton.addEventListener('click', toggleSettings); @@ -115,6 +120,7 @@ const main = async () => { relativeMovementEnabled: enableRelativeMovement, soundsEnabled: enableSounds, vibrationEnabled: enableVibration, + musicEnabled: enableMusic, }); logoutButton.addEventListener('click', () => { diff --git a/frontend/src/scripts/options-handler.ts b/frontend/src/scripts/options-handler.ts index 1806322..b67b50e 100644 --- a/frontend/src/scripts/options-handler.ts +++ b/frontend/src/scripts/options-handler.ts @@ -1,7 +1,10 @@ +import { SoundHandler, Sounds } from './sound-handler'; + interface Options { vibrationEnabled: boolean; soundsEnabled: boolean; relativeMovementEnabled: boolean; + musicEnabled: boolean; } export abstract class OptionsHandler { @@ -10,6 +13,7 @@ export abstract class OptionsHandler { vibrationEnabled: true, soundsEnabled: true, relativeMovementEnabled: false, + musicEnabled: true, }; public static initialize( @@ -23,6 +27,14 @@ export abstract class OptionsHandler { ...this._options, ...stored, }; + + if (this._options.musicEnabled) { + const firstClickListener = () => { + document.removeEventListener('click', firstClickListener); + SoundHandler.playAmbient(); + }; + document.addEventListener('click', firstClickListener); + } } for (const k in inputElements) { @@ -30,6 +42,21 @@ export abstract class OptionsHandler { element.checked = OptionsHandler._options[k as keyof Options]; element.addEventListener('change', function () { OptionsHandler._options[k as keyof Options] = this.checked; + if (!this.checked && k === 'soundsEnabled') { + OptionsHandler._options.musicEnabled = false; + inputElements.musicEnabled.checked = false; + SoundHandler.stopAmbient(); + } + + if (k === 'musicEnabled') { + this.checked ? SoundHandler.playAmbient() : SoundHandler.stopAmbient(); + } + + if (this.checked && k === 'vibrationEnabled') { + navigator.vibrate(100); + } + + SoundHandler.play(Sounds.click); OptionsHandler.save(); }); } diff --git a/frontend/src/scripts/sound-handler.ts b/frontend/src/scripts/sound-handler.ts new file mode 100644 index 0000000..97112ac --- /dev/null +++ b/frontend/src/scripts/sound-handler.ts @@ -0,0 +1,44 @@ +import hitSound from '../../static/hit.mp3'; +import shootSound from '../../static/shoot.mp3'; +import clickSound from '../../static/click.mp3'; +import ambientSound from '../../static/ambient.mp3'; +import { OptionsHandler } from './options-handler'; + +export enum Sounds { + hit = 'hit', + shoot = 'shoot', + click = 'click', + ambient = 'ambient', +} + +export abstract class SoundHandler { + private static sounds: { [key in Sounds]: HTMLAudioElement }; + + public static initialize() { + this.sounds = { + [Sounds.hit]: new Audio(hitSound), + [Sounds.shoot]: new Audio(shootSound), + [Sounds.click]: new Audio(clickSound), + [Sounds.ambient]: new Audio(ambientSound), + }; + this.sounds.ambient.volume = 0.5; + } + + public static play(sound: Sounds) { + if (OptionsHandler.options.soundsEnabled) { + if (this.sounds[sound].currentTime > 0) { + (this.sounds[sound].cloneNode() as HTMLAudioElement).play(); + } else { + this.sounds[sound].play(); + } + } + } + + public static playAmbient() { + this.sounds.ambient.play(); + } + + public static stopAmbient() { + this.sounds.ambient.pause(); + } +} diff --git a/frontend/static/ambient.mp3 b/frontend/static/ambient.mp3 new file mode 100644 index 0000000..218cc96 Binary files /dev/null and b/frontend/static/ambient.mp3 differ diff --git a/frontend/static/click.mp3 b/frontend/static/click.mp3 new file mode 100644 index 0000000..50a022c Binary files /dev/null and b/frontend/static/click.mp3 differ diff --git a/frontend/static/hit.mp3 b/frontend/static/hit.mp3 new file mode 100644 index 0000000..d088388 Binary files /dev/null and b/frontend/static/hit.mp3 differ diff --git a/frontend/static/music.svg b/frontend/static/music.svg new file mode 100644 index 0000000..5f68ba1 --- /dev/null +++ b/frontend/static/music.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/frontend/static/shoot.mp3 b/frontend/static/shoot.mp3 new file mode 100644 index 0000000..435a99e Binary files /dev/null and b/frontend/static/shoot.mp3 differ diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index d6b7efa..d189f31 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -80,7 +80,17 @@ module.exports = { }, }, { - test: /\.png$/, + test: /\.mp3$/, + use: { + loader: 'file-loader', + query: { + outputPath: '/static', + name: '[name].[ext]', + }, + }, + }, + { + test: /og-image.png$/, use: { loader: 'file-loader', query: {