Klima: Engine extrahiert + 2D refactored + Drama-Track + Bugfixes
- engine.js als headless Single Source of Truth (KlimaEngine) - game-2d.html nutzt engine (-515 Zeilen Duplikation) - rising-pressure.mp3 als Drama-Slot, Auto-Switch bei kritischem State - state.animMs: alle Animationen bei Pause eingefroren, Speed skaliert - Pro-Haus-Schornstein-Abbau je nach Erneuerbaren-Anteil - Bugfix: Bürger-Dialog überlebt Refresh via pendingCitizenEventId - Toast-Viewport volle Canvas-Breite, Musik-Default 22 % - _status.md Konvention eingeführt, Inbox-Nachrichten erhalten
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* Klimawächter Audio-Modul
|
||||
* ------------------------------------------------------------------
|
||||
* Kleines SFX-System auf Basis von <audio>-Elementen mit Pooling,
|
||||
* damit dasselbe SFX mehrfach kurz hintereinander abgespielt werden
|
||||
* kann (z. B. rasche Kauf-Sounds). Persistiert Volume + Mute in
|
||||
* localStorage, reagiert auf Speed 0 (Pause) indem es nichts abspielt.
|
||||
*
|
||||
* Nutzung aus dem Spiel:
|
||||
* kwAudio.play('ui-click');
|
||||
* kwAudio.play('build-wind');
|
||||
* kwAudio.setVolume(0.5); // 0..1
|
||||
* kwAudio.toggleMute();
|
||||
*
|
||||
* Alle Sound-Dateien liegen in ./assets/sounds/<name>.mp3.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const BASE = 'assets/sounds/';
|
||||
const POOL_SIZE = 3; // parallele Instanzen pro Sound (überlappen)
|
||||
const LS_VOL = 'kw-sfx-volume';
|
||||
const LS_MUTE = 'kw-sfx-muted';
|
||||
|
||||
// Mapping Welt-Event/Key → Sound-Datei. Die Keys werden vom Spiel
|
||||
// direkt als `play(key)`-Argument übergeben.
|
||||
const SOUNDS = {
|
||||
// UI + Meta
|
||||
'ui-click': 'ui-click.mp3',
|
||||
'ui-error': 'ui-error.mp3',
|
||||
'ui-confirm': 'ui-confirm.mp3',
|
||||
'achievement': 'achievement.mp3',
|
||||
'warning': 'warning.mp3',
|
||||
'praise': 'praise.mp3',
|
||||
'level-won': 'level-won.mp3',
|
||||
'level-lost': 'level-lost.mp3',
|
||||
'game-start': 'game-start.mp3',
|
||||
'demolish': 'demolish.mp3',
|
||||
// Bau-Sounds
|
||||
'build-generic': 'build-generic.mp3',
|
||||
'build-forest': 'build-forest.mp3',
|
||||
'build-solar': 'build-solar.mp3',
|
||||
'build-wind': 'build-wind.mp3',
|
||||
'build-green-roof': 'build-green-roof.mp3',
|
||||
'build-bikes': 'build-bikes.mp3',
|
||||
'build-mangrove': 'build-mangrove.mp3',
|
||||
'build-sand-fill': 'build-sand-fill.mp3',
|
||||
'build-dike': 'build-dike.mp3',
|
||||
'build-sea-wall': 'build-sea-wall.mp3',
|
||||
'build-coal': 'build-coal.mp3',
|
||||
'build-airport': 'build-airport.mp3',
|
||||
'build-cloud-seed': 'build-cloud-seed.mp3',
|
||||
// Ereignis-Sounds
|
||||
'event-temperature': 'event-temperature.mp3',
|
||||
'event-flood': 'event-flood.mp3',
|
||||
'event-vegetation': 'event-vegetation.mp3',
|
||||
'event-water': 'event-water.mp3',
|
||||
'event-glacier': 'event-glacier.mp3',
|
||||
'event-blackout': 'event-blackout.mp3',
|
||||
'event-power-back': 'event-power-back.mp3',
|
||||
'event-tourism': 'event-tourism.mp3',
|
||||
'event-co2': 'event-co2.mp3',
|
||||
// Dramatik
|
||||
'drama-paris-missed': 'drama-paris-missed.mp3',
|
||||
'drama-disaster': 'drama-disaster.mp3',
|
||||
'drama-game-over': 'drama-game-over.mp3',
|
||||
};
|
||||
|
||||
// Mapping buy(measureId) → sound-key
|
||||
const BUILD_MAP = {
|
||||
'forest': 'build-forest',
|
||||
'solar': 'build-solar',
|
||||
'wind': 'build-wind',
|
||||
'green-roof': 'build-green-roof',
|
||||
'bikes': 'build-bikes',
|
||||
'mangrove': 'build-mangrove',
|
||||
'sand-fill': 'build-sand-fill',
|
||||
'dike': 'build-dike',
|
||||
'sea-wall': 'build-sea-wall',
|
||||
'coal': 'build-coal',
|
||||
'airport': 'build-airport',
|
||||
'cloud-seed': 'build-cloud-seed',
|
||||
};
|
||||
|
||||
// Sound-Pools (pro Key ein Array von <audio>-Elementen im Round-Robin)
|
||||
const _pools = {};
|
||||
const _poolIdx = {};
|
||||
|
||||
let _volume = parseFloat(localStorage.getItem(LS_VOL));
|
||||
if (isNaN(_volume)) _volume = 0.6;
|
||||
let _muted = localStorage.getItem(LS_MUTE) === '1';
|
||||
|
||||
function _getPool(key) {
|
||||
if (_pools[key]) return _pools[key];
|
||||
const file = SOUNDS[key];
|
||||
if (!file) return null;
|
||||
const arr = [];
|
||||
for (let i = 0; i < POOL_SIZE; i++) {
|
||||
const a = new Audio(BASE + file);
|
||||
a.preload = 'auto';
|
||||
a.volume = _volume;
|
||||
arr.push(a);
|
||||
}
|
||||
_pools[key] = arr;
|
||||
_poolIdx[key] = 0;
|
||||
return arr;
|
||||
}
|
||||
|
||||
function play(key, opts) {
|
||||
if (_muted) return;
|
||||
const pool = _getPool(key);
|
||||
if (!pool) { console.warn('[kwAudio] unbekannt:', key); return; }
|
||||
const idx = _poolIdx[key];
|
||||
_poolIdx[key] = (idx + 1) % pool.length;
|
||||
const a = pool[idx];
|
||||
try {
|
||||
a.currentTime = 0;
|
||||
a.volume = (opts && opts.volume != null) ? opts.volume : _volume;
|
||||
const p = a.play();
|
||||
if (p && p.catch) p.catch(() => { /* Autoplay-Policy o.ä. — ignorieren */ });
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
function playForBuy(measureId) {
|
||||
const key = BUILD_MAP[measureId] || 'build-generic';
|
||||
play(key);
|
||||
}
|
||||
|
||||
function setVolume(v) {
|
||||
_volume = Math.max(0, Math.min(1, v));
|
||||
localStorage.setItem(LS_VOL, String(_volume));
|
||||
for (const k in _pools) {
|
||||
for (const a of _pools[k]) a.volume = _volume;
|
||||
}
|
||||
}
|
||||
|
||||
function setMuted(m) {
|
||||
_muted = !!m;
|
||||
localStorage.setItem(LS_MUTE, _muted ? '1' : '0');
|
||||
if (_muted) stopAll();
|
||||
}
|
||||
|
||||
function toggleMute() { setMuted(!_muted); return _muted; }
|
||||
function isMuted() { return _muted; }
|
||||
function getVolume() { return _volume; }
|
||||
|
||||
function stopAll() {
|
||||
for (const k in _pools) {
|
||||
for (const a of _pools[k]) { try { a.pause(); a.currentTime = 0; } catch (e) {} }
|
||||
}
|
||||
}
|
||||
|
||||
// Exportieren
|
||||
window.kwAudio = {
|
||||
play,
|
||||
playForBuy,
|
||||
setVolume,
|
||||
getVolume,
|
||||
setMuted,
|
||||
isMuted,
|
||||
toggleMute,
|
||||
stopAll,
|
||||
SOUNDS,
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user