| Server IP : 202.61.199.114 / Your IP : 216.73.217.139 Web Server : nginx/1.22.1 System : Linux de.arni-solutions.de 6.1.0-49-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.174-1 (2026-05-26) x86_64 User : web20 ( 1018) PHP Version : 8.4.23 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /proc/3951572/cwd/tmp/rrpkg/ |
Upload File : |
/* Reel Royale – AnimationEngine
Gewinnlinien, Zellen-Highlights, Counter-Animation, Win-Stufen. */
import { $, money } from '../util.js';
const SVGNS = 'http://www.w3.org/2000/svg';
const LINE_COLORS = ['#ffce53', '#56d6ff', '#4ad991', '#ff5d6c', '#c77dff', '#ff9f1b', '#7CFFB2', '#ff79c6', '#8be9fd', '#f1fa8c'];
export class AnimationEngine {
constructor({ reels, particles, audio }) {
this.reels = reels;
this.particles = particles;
this.audio = audio;
this.svg = $('winLines');
this.winPop = $('winPop');
}
clear() {
this.svg.innerHTML = '';
this.winPop.hidden = true;
this.winPop.classList.remove('show');
document.querySelectorAll('.rr-cell.is-win,.rr-cell.is-special,.rr-cell.is-collected,.rr-cell.is-dance')
.forEach((c) => c.classList.remove('is-win', 'is-special', 'is-collected', 'is-dance'));
document.querySelectorAll('.rr-cell-coin').forEach((n) => n.remove());
}
/** Nur BESONDERE Symbole (Scatter/Bonus) wackeln & hüpfen – und auch nur dann,
wenn sie tatsächlich gewinnen bzw. ein Feature auslösen (in einer Reihe auftreten).
Normale Gewinnsymbole leuchten lediglich (is-win), ohne zu tanzen. */
danceSpecial(game) {
document.querySelectorAll('.rr-cell').forEach((cell) => {
const sym = game.symbols[cell.dataset.code];
const t = sym && sym.type;
const isSpecial = t === 'scatter' || t === 'bonus';
const significant = cell.classList.contains('is-win') || cell.classList.contains('is-special');
if (isSpecial && significant) cell.classList.add('is-dance');
});
}
_center(col, row, svgRect) {
const cell = this.reels.cellAt(col, row);
if (!cell) return null;
const r = cell.getBoundingClientRect();
return { x: r.left - svgRect.left + r.width / 2, y: r.top - svgRect.top + r.height / 2 };
}
highlightWins(wins) {
// Niemals auf sich drehenden Walzen zeichnen (verhindert Geisterlinien)
if (this.reels.spinning) return;
const svgRect = this.svg.getBoundingClientRect();
if (svgRect.width < 10 || svgRect.height < 10) return;
// Kein dynamisches viewBox → 1:1 px-Mapping, kein Skalieren bei Layout-Änderungen
this.svg.removeAttribute('viewBox');
const inBounds = (p) => p && p.x >= -8 && p.x <= svgRect.width + 8 && p.y >= -8 && p.y <= svgRect.height + 8;
let colorIdx = 0;
wins.forEach((win) => {
(win.positions || []).forEach(([col, row]) => {
const cell = this.reels.cellAt(col, row);
if (cell) cell.classList.add('is-win');
});
if (win.path && win.path.length && !win.is_scatter) {
const pts = win.path.map(([col, row]) => this._center(col, row, svgRect));
// Nur zeichnen, wenn ALLE Punkte gültig & im Rahmen sind (sonst veraltete Zelle → überspringen)
if (pts.length > 1 && pts.every(inBounds)) {
const poly = document.createElementNS(SVGNS, 'polyline');
poly.setAttribute('points', pts.map((p) => `${p.x},${p.y}`).join(' '));
poly.setAttribute('class', 'rr-winline');
poly.style.color = LINE_COLORS[colorIdx++ % LINE_COLORS.length];
poly.style.stroke = poly.style.color;
this.svg.appendChild(poly);
}
}
});
}
markSpecial(positions, cls = 'is-special') {
(positions || []).forEach(([col, row]) => {
const cell = this.reels.cellAt(col, row);
if (cell) cell.classList.add(cls);
});
}
showCollected(collected) {
const svgRect = this.svg.getBoundingClientRect();
(collected || []).forEach((c, i) => {
const cell = this.reels.cellAt(c.col, c.row);
if (!cell) return;
cell.classList.add('is-special');
const badge = document.createElement('div');
badge.className = 'rr-cell-coin';
badge.textContent = money(c.win);
cell.appendChild(badge);
const ctr = this._center(c.col, c.row, svgRect);
if (ctr) setTimeout(() => this.particles.burst(svgRect.left + ctr.x, svgRect.top + ctr.y, 'gold', 10), i * 90);
});
}
countUp(el, to, dur = 900) {
const from = 0;
const start = performance.now();
const step = (now) => {
const t = Math.min(1, (now - start) / dur);
const eased = 1 - Math.pow(1 - t, 3);
el.textContent = money(from + (to - from) * eased);
if (t < 1) requestAnimationFrame(step);
else el.textContent = money(to);
};
requestAnimationFrame(step);
}
/** Liefert die Gewinnstufe als {key,label} oder null. */
tier(amount, totalBet, tiers) {
if (amount <= 0 || totalBet <= 0) return null;
const m = amount / totalBet;
if (m >= (tiers.epic || 60)) return { key: 'epic', label: 'EPIC WIN' };
if (m >= (tiers.mega || 25)) return { key: 'mega', label: 'MEGA WIN' };
if (m >= (tiers.big || 12)) return { key: 'big', label: 'BIG WIN' };
if (m >= (tiers.nice || 5)) return { key: 'nice', label: 'NICE WIN' };
return { key: 'small', label: '' };
}
/** Win-Pop in der Reel-Mitte (kleinere Gewinne). */
popWin(amount) {
this.winPop.textContent = `+${money(amount)}`;
this.winPop.hidden = false;
this.winPop.classList.remove('show');
void this.winPop.offsetWidth;
this.winPop.classList.add('show');
}
/** Big/Mega/Epic-Gewinn als seitlich eingeblendetes Panel (kein Vollbild) mit Counter.
Konfetti + Feuerwerk bei besonders großen Gewinnen. Nach ~4 s geht es automatisch weiter. */
bigWin(label, amount, tierKey) {
const dock = $('bigWin');
$('bigWinTitle').textContent = label;
const amtEl = $('bigWinAmount');
dock.hidden = false;
this.audio.play('bigwin');
this.particles.coinShower(tierKey === 'epic' ? 80 : 50);
if (tierKey === 'mega' || tierKey === 'epic') this.particles.confetti(tierKey === 'epic' ? 110 : 70);
if (tierKey === 'epic') this.particles.fireworks(6);
this.countUp(amtEl, amount, 900);
// Etwas längere Wartezeit (~4 s), dann automatisch weiter; Klick schließt sofort.
return new Promise((resolve) => {
let done = false;
let timer = null;
const close = () => {
if (done) return;
done = true;
if (timer) clearTimeout(timer);
dock.hidden = true;
$('bigWinClose').removeEventListener('click', close);
resolve();
};
$('bigWinClose').addEventListener('click', close);
timer = setTimeout(close, 4000);
});
}
}