47c7f17c90
— 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__
225 lines
7.5 KiB
JavaScript
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());
|