diff --git a/bin/main/db/migration/V7__fix_aktive_sperre_fuer_fk.sql b/bin/main/db/migration/V7__fix_aktive_sperre_fuer_fk.sql new file mode 100644 index 0000000..5ffc356 --- /dev/null +++ b/bin/main/db/migration/V7__fix_aktive_sperre_fuer_fk.sql @@ -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); diff --git a/bin/main/de/oaa/xxx/games/chastity/cardlock/CardLockController$AssignTaskRequest.class b/bin/main/de/oaa/xxx/games/chastity/cardlock/CardLockController$AssignTaskRequest.class index 49dd400..796a7e0 100644 Binary files a/bin/main/de/oaa/xxx/games/chastity/cardlock/CardLockController$AssignTaskRequest.class and b/bin/main/de/oaa/xxx/games/chastity/cardlock/CardLockController$AssignTaskRequest.class differ diff --git a/bin/main/de/oaa/xxx/games/chastity/cardlock/CardLockController$FreezeRequest.class b/bin/main/de/oaa/xxx/games/chastity/cardlock/CardLockController$FreezeRequest.class index 40eb827..e3b467a 100644 Binary files a/bin/main/de/oaa/xxx/games/chastity/cardlock/CardLockController$FreezeRequest.class and b/bin/main/de/oaa/xxx/games/chastity/cardlock/CardLockController$FreezeRequest.class differ diff --git a/bin/main/de/oaa/xxx/games/chastity/cardlock/CardLockController$ModifyCardsRequest.class b/bin/main/de/oaa/xxx/games/chastity/cardlock/CardLockController$ModifyCardsRequest.class index a15bb8d..ee9f477 100644 Binary files a/bin/main/de/oaa/xxx/games/chastity/cardlock/CardLockController$ModifyCardsRequest.class and b/bin/main/de/oaa/xxx/games/chastity/cardlock/CardLockController$ModifyCardsRequest.class differ diff --git a/bin/main/de/oaa/xxx/games/chastity/cardlock/CardLockController$SpeedConfirmRequest.class b/bin/main/de/oaa/xxx/games/chastity/cardlock/CardLockController$SpeedConfirmRequest.class index 3cdcf77..4a66943 100644 Binary files a/bin/main/de/oaa/xxx/games/chastity/cardlock/CardLockController$SpeedConfirmRequest.class and b/bin/main/de/oaa/xxx/games/chastity/cardlock/CardLockController$SpeedConfirmRequest.class differ diff --git a/bin/main/de/oaa/xxx/games/chastity/cardlock/CardLockController.class b/bin/main/de/oaa/xxx/games/chastity/cardlock/CardLockController.class index 94ab4ad..a7863f5 100644 Binary files a/bin/main/de/oaa/xxx/games/chastity/cardlock/CardLockController.class and b/bin/main/de/oaa/xxx/games/chastity/cardlock/CardLockController.class differ diff --git a/bin/main/de/oaa/xxx/games/chastity/cardlock/CardLockService.class b/bin/main/de/oaa/xxx/games/chastity/cardlock/CardLockService.class index 8cbf06f..cc03d99 100644 Binary files a/bin/main/de/oaa/xxx/games/chastity/cardlock/CardLockService.class and b/bin/main/de/oaa/xxx/games/chastity/cardlock/CardLockService.class differ diff --git a/bin/main/de/oaa/xxx/games/chastity/common/BaseLockService.class b/bin/main/de/oaa/xxx/games/chastity/common/BaseLockService.class index a699b83..668d040 100644 Binary files a/bin/main/de/oaa/xxx/games/chastity/common/BaseLockService.class and b/bin/main/de/oaa/xxx/games/chastity/common/BaseLockService.class differ diff --git a/bin/main/de/oaa/xxx/games/chastity/common/LockGameController.class b/bin/main/de/oaa/xxx/games/chastity/common/LockGameController.class index 546a987..32b5510 100644 Binary files a/bin/main/de/oaa/xxx/games/chastity/common/LockGameController.class and b/bin/main/de/oaa/xxx/games/chastity/common/LockGameController.class differ diff --git a/bin/main/de/oaa/xxx/games/chastity/common/LockGameService.class b/bin/main/de/oaa/xxx/games/chastity/common/LockGameService.class index 149dab3..872fc59 100644 Binary files a/bin/main/de/oaa/xxx/games/chastity/common/LockGameService.class and b/bin/main/de/oaa/xxx/games/chastity/common/LockGameService.class differ diff --git a/bin/main/de/oaa/xxx/games/chastity/community/CommunityTaskVoteScheduler.class b/bin/main/de/oaa/xxx/games/chastity/community/CommunityTaskVoteScheduler.class index a42238c..7bea13a 100644 Binary files a/bin/main/de/oaa/xxx/games/chastity/community/CommunityTaskVoteScheduler.class and b/bin/main/de/oaa/xxx/games/chastity/community/CommunityTaskVoteScheduler.class differ diff --git a/bin/main/de/oaa/xxx/games/chastity/keyholder/KeyholderTaskChoiceController$PenaltyRequest.class b/bin/main/de/oaa/xxx/games/chastity/keyholder/KeyholderTaskChoiceController$PenaltyRequest.class index c9e900a..7d86890 100644 Binary files a/bin/main/de/oaa/xxx/games/chastity/keyholder/KeyholderTaskChoiceController$PenaltyRequest.class and b/bin/main/de/oaa/xxx/games/chastity/keyholder/KeyholderTaskChoiceController$PenaltyRequest.class differ diff --git a/bin/main/de/oaa/xxx/games/chastity/keyholder/KeyholderTaskChoiceController.class b/bin/main/de/oaa/xxx/games/chastity/keyholder/KeyholderTaskChoiceController.class index be409bc..d148e55 100644 Binary files a/bin/main/de/oaa/xxx/games/chastity/keyholder/KeyholderTaskChoiceController.class and b/bin/main/de/oaa/xxx/games/chastity/keyholder/KeyholderTaskChoiceController.class differ diff --git a/bin/main/de/oaa/xxx/games/chastity/keyholder/KeyholderTaskChoiceScheduler.class b/bin/main/de/oaa/xxx/games/chastity/keyholder/KeyholderTaskChoiceScheduler.class index cede554..af1101c 100644 Binary files a/bin/main/de/oaa/xxx/games/chastity/keyholder/KeyholderTaskChoiceScheduler.class and b/bin/main/de/oaa/xxx/games/chastity/keyholder/KeyholderTaskChoiceScheduler.class differ diff --git a/bin/main/de/oaa/xxx/games/chastity/timelock/TimeLockService.class b/bin/main/de/oaa/xxx/games/chastity/timelock/TimeLockService.class index b311aea..f5f3c77 100644 Binary files a/bin/main/de/oaa/xxx/games/chastity/timelock/TimeLockService.class and b/bin/main/de/oaa/xxx/games/chastity/timelock/TimeLockService.class differ diff --git a/bin/main/static/games/chastity/activelock.html b/bin/main/static/games/chastity/activelock.html index 0154fb8..e17d936 100644 --- a/bin/main/static/games/chastity/activelock.html +++ b/bin/main/static/games/chastity/activelock.html @@ -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; } diff --git a/bin/main/static/games/chastity/meine-locks.html b/bin/main/static/games/chastity/meine-locks.html index 13f85b0..db84f6a 100644 --- a/bin/main/static/games/chastity/meine-locks.html +++ b/bin/main/static/games/chastity/meine-locks.html @@ -405,13 +405,9 @@
+ Deaktiviere Toys, die nicht zur Verfügung stehen. + Aufgaben, die diese benötigen, werden deaktiviert. +
+ +' + + 'Keine Toys erforderlich – alle Aufgaben werden gespielt.
'; + } else { + list.innerHTML = toys.map(t => ` + `).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 => `🔓 ${esc(t)}
`).join(''); + box.innerHTML = valid.map(t => `🔓 ${esc(t)}
`).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); } diff --git a/src/main/java/de/oaa/xxx/config/CookieFactory.java b/src/main/java/de/oaa/xxx/config/CookieFactory.java index 19c00fd..1979dcd 100644 --- a/src/main/java/de/oaa/xxx/config/CookieFactory.java +++ b/src/main/java/de/oaa/xxx/config/CookieFactory.java @@ -1 +1,27 @@ -src/main/java/de/oaa/xxx/config/CookieFactory.java +package de.oaa.xxx.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseCookie; +import org.springframework.stereotype.Component; + +import java.time.Duration; + +@Component +public class CookieFactory { + + private final boolean secure; + + public CookieFactory(@Value("${app.cookie.secure:true}") boolean secure) { + this.secure = secure; + } + + public ResponseCookie jwtCookie(String token, Duration maxAge) { + return ResponseCookie.from("jwt", token) + .httpOnly(true) + .secure(secure) + .sameSite("Strict") + .path("/") + .maxAge(maxAge) + .build(); + } +} diff --git a/src/main/java/de/oaa/xxx/games/chastity/cardlock/CardLockController.java b/src/main/java/de/oaa/xxx/games/chastity/cardlock/CardLockController.java index 63f9a78..c74e72c 100644 --- a/src/main/java/de/oaa/xxx/games/chastity/cardlock/CardLockController.java +++ b/src/main/java/de/oaa/xxx/games/chastity/cardlock/CardLockController.java @@ -644,10 +644,11 @@ public class CardLockController { meName + " hat die Spiel-Karte nicht innerhalb einer Stunde gestartet. Das Lock wurde für 4 Stunden eingefroren.", "/games/chastity/keyholder.html", de.oaa.xxx.social.entity.MessageCause.GAME_STATE); } + cardLockServiceFactory.create(l).penaltyLockGame(); l.setGameCardParkedAt(null); cardlockRepository.save(l); result.put("gameCardParkedAt", null); - result.put("gameSetId", null); + result.put("gameSetId", l.getGameSetId() != null ? l.getGameSetId().toString() : null); } else { result.put("gameCardParkedAt", l.getGameCardParkedAt().toString()); result.put("gameSetId", l.getGameSetId() != null ? l.getGameSetId().toString() : null); @@ -655,7 +656,7 @@ public class CardLockController { } } else { result.put("gameCardParkedAt", null); - result.put("gameSetId", null); + result.put("gameSetId", l.getGameSetId() != null ? l.getGameSetId().toString() : null); } result.put("gameActive", l.isGameActive()); diff --git a/src/main/java/de/oaa/xxx/games/chastity/cardlock/CardLockService.java b/src/main/java/de/oaa/xxx/games/chastity/cardlock/CardLockService.java index c52bc07..4ba2516 100644 --- a/src/main/java/de/oaa/xxx/games/chastity/cardlock/CardLockService.java +++ b/src/main/java/de/oaa/xxx/games/chastity/cardlock/CardLockService.java @@ -116,6 +116,7 @@ public class CardLockService extends BaseLockService implements LockControlCallb CardDTO card = null; if (lock.isKeyholderRequestedUnlock() || (lock.getLatestOpeningtime() != null && lock.getLatestOpeningtime().isAfter(LocalDateTime.now()))) { + LOGGER.info("[CardLock {}] Karte gezogen: GREEN (erzwungen – Keyholder oder Öffnungszeitlimit)", lock.getLockee()); card = getGreenCard(); } else if (lock.isAccumulatePicks()) { if (lock.getNextCardIn().isBefore(LocalDateTime.now())) { @@ -139,9 +140,10 @@ public class CardLockService extends BaseLockService implements LockControlCallb var cards = lock.getAvailableCards(); if (!cards.isEmpty()) { var card = cards.get(new Random().nextInt(cards.size())); - LOGGER.debug("Card drafted: {}", card); lock.getAvailableCards().remove(card); - return card.get().processCard(this); + CardDTO result = card.get().processCard(this); + LOGGER.info("[CardLock {}] Karte gezogen: {} | Verbleibende Karten: {}", lock.getLockee(), result.card(), lock.getAvailableCards().size()); + return result; } LOGGER.error("Keine Karten mehr im Lock - generiere Notfall Grüne Karte"); return getGreenCard(); @@ -155,20 +157,20 @@ public class CardLockService extends BaseLockService implements LockControlCallb public String doubleUp() { var cards = lock.getAvailableCards(); - LOGGER.debug("Double up {} cards", cards.size()); + int before = cards.size(); lock.getAvailableCards().addAll(cards); - LOGGER.debug("Now {} cards", lock.getAvailableCards().size()); + LOGGER.info("[CardLock {}] DOUBLE_UP: Karten verdoppelt {} -> {}", lock.getLockee(), before, lock.getAvailableCards().size()); return ""; } public String reset() { - LOGGER.debug("Reset to initial cards"); lock.setAvailableCards(lock.getInitialCards()); + LOGGER.info("[CardLock {}] RESET: zurück auf {} initiale Karten", lock.getLockee(), lock.getInitialCards().size()); return ""; } public String green() { - LOGGER.debug("Green Card drafted"); + LOGGER.info("[CardLock {}] GREEN: Unlock-Code bereitgestellt (Code vorhanden: {})", lock.getLockee(), lock.getUnlockCode() != null); return lock.getUnlockCode(); } @@ -182,7 +184,7 @@ public class CardLockService extends BaseLockService implements LockControlCallb LocalDateTime frozenTill = LocalDateTime.now().plus((long) multiplier, ChronoUnit.MINUTES); lock.setFrozenUntil(frozenTill); lock.setNextCardIn(frozenTill); - LOGGER.debug("Frozen until {}", lock.getFrozenUntil()); + LOGGER.info("[CardLock {}] FREEZE: eingefroren für {} Minuten (bis {})", lock.getLockee(), (long) multiplier, frozenTill); return ""; } @@ -200,6 +202,7 @@ public class CardLockService extends BaseLockService implements LockControlCallb } } pendingTaskMode = lock.getTaskMode().name(); + LOGGER.info("[CardLock {}] TASK: Modus={}, testLock={}", lock.getLockee(), lock.getTaskMode(), lock.isTestLock()); return ""; } @@ -209,21 +212,23 @@ public class CardLockService extends BaseLockService implements LockControlCallb } public String redCard() { + LOGGER.info("[CardLock {}] RED: Rote Karte gezogen", lock.getLockee()); return ""; } public String yellowCard() { Random random = new Random(); + int before = lock.getAvailableCards().size(); if (random.nextBoolean()) { for (int i = 0; i < random.nextInt(1, 3); i++) { - LOGGER.debug("Adding Red card"); lock.getAvailableCards().add(CardEnum.RED); } + LOGGER.info("[CardLock {}] YELLOW: Rote Karten hinzugefügt | Kartenanzahl {} -> {}", lock.getLockee(), before, lock.getAvailableCards().size()); } else { for (int i = 0; i < random.nextInt(1, 3); i++) { - LOGGER.debug("Removing Red card if possible"); lock.getAvailableCards().remove(CardEnum.RED); } + LOGGER.info("[CardLock {}] YELLOW: Rote Karten entfernt | Kartenanzahl {} -> {}", lock.getLockee(), before, lock.getAvailableCards().size()); } return ""; } @@ -250,6 +255,7 @@ public class CardLockService extends BaseLockService implements LockControlCallb // ── Cum cards ───────────────────────────────────────────────────────────── public String cum(boolean tempUnlock) { + LOGGER.info("[CardLock {}] CUM: tempUnlock={}", lock.getLockee(), tempUnlock); if (tempUnlock) { startTempOpening(TempOpeningReason.CARD, 0); } @@ -278,10 +284,12 @@ public class CardLockService extends BaseLockService implements LockControlCallb } public String slowmo() { + LOGGER.info("[CardLock {}] SLOWMO_CARD: Zeitlupen-Effekt ausgelöst", lock.getLockee()); return ""; } public String speedup() { + LOGGER.info("[CardLock {}] SPEEDUP_CARD: Beschleunigungs-Effekt ausgelöst", lock.getLockee()); return ""; } @@ -290,7 +298,7 @@ public class CardLockService extends BaseLockService implements LockControlCallb lock.setGameCardParkedAt(LocalDateTime.now()); lock.setFrozenUntil(deadline); lock.setNextCardIn(deadline); - lock.setOpenPicks(0); + LOGGER.info("[CardLock {}] GAME_CARD: Spiel-Lock aktiv bis {}", lock.getLockee(), deadline); return ""; } @@ -313,4 +321,15 @@ public class CardLockService extends BaseLockService implements LockControlCallb } return 1.0; } + + @Override + protected void handleLockGameFinished(int timeInMinutes) { + int freezeTime = (int) (timeInMinutes * new Random().nextDouble(1.0, 4.0)); + freeze(freezeTime); + } + + @Override + public void penaltyLockGame() { + handleLockGameFinished(60); + } } diff --git a/src/main/java/de/oaa/xxx/games/chastity/common/BaseLockService.java b/src/main/java/de/oaa/xxx/games/chastity/common/BaseLockService.java index 868ab2f..2af66d7 100644 --- a/src/main/java/de/oaa/xxx/games/chastity/common/BaseLockService.java +++ b/src/main/java/de/oaa/xxx/games/chastity/common/BaseLockService.java @@ -70,6 +70,10 @@ public abstract class BaseLockService { /** TimeLock: lockControl.lock() nach dem Schließen der Hygiene-Öffnung aufrufen. */ protected void afterHygieneClosing() {} + protected abstract void handleLockGameFinished(int timeInMinutes); + + public abstract void penaltyLockGame(); + public BaseLockService( CommunityVerificationVoteRepository communityVerificationVoteRepository, CommunityVerificationRepository communityVerificationRepository, @@ -141,11 +145,21 @@ public abstract class BaseLockService { systemMessageService.send(senderId, receiverId, text, targetUrl, cause); } + // ── Lock-Game Abschluss ─────────────────────────────────────────────────── + + public void lockGameFinished(int timeInMinutes) { + LOGGER.info("[Lock {}] lockGameFinished nach {} Minuten", getLock().getLockee(), timeInMinutes); + handleLockGameFinished(timeInMinutes); + } + + + // ── Aufgaben ────────────────────────────────────────────────────────────── public void task(Task task) { BaseLockEntity lock = getLock(); - LOGGER.debug("Apply task {}", task); + LOGGER.info("[Lock {}] Aufgabe zugewiesen: title='{}', minutes={}, description='{}'", + lock.getLockee(), task.getTitle(), task.getMinutes(), task.getDescription()); lock.setCurrentTask(task.getTitle()); lock.setCurrentTaskDescription(task.getDescription()); if (task.getMinutes() != null && task.getMinutes() > 0) { @@ -163,10 +177,14 @@ public abstract class BaseLockService { } protected void applyRandomTask() { - LOGGER.debug("Apply random task"); var tasks = getLock().getTasks(); if (tasks != null && !tasks.isEmpty()) { - task(tasks.get(new Random().nextInt(tasks.size()))); + int idx = new Random().nextInt(tasks.size()); + LOGGER.info("[Lock {}] RANDOM-Aufgabe gezogen: Index {} von {} verfügbaren Aufgaben", + getLock().getLockee(), idx, tasks.size()); + task(tasks.get(idx)); + } else { + LOGGER.warn("[Lock {}] RANDOM-Aufgabe: keine Aufgaben im Lock vorhanden", getLock().getLockee()); } } diff --git a/src/main/java/de/oaa/xxx/games/chastity/common/LockGameController.java b/src/main/java/de/oaa/xxx/games/chastity/common/LockGameController.java index 80a51fc..b9de450 100644 --- a/src/main/java/de/oaa/xxx/games/chastity/common/LockGameController.java +++ b/src/main/java/de/oaa/xxx/games/chastity/common/LockGameController.java @@ -5,8 +5,10 @@ import java.time.LocalDateTime; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.GetMapping; @@ -17,6 +19,7 @@ import org.springframework.web.bind.annotation.RestController; import com.fasterxml.jackson.databind.ObjectMapper; +import de.oaa.xxx.games.chastity.cardlock.CardLockServiceFactory; import de.oaa.xxx.games.chastity.cardlock.CardlockRepository; import de.oaa.xxx.games.common.aufgaben.AufgabenList; import de.oaa.xxx.games.common.aufgaben.AvailableIn; @@ -24,6 +27,7 @@ import de.oaa.xxx.games.common.entity.AufgabeEntity; import de.oaa.xxx.games.common.entity.AufgabenGruppeEntity; import de.oaa.xxx.games.common.entity.FinisherEntity; import de.oaa.xxx.games.common.entity.SperreEntity; +import de.oaa.xxx.games.common.entity.ToyEntity; import de.oaa.xxx.games.common.repository.AufgabeRepository; import de.oaa.xxx.games.common.repository.AufgabenGruppeRepository; import de.oaa.xxx.games.common.repository.FinisherRepository; @@ -45,6 +49,7 @@ public class LockGameController { private final UserService userService; private final ObjectMapper objectMapper; private final CardlockRepository cardlockRepository; + private final CardLockServiceFactory cardLockServiceFactory; public LockGameController(LockGameRepository lockGameRepository, LockGameLockRepository lockGameLockRepository, @@ -54,7 +59,8 @@ public class LockGameController { FinisherRepository finisherRepository, UserService userService, ObjectMapper objectMapper, - CardlockRepository cardlockRepository) { + CardlockRepository cardlockRepository, + CardLockServiceFactory cardLockServiceFactory) { this.lockGameRepository = lockGameRepository; this.lockGameLockRepository = lockGameLockRepository; this.aufgabenGruppeRepository = aufgabenGruppeRepository; @@ -64,6 +70,7 @@ public class LockGameController { this.userService = userService; this.objectMapper = objectMapper; this.cardlockRepository = cardlockRepository; + this.cardLockServiceFactory = cardLockServiceFactory; } /** Verfügbare CHASTITY_ONLY-Gruppen des angemeldeten Users. */ @@ -82,13 +89,55 @@ public class LockGameController { return ResponseEntity.ok(gruppen); } + /** + * Gibt alle unique Toys zurück, die von Aufgaben, Sperren oder Finishern der Gruppe benötigt werden. + */ + @Transactional + @GetMapping("/toys") + public ResponseEntity