Weitere Fehler im Chastity ingame game behoben
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled
This commit is contained in:
3
bin/main/db/migration/V7__fix_aktive_sperre_fuer_fk.sql
Normal file
3
bin/main/db/migration/V7__fix_aktive_sperre_fuer_fk.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE aktive_sperre_fuer DROP FOREIGN KEY FK36uaxlluxoow36iy1pqd4ig8b;
|
||||
ALTER TABLE aktive_sperre_fuer ADD CONSTRAINT fk_aktive_sperre_fuer_lock_game_lock
|
||||
FOREIGN KEY (aktive_sperre_id) REFERENCES lock_game_lock (lock_game_lock_id);
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -902,6 +902,10 @@
|
||||
else document.getElementById('lockContent').textContent = 'Kein Lock angegeben.';
|
||||
});
|
||||
|
||||
window.addEventListener('pageshow', (e) => {
|
||||
if (e.persisted && lockId) loadLock();
|
||||
});
|
||||
|
||||
async function loadLock() {
|
||||
const res = await fetch('/keyholder/cardlock/' + lockId);
|
||||
if (res.status === 404) {
|
||||
@@ -1322,7 +1326,13 @@
|
||||
const cdEl = document.getElementById('gameCardCountdown');
|
||||
function tick() {
|
||||
const diff = deadline - Date.now();
|
||||
if (diff <= 0) { panel.style.display = 'none'; clearInterval(gameCardPanelTick); gameCardPanelTick = null; return; }
|
||||
if (diff <= 0) {
|
||||
clearInterval(gameCardPanelTick); gameCardPanelTick = null;
|
||||
panel.style.display = 'none';
|
||||
fetch('/lock-game/penalty?lockId=' + lockId, { method: 'POST' }).catch(() => {});
|
||||
loadLock();
|
||||
return;
|
||||
}
|
||||
cdEl.textContent = fmtCountdown(diff);
|
||||
}
|
||||
tick();
|
||||
@@ -1335,7 +1345,8 @@
|
||||
const data = await res.json();
|
||||
const gameSetId = data.gameSetId;
|
||||
const url = '/games/chastity/taskgame.html?lockId=' + lockId
|
||||
+ (gameSetId ? '&gameSetId=' + gameSetId : '');
|
||||
+ (gameSetId ? '&gameSetId=' + gameSetId : '')
|
||||
+ '&fresh=1';
|
||||
window.location.href = url;
|
||||
}
|
||||
|
||||
|
||||
@@ -405,13 +405,9 @@
|
||||
<div id="subGameSet" style="display:none;">
|
||||
<div class="form-row">
|
||||
<label>Aufgaben-Set (Chastity) <span class="required-star">*</span></label>
|
||||
<div style="position:relative;">
|
||||
<input type="text" id="gameSetSearch" placeholder="Name eingeben zum Suchen…"
|
||||
autocomplete="off" oninput="onGameSetSearch(this.value)" onfocus="onGameSetSearchFocus()">
|
||||
<div id="gameSetDropdown" class="gs-dropdown"></div>
|
||||
</div>
|
||||
<div id="gameSetSelected" class="gs-selected" style="display:none;"></div>
|
||||
<input type="hidden" id="fGameSetId">
|
||||
<select id="fGameSetId" onchange="markDirty()">
|
||||
<option value="">Kein Aufgaben-Set</option>
|
||||
</select>
|
||||
<div class="field-error-msg" id="errGameSet" style="display:none;">Bitte ein Aufgaben-Set für Spiel-Karten auswählen.</div>
|
||||
</div>
|
||||
<div class="form-row" style="margin-bottom:0;">
|
||||
@@ -757,8 +753,30 @@
|
||||
{ label: 'Lang' },
|
||||
{ label: 'Sehr lang' },
|
||||
];
|
||||
let _gameSetSearchTimer = null;
|
||||
let _gameSetResults = [];
|
||||
let _gameGroups = [];
|
||||
|
||||
async function loadGameGroups() {
|
||||
try {
|
||||
const res = await fetch('/lock-game/groups');
|
||||
if (!res.ok) return;
|
||||
_gameGroups = await res.json();
|
||||
populateGameSetSelect();
|
||||
} catch(e) { console.error(e); }
|
||||
}
|
||||
|
||||
function populateGameSetSelect() {
|
||||
const sel = document.getElementById('fGameSetId');
|
||||
if (!sel) return;
|
||||
const cur = sel.value;
|
||||
sel.innerHTML = '<option value="">Kein Aufgaben-Set</option>';
|
||||
_gameGroups.forEach(g => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = g.gruppenId;
|
||||
opt.textContent = g.name + (g.beschreibung ? ' – ' + g.beschreibung : '');
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
sel.value = cur;
|
||||
}
|
||||
|
||||
function checkGameCardSection() {
|
||||
const minV = parseInt(document.getElementById('min_GAME_CARD')?.value) || 0;
|
||||
@@ -791,72 +809,6 @@
|
||||
document.getElementById('valGameSpieldauer').textContent = GAME_SPIELDAUER[+val].label;
|
||||
}
|
||||
|
||||
function onGameSetSearchFocus() {
|
||||
if (!document.getElementById('fGameSetId').value) onGameSetSearch(document.getElementById('gameSetSearch').value);
|
||||
}
|
||||
|
||||
function onGameSetSearch(value) {
|
||||
clearTimeout(_gameSetSearchTimer);
|
||||
if (value.length === 0) {
|
||||
_gameSetSearchTimer = setTimeout(() => doGameSetSearch(''), 0);
|
||||
} else if (value.length < 2) {
|
||||
document.getElementById('gameSetDropdown').style.display = 'none';
|
||||
} else {
|
||||
_gameSetSearchTimer = setTimeout(() => doGameSetSearch(value), 300);
|
||||
}
|
||||
}
|
||||
|
||||
async function doGameSetSearch(search) {
|
||||
try {
|
||||
const url = '/gruppe/chastity' + (search ? '?search=' + encodeURIComponent(search) : '');
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
_gameSetResults = data.gruppen || [];
|
||||
renderGameSetDropdown();
|
||||
} catch(e) { console.error(e); }
|
||||
}
|
||||
|
||||
function renderGameSetDropdown() {
|
||||
const dd = document.getElementById('gameSetDropdown');
|
||||
if (!dd) return;
|
||||
if (!_gameSetResults.length) { dd.style.display = 'none'; return; }
|
||||
dd.innerHTML = _gameSetResults.map(g => `
|
||||
<div class="gs-dropdown-item" onclick="selectGameSet('${esc(g.gruppenId)}','${esc(g.name).replace(/'/g, "\\'")}')">
|
||||
<div class="gs-item-name">${esc(g.name)}</div>
|
||||
${g.beschreibung ? `<div class="gs-item-desc">${esc(g.beschreibung)}</div>` : ''}
|
||||
</div>`).join('');
|
||||
dd.style.display = 'block';
|
||||
}
|
||||
|
||||
function selectGameSet(id, name, suppressDirty = false) {
|
||||
document.getElementById('fGameSetId').value = id;
|
||||
document.getElementById('gameSetSearch').value = '';
|
||||
document.getElementById('gameSetDropdown').style.display = 'none';
|
||||
document.getElementById('gameSetSelected').innerHTML =
|
||||
`<span style="flex:1;">${esc(name)}</span>
|
||||
<button type="button" onclick="clearGameSet()" title="Auswahl entfernen">✕</button>`;
|
||||
document.getElementById('gameSetSelected').style.display = 'flex';
|
||||
document.getElementById('errGameSet').style.display = 'none';
|
||||
if (!suppressDirty) markDirty();
|
||||
}
|
||||
|
||||
function clearGameSet() {
|
||||
document.getElementById('fGameSetId').value = '';
|
||||
document.getElementById('gameSetSearch').value = '';
|
||||
document.getElementById('gameSetSelected').style.display = 'none';
|
||||
document.getElementById('gameSetSelected').innerHTML = '';
|
||||
markDirty();
|
||||
}
|
||||
|
||||
document.addEventListener('click', e => {
|
||||
const search = document.getElementById('gameSetSearch');
|
||||
const dd = document.getElementById('gameSetDropdown');
|
||||
if (dd && search && !search.contains(e.target) && !dd.contains(e.target)) {
|
||||
dd.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// ── Karten-Info ──
|
||||
function openCardInfo(cardId) {
|
||||
const c = CARD_DEFS.find(x => x.id === cardId); if (!c) return;
|
||||
@@ -1300,17 +1252,12 @@
|
||||
|
||||
// Task-Karte und Spiel-Karte
|
||||
checkTaskCardSection();
|
||||
clearGameSet();
|
||||
checkGameCardSection();
|
||||
const gsi = template?.gameSpieldauerIdx ?? 2;
|
||||
document.getElementById('sldGameSpieldauer').value = gsi;
|
||||
updateGameSpieldauer(gsi);
|
||||
if (template?.gameSetId) {
|
||||
fetch(`/gruppe/${template.gameSetId}`)
|
||||
.then(r => r.ok ? r.json() : null)
|
||||
.then(g => { if (g?.name) selectGameSet(template.gameSetId, g.name, true); })
|
||||
.catch(() => {});
|
||||
}
|
||||
populateGameSetSelect();
|
||||
document.getElementById('fGameSetId').value = template?.gameSetId || '';
|
||||
}
|
||||
|
||||
if (type === 'TIMELOCK') {
|
||||
@@ -1664,6 +1611,7 @@
|
||||
document.getElementById('templateList').innerHTML = '';
|
||||
document.getElementById('listEmpty').style.display = 'none';
|
||||
await loadTaskSets();
|
||||
await loadGameGroups();
|
||||
loadNextPage();
|
||||
loadSubscribedTemplates();
|
||||
}
|
||||
|
||||
@@ -22,22 +22,36 @@
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: 0.5rem;
|
||||
height: 1.2rem;
|
||||
}
|
||||
.game-text {
|
||||
font-size: 1rem;
|
||||
line-height: 1.6;
|
||||
color: var(--color-text);
|
||||
white-space: pre-wrap;
|
||||
height: 14rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.game-timer {
|
||||
font-size: 2.2rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-primary);
|
||||
text-align: center;
|
||||
margin: 0.75rem 0;
|
||||
letter-spacing: 0.04em;
|
||||
height: 4rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
.game-timer.active { opacity: 1; }
|
||||
.game-timer.urgent { color: #e74c3c; }
|
||||
.game-btn-row {
|
||||
margin-top: 1rem;
|
||||
height: 2.75rem;
|
||||
}
|
||||
|
||||
.level-display {
|
||||
display: flex;
|
||||
@@ -82,6 +96,20 @@
|
||||
}
|
||||
.group-item input[type=radio] { accent-color: var(--color-primary); }
|
||||
|
||||
.toy-item {
|
||||
display: flex; align-items: center; gap: 0.6rem;
|
||||
padding: 0.6rem 0.85rem; border-radius: 8px;
|
||||
background: var(--color-card); border: 1px solid var(--color-secondary);
|
||||
margin-bottom: 0.5rem; cursor: pointer; transition: border-color 0.15s; user-select: none;
|
||||
}
|
||||
.toy-item.is-checked { border-color: var(--color-primary); }
|
||||
.toy-item input { accent-color: var(--color-primary); flex-shrink: 0; width: 14px; height: 14px; cursor: pointer; }
|
||||
.toy-item span { flex: 1; min-width: 0; }
|
||||
.toy-item-name { font-size: 0.95rem; font-weight: 600; color: var(--color-text); }
|
||||
.toy-item-desc { display: block; font-size: 0.8rem; color: var(--color-muted); margin-top: 0.15rem; }
|
||||
.toy-item-img { width: 38px; height: 38px; object-fit: cover; border-radius: 6px; flex-shrink: 0; }
|
||||
.toys-hint { font-size: 0.85rem; color: var(--color-muted); margin: 0 0 1rem; line-height: 1.5; }
|
||||
|
||||
.btn-primary {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
@@ -130,6 +158,21 @@
|
||||
<!-- Freigegebene Locks (checkLocks-Meldungen) -->
|
||||
<div id="lockMessages" class="lock-messages" style="display:none;"></div>
|
||||
|
||||
<!-- Toy-Auswahl vor Spielstart -->
|
||||
<div id="toyBox" style="display:none;">
|
||||
<div class="game-card">
|
||||
<div class="game-label">Verfügbare Toys</div>
|
||||
<p class="toys-hint">
|
||||
Deaktiviere Toys, die nicht zur Verfügung stehen.
|
||||
Aufgaben, die diese benötigen, werden deaktiviert.
|
||||
</p>
|
||||
<div id="toyToggleList"></div>
|
||||
<div style="margin-top:1.25rem;">
|
||||
<button class="btn-primary" onclick="handleToyConfirm()">▶ Spiel starten</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Initialisierung: Gruppe wählen -->
|
||||
<div id="initBox" class="init-box" style="display:none;">
|
||||
<h2>Spiel-Set auswählen</h2>
|
||||
@@ -143,22 +186,22 @@
|
||||
<!-- Laufendes Spiel -->
|
||||
<div id="gameBox" style="display:none;">
|
||||
|
||||
<!-- Task oder Lock in Queue -->
|
||||
<div id="queueBox" class="game-card" style="display:none;">
|
||||
<div class="game-label" id="queueLabel"></div>
|
||||
<div class="game-text" id="queueText"></div>
|
||||
<div style="display:flex;gap:0.6rem;margin-top:1.1rem;">
|
||||
<button class="btn-primary" id="btnOk" onclick="handleOk()">OK</button>
|
||||
<!-- Einheitliche Spielkarte -->
|
||||
<div id="gameCard" class="game-card" style="display:none;">
|
||||
<div class="game-label" id="gameLabel"></div>
|
||||
<div class="game-text" id="gameText"></div>
|
||||
<div class="game-timer" id="gameTimer"></div>
|
||||
<div class="game-btn-row">
|
||||
<button class="btn-primary" id="gameBtn" onclick="handleGameBtn()" style="width:100%;height:100%;"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Aktive Aufgabe (läuft) -->
|
||||
<div id="activeBox" class="game-card" style="display:none;">
|
||||
<div class="game-label">Aktive Aufgabe</div>
|
||||
<div class="game-text" id="activeText"></div>
|
||||
<div class="game-timer" id="activeTimer" style="display:none;"></div>
|
||||
<div style="display:flex;gap:0.6rem;margin-top:1.1rem;">
|
||||
<button class="btn-primary" id="btnErledigt" onclick="handleErledigt()">✓ Erledigt</button>
|
||||
<!-- Release-Text (Sperren) -->
|
||||
<div id="lockReleaseBox" class="game-card" style="display:none;">
|
||||
<div class="game-label">🔓 Sperre aufgehoben</div>
|
||||
<div class="game-text" id="releaseText"></div>
|
||||
<div style="margin-top:1.1rem;">
|
||||
<button class="btn-primary" id="btnReleaseOk">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -168,7 +211,12 @@
|
||||
<h2>Level 6 erreicht!</h2>
|
||||
<div class="game-label" id="finisherTitle"></div>
|
||||
<div class="game-text" id="finisherText" style="margin-top:0.5rem;text-align:left;"></div>
|
||||
<button class="btn-primary" onclick="completeGame()" style="margin-top:1.25rem;">✓ Spiel beenden</button>
|
||||
<button class="btn-primary" id="btnFinisherOk" style="margin-top:1.25rem;">✓ OK</button>
|
||||
</div>
|
||||
|
||||
<!-- Debug -->
|
||||
<div style="margin-top:1.5rem;">
|
||||
<button onclick="debugExit()" style="width:100%;padding:0.45rem;border-radius:8px;border:1px dashed #666;background:transparent;color:#666;font-size:0.78rem;cursor:pointer;">🐛 Debug exit</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -187,17 +235,18 @@
|
||||
const params = new URLSearchParams(location.search);
|
||||
const lockId = params.get('lockId');
|
||||
const autoGameSetId = params.get('gameSetId');
|
||||
let _state = null;
|
||||
let _timerInt = null;
|
||||
let _pendingIsLock = false;
|
||||
let _pendingHasDuration = false;
|
||||
const freshStart = params.get('fresh') === '1';
|
||||
let _resolvedGameSetId = autoGameSetId;
|
||||
let _state = null;
|
||||
let _timerInt = null;
|
||||
let _gameAction = null; // 'queue-start' | 'queue-done' | 'active-running' | 'active-done'
|
||||
|
||||
function goBack() {
|
||||
if (lockId) location.href = '/games/chastity/activelock.html?lockId=' + lockId;
|
||||
else history.back();
|
||||
}
|
||||
|
||||
async function completeGame() {
|
||||
async function debugExit() {
|
||||
const url = '/lock-game/complete' + (lockId ? '?lockId=' + lockId : '');
|
||||
await fetch(url, { method: 'POST' });
|
||||
goBack();
|
||||
@@ -207,40 +256,134 @@
|
||||
|
||||
async function boot() {
|
||||
try {
|
||||
const r = await fetch('/lock-game/state');
|
||||
if (r.status === 404) {
|
||||
if (autoGameSetId) {
|
||||
await autoStartGame(autoGameSetId);
|
||||
} else {
|
||||
await loadGroups();
|
||||
if (!freshStart) {
|
||||
const r = await fetch('/lock-game/state');
|
||||
if (r.ok) {
|
||||
_state = await r.json();
|
||||
hide('loadingHint');
|
||||
await runGameLoop();
|
||||
return;
|
||||
}
|
||||
return;
|
||||
if (r.status !== 404) throw new Error('Fehler beim Laden des Spielzustands');
|
||||
}
|
||||
|
||||
// Fresh start or no existing game: resolve gameSetId, then show toy selection
|
||||
let gameSetId = autoGameSetId;
|
||||
if (!gameSetId && lockId) {
|
||||
try {
|
||||
const lockR = await fetch('/keyholder/cardlock/' + lockId);
|
||||
if (lockR.ok) {
|
||||
const lockData = await lockR.json();
|
||||
gameSetId = lockData.gameSetId || null;
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
_resolvedGameSetId = gameSetId;
|
||||
if (gameSetId) {
|
||||
await loadAndShowToys(gameSetId);
|
||||
} else {
|
||||
await loadGroups();
|
||||
}
|
||||
if (!r.ok) throw new Error('Fehler beim Laden des Spielzustands');
|
||||
_state = await r.json();
|
||||
hide('loadingHint');
|
||||
await runGameLoop();
|
||||
} catch (e) {
|
||||
showError(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function autoStartGame(gameSetId) {
|
||||
// ── Toy-Auswahl ──────────────────────────────────────────────────────────
|
||||
|
||||
async function loadAndShowToys(gameSetId) {
|
||||
try {
|
||||
const r = await fetch('/lock-game/init?aufgabenGruppeId=' + gameSetId, { method: 'POST' });
|
||||
if (!r.ok) throw new Error('Initialisierung fehlgeschlagen');
|
||||
const stateR = await fetch('/lock-game/state');
|
||||
_state = await stateR.json();
|
||||
const r = await fetch('/lock-game/toys?aufgabenGruppeId=' + gameSetId);
|
||||
if (!r.ok) throw new Error('Fehler beim Laden der Toys');
|
||||
const toys = await r.json();
|
||||
hide('loadingHint');
|
||||
await runGameLoop();
|
||||
|
||||
const list = document.getElementById('toyToggleList');
|
||||
if (toys.length === 0) {
|
||||
list.innerHTML = '<p style="font-size:0.85rem;color:var(--color-muted);font-style:italic;margin:0;">'
|
||||
+ 'Keine Toys erforderlich – alle Aufgaben werden gespielt.</p>';
|
||||
} else {
|
||||
list.innerHTML = toys.map(t => `
|
||||
<label class="toy-item is-checked">
|
||||
<input type="checkbox" value="${esc(t.toyId)}" checked>
|
||||
<span>
|
||||
<span class="toy-item-name">${esc(t.name)}</span>
|
||||
${t.beschreibung ? `<span class="toy-item-desc">${esc(t.beschreibung)}</span>` : ''}
|
||||
</span>
|
||||
${t.bild ? `<img class="toy-item-img" src="data:image/png;base64,${t.bild}" alt="">` : ''}
|
||||
</label>`).join('');
|
||||
|
||||
list.addEventListener('change', e => {
|
||||
const cb = e.target;
|
||||
if (cb.type === 'checkbox') cb.closest('.toy-item')?.classList.toggle('is-checked', cb.checked);
|
||||
}, { once: false });
|
||||
}
|
||||
show('toyBox');
|
||||
} catch (e) {
|
||||
showError(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleToyConfirm() {
|
||||
const excludedToyIds = [];
|
||||
document.querySelectorAll('#toyToggleList input[type="checkbox"]').forEach(cb => {
|
||||
if (!cb.checked) excludedToyIds.push(cb.value);
|
||||
});
|
||||
hide('toyBox');
|
||||
show('loadingHint');
|
||||
try {
|
||||
await startWithExcludedToys(_resolvedGameSetId, excludedToyIds);
|
||||
} catch (e) {
|
||||
showError(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function startWithExcludedToys(gameSetId, excludedToyIds) {
|
||||
const params = new URLSearchParams({ aufgabenGruppeId: gameSetId });
|
||||
excludedToyIds.forEach(id => params.append('excludedToyIds', id));
|
||||
const r = await fetch('/lock-game/init?' + params.toString(), { method: 'POST' });
|
||||
|
||||
if (r.status === 422) {
|
||||
const body = await r.json().catch(() => ({}));
|
||||
await showValidationError(body.error || 'Das Aufgaben-Set ist nicht vollständig.');
|
||||
return;
|
||||
}
|
||||
if (!r.ok) throw new Error('Initialisierung fehlgeschlagen');
|
||||
|
||||
const stateR = await fetch('/lock-game/state');
|
||||
_state = await stateR.json();
|
||||
hide('loadingHint');
|
||||
await runGameLoop();
|
||||
}
|
||||
|
||||
async function showValidationError(msg) {
|
||||
hide('loadingHint');
|
||||
showError('Das Spiel kann nicht gestartet werden: ' + msg
|
||||
+ ' Du wirst in Kürze zurückgeleitet und erhältst eine Strafe.');
|
||||
|
||||
if (lockId) {
|
||||
fetch('/lock-game/penalty?lockId=' + lockId, { method: 'POST' }).catch(() => {});
|
||||
}
|
||||
|
||||
let secs = 5;
|
||||
const interval = setInterval(() => {
|
||||
const box = document.getElementById('errorBox');
|
||||
if (box) box.textContent = 'Das Spiel kann nicht gestartet werden: ' + msg
|
||||
+ ` Rückleitung in ${--secs}s…`;
|
||||
if (secs <= 0) { clearInterval(interval); goBack(); }
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
async function loadGroups() {
|
||||
const r = await fetch('/lock-game/groups');
|
||||
const groups = r.ok ? await r.json() : [];
|
||||
|
||||
if (groups.length === 1) {
|
||||
_resolvedGameSetId = groups[0].gruppenId;
|
||||
await loadAndShowToys(groups[0].gruppenId);
|
||||
return;
|
||||
}
|
||||
|
||||
hide('loadingHint');
|
||||
const list = document.getElementById('groupList');
|
||||
if (groups.length === 0) {
|
||||
@@ -270,40 +413,39 @@
|
||||
if (!sel) return;
|
||||
hide('initBox');
|
||||
show('loadingHint');
|
||||
try {
|
||||
const r = await fetch('/lock-game/init?aufgabenGruppeId=' + sel.value, { method: 'POST' });
|
||||
if (!r.ok) throw new Error('Initialisierung fehlgeschlagen');
|
||||
const stateR = await fetch('/lock-game/state');
|
||||
_state = await stateR.json();
|
||||
hide('loadingHint');
|
||||
await runGameLoop();
|
||||
} catch (e) {
|
||||
showError(e.message);
|
||||
}
|
||||
_resolvedGameSetId = sel.value;
|
||||
await loadAndShowToys(sel.value);
|
||||
}
|
||||
|
||||
// ── Game Loop ─────────────────────────────────────────────────────────────
|
||||
|
||||
function setGameCard(label, text, action, btnLabel) {
|
||||
document.getElementById('gameLabel').textContent = label;
|
||||
document.getElementById('gameText').textContent = text;
|
||||
const timerEl = document.getElementById('gameTimer');
|
||||
timerEl.classList.remove('active', 'urgent');
|
||||
timerEl.textContent = '';
|
||||
_gameAction = action;
|
||||
document.getElementById('gameBtn').textContent = btnLabel;
|
||||
}
|
||||
|
||||
async function runGameLoop() {
|
||||
hide('queueBox');
|
||||
hide('activeBox');
|
||||
hide('gameCard');
|
||||
hide('finisherBox');
|
||||
clearTimer();
|
||||
|
||||
if (_state.level >= 6) {
|
||||
await showFinisher();
|
||||
await showFinisherFlow();
|
||||
return;
|
||||
}
|
||||
|
||||
renderLevelBar(_state.level);
|
||||
|
||||
// Aktive Aufgabe läuft noch
|
||||
if (_state.activeTask) {
|
||||
showActiveTask(_state.activeTask, _state.activeTaskEnd);
|
||||
return;
|
||||
}
|
||||
|
||||
// Queue leer → nächsten Task holen
|
||||
if (!_state.taskInQueue && !_state.lockInQueue) {
|
||||
await fetch('/lock-game/next-task', { method: 'POST' });
|
||||
const r = await fetch('/lock-game/state');
|
||||
@@ -317,90 +459,155 @@
|
||||
if (_state.lockInQueue) {
|
||||
let sperre;
|
||||
try { sperre = JSON.parse(_state.lockInQueue); } catch { sperre = {}; }
|
||||
_pendingIsLock = true;
|
||||
_pendingHasDuration = !!(sperre.minutenVon || sperre.minutenBis);
|
||||
document.getElementById('queueLabel').textContent = '🔒 Neue Sperre';
|
||||
document.getElementById('queueText').textContent = sperre.text || _state.lockInQueue;
|
||||
setGameCard('🔒 Neue Sperre', sperre.text || sperre.kurzText || '', 'queue-start', '▶ Starten');
|
||||
} else if (_state.taskInQueue) {
|
||||
let aufgabe;
|
||||
try { aufgabe = JSON.parse(_state.taskInQueue); } catch { aufgabe = {}; }
|
||||
_pendingIsLock = false;
|
||||
_pendingHasDuration = !!(aufgabe.sekundenVon || aufgabe.sekundenBis);
|
||||
document.getElementById('queueLabel').textContent = '🎯 Neue Aufgabe';
|
||||
document.getElementById('queueText').textContent = aufgabe.text || _state.taskInQueue;
|
||||
const hasDuration = !!(aufgabe.sekundenVon || aufgabe.sekundenBis);
|
||||
setGameCard('🎯 Neue Aufgabe', aufgabe.text || '', hasDuration ? 'queue-start' : 'queue-done',
|
||||
hasDuration ? '▶ Starten' : '✓ Erledigt');
|
||||
}
|
||||
show('gameBox');
|
||||
show('queueBox');
|
||||
show('gameCard');
|
||||
}
|
||||
|
||||
function showActiveTask(text, endIso) {
|
||||
document.getElementById('activeText').textContent = text;
|
||||
show('gameBox');
|
||||
show('activeBox');
|
||||
show('gameCard');
|
||||
const timerEl = document.getElementById('gameTimer');
|
||||
timerEl.classList.remove('active', 'urgent');
|
||||
timerEl.textContent = '';
|
||||
document.getElementById('gameLabel').textContent = 'Aktive Aufgabe';
|
||||
document.getElementById('gameText').textContent = text;
|
||||
|
||||
if (endIso) {
|
||||
const end = new Date(endIso);
|
||||
startTimer(end, document.getElementById('activeTimer'));
|
||||
if (end > Date.now()) {
|
||||
_gameAction = 'active-running';
|
||||
document.getElementById('gameBtn').textContent = '✕ Abbrechen';
|
||||
startTimer(end, timerEl);
|
||||
return;
|
||||
}
|
||||
}
|
||||
_gameAction = 'active-done';
|
||||
document.getElementById('gameBtn').textContent = '✓ Erledigt';
|
||||
}
|
||||
|
||||
function handleGameBtn() {
|
||||
switch (_gameAction) {
|
||||
case 'queue-start': doQueueStart(); break;
|
||||
case 'queue-done': doQueueDone(); break;
|
||||
case 'active-running': doCancelCountdown(); break;
|
||||
case 'active-done': doErledigt(); break;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleOk() {
|
||||
// Locks prüfen (nach jeder Aktion)
|
||||
await checkAndShowLocks();
|
||||
|
||||
if (_pendingIsLock || _pendingHasDuration) {
|
||||
// Task/Sperre aktivieren und Timer starten
|
||||
async function doQueueStart() {
|
||||
try {
|
||||
await checkAndShowLocks();
|
||||
const r = await fetch('/lock-game/apply-task', { method: 'POST' });
|
||||
if (!r.ok) { showError('Fehler'); return; }
|
||||
if (!r.ok) { showError('Fehler beim Starten'); return; }
|
||||
const stateR = await fetch('/lock-game/state');
|
||||
_state = await stateR.json();
|
||||
} else {
|
||||
// Keine Dauer → sofort nächsten Task
|
||||
await fetch('/lock-game/next-task', { method: 'POST' });
|
||||
const r = await fetch('/lock-game/state');
|
||||
_state = await r.json();
|
||||
}
|
||||
await runGameLoop();
|
||||
await runGameLoop();
|
||||
} catch (e) { showError(e.message || 'Fehler (Starten)'); }
|
||||
}
|
||||
|
||||
async function handleErledigt() {
|
||||
await checkAndShowLocks();
|
||||
async function doQueueDone() {
|
||||
try {
|
||||
await checkAndShowLocks();
|
||||
const applyR = await fetch('/lock-game/apply-task', { method: 'POST' });
|
||||
if (!applyR.ok) { showError('Fehler beim Anwenden'); return; }
|
||||
const nextR = await fetch('/lock-game/next-task', { method: 'POST' });
|
||||
if (!nextR.ok) { showError('Fehler beim Ziehen'); return; }
|
||||
const stateR = await fetch('/lock-game/state');
|
||||
_state = await stateR.json();
|
||||
await runGameLoop();
|
||||
} catch (e) { showError(e.message || 'Fehler (Erledigt)'); }
|
||||
}
|
||||
|
||||
if (_state.level >= 6) {
|
||||
await showFinisher();
|
||||
return;
|
||||
}
|
||||
function doCancelCountdown() {
|
||||
clearTimer();
|
||||
_gameAction = 'active-done';
|
||||
document.getElementById('gameBtn').textContent = '✓ Erledigt';
|
||||
}
|
||||
|
||||
await fetch('/lock-game/next-task', { method: 'POST' });
|
||||
const r = await fetch('/lock-game/state');
|
||||
_state = await r.json();
|
||||
await runGameLoop();
|
||||
async function doErledigt() {
|
||||
try {
|
||||
await checkAndShowLocks();
|
||||
if (_state.level >= 6) { await showFinisherFlow(); return; }
|
||||
const r = await fetch('/lock-game/next-task', { method: 'POST' });
|
||||
if (!r.ok) { showError('Fehler beim Ziehen'); return; }
|
||||
const stateR = await fetch('/lock-game/state');
|
||||
_state = await stateR.json();
|
||||
await runGameLoop();
|
||||
} catch (e) { showError(e.message || 'Fehler (Erledigt)'); }
|
||||
}
|
||||
|
||||
async function checkAndShowLocks() {
|
||||
const r = await fetch('/lock-game/check-locks', { method: 'POST' });
|
||||
if (!r.ok) return;
|
||||
const texts = await r.json();
|
||||
if (texts && texts.length > 0) {
|
||||
const valid = texts ? texts.filter(t => t != null && t !== '') : [];
|
||||
if (valid.length > 0) {
|
||||
const box = document.getElementById('lockMessages');
|
||||
box.innerHTML = texts.map(t => `<p>🔓 ${esc(t)}</p>`).join('');
|
||||
box.innerHTML = valid.map(t => `<p>🔓 ${esc(t)}</p>`).join('');
|
||||
show('lockMessages');
|
||||
await new Promise(res => setTimeout(res, 2000));
|
||||
}
|
||||
}
|
||||
|
||||
async function showFinisher() {
|
||||
async function showFinisherFlow() {
|
||||
show('gameBox');
|
||||
const r = await fetch('/lock-game/finisher');
|
||||
if (!r.ok) {
|
||||
document.getElementById('finisherTitle').textContent = '';
|
||||
document.getElementById('finisherText').textContent = 'Glückwunsch – du hast Level 6 erreicht!';
|
||||
} else {
|
||||
const f = await r.json();
|
||||
document.getElementById('finisherTitle').textContent = f.kurzText || '';
|
||||
document.getElementById('finisherText').textContent = f.text || '';
|
||||
}
|
||||
show('finisherBox');
|
||||
hide('gameCard');
|
||||
hide('lockReleaseBox');
|
||||
hide('finisherBox');
|
||||
|
||||
// 1. Release-Texte sequenziell anzeigen
|
||||
try {
|
||||
const r = await fetch('/lock-game/release-locks');
|
||||
if (r.ok) {
|
||||
const texts = await r.json();
|
||||
for (const text of texts) {
|
||||
await waitForReleaseOk(text);
|
||||
}
|
||||
}
|
||||
} catch (_) { /* ignorieren */ }
|
||||
|
||||
// 2. Finisher laden und Zeit messen
|
||||
const finisherStartTime = Date.now();
|
||||
let finisher = null;
|
||||
try {
|
||||
const r = await fetch('/lock-game/finisher');
|
||||
if (r.ok) finisher = await r.json();
|
||||
} catch (_) { /* ignorieren */ }
|
||||
|
||||
document.getElementById('finisherTitle').textContent = finisher?.kurzText || '';
|
||||
document.getElementById('finisherText').textContent = finisher?.text || 'Glückwunsch – du hast Level 6 erreicht!';
|
||||
|
||||
// 3. Warten bis Nutzer OK drückt
|
||||
await new Promise(resolve => {
|
||||
document.getElementById('btnFinisherOk').onclick = resolve;
|
||||
show('finisherBox');
|
||||
});
|
||||
|
||||
// 4. Zeit berechnen und Spiel beenden
|
||||
const timeInMinutes = Math.round((Date.now() - finisherStartTime) / 60000);
|
||||
const params = new URLSearchParams({ timeInMinutes });
|
||||
if (lockId) params.set('lockId', lockId);
|
||||
await fetch('/lock-game/complete?' + params.toString(), { method: 'POST' });
|
||||
goBack();
|
||||
}
|
||||
|
||||
function waitForReleaseOk(text) {
|
||||
return new Promise(resolve => {
|
||||
document.getElementById('releaseText').textContent = text;
|
||||
document.getElementById('btnReleaseOk').onclick = () => {
|
||||
hide('lockReleaseBox');
|
||||
resolve();
|
||||
};
|
||||
show('lockReleaseBox');
|
||||
});
|
||||
}
|
||||
|
||||
// ── Level-Bar ─────────────────────────────────────────────────────────────
|
||||
@@ -413,8 +620,12 @@
|
||||
|
||||
// ── Timer ─────────────────────────────────────────────────────────────────
|
||||
|
||||
function playSound(src) {
|
||||
try { new Audio(src).play().catch(() => {}); } catch (_) {}
|
||||
}
|
||||
|
||||
function startTimer(endDate, el) {
|
||||
el.style.display = '';
|
||||
el.classList.add('active');
|
||||
clearTimer();
|
||||
_timerInt = setInterval(() => {
|
||||
const diff = Math.max(0, Math.round((endDate - Date.now()) / 1000));
|
||||
@@ -422,7 +633,12 @@
|
||||
const s = String(diff % 60).padStart(2, '0');
|
||||
el.textContent = m + ':' + s;
|
||||
el.classList.toggle('urgent', diff < 30);
|
||||
if (diff === 0) clearTimer();
|
||||
if (diff === 0) {
|
||||
clearTimer();
|
||||
playSound('/audio/alarm.mp3');
|
||||
_gameAction = 'active-done';
|
||||
document.getElementById('gameBtn').textContent = '✓ Erledigt';
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user