Files
dodep-simulator/static/js/sounds.js
T
sasheg 47c7f17c90 RPU v2, promo/bank система, админка, оптимизации
— RPU v2: бюджет удачи, потолок (ceiling), комбек после
  проигрышей, hot/cold анализ, интеграция с promo-фазами
— Bank API: mock-банк для карты (депозит/вывод с балансом 1000₽)
— Promo-сервис: автогенерация промокодов каждый час, типы
  (card_to_site, luck_boost, ceiling_boost, free_case, reset_streak)
— Promo-фазы: LUCKY/NEUTRAL/UNLUCKY, переключение каждые 1-6ч
— Admin: история транзакций (user_history), управление промокодами,
  управление promo-фазами, API фильтров/коллекций, RPU info
— Database: WAL mode, новые поля RPU v2, TransactionLog,
  карточный баланс, статистики (streaks, ceiling, budget)
— Frontend: /deposit, /promo/activate, /withdraw, /card-balance,
  /online-count, /rpu-info, lifespan вместо startup event
— UI: user_history.html, улучшения профиля, кейсов, навигации
— Cases: обновление cases.json (1892 строки новых данных)
— Achievements: кэш коллекций, оптимизация is_custom проверок
— Sounds: дополнительные звуковые эффекты
— Upgrade: интеграция с RPU (множители, streaks)
— .gitignore: исключены .db, .bak, __pycache__
2026-07-25 13:11:08 +00:00

225 lines
7.5 KiB
JavaScript

const SoundManager = {
_ctx: null,
_enabled: true,
_volume: 0.3,
_activeNodes: new Set(),
_timeouts: [],
init() {
try {
this._ctx = new (window.AudioContext || window.webkitAudioContext)();
} catch (e) {
console.warn('Web Audio API not supported');
this._enabled = false;
}
const saved = localStorage.getItem('sound_enabled');
if (saved !== null) this._enabled = saved === 'true';
},
setEnabled(on) {
this._enabled = on;
localStorage.setItem('sound_enabled', on);
},
toggle() {
this.setEnabled(!this._enabled);
return this._enabled;
},
stopAll() {
this._timeouts.forEach(clearTimeout);
this._timeouts = [];
this._activeNodes.forEach(node => {
try { node.stop(); } catch (e) {}
try { node.disconnect(); } catch (e) {}
});
this._activeNodes.clear();
},
_trackNode(node, duration) {
this._activeNodes.add(node);
node.addEventListener('ended', () => this._activeNodes.delete(node));
setTimeout(() => {
this._activeNodes.delete(node);
try { node.disconnect(); } catch (e) {}
}, (duration + 0.1) * 1000);
},
_play(freq, duration, type = 'sine', volume = 1) {
if (!this._enabled || !this._ctx) return;
if (this._ctx.state === 'suspended') this._ctx.resume();
const osc = this._ctx.createOscillator();
const gain = this._ctx.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, this._ctx.currentTime);
gain.gain.setValueAtTime(this._volume * volume, this._ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, this._ctx.currentTime + duration);
osc.connect(gain);
gain.connect(this._ctx.destination);
osc.start();
osc.stop(this._ctx.currentTime + duration);
this._trackNode(osc, duration);
this._trackNode(gain, duration);
},
_noise(duration, volume = 1) {
if (!this._enabled || !this._ctx) return;
if (this._ctx.state === 'suspended') this._ctx.resume();
const bufferSize = this._ctx.sampleRate * duration;
const buffer = this._ctx.createBuffer(1, bufferSize, this._ctx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
data[i] = Math.random() * 2 - 1;
}
const source = this._ctx.createBufferSource();
source.buffer = buffer;
const gain = this._ctx.createGain();
gain.gain.setValueAtTime(this._volume * volume, this._ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, this._ctx.currentTime + duration);
source.connect(gain);
gain.connect(this._ctx.destination);
source.start();
this._trackNode(source, duration);
this._trackNode(gain, duration);
},
_delay(fn, ms) {
const id = setTimeout(() => {
this._timeouts = this._timeouts.filter(t => t !== id);
fn();
}, ms);
this._timeouts.push(id);
return id;
},
caseOpen() {
this._play(800, 0.1, 'square', 0.3);
this._delay(() => this._play(400, 0.15, 'sawtooth', 0.2), 80);
this._delay(() => this._play(200, 0.2, 'sine', 0.15), 180);
this._noise(0.05, 0.4);
},
caseRare() {
this._play(523, 0.15, 'sine', 0.3);
this._delay(() => this._play(659, 0.15, 'sine', 0.3), 100);
this._delay(() => this._play(784, 0.15, 'sine', 0.3), 200);
this._delay(() => this._play(1047, 0.3, 'sine', 0.4), 300);
},
caseCovert() {
this._play(392, 0.2, 'sawtooth', 0.3);
this._delay(() => this._play(523, 0.2, 'sawtooth', 0.3), 150);
this._delay(() => this._play(659, 0.2, 'sawtooth', 0.3), 300);
this._delay(() => this._play(784, 0.4, 'sine', 0.5), 450);
this._delay(() => this._play(1047, 0.6, 'sine', 0.6), 600);
},
slotSpin() {
for (let i = 0; i < 8; i++) {
this._delay(() => {
this._play(300 + Math.random() * 400, 0.05, 'square', 0.15);
}, i * 80);
}
},
slotMatch() {
this._play(523, 0.1, 'sine', 0.3);
this._delay(() => this._play(659, 0.1, 'sine', 0.3), 100);
this._delay(() => this._play(784, 0.1, 'sine', 0.3), 200);
this._delay(() => this._play(1047, 0.4, 'sine', 0.5), 300);
},
contractSubmit() {
this._play(150, 0.3, 'sawtooth', 0.2);
this._delay(() => this._play(200, 0.2, 'square', 0.2), 200);
this._delay(() => this._play(250, 0.1, 'square', 0.15), 350);
},
contractResult() {
this._play(400, 0.15, 'sine', 0.3);
this._delay(() => this._play(600, 0.15, 'sine', 0.3), 120);
this._delay(() => this._play(800, 0.3, 'sine', 0.4), 240);
},
wheelSpin() {
this._play(600 + Math.random() * 400, 0.04, 'triangle', 0.12);
this._play(200, 0.02, 'square', 0.06);
},
wheelWin() {
const t = this._ctx.currentTime;
if (!this._enabled || !this._ctx) return;
if (this._ctx.state === 'suspended') this._ctx.resume();
[523, 659, 784, 1047].forEach((freq, i) => {
const osc = this._ctx.createOscillator();
const gain = this._ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(freq, t + i * 0.1);
gain.gain.setValueAtTime(0, t + i * 0.1);
gain.gain.linearRampToValueAtTime(this._volume * 0.35, t + i * 0.1 + 0.05);
gain.gain.exponentialRampToValueAtTime(0.001, t + i * 0.1 + 0.5);
osc.connect(gain);
gain.connect(this._ctx.destination);
osc.start(t + i * 0.1);
osc.stop(t + i * 0.1 + 0.5);
this._trackNode(osc, 0.6);
this._trackNode(gain, 0.6);
});
this._delay(() => this._play(1568, 0.4, 'sine', 0.25), 350);
this._delay(() => this._play(2093, 0.6, 'sine', 0.2), 500);
},
wheelLose() {
this._play(300, 0.15, 'sawtooth', 0.25);
this._delay(() => this._play(200, 0.2, 'sawtooth', 0.2), 100);
this._delay(() => this._play(120, 0.3, 'sawtooth', 0.15), 220);
this._noise(0.15, 0.3);
this._delay(() => {
this._play(60, 0.5, 'sine', 0.2);
this._play(45, 0.6, 'sine', 0.15);
}, 350);
},
achievement() {
this._play(659, 0.1, 'sine', 0.3);
this._delay(() => this._play(523, 0.1, 'sine', 0.3), 100);
this._delay(() => this._play(784, 0.1, 'sine', 0.3), 200);
this._delay(() => this._play(659, 0.1, 'sine', 0.3), 300);
this._delay(() => this._play(1047, 0.4, 'sine', 0.5), 400);
},
crashBet() {
this._play(300, 0.1, 'square', 0.15);
},
crashTick() {
this._play(500 + Math.random() * 500, 0.03, 'sine', 0.08);
},
crashCrashed() {
this._noise(0.3, 0.6);
this._play(80, 0.5, 'sawtooth', 0.4);
this._delay(() => this._play(50, 0.8, 'sine', 0.3), 100);
},
crashCashout() {
this._play(600, 0.1, 'sine', 0.3);
this._delay(() => this._play(800, 0.1, 'sine', 0.3), 80);
this._delay(() => this._play(1000, 0.2, 'sine', 0.4), 160);
},
click() {
this._play(800, 0.03, 'sine', 0.1);
},
error() {
this._play(200, 0.15, 'square', 0.2);
},
balanceUpdate() {
this._play(1000, 0.05, 'sine', 0.1);
}
};
document.addEventListener('DOMContentLoaded', () => SoundManager.init());