Weiter am Taskgame gebastelt
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:
@@ -256,9 +256,14 @@
|
||||
</div>
|
||||
|
||||
<div class="checkbox-row" id="rowTestLock">
|
||||
<input type="checkbox" id="testLock">
|
||||
<input type="checkbox" id="testLock" onchange="onTestLockChange()">
|
||||
<label for="testLock">Test-Lock <span class="form-hint">(kein echter Lock, zum Ausprobieren)</span></label>
|
||||
</div>
|
||||
<div id="rowSpeedFactor" style="display:none; align-items:center; gap:12px; padding:6px 0;">
|
||||
<label for="speedFactor" style="white-space:nowrap;">Geschwindigkeit:</label>
|
||||
<input type="range" id="speedFactor" min="1" max="10" value="1" style="flex:1;" oninput="document.getElementById('speedFactorLabel').textContent = '×' + this.value">
|
||||
<span id="speedFactorLabel" style="min-width:32px; text-align:right;">×1</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="error-msg" id="errorMsg"></div>
|
||||
@@ -515,6 +520,7 @@
|
||||
khInput.readOnly = true;
|
||||
khInput.style.opacity = '0.6';
|
||||
document.getElementById('rowTestLock').style.display = 'none';
|
||||
document.getElementById('rowSpeedFactor').style.display = 'none';
|
||||
document.getElementById('rowDetailsVisible').style.display = '';
|
||||
} else {
|
||||
khInput.readOnly = false;
|
||||
@@ -728,6 +734,15 @@
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
|
||||
function onTestLockChange() {
|
||||
const checked = document.getElementById('testLock').checked;
|
||||
document.getElementById('rowSpeedFactor').style.display = checked ? 'flex' : 'none';
|
||||
if (!checked) {
|
||||
document.getElementById('speedFactor').value = 1;
|
||||
document.getElementById('speedFactorLabel').textContent = '×1';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Absenden ──
|
||||
async function createSession() {
|
||||
document.getElementById('errorMsg').style.display = 'none';
|
||||
@@ -756,6 +771,7 @@
|
||||
const isFriendLockee = lockeeVal && lockeeVal !== myUserId;
|
||||
const unlockCodeLen = isFriendLockee ? null : (parseInt(document.getElementById('unlockCodeLines').value) || 5);
|
||||
const isTestLock = isFriendLockee ? false : document.getElementById('testLock').checked;
|
||||
const speedFactor = isTestLock ? parseInt(document.getElementById('speedFactor').value) : 1;
|
||||
|
||||
let endpoint, body;
|
||||
|
||||
@@ -769,6 +785,7 @@
|
||||
testLock: isTestLock,
|
||||
unlockCodeLength: unlockCodeLen,
|
||||
controllType: selectedLockControl,
|
||||
speedFactor: speedFactor,
|
||||
};
|
||||
} else {
|
||||
// CardLock
|
||||
@@ -798,6 +815,7 @@
|
||||
controllType: selectedLockControl,
|
||||
gameSetId: t.gameSetId || null,
|
||||
gameSpieldauerIdx: t.gameSpieldauerIdx ?? null,
|
||||
speedFactor: speedFactor,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,48 @@
|
||||
height: 2.75rem;
|
||||
}
|
||||
|
||||
.game-requirements {
|
||||
margin: 0.75rem 0 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
.game-requirements-label {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.07em;
|
||||
color: var(--color-muted);
|
||||
margin-bottom: 0.1rem;
|
||||
}
|
||||
.req-check {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
padding: 0.4rem 0.6rem;
|
||||
border-radius: 7px;
|
||||
background: rgba(255,255,255,0.03);
|
||||
border: 1px solid var(--color-secondary);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: border-color 0.15s, background 0.15s;
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-text);
|
||||
}
|
||||
.req-check input[type="checkbox"] {
|
||||
accent-color: var(--color-primary);
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.req-check.done {
|
||||
border-color: var(--color-primary);
|
||||
background: rgba(233,69,96,0.07);
|
||||
color: var(--color-muted);
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.level-display {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -190,6 +232,7 @@
|
||||
<div id="gameCard" class="game-card" style="display:none;">
|
||||
<div class="game-label" id="gameLabel"></div>
|
||||
<div class="game-text" id="gameText"></div>
|
||||
<div id="gameRequirements" class="game-requirements" style="display:none;"></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>
|
||||
@@ -205,13 +248,32 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Temporäre Öffnung -->
|
||||
<div id="tempOpeningBox" class="game-card" style="display:none;">
|
||||
<div class="game-label">🔓 Temporäre Öffnung erforderlich</div>
|
||||
<div class="game-text" id="tempOpeningTask"></div>
|
||||
<div id="tempOpeningCodeRow" style="display:none; margin-top:1rem; text-align:center;">
|
||||
<div class="game-label">Entsperrcode</div>
|
||||
<div id="tempOpeningCode" style="font-size:1.8rem; font-weight:700; letter-spacing:0.18em; padding:0.6rem 0; color:var(--color-primary);"></div>
|
||||
</div>
|
||||
<div class="game-btn-row">
|
||||
<button class="btn-primary" onclick="doEndTempOpening()">✓ Erledigt</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Finisher -->
|
||||
<div id="finisherBox" style="display:none;">
|
||||
<div class="trophy">🏆</div>
|
||||
<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" id="btnFinisherOk" style="margin-top:1.25rem;">✓ OK</button>
|
||||
<div id="finisherStart" style="margin-top:1.25rem;">
|
||||
<button class="btn-primary" onclick="doStartFinisher()">▶ Starten</button>
|
||||
</div>
|
||||
<div id="finisherRunning" style="display:none;margin-top:1.25rem;">
|
||||
<div class="game-timer active" id="finisherTimer">00:00</div>
|
||||
<button class="btn-primary" onclick="doEndFinisher()" style="margin-top:1rem;">✓ Erledigt</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Debug -->
|
||||
@@ -340,6 +402,7 @@
|
||||
|
||||
async function startWithExcludedToys(gameSetId, excludedToyIds) {
|
||||
const params = new URLSearchParams({ aufgabenGruppeId: gameSetId });
|
||||
if (lockId) params.append('lockId', lockId);
|
||||
excludedToyIds.forEach(id => params.append('excludedToyIds', id));
|
||||
const r = await fetch('/lock-game/init?' + params.toString(), { method: 'POST' });
|
||||
|
||||
@@ -417,6 +480,27 @@
|
||||
await loadAndShowToys(sel.value);
|
||||
}
|
||||
|
||||
// ── Benötigt-Checkboxen ───────────────────────────────────────────────────
|
||||
|
||||
const WERKZEUG_LABEL = {
|
||||
MUND: 'Mund', VAGINA: 'Vagina', PENIS: 'Penis',
|
||||
ANUS: 'Anus', UMSCHNALLDILDO: 'Umschnall-Dildo'
|
||||
};
|
||||
|
||||
function renderRequirements(list) {
|
||||
const box = document.getElementById('gameRequirements');
|
||||
if (!list || list.length === 0) { box.style.display = 'none'; box.innerHTML = ''; return; }
|
||||
box.innerHTML = '<div class="game-requirements-label">Benötigt</div>' +
|
||||
list.map(w => {
|
||||
const label = WERKZEUG_LABEL[w] || w;
|
||||
return `<label class="req-check" onclick="this.classList.toggle('done',this.querySelector('input').checked)">
|
||||
<input type="checkbox" onchange="this.closest('.req-check').classList.toggle('done',this.checked)">
|
||||
<span>${label}</span>
|
||||
</label>`;
|
||||
}).join('');
|
||||
box.style.display = 'flex';
|
||||
}
|
||||
|
||||
// ── Game Loop ─────────────────────────────────────────────────────────────
|
||||
|
||||
function setGameCard(label, text, action, btnLabel) {
|
||||
@@ -432,10 +516,16 @@
|
||||
async function runGameLoop() {
|
||||
hide('gameCard');
|
||||
hide('finisherBox');
|
||||
hide('tempOpeningBox');
|
||||
clearTimer();
|
||||
|
||||
if (_state.level >= 6) {
|
||||
await showFinisherFlow();
|
||||
if (_state.finisher) {
|
||||
showFinisherUI();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_state.tempOpeningTime) {
|
||||
showTempOpeningDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -460,12 +550,14 @@
|
||||
let sperre;
|
||||
try { sperre = JSON.parse(_state.lockInQueue); } catch { sperre = {}; }
|
||||
setGameCard('🔒 Neue Sperre', sperre.text || sperre.kurzText || '', 'queue-start', '▶ Starten');
|
||||
renderRequirements(null);
|
||||
} else if (_state.taskInQueue) {
|
||||
let aufgabe;
|
||||
try { aufgabe = JSON.parse(_state.taskInQueue); } catch { aufgabe = {}; }
|
||||
const hasDuration = !!(aufgabe.sekundenVon || aufgabe.sekundenBis);
|
||||
setGameCard('🎯 Neue Aufgabe', aufgabe.text || '', hasDuration ? 'queue-start' : 'queue-done',
|
||||
hasDuration ? '▶ Starten' : '✓ Erledigt');
|
||||
renderRequirements(aufgabe.benoetigtAktiv);
|
||||
}
|
||||
show('gameBox');
|
||||
show('gameCard');
|
||||
@@ -479,6 +571,7 @@
|
||||
timerEl.textContent = '';
|
||||
document.getElementById('gameLabel').textContent = 'Aktive Aufgabe';
|
||||
document.getElementById('gameText').textContent = text;
|
||||
renderRequirements(_state.activeTaskBenoetigtAktiv);
|
||||
|
||||
if (endIso) {
|
||||
const end = new Date(endIso);
|
||||
@@ -504,12 +597,31 @@
|
||||
|
||||
async function doQueueStart() {
|
||||
try {
|
||||
await checkAndShowLocks();
|
||||
const wasLock = !!_state.lockInQueue;
|
||||
let tempUnlockRequired = false;
|
||||
if (wasLock) {
|
||||
try { tempUnlockRequired = JSON.parse(_state.lockInQueue).tempUnlockRequired === true; } catch (_) {}
|
||||
}
|
||||
|
||||
const r = await fetch('/lock-game/apply-task', { method: 'POST' });
|
||||
if (!r.ok) { showError('Fehler beim Starten'); return; }
|
||||
const stateR = await fetch('/lock-game/state');
|
||||
_state = await stateR.json();
|
||||
await runGameLoop();
|
||||
|
||||
if (wasLock && tempUnlockRequired) {
|
||||
await fetch('/lock-game/start-temp-opening', { method: 'POST' });
|
||||
const stateR = await fetch('/lock-game/state');
|
||||
_state = await stateR.json();
|
||||
showTempOpeningDialog();
|
||||
} else if (wasLock) {
|
||||
const nextR = await fetch('/lock-game/abandon-task', { method: 'POST' });
|
||||
if (!nextR.ok) { showError('Fehler beim Ziehen'); return; }
|
||||
const stateR = await fetch('/lock-game/state');
|
||||
_state = await stateR.json();
|
||||
await runGameLoop();
|
||||
} else {
|
||||
const stateR = await fetch('/lock-game/state');
|
||||
_state = await stateR.json();
|
||||
await runGameLoop();
|
||||
}
|
||||
} catch (e) { showError(e.message || 'Fehler (Starten)'); }
|
||||
}
|
||||
|
||||
@@ -526,16 +638,28 @@
|
||||
} catch (e) { showError(e.message || 'Fehler (Erledigt)'); }
|
||||
}
|
||||
|
||||
function doCancelCountdown() {
|
||||
async function doCancelCountdown() {
|
||||
clearTimer();
|
||||
const lockR = await fetch('/lock-game/check-locks', { method: 'POST' });
|
||||
if (lockR.ok) {
|
||||
const texts = await lockR.json();
|
||||
for (const text of (texts || [])) {
|
||||
if (text != null && text !== '') await waitForReleaseOk(text);
|
||||
}
|
||||
}
|
||||
_gameAction = 'active-done';
|
||||
document.getElementById('gameBtn').textContent = '✓ Erledigt';
|
||||
}
|
||||
|
||||
async function doErledigt() {
|
||||
try {
|
||||
await checkAndShowLocks();
|
||||
if (_state.level >= 6) { await showFinisherFlow(); return; }
|
||||
const lockR = await fetch('/lock-game/check-locks', { method: 'POST' });
|
||||
if (lockR.ok) {
|
||||
const texts = await lockR.json();
|
||||
for (const text of (texts || [])) {
|
||||
if (text != null && text !== '') await waitForReleaseOk(text);
|
||||
}
|
||||
}
|
||||
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');
|
||||
@@ -557,51 +681,87 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function showFinisherFlow() {
|
||||
function showTempOpeningDialog() {
|
||||
show('gameBox');
|
||||
hide('gameCard');
|
||||
hide('lockReleaseBox');
|
||||
hide('finisherBox');
|
||||
|
||||
// 1. Release-Texte sequenziell anzeigen
|
||||
document.getElementById('tempOpeningTask').textContent = _state.activeTask || '';
|
||||
const code = _state.tempOpeningCode;
|
||||
if (code) {
|
||||
document.getElementById('tempOpeningCode').textContent = code;
|
||||
show('tempOpeningCodeRow');
|
||||
} else {
|
||||
hide('tempOpeningCodeRow');
|
||||
}
|
||||
show('tempOpeningBox');
|
||||
}
|
||||
|
||||
async function doEndTempOpening() {
|
||||
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 */ }
|
||||
await fetch('/lock-game/end-temp-opening', { method: 'POST' });
|
||||
await fetch('/lock-game/abandon-task', { method: 'POST' });
|
||||
const stateR = await fetch('/lock-game/state');
|
||||
_state = await stateR.json();
|
||||
hide('tempOpeningBox');
|
||||
await runGameLoop();
|
||||
} catch (e) { showError(e.message || 'Fehler beim Abschluss der temporären Öffnung'); }
|
||||
}
|
||||
|
||||
// 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 */ }
|
||||
function showFinisherUI() {
|
||||
show('gameBox');
|
||||
hide('gameCard');
|
||||
hide('lockReleaseBox');
|
||||
|
||||
document.getElementById('finisherTitle').textContent = finisher?.kurzText || '';
|
||||
document.getElementById('finisherText').textContent = finisher?.text || 'Glückwunsch – du hast Level 6 erreicht!';
|
||||
let finisher = {};
|
||||
try { finisher = JSON.parse(_state.finisher); } catch (_) {}
|
||||
document.getElementById('finisherTitle').textContent = finisher.kurzText || '';
|
||||
document.getElementById('finisherText').textContent = finisher.text || '';
|
||||
|
||||
// 3. Warten bis Nutzer OK drückt
|
||||
await new Promise(resolve => {
|
||||
document.getElementById('btnFinisherOk').onclick = resolve;
|
||||
show('finisherBox');
|
||||
});
|
||||
if (_state.finisherStartedAt) {
|
||||
hide('finisherStart');
|
||||
show('finisherRunning');
|
||||
startElapsedTimer(new Date(_state.finisherStartedAt));
|
||||
} else {
|
||||
show('finisherStart');
|
||||
hide('finisherRunning');
|
||||
}
|
||||
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' });
|
||||
async function doStartFinisher() {
|
||||
await fetch('/lock-game/start-finisher', { method: 'POST' });
|
||||
const r = await fetch('/lock-game/state');
|
||||
_state = await r.json();
|
||||
hide('finisherStart');
|
||||
show('finisherRunning');
|
||||
startElapsedTimer(new Date(_state.finisherStartedAt));
|
||||
}
|
||||
|
||||
async function doEndFinisher() {
|
||||
clearTimer();
|
||||
await fetch('/lock-game/end-finisher', { method: 'POST' });
|
||||
const url = '/lock-game/complete' + (lockId ? '?lockId=' + lockId : '');
|
||||
await fetch(url, { method: 'POST' });
|
||||
goBack();
|
||||
}
|
||||
|
||||
function startElapsedTimer(startDate) {
|
||||
clearTimer();
|
||||
const el = document.getElementById('finisherTimer');
|
||||
_timerInt = setInterval(() => {
|
||||
const diff = Math.floor((Date.now() - startDate) / 1000);
|
||||
const m = String(Math.floor(diff / 60)).padStart(2, '0');
|
||||
const s = String(diff % 60).padStart(2, '0');
|
||||
el.textContent = m + ':' + s;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function waitForReleaseOk(text) {
|
||||
return new Promise(resolve => {
|
||||
document.getElementById('releaseText').textContent = text;
|
||||
hide('gameCard');
|
||||
document.getElementById('releaseText').textContent = text || '';
|
||||
document.getElementById('btnReleaseOk').onclick = () => {
|
||||
hide('lockReleaseBox');
|
||||
resolve();
|
||||
|
||||
Reference in New Issue
Block a user