mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:30:45 +00:00
1ebac8e854
## Description: * Move proprietary brand images (logos, favicon) from resources/images/ to proprietary/images/ to separate open-source assets from proprietary ones * Extend the asset pipeline (PublicAssetManifest, vite.config.ts) to support multiple source directories (resources/ + proprietary/), so buildAssetUrl resolves assets from either location transparently * In dev, serve proprietary/ as a fallback middleware (registered after Vite's publicDir handler) so resources/ takes precedence when files exist in both. The idea is we could have placeholder assets placeholders that can be used by forks, and only the production build uses proprietary assets. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: evan
180 lines
5.7 KiB
TypeScript
180 lines
5.7 KiB
TypeScript
import { Howl } from "howler";
|
|
import { assetUrl } from "../../core/AssetUrls";
|
|
import { EventBus } from "../../core/EventBus";
|
|
import { UserSettings } from "../../core/game/UserSettings";
|
|
import {
|
|
PlaySoundEffectEvent,
|
|
SetBackgroundMusicVolumeEvent,
|
|
SetSoundEffectsVolumeEvent,
|
|
SoundEffect,
|
|
soundEffectUrls,
|
|
} from "./Sounds";
|
|
|
|
export const MAX_CONCURRENT_SOUNDS = 8;
|
|
|
|
export class SoundManager {
|
|
private backgroundMusic: Howl[] = [];
|
|
private currentTrack: number = 0;
|
|
private soundEffects: Map<SoundEffect, Howl> = new Map();
|
|
private soundEffectsVolume: number = 1;
|
|
private backgroundMusicVolume: number = 0;
|
|
private activeSounds: { howl: Howl; id: number }[] = [];
|
|
private eventBus: EventBus;
|
|
private onPlaySoundEffect: (e: PlaySoundEffectEvent) => void;
|
|
private onSetBackgroundMusicVolume: (
|
|
e: SetBackgroundMusicVolumeEvent,
|
|
) => void;
|
|
private onSetSoundEffectsVolume: (e: SetSoundEffectsVolumeEvent) => void;
|
|
|
|
constructor(eventBus: EventBus, userSettings: UserSettings) {
|
|
this.eventBus = eventBus;
|
|
this.safely("initialize background music", () => {
|
|
this.backgroundMusic = [
|
|
new Howl({
|
|
src: [assetUrl("sounds/music/of4.mp3")],
|
|
loop: false,
|
|
onend: this.playNext.bind(this),
|
|
volume: 0,
|
|
}),
|
|
new Howl({
|
|
src: [assetUrl("sounds/music/openfront.mp3")],
|
|
loop: false,
|
|
onend: this.playNext.bind(this),
|
|
volume: 0,
|
|
}),
|
|
new Howl({
|
|
src: [assetUrl("sounds/music/war.mp3")],
|
|
loop: false,
|
|
onend: this.playNext.bind(this),
|
|
volume: 0,
|
|
}),
|
|
];
|
|
});
|
|
this.setBackgroundMusicVolume(userSettings.backgroundMusicVolume());
|
|
this.setSoundEffectsVolume(userSettings.soundEffectsVolume());
|
|
this.onPlaySoundEffect = (e) => this.playSoundEffect(e.effect);
|
|
this.onSetBackgroundMusicVolume = (e) =>
|
|
this.setBackgroundMusicVolume(e.volume);
|
|
this.onSetSoundEffectsVolume = (e) => this.setSoundEffectsVolume(e.volume);
|
|
eventBus.on(PlaySoundEffectEvent, this.onPlaySoundEffect);
|
|
eventBus.on(SetBackgroundMusicVolumeEvent, this.onSetBackgroundMusicVolume);
|
|
eventBus.on(SetSoundEffectsVolumeEvent, this.onSetSoundEffectsVolume);
|
|
}
|
|
|
|
public dispose(): void {
|
|
this.eventBus.off(PlaySoundEffectEvent, this.onPlaySoundEffect);
|
|
this.eventBus.off(
|
|
SetBackgroundMusicVolumeEvent,
|
|
this.onSetBackgroundMusicVolume,
|
|
);
|
|
this.eventBus.off(SetSoundEffectsVolumeEvent, this.onSetSoundEffectsVolume);
|
|
this.backgroundMusic.forEach((track) => {
|
|
this.safely("stop background track", () => track.stop());
|
|
this.safely("unload background track", () => track.unload());
|
|
});
|
|
this.soundEffects.forEach((sound) => {
|
|
this.safely("stop sound effect", () => sound.stop());
|
|
this.safely("unload sound effect", () => sound.unload());
|
|
});
|
|
this.soundEffects.clear();
|
|
this.activeSounds = [];
|
|
}
|
|
|
|
private safely(action: string, fn: () => void): void {
|
|
try {
|
|
fn();
|
|
} catch (err) {
|
|
console.error(`SoundManager: failed to ${action}`, err);
|
|
}
|
|
}
|
|
|
|
public playBackgroundMusic(): void {
|
|
this.safely("play background music", () => {
|
|
if (
|
|
this.backgroundMusic.length > 0 &&
|
|
!this.backgroundMusic[this.currentTrack].playing()
|
|
) {
|
|
this.backgroundMusic[this.currentTrack].play();
|
|
}
|
|
});
|
|
}
|
|
|
|
public stopBackgroundMusic(): void {
|
|
this.safely("stop background music", () => {
|
|
if (this.backgroundMusic.length > 0) {
|
|
this.backgroundMusic[this.currentTrack].stop();
|
|
}
|
|
});
|
|
}
|
|
|
|
public setBackgroundMusicVolume(volume: number): void {
|
|
this.backgroundMusicVolume = Math.max(0, Math.min(1, volume));
|
|
this.safely("set background music volume", () => {
|
|
this.backgroundMusic.forEach((track) => {
|
|
track.volume(this.backgroundMusicVolume);
|
|
});
|
|
});
|
|
}
|
|
|
|
private playNext(): void {
|
|
this.currentTrack = (this.currentTrack + 1) % this.backgroundMusic.length;
|
|
this.playBackgroundMusic();
|
|
}
|
|
|
|
private getOrLoadSoundEffect(name: SoundEffect): Howl | null {
|
|
let sound = this.soundEffects.get(name);
|
|
if (sound) return sound;
|
|
const src = soundEffectUrls.get(name);
|
|
if (!src) return null;
|
|
try {
|
|
sound = new Howl({ src: [src], volume: this.soundEffectsVolume });
|
|
this.soundEffects.set(name, sound);
|
|
return sound;
|
|
} catch (err) {
|
|
console.error(`SoundManager: failed to load sound ${name}`, err);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private removeActiveSoundById(id: number): void {
|
|
this.activeSounds = this.activeSounds.filter((s) => s.id !== id);
|
|
}
|
|
|
|
public playSoundEffect(name: SoundEffect): void {
|
|
this.safely(`play sound ${name}`, () => {
|
|
const howl = this.getOrLoadSoundEffect(name);
|
|
if (!howl) return;
|
|
|
|
if (this.activeSounds.length >= MAX_CONCURRENT_SOUNDS) {
|
|
const oldest = this.activeSounds[0];
|
|
oldest.howl.stop(oldest.id);
|
|
this.removeActiveSoundById(oldest.id);
|
|
}
|
|
|
|
const id = howl.play();
|
|
this.activeSounds.push({ howl, id });
|
|
howl.once("end", () => this.removeActiveSoundById(id), id);
|
|
howl.once("stop", () => this.removeActiveSoundById(id), id);
|
|
});
|
|
}
|
|
|
|
public setSoundEffectsVolume(volume: number): void {
|
|
this.soundEffectsVolume = Math.max(0, Math.min(1, volume));
|
|
this.safely("set sound effects volume", () => {
|
|
this.soundEffects.forEach((sound) => {
|
|
sound.volume(this.soundEffectsVolume);
|
|
});
|
|
});
|
|
}
|
|
|
|
public stopSoundEffect(name: SoundEffect): void {
|
|
this.safely(`stop sound ${name}`, () => {
|
|
const howl = this.soundEffects.get(name);
|
|
if (howl) {
|
|
howl.stop();
|
|
this.activeSounds = this.activeSounds.filter((s) => s.howl !== howl);
|
|
}
|
|
});
|
|
}
|
|
}
|