403Webshell
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 :  /var/www/arni-solutions.de/web/slots/js/engine/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/arni-solutions.de/web/slots/js/engine/GameEngine.js
/* Reel Royale – GameEngine: zentraler Spielablauf-Orchestrator. */
import { $, money, postJSON, wait, applyTheme } from '../util.js';

export class GameEngine {
  constructor({ boot, reels, anim, audio, particles, freeSpins, bonus, stats, gamble, session }) {
    this.boot = boot;
    this.reels = reels;
    this.anim = anim;
    this.audio = audio;
    this.particles = particles;
    this.fs = freeSpins;
    this.bonus = bonus;
    this.stats = stats;
    this.gamble = gamble;
    this.session = session;

    this.balance = boot.user.balance;
    this.freeSpins = boot.user.free_spins;
    this.game = null;
    this.busy = false;
    this.lines = 10;
    this.levelIdx = 0;           // Index in levelOptions
    this.auto = { active: false, count: 0, total: 0 };
    this.turbo = false;

    this.reels.onReelStop = () => this.audio.play('reelStop');
    this.reels.onAnticipate = () => this.audio.play('anticipate');
  }

  get coinValue() { return this.boot.coinValue; }
  get level() { return this.boot.levelOptions[this.levelIdx]; }
  get betPerLine() { return this.level * this.coinValue; }
  get totalBet() { return this.betPerLine * this.lines; }

  loadGame(game) {
    this.game = game;
    this.lines = game.max_lines;
    this.levelIdx = 0;
    this.reels.build(game);
    applyTheme(game.theme);
    this.particles.setMode(game.theme.particle, game.theme.accent);
    this.audio.startMusic(game.theme.music);
    this.stats.use(game.id);
    // Banner
    const banner = $('featureBanner');
    banner.textContent = '★ ' + (game.feature.name || 'Freispiele');
    banner.hidden = false;
    $('gameTitle').textContent = game.title;
    $('gameSubtitle').textContent = game.subtitle;
    $('statRtp').textContent = 'RTP ' + Math.round((game.rtp || 0.96) * 100) + '%';
    $('statVol').textContent = game.volatility || '';
    this.updateReadouts();
    this.refreshHud();
    // Falls offene Freispiele aus dieser Session zu diesem Spiel gehören → Zustand wiederherstellen
    if (this.freeSpins > 0 && this.boot.user.fs_game === game.id) {
      const total = this.boot.user.fs_total || this.freeSpins;
      const played = Math.max(0, total - this.freeSpins);
      this.fs.active = true; this.fs.total = total; this.fs.played = played;
      document.body.classList.add('rr-freespin');
      this._updateFsLabel(played, total, true);
      $('spinLabel').textContent = 'FREE';
    }
  }

  adjustLines(d) { if (this.busy) return; this.lines = Math.max(1, Math.min(this.game.max_lines, this.lines + d)); this.updateReadouts(); this.audio.play('click'); }
  adjustBet(d) { if (this.busy) return; this.levelIdx = Math.max(0, Math.min(this.boot.levelOptions.length - 1, this.levelIdx + d)); this.updateReadouts(); this.audio.play('click'); }
  maxBet() { if (this.busy) return; this.levelIdx = this.boot.levelOptions.length - 1; this.lines = this.game.max_lines; this.updateReadouts(); this.audio.play('click'); }

  updateReadouts() {
    $('lineValue').textContent = this.lines;
    $('betValue').textContent = money(this.betPerLine);
    $('totalBetValue').textContent = money(this.totalBet);
  }

  refreshHud() {
    const b = $('hudBalance'); b.textContent = money(this.balance); this._bump(b);
    if (this.session) this.session.render(this.balance);
    const chip = $('hudFsChip');
    if (this.fs.active && this.fs.total > 0) {
      chip.hidden = false; $('hudFreeSpins').textContent = `${Math.min(this.fs.played, this.fs.total)} / ${this.fs.total}`;
    } else if (this.freeSpins > 0) {
      chip.hidden = false; $('hudFreeSpins').textContent = this.freeSpins;
    } else chip.hidden = true;
  }
  _bump(el) { el.classList.remove('bump'); void el.offsetWidth; el.classList.add('bump'); }

  setSpinning(on) {
    const btn = $('spinBtn');
    btn.classList.toggle('is-spinning', on);
    btn.disabled = on;
    let label = on ? '' : (this.freeSpins > 0 ? 'FREE' : 'SPIN');
    // Auto-Dreh: aktuellen Spin als „x/y" anzeigen (Lade-Kreis läuft dahinter)
    if (on && this.auto.active && this.auto.total) {
      label = (this.auto.total - this.auto.count + 1) + '/' + this.auto.total;
    }
    $('spinLabel').textContent = label;
  }

  startAuto(count) {
    this.auto = { active: true, count, total: count };
    $('autoBtn').textContent = 'STOP';
    $('spinBtn').classList.add('is-auto');
    if (!this.busy && this.freeSpins === 0) this.spin();
  }
  stopAuto() {
    this.auto = { active: false, count: 0, total: 0 };
    $('autoBtn').textContent = 'AUTO';
    $('spinBtn').classList.remove('is-auto');
  }

  async spin() {
    if (this.busy || !this.game) return;
    const isFreeBefore = this.freeSpins > 0;
    if (!isFreeBefore && this.balance < this.totalBet) {
      this.toast('Zu wenig Coins', 'Bitte Einsatz senken oder Tagesbonus holen.', 'error');
      this.stopAuto();
      return;
    }
    this.busy = true;
    this.setSpinning(true);
    this.anim.clear();
    $('winPop').hidden = true;
    this.audio.play('reelStart');
    $('winSummary').textContent = isFreeBefore ? 'Freispiel läuft…' : 'Walzen drehen…';

    let data;
    try {
      data = await postJSON('api/spin.php', {
        game: this.game.id, lines: this.lines, level: this.level, csrf: this.boot.csrf,
      });
    } catch (err) {
      this.busy = false; this.setSpinning(false); this.stopAuto();
      this.toast('Fehler', err.message, 'error');
      $('winSummary').textContent = err.message;
      return;
    }

    const fastSpin = this.turbo || this.auto.active || isFreeBefore;
    if (data.respin && data.respin.first_grid && data.respin.held) {
      // Hold & Respin: erst Auslöse-Dreh (3 Freispielsymbole), dann Scatter-Walzen halten
      // und die übrigen einmal neu drehen (Chance auf mehr Freispielsymbole → mehr Freispiele).
      await this.reels.spin(data.respin.first_grid, { fast: fastSpin });
      this.audio.play('anticipate');
      this.toast('🎰 Respin', 'Freispiel-Symbole gehalten', 'gold');
      await wait(650);
      await this.reels.spin(data.grid, { fast: fastSpin, hold: data.respin.held });
    } else {
      await this.reels.spin(data.grid, { fast: fastSpin });
    }

    // Zustand übernehmen
    this.balance = data.balance;
    const fsBefore = this.freeSpins;
    this.freeSpins = data.free_spins;
    this.stats.record(data);

    // Spezial-Highlights
    const meta = data.meta || {};
    this.anim.markSpecial(meta.expanded, 'is-special');
    this.anim.markSpecial(meta.transformed, 'is-special');
    this.anim.markSpecial(meta.walk, 'is-special');
    if (meta.collected && meta.collected.length) this.anim.showCollected(meta.collected);

    // Vollbild-Bonus des Glückssymbols („extra viel Punkte")
    if (meta.fullscreen) {
      this.audio.play('bigwin');
      this.particles.coinShower(90);
      this.particles.confetti(90);
      if (this.particles.fireworks) this.particles.fireworks(6);
      this.toast('🍀 VOLLBILD · Glückssymbol', `+${money(meta.fullscreen_win || 0)} ${this.boot.currency}`, 'gold');
    }

    // Multiplikator / Sammler-Anzeige
    this._updateFeatureBars(data);

    // Gewinnlinien
    if (data.wins && data.wins.length) this.anim.highlightWins(data.wins);
    // Tanzende Symbole: Scatter, Bonus und Gewinn-Symbole pulsieren
    this.anim.danceSpecial(this.game);

    this.refreshHud();
    $('lastWin').textContent = money(data.total_win);

    // Gewinn-Feedback
    const tiers = this.boot.winTiers;
    const tier = this.anim.tier(data.total_win, data.total_bet || (this.betPerLine * this.lines) || 1, tiers);
    if (data.total_win > 0) {
      this.audio.play('win');
      this.particles.burst(window.innerWidth / 2, window.innerHeight / 2, this.game.theme.particle === 'fire' ? 'fire' : 'gold', 16);
      this.anim.popWin(data.total_win);
      $('winSummary').textContent = `Gewinn: +${money(data.total_win)} ${this.boot.currency}`;
      if (tier && data.wins) await wait(700);
    } else {
      $('winSummary').textContent = isFreeBefore ? 'Kein Gewinn in diesem Freispiel.' : 'Kein Treffer. Nächster Spin?';
    }

    // Bonusspiel (Rad)
    let stopForBonus = false;
    if (meta.bonus) {
      stopForBonus = true;
      await this.bonus.play(meta.bonus, data.total_bet);
      this.refreshHud();
    }

    // Big/Mega/Epic Overlay
    let stopForBig = false;
    if (tier && tier.key !== 'small') {
      stopForBig = ['big', 'mega', 'epic'].includes(tier.key);
      if (stopForBig) {
        await this.anim.bigWin(tier.label, data.total_win, tier.key);
      } else {
        this.toast(tier.label, `+${money(data.total_win)} ${this.boot.currency}`, 'gold');
      }
    }

    // Verdopplungsspiel (Gamble) – nur Basisspiel-Gewinne, nicht beim Freispiel-Auslöser/Bonus
    if (this.gamble && data.gamble_available && !isFreeBefore && !data.fs_entered && !(meta && meta.bonus) && data.total_win > 0) {
      const autoGamble = this.auto.active;
      const bal = await this.gamble.offer(data.total_win, autoGamble);
      if (bal != null && !Number.isNaN(bal)) {
        this.balance = bal;
        this.refreshHud();
      }
    }

    // Freispiel-Status (servergesteuert)
    const fsTotal = data.fs_total || 0;
    const fsPlayed = data.fs_played || 0;
    let enteredFree = false;
    if (data.fs_entered && !this.fs.active) {
      enteredFree = true;
      this.stopAuto();
      await this.fs.enter(this.freeSpins, data.special, this.game, fsTotal);
    } else if (data.is_free_spin && data.free_spins_won > 0 && this.fs.active) {
      await this.fs.retrigger(data.free_spins_won, fsTotal);
    }
    this.fs.setProgress(fsPlayed, fsTotal, data.fs_active);
    this._updateFsLabel(fsPlayed, fsTotal, data.fs_active);
    if (data.fs_ended) {
      await this.fs.end(data.fs_session_win);
      this._hideFeatureBars();
    }

    this.busy = false;
    this.setSpinning(false);

    // Auto-Stopp nur bei Freispiel-Auslösung (Big Win & Bonus laufen automatisch weiter)
    if (enteredFree && !this.fs.active) this.stopAuto();

    // Fortsetzung
    if (this.freeSpins > 0) {
      await wait(750);
      this.spin();
    } else if (this.auto.active) {
      this.auto.count--;
      if (this.auto.count <= 0) this.stopAuto();
      else { await wait(420); if (this.auto.active) this.spin(); }
    }
  }

  _updateFeatureBars(data) {
    const bar = $('collectBar');
    const mult = data.spin_mult || 1;
    const feat = this.game.feature.type;
    const label = bar.querySelector('span');
    if (this.fs.active && feat === 'collector') {
      bar.hidden = false;
      const isLion = this.game.id === 'lion_pride';
      const count = data.fs_collected || 0;
      const cur = (data.meta && data.meta.collect_win) || 0;
      if (label) label.textContent = isLion ? '🦁 Löwinnen erobert' : 'Sammler';
      $('collectTotal').textContent = `${count}` + (cur > 0 ? ` · +${money(cur)}` : '');
      const pill = $('multPill');
      if (mult > 1) { pill.hidden = false; pill.textContent = '×' + mult; } else pill.hidden = true;
    } else if (this.fs.active && mult > 1) {
      bar.hidden = false;
      if (label) label.textContent = 'Bonus';
      $('collectTotal').textContent = '×' + mult + ' Multiplikator';
      $('multPill').hidden = true;
    } else {
      bar.hidden = true;
    }
  }
  _hideFeatureBars() { $('collectBar').hidden = true; $('multPill').hidden = true; }

  _updateFsLabel(played, total, active) {
    const chip = $('hudFsChip');
    const banner = $('featureBanner');
    if (active && total > 0) {
      chip.hidden = false;
      $('hudFreeSpins').textContent = `${Math.min(played, total)} / ${total}`;
      if (this.game) banner.textContent = `★ ${this.game.feature.name} · Freispiel ${Math.min(played, total)}/${total}`;
    } else {
      chip.hidden = true;
      if (this.game) banner.textContent = '★ ' + (this.game.feature.name || 'Freispiele');
    }
  }

  toast(title, msg, kind = 'win') {
    const wrap = $('toasts');
    const t = document.createElement('div');
    t.className = 'rr-toast ' + kind;
    t.innerHTML = `<b>${title}</b><span>${msg}</span>`;
    wrap.appendChild(t);
    requestAnimationFrame(() => t.classList.add('show'));
    setTimeout(() => { t.classList.remove('show'); setTimeout(() => t.remove(), 350); }, this.boot.toastMs || 3200);
  }
}

Youez - 2016 - github.com/yon3zu
LinuXploit