Files
deepseek 8fbe70d0b0 Repterra Web — full game: base building, power grid, taming & breeding, aquatic raiders, day/night, save/load
- Isometric canvas RTS vs dinosaur waves (fan demake of Repterra)
- Economy: houses/taxes, farms, foresters, quarries; colonist staffing
- Power grid: generators extend build range; brownout + recovery
- Defense: walls/gates, watchtowers (AA), cannon towers (ground-only)
- Taming: Primal Pen + Tamers collar weakened dinos; pets obey commands
- Breeding: tamed pairs incubate eggs at the pen; hatchlings grow up
- 7 dino species incl. flying Pteranodons and lake-raiding Suchomimus
- Telegraphed waves with direction arrows; day-15 final horde; 3 difficulties
- Day/night cycle, fog of war, minimap, synth audio, 1x-3x speeds
- Save/Load/Continue + dawn autosave (full JSON state snapshots)
- Tests: 80-assertion headless suite, browser boot + E2E, balance harness
2026-08-23 07:00:23 +00:00

98 lines
3.9 KiB
JavaScript

/* =========================================================
* REPRTERRA WEB — audio.js
* Tiny WebAudio synth for SFX. No assets needed.
* ========================================================= */
'use strict';
window.RTS = window.RTS || {};
RTS.audio = (function () {
let ctx = null;
let master = null;
let muted = false;
let lastPlay = {}; // throttle
function ensure() {
if (ctx) return true;
try {
const AC = window.AudioContext || window.webkitAudioContext;
if (!AC) return false;
ctx = new AC();
master = ctx.createGain();
master.gain.value = 0.32;
master.connect(ctx.destination);
} catch (e) { return false; }
return true;
}
function resume() {
if (!ensure()) return;
if (ctx.state === 'suspended') ctx.resume();
}
function throttled(name, minGap) {
const t = performance.now();
if (lastPlay[name] && t - lastPlay[name] < minGap) return true;
lastPlay[name] = t;
return false;
}
// basic tone
function tone(freq, dur, type, vol, slideTo, delay) {
if (muted || !ensure()) return;
const t0 = ctx.currentTime + (delay || 0);
const o = ctx.createOscillator();
const g = ctx.createGain();
o.type = type || 'square';
o.frequency.setValueAtTime(freq, t0);
if (slideTo) o.frequency.exponentialRampToValueAtTime(Math.max(20, slideTo), t0 + dur);
g.gain.setValueAtTime(vol || 0.2, t0);
g.gain.exponentialRampToValueAtTime(0.001, t0 + dur);
o.connect(g); g.connect(master);
o.start(t0); o.stop(t0 + dur + 0.02);
}
// noise burst
function noise(dur, vol, filterFreq, delay) {
if (muted || !ensure()) return;
const t0 = ctx.currentTime + (delay || 0);
const len = Math.max(1, Math.floor(ctx.sampleRate * dur));
const buf = ctx.createBuffer(1, len, ctx.sampleRate);
const d = buf.getChannelData(0);
for (let i = 0; i < len; i++) d[i] = Math.random() * 2 - 1;
const src = ctx.createBufferSource();
src.buffer = buf;
const f = ctx.createBiquadFilter();
f.type = 'lowpass';
f.frequency.value = filterFreq || 1200;
const g = ctx.createGain();
g.gain.setValueAtTime(vol || 0.2, t0);
g.gain.exponentialRampToValueAtTime(0.001, t0 + dur);
src.connect(f); f.connect(g); g.connect(master);
src.start(t0); src.stop(t0 + dur + 0.02);
}
const S = {};
S.click = () => { tone(700, 0.05, 'square', 0.10); };
S.build = () => { noise(0.15, 0.3, 500); tone(140, 0.12, 'triangle', 0.25, 90); };
S.deny = () => { tone(180, 0.12, 'sawtooth', 0.14, 120); };
S.shoot = () => { if (!throttled('shoot', 60)) noise(0.06, 0.14, 3200); };
S.arrow = () => { if (!throttled('arrow', 70)) tone(900, 0.07, 'triangle', 0.08, 300); };
S.cannon = () => { noise(0.35, 0.5, 350); tone(70, 0.3, 'sine', 0.4, 40); };
S.spit = () => { if (!throttled('spit', 120)) tone(300, 0.1, 'sawtooth', 0.09, 160); };
S.die = () => { if (!throttled('die', 80)) tone(220, 0.18, 'sawtooth', 0.16, 60); };
S.thud = () => { if (!throttled('thud', 120)) { noise(0.08, 0.22, 240); tone(90, 0.09, 'sine', 0.2, 55); } };
S.roar = () => { tone(110, 0.7, 'sawtooth', 0.34, 55); noise(0.5, 0.22, 300); };
S.alarm = () => { tone(520, 0.28, 'square', 0.16); tone(380, 0.28, 'square', 0.16, null, 0.3); tone(520, 0.28, 'square', 0.16, null, 0.6); };
S.coin = () => { tone(880, 0.05, 'sine', 0.08); tone(1320, 0.08, 'sine', 0.08, null, 0.05); };
S.train = () => { tone(440, 0.08, 'square', 0.1); tone(660, 0.1, 'square', 0.1, null, 0.08); };
S.win = () => { [523, 659, 784, 1046].forEach((f, i) => tone(f, 0.22, 'triangle', 0.22, null, i * 0.16)); };
S.lose = () => { [392, 330, 262, 196].forEach((f, i) => tone(f, 0.3, 'sawtooth', 0.2, null, i * 0.22)); };
S.explode = () => { noise(0.6, 0.55, 260); tone(55, 0.5, 'sine', 0.45, 30); };
S.toggleMute = function () { muted = !muted; return muted; };
S.isMuted = () => muted;
S.resume = resume;
return S;
})();