initial commit

This commit is contained in:
icslucas
2025-07-26 14:42:54 +02:00
committed by GitHub
parent 4a38561583
commit c312a6bc23
5 changed files with 270 additions and 3 deletions
+32 -3
View File
@@ -6,6 +6,7 @@ import { GameUpdateType } from "../../../core/game/GameUpdates";
import { GameView } from "../../../core/game/GameView";
import { UserSettings } from "../../../core/game/UserSettings";
import { AlternateViewEvent, RefreshGraphicsEvent } from "../../InputHandler";
import { soundManager } from "../../SoundManager";
import { PauseGameEvent } from "../../Transport";
import { translateText } from "../../Utils";
import { Layer } from "./Layer";
@@ -75,9 +76,7 @@ export class OptionsMenu extends LitElement implements Layer {
private onExitButtonClick() {
const isAlive = this.game.myPlayer()?.isAlive();
if (isAlive) {
const isConfirmed = confirm(
translateText("help_modal.exit_confirmation"),
);
const isConfirmed = confirm("Are you sure you want to exit the game?");
if (!isConfirmed) return;
}
// redirect to the home page
@@ -137,6 +136,21 @@ export class OptionsMenu extends LitElement implements Layer {
this.requestUpdate();
}
private onMuteButtonClick() {
if (soundManager.isMuted()) {
soundManager.unmute();
} else {
soundManager.mute();
}
this.requestUpdate();
}
private onVolumeChange(e: Event) {
const volume = parseFloat((e.target as HTMLInputElement).value);
soundManager.setMasterVolume(volume);
this.requestUpdate();
}
init() {
console.log("init called from OptionsMenu");
this.showPauseButton =
@@ -260,6 +274,21 @@ export class OptionsMenu extends LitElement implements Layer {
? "Focus locked"
: "Hover focus"),
})} -->
${button({
onClick: this.onMuteButtonClick,
title: "Mute",
children: soundManager.isMuted() ? "🔇" : "🔊",
})}
<input
type="range"
min="0"
max="1"
step="0.01"
.value=${soundManager.getMasterVolume()}
@input=${this.onVolumeChange}
/>
</div>
</div>
`;
+191
View File
@@ -0,0 +1,191 @@
import { UserSettings } from "../core/game/UserSettings";
class SoundManager {
private static instance: SoundManager;
private audioContext: AudioContext;
private soundBuffers: Map<string, AudioBuffer> = new Map();
private musicSource: AudioBufferSourceNode | null = null;
private userSettings: UserSettings;
private masterVolume: GainNode;
private musicVolume: GainNode;
private soundEffectsVolume: GainNode;
private muted: boolean;
private activeSources: Map<string, AudioBufferSourceNode[]> = new Map();
private constructor() {
this.audioContext = new (window.AudioContext ||
(window as any).webkitAudioContext)();
this.userSettings = new UserSettings();
this.masterVolume = this.audioContext.createGain();
this.musicVolume = this.audioContext.createGain();
this.soundEffectsVolume = this.audioContext.createGain();
this.musicVolume.connect(this.masterVolume);
this.soundEffectsVolume.connect(this.masterVolume);
this.masterVolume.connect(this.audioContext.destination);
this.muted = this.userSettings.getMuted();
this.setMasterVolume(this.userSettings.getVolume());
if (this.muted) {
this.mute();
}
if (this.userSettings.getMuteMusic()) {
this.muteMusic();
}
if (this.userSettings.getMuteSoundEffects()) {
this.muteSoundEffects();
}
}
public static getInstance(): SoundManager {
if (!SoundManager.instance) {
SoundManager.instance = new SoundManager();
}
return SoundManager.instance;
}
public async loadSounds(
sounds: { name: string; path: string }[],
): Promise<void> {
const promises = sounds.map((sound) =>
this.loadSound(sound.name, sound.path),
);
await Promise.all(promises);
}
private async loadSound(name: string, path: string): Promise<void> {
try {
const response = await fetch(path);
const arrayBuffer = await response.arrayBuffer();
const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
this.soundBuffers.set(name, audioBuffer);
} catch (error) {
console.error(`Failed to load sound: ${path}`, error);
}
}
public playSound(name: string, loop: boolean = false): void {
const soundBuffer = this.soundBuffers.get(name);
if (soundBuffer) {
const source = this.audioContext.createBufferSource();
source.buffer = soundBuffer;
source.loop = loop;
source.connect(this.soundEffectsVolume);
source.start(0);
if (!this.activeSources.has(name)) {
this.activeSources.set(name, []);
}
this.activeSources.get(name)?.push(source);
source.onended = () => {
const sources = this.activeSources.get(name);
if (sources) {
const index = sources.indexOf(source);
if (index > -1) {
sources.splice(index, 1);
}
}
};
}
}
public stopSound(name: string): void {
const sources = this.activeSources.get(name);
if (sources) {
sources.forEach((source) => source.stop());
this.activeSources.set(name, []);
}
}
public playMusic(name: string): void {
if (this.musicSource) {
this.musicSource.stop();
}
const musicBuffer = this.soundBuffers.get(name);
if (musicBuffer) {
this.musicSource = this.audioContext.createBufferSource();
this.musicSource.buffer = musicBuffer;
this.musicSource.loop = true;
this.musicSource.connect(this.musicVolume);
this.musicSource.start(0);
}
}
public setMasterVolume(volume: number): void {
this.masterVolume.gain.setValueAtTime(
volume,
this.audioContext.currentTime,
);
this.userSettings.setVolume(volume);
}
public getMasterVolume(): number {
return this.masterVolume.gain.value;
}
public mute(): void {
this.masterVolume.gain.setValueAtTime(0, this.audioContext.currentTime);
this.muted = true;
this.userSettings.setMuted(true);
}
public unmute(): void {
this.masterVolume.gain.setValueAtTime(
this.userSettings.getVolume(),
this.audioContext.currentTime,
);
this.muted = false;
this.userSettings.setMuted(false);
}
public isMuted(): boolean {
return this.muted;
}
public muteMusic(): void {
this.musicVolume.gain.setValueAtTime(0, this.audioContext.currentTime);
this.userSettings.setMuteMusic(true);
}
public unmuteMusic(): void {
this.musicVolume.gain.setValueAtTime(1, this.audioContext.currentTime);
this.userSettings.setMuteMusic(false);
}
public muteSoundEffects(): void {
this.soundEffectsVolume.gain.setValueAtTime(0, this.audioContext.currentTime);
this.userSettings.setMuteSoundEffects(true);
}
public unmuteSoundEffects(): void {
this.soundEffectsVolume.gain.setValueAtTime(1, this.audioContext.currentTime);
this.userSettings.setMuteSoundEffects(false);
}
public playSpatialSound(name: string, x: number, y: number, z: number): void {
const soundBuffer = this.soundBuffers.get(name);
if (soundBuffer) {
const source = this.audioContext.createBufferSource();
source.buffer = soundBuffer;
const panner = this.audioContext.createPanner();
panner.panningModel = "HRTF";
panner.distanceModel = "inverse";
panner.refDistance = 1;
panner.maxDistance = 10000;
panner.rolloffFactor = 1;
panner.coneInnerAngle = 360;
panner.coneOuterAngle = 0;
panner.coneOuterGain = 0;
panner.setPosition(x, y, z);
source.connect(panner);
panner.connect(this.masterVolume);
source.start(0);
}
}
}
export const soundManager = SoundManager.getInstance();
+36
View File
@@ -87,6 +87,42 @@ export class UserSettings {
}
}
getVolume(): number {
const value = localStorage.getItem("volume");
if (value) {
return parseFloat(value);
}
return 1;
}
setVolume(volume: number): void {
localStorage.setItem("volume", volume.toString());
}
getMuted(): boolean {
return this.get("muted", false);
}
setMuted(muted: boolean): void {
this.set("muted", muted);
}
getMuteMusic(): boolean {
return this.get("muteMusic", false);
}
setMuteMusic(mute: boolean): void {
this.set("muteMusic", mute);
}
getMuteSoundEffects(): boolean {
return this.get("muteSoundEffects", false);
}
setMuteSoundEffects(mute: boolean): void {
this.set("muteSoundEffects", mute);
}
getSelectedPattern(): string | undefined {
return localStorage.getItem(PATTERN_KEY) ?? undefined;
}
+4
View File
@@ -40,3 +40,7 @@ declare module "*.xml" {
const value: string;
export default value;
}
declare module "*.mp3" {
const value: string;
export default value;
}
+7
View File
@@ -93,6 +93,13 @@ export default async (env, argv) => {
filename: "fonts/[name].[contenthash][ext]", // Added content hash and fixed path
},
},
{
test: /\.mp3$/,
type: "asset/resource",
generator: {
filename: "sound/[name].[contenthash][ext]",
},
},
],
},
resolve: {