261 lines
11 KiB
JavaScript
261 lines
11 KiB
JavaScript
/* ============================================================
|
|
public/js/buildings/daily.js
|
|
|
|
/* ── Positionen (% in viewBox 0 0 100 100) ───────────────── */
|
|
const STATIONS = [
|
|
{ id:1, cx:48.5, cy:87.0 },
|
|
{ id:2, cx:36.5, cy:73.5 },
|
|
{ id:3, cx:44.0, cy:63.0 },
|
|
{ id:4, cx:53.5, cy:54.5 },
|
|
{ id:5, cx:47.5, cy:46.5 },
|
|
{ id:6, cx:43.5, cy:38.5 },
|
|
{ id:7, cx:49.5, cy:23.0 },
|
|
];
|
|
const R = 4.8; // Kreis-Radius in SVG-Einheiten
|
|
|
|
let dailyDeckId = null;
|
|
let dailyOverlay = null;
|
|
|
|
/* ── Karten-Pfad öffnen ──────────────────────────────────── */
|
|
export async function openDailyMap(deckId) {
|
|
dailyDeckId = deckId;
|
|
document.getElementById('daily-map-overlay')?.remove();
|
|
|
|
let completed = [];
|
|
try {
|
|
const r = await fetch('/api/himmelstor/daily/progress');
|
|
if (r.ok) completed = (await r.json()).completed ?? [];
|
|
} catch {}
|
|
|
|
dailyOverlay = document.createElement('div');
|
|
dailyOverlay.id = 'daily-map-overlay';
|
|
dailyOverlay.innerHTML = `
|
|
<div id="daily-map-wrap">
|
|
<img id="daily-map-img"
|
|
src="/images/KI_arena/daily.jpeg"
|
|
onerror="this.src='/images/items/rueckseite.png'" />
|
|
|
|
${buildSvg(completed)}
|
|
|
|
<div id="daily-map-title">☀️ TAGESHERAUSFORDERUNG</div>
|
|
<button id="daily-map-close">✕</button>
|
|
|
|
<div id="daily-progress-bar-wrap">
|
|
<div id="daily-progress-text">${completed.length} / 7 Stationen abgeschlossen</div>
|
|
<div id="daily-progress-track">
|
|
<div id="daily-progress-fill" style="width:${Math.round(completed.length/7*100)}%"></div>
|
|
</div>
|
|
</div>
|
|
${completed.length >= 7 ? allDoneHtml() : ''}
|
|
</div>`;
|
|
|
|
document.body.appendChild(dailyOverlay);
|
|
document.getElementById('daily-map-close').addEventListener('click', closeMap);
|
|
dailyOverlay.addEventListener('click', e => { if (e.target === dailyOverlay) closeMap(); });
|
|
|
|
/* Klick-Listener auf SVG-Hitboxen */
|
|
dailyOverlay.querySelectorAll('.ds-hit[data-station]').forEach(el => {
|
|
el.addEventListener('click', () => startStation(parseInt(el.dataset.station, 10)));
|
|
});
|
|
}
|
|
|
|
/* ── SVG aufbauen ────────────────────────────────────────── */
|
|
function buildSvg(completed) {
|
|
const maxDone = completed.length;
|
|
const parts = [];
|
|
|
|
/* Pfadlinien zwischen Stationen */
|
|
for (let i = 0; i < STATIONS.length - 1; i++) {
|
|
const a = STATIONS[i];
|
|
const b = STATIONS[i + 1];
|
|
const done = completed.includes(a.id);
|
|
parts.push(`<line class="ds-path-line${done ? ' done' : ''}"
|
|
x1="${a.cx}" y1="${a.cy}" x2="${b.cx}" y2="${b.cy}"/>`);
|
|
}
|
|
|
|
/* Stationskreise */
|
|
STATIONS.forEach(s => {
|
|
const isDone = completed.includes(s.id);
|
|
const isAvail = !isDone && s.id === maxDone + 1;
|
|
const cls = isDone ? 'ds-done' : isAvail ? 'ds-available' : 'ds-locked';
|
|
const fSize = R * 0.72;
|
|
|
|
if (isDone) {
|
|
parts.push(`
|
|
<g class="ds-station ${cls}" data-station="${s.id}">
|
|
<circle class="ds-overlay" cx="${s.cx}" cy="${s.cy}" r="${R}"/>
|
|
<text class="ds-check" x="${s.cx}" y="${s.cy}"
|
|
text-anchor="middle" dominant-baseline="central"
|
|
font-size="${fSize * 1.1}">✓</text>
|
|
</g>`);
|
|
|
|
} else if (isAvail) {
|
|
// Available: nur pulsierende Ringe über dem Bild-Kreis, kein eigener Kreis
|
|
parts.push(`
|
|
<g class="ds-station ${cls}" data-station="${s.id}">
|
|
<circle class="ds-ring1" cx="${s.cx}" cy="${s.cy}" r="${R + 1.2}"
|
|
fill="none" stroke="rgba(80,200,255,.8)" stroke-width="1.2">
|
|
<animate attributeName="r"
|
|
values="${R+1.2};${R+2.8};${R+1.2}" dur="2s" repeatCount="indefinite"/>
|
|
<animate attributeName="opacity"
|
|
values=".8;0;.8" dur="2s" repeatCount="indefinite"/>
|
|
</circle>
|
|
<circle class="ds-ring2" cx="${s.cx}" cy="${s.cy}" r="${R + 2}"
|
|
fill="none" stroke="rgba(80,200,255,.4)" stroke-width="0.8">
|
|
<animate attributeName="r"
|
|
values="${R+2};${R+4};${R+2}" dur="2s" begin=".7s" repeatCount="indefinite"/>
|
|
<animate attributeName="opacity"
|
|
values=".5;0;.5" dur="2s" begin=".7s" repeatCount="indefinite"/>
|
|
</circle>
|
|
<!-- Transparente Hitbox -->
|
|
<circle class="ds-hit" cx="${s.cx}" cy="${s.cy}" r="${R + 1.5}"
|
|
fill="transparent" stroke="none" data-station="${s.id}"/>
|
|
</g>`);
|
|
|
|
} else {
|
|
// Locked: unsichtbare Hitfläche, kein eigener Kreis (Bild-Kreis bleibt sichtbar)
|
|
parts.push(`
|
|
<g class="ds-station ${cls}" data-station="${s.id}">
|
|
<circle fill="rgba(0,0,0,0.35)" stroke="none" cx="${s.cx}" cy="${s.cy}" r="${R}"/>
|
|
<circle fill="transparent" stroke="none" cx="${s.cx}" cy="${s.cy}" r="${R + 1}"/>
|
|
</g>`);
|
|
}
|
|
});
|
|
|
|
return `<svg id="daily-map-svg"
|
|
viewBox="0 0 100 100"
|
|
preserveAspectRatio="none">${parts.join('')}</svg>`;
|
|
}
|
|
|
|
/* ── Station starten ─────────────────────────────────────── */
|
|
function startStation(station) {
|
|
if (!dailyDeckId) { alert('Bitte zuerst ein Deck auswählen.'); return; }
|
|
const socket = window._socket;
|
|
if (!socket) return;
|
|
|
|
/* Hitbox deaktivieren während Laden */
|
|
dailyOverlay.querySelectorAll(`.ds-hit[data-station="${station}"]`).forEach(el => {
|
|
el.style.pointerEvents = 'none';
|
|
el.style.opacity = '.4';
|
|
});
|
|
|
|
socket.once('ht_daily_match_found', data => {
|
|
closeMap();
|
|
openPopup(
|
|
`/himmelstor/daily?match=${encodeURIComponent(data.matchId)}`
|
|
+ `&slot=${encodeURIComponent(data.mySlot)}`
|
|
+ `&deck=${encodeURIComponent(dailyDeckId)}`
|
|
+ `&station=${station}`
|
|
+ `&opponent=${encodeURIComponent('Wächter ' + station)}`,
|
|
'Wächter ' + station, data.matchId
|
|
);
|
|
});
|
|
|
|
socket.once('ht_daily_error', data => {
|
|
dailyOverlay.querySelectorAll(`.ds-hit[data-station="${station}"]`).forEach(el => {
|
|
el.style.pointerEvents = '';
|
|
el.style.opacity = '';
|
|
});
|
|
alert(data.message || 'Fehler beim Starten.');
|
|
});
|
|
|
|
socket.emit('ht_join_daily', { station, deckId: dailyDeckId });
|
|
}
|
|
|
|
/* ── Station als erledigt markieren ─────────────────────── */
|
|
export function markDailyStationDone(station) {
|
|
if (!dailyOverlay) return;
|
|
|
|
/* SVG-Gruppe aktualisieren */
|
|
const g = dailyOverlay.querySelector(`.ds-station[data-station="${station}"]`);
|
|
if (g) {
|
|
g.className.baseVal = 'ds-station ds-done';
|
|
g.innerHTML = `
|
|
<circle class="ds-overlay" cx="${STATIONS[station-1].cx}" cy="${STATIONS[station-1].cy}" r="${R}"/>
|
|
<text class="ds-check" x="${STATIONS[station-1].cx}" y="${STATIONS[station-1].cy}"
|
|
text-anchor="middle" dominant-baseline="central" font-size="${R*0.8}">✓</text>`;
|
|
}
|
|
|
|
/* Pfadlinie grün */
|
|
const line = dailyOverlay.querySelector(`.ds-path-line:nth-of-type(${station})`);
|
|
if (line) line.classList.add('done');
|
|
|
|
/* Nächste Station freischalten */
|
|
const nextStation = STATIONS.find(s => s.id === station + 1);
|
|
const nextG = nextStation
|
|
? dailyOverlay.querySelector(`.ds-station[data-station="${station + 1}"]`)
|
|
: null;
|
|
|
|
if (nextG && nextStation) {
|
|
const fSize = R * 0.72;
|
|
nextG.className.baseVal = 'ds-station ds-available';
|
|
nextG.innerHTML = `
|
|
<circle class="ds-ring1" cx="${nextStation.cx}" cy="${nextStation.cy}" r="${R+1.2}">
|
|
<animate attributeName="r" values="${R+1.2};${R+2.6};${R+1.2}" dur="2s" repeatCount="indefinite"/>
|
|
<animate attributeName="opacity" values=".7;0;.7" dur="2s" repeatCount="indefinite"/>
|
|
</circle>
|
|
<circle class="ds-ring2" cx="${nextStation.cx}" cy="${nextStation.cy}" r="${R+2}">
|
|
<animate attributeName="r" values="${R+2};${R+3.6};${R+2}" dur="2s" begin=".7s" repeatCount="indefinite"/>
|
|
<animate attributeName="opacity" values=".4;0;.4" dur="2s" begin=".7s" repeatCount="indefinite"/>
|
|
</circle>
|
|
<circle class="ds-overlay" cx="${nextStation.cx}" cy="${nextStation.cy}" r="${R}"/>
|
|
<text class="ds-num" x="${nextStation.cx}" y="${nextStation.cy}"
|
|
text-anchor="middle" dominant-baseline="central" font-size="${fSize}">${station+1}</text>
|
|
<circle class="ds-hit" cx="${nextStation.cx}" cy="${nextStation.cy}" r="${R+1.5}"
|
|
data-station="${station+1}"/>`;
|
|
nextG.querySelector('.ds-hit')?.addEventListener('click', () => startStation(station + 1));
|
|
}
|
|
|
|
/* Fortschritt */
|
|
const done = dailyOverlay.querySelectorAll('.ds-done').length;
|
|
const pct = Math.round(done / 7 * 100);
|
|
const fill = document.getElementById('daily-progress-fill');
|
|
const text = document.getElementById('daily-progress-text');
|
|
if (fill) fill.style.width = pct + '%';
|
|
if (text) text.textContent = `${done} / 7 Stationen abgeschlossen`;
|
|
|
|
if (done >= 7) {
|
|
const wrap = document.getElementById('daily-map-wrap');
|
|
if (wrap && !document.getElementById('daily-all-done')) {
|
|
wrap.insertAdjacentHTML('beforeend', allDoneHtml());
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ── Popup öffnen ────────────────────────────────────────── */
|
|
function openPopup(src, opponent, matchId) {
|
|
document.getElementById('ht-daily-backdrop')?.remove();
|
|
document.getElementById('ht-daily-popup')?.remove();
|
|
|
|
const bd = document.createElement('div');
|
|
bd.id = 'ht-daily-backdrop';
|
|
bd.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.82);backdrop-filter:blur(5px);z-index:9998;';
|
|
|
|
const pp = document.createElement('div');
|
|
pp.id = 'ht-daily-popup';
|
|
pp.style.cssText = 'position:fixed;inset:50px;z-index:9999;display:flex;flex-direction:column;border-radius:14px;overflow:hidden;box-shadow:0 0 0 1px rgba(80,140,255,.4),0 30px 90px rgba(0,0,0,.85);';
|
|
pp.innerHTML = `
|
|
<div style="display:flex;align-items:center;justify-content:space-between;background:rgba(5,8,20,.95);border-bottom:1px solid rgba(80,140,255,.3);padding:0 16px;height:42px;flex-shrink:0;">
|
|
<span style="font-family:'Cinzel',serif;font-size:13px;letter-spacing:4px;color:rgba(160,200,255,.85);text-transform:uppercase;">☀️ Daily · vs ${opponent}</span>
|
|
<span style="font-size:11px;color:rgba(255,255,255,.22);">${matchId}</span>
|
|
</div>
|
|
<iframe src="${src}" allowfullscreen style="flex:1;border:none;width:100%;display:block;"></iframe>`;
|
|
|
|
document.body.appendChild(bd);
|
|
document.body.appendChild(pp);
|
|
}
|
|
|
|
function closeMap() {
|
|
document.getElementById('daily-map-overlay')?.remove();
|
|
dailyOverlay = null;
|
|
}
|
|
|
|
function allDoneHtml() {
|
|
return `<div id="daily-all-done">
|
|
<div class="done-icon">🏆</div>
|
|
<div class="done-title">TAGESQUEST ABGESCHLOSSEN!</div>
|
|
<div class="done-sub">Du hast alle 7 Stationen gemeistert.</div>
|
|
<button class="done-btn" onclick="document.getElementById('daily-map-overlay')?.remove()">✔ SCHLIESSEN</button>
|
|
</div>`;
|
|
}
|