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:
2
bin/main/db/migration/V8__lock_game_aufgaben_text.sql
Normal file
2
bin/main/db/migration/V8__lock_game_aufgaben_text.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE lock_game
|
||||
MODIFY COLUMN aufgaben TEXT NULL;
|
||||
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.
Binary file not shown.
BIN
bin/main/de/oaa/xxx/games/chastity/common/BaseLockHelper.class
Normal file
BIN
bin/main/de/oaa/xxx/games/chastity/common/BaseLockHelper.class
Normal file
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.
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.
@@ -263,6 +263,12 @@
|
||||
cursor: pointer; transition: border-color 0.15s, color 0.15s;
|
||||
}
|
||||
.btn-item-edit:hover { border-color: var(--color-text); color: var(--color-text); }
|
||||
.btn-item-copy {
|
||||
background: none; border: 1px solid rgba(100,160,255,0.4); border-radius: 5px;
|
||||
color: var(--color-muted); font-size: 0.75rem; padding: 0.2rem 0.6rem;
|
||||
cursor: pointer; transition: border-color 0.15s, color 0.15s;
|
||||
}
|
||||
.btn-item-copy:hover { border-color: rgba(100,160,255,0.9); color: var(--color-text); }
|
||||
.btn-item-delete {
|
||||
background: none; border: 1px solid rgba(233,69,96,0.4); border-radius: 5px;
|
||||
color: var(--color-primary); font-size: 0.75rem; padding: 0.2rem 0.6rem;
|
||||
@@ -533,17 +539,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="iTempUnlockRow">
|
||||
<label>Temporäre Öffnungen</label>
|
||||
<div style="display:flex; flex-direction:column; gap:0.5rem; margin-top:0.5rem;">
|
||||
<label style="display:flex; align-items:center; gap:0.6rem; font-size:0.85rem; cursor:pointer;">
|
||||
<input type="checkbox" id="iTempUnlockBefore" style="accent-color:var(--color-primary); width:1rem; height:1rem;">
|
||||
Temporäre Öffnung <em>vor</em> der Zeitstrafe erforderlich
|
||||
<label class="toggle-switch" style="display:flex; align-items:center; gap:0.75rem; cursor:pointer; margin-top:0.25rem;">
|
||||
<input type="checkbox" id="iTempUnlockRequired">
|
||||
<span class="toggle-track"></span>
|
||||
<span style="font-size:0.9rem;">Temporäre Öffnung erforderlich</span>
|
||||
</label>
|
||||
<label style="display:flex; align-items:center; gap:0.6rem; font-size:0.85rem; cursor:pointer;">
|
||||
<input type="checkbox" id="iTempUnlockAfter" style="accent-color:var(--color-primary); width:1rem; height:1rem;">
|
||||
Temporäre Öffnung <em>nach</em> der Zeitstrafe erforderlich
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="iReleaseTextRow">
|
||||
<label for="iReleaseText">Text bei Aufhebung</label>
|
||||
@@ -765,7 +765,7 @@
|
||||
${g.beschreibung ? `<div class="gruppe-desc">${esc(g.beschreibung)}</div>` : ''}
|
||||
${renderSubSection('Aufgaben', sortByLevelThenName(g.aufgaben || []), 'aufgabe', renderAufgabe, g.gruppenId, type)}
|
||||
${g.availableIn !== 'CHASTITY_ONLY' ? renderSubSection('Strafen', sortByLevelThenName(g.strafen || []), 'strafe', renderStrafe, g.gruppenId, type) : ''}
|
||||
${renderSubSection('Zeitstrafen',sortByName(g.sperren || []), 'zeitstrafe',renderZeitstrafe, g.gruppenId, type)}
|
||||
${renderSubSection('Zeitstrafen',sortByLevelThenName(g.sperren || []), 'zeitstrafe',renderZeitstrafe, g.gruppenId, type)}
|
||||
${renderSubSection('Finisher', sortByGeschlecht(g.finisher || []), 'finisher', renderFinisher, g.gruppenId, type)}
|
||||
</div>
|
||||
</div>`;
|
||||
@@ -814,6 +814,7 @@
|
||||
function renderAufgabe(a, type, gruppenId) {
|
||||
_itemData[a.aufgabeId] = { ...a, _kind: 'aufgabe', _gruppenId: gruppenId };
|
||||
const badges = [];
|
||||
(a.benoetigteToys || []).forEach(t => badges.push(`<span class="item-detail-chip item-detail-chip-toy">${esc(t.name || t)}</span>`));
|
||||
const zeit = formatSek(a.sekundenVon, a.sekundenBis);
|
||||
if (zeit) badges.push(`<span class="badge badge-neutral">${esc(zeit)}</span>`);
|
||||
if (a.level != null) badges.push(`<span class="badge">Level ${esc(String(a.level))}</span>`);
|
||||
@@ -826,6 +827,7 @@
|
||||
const actionBtns = type === 'user' ? `
|
||||
<div class="item-action-btns">
|
||||
<button class="btn-item-edit" onclick="openEditItemModal('${esc(a.aufgabeId)}',event)">✎ Bearbeiten</button>
|
||||
<button class="btn-item-copy" onclick="duplicateItem('aufgabe','${esc(a.aufgabeId)}','${esc(gruppenId)}',event)">⧉ Duplizieren</button>
|
||||
<button class="btn-item-delete" onclick="deleteItem('aufgabe','${esc(a.aufgabeId)}','${esc(gruppenId)}',event)">✕ Löschen</button>
|
||||
</div>` : '';
|
||||
|
||||
@@ -841,6 +843,7 @@
|
||||
function renderStrafe(s, type, gruppenId) {
|
||||
_itemData[s.strafeId] = { ...s, _kind: 'strafe', _gruppenId: gruppenId };
|
||||
const badges = [];
|
||||
(s.benoetigteToys || []).forEach(t => badges.push(`<span class="item-detail-chip item-detail-chip-toy">${esc(t.name || t)}</span>`));
|
||||
const zeit = formatSek(s.sekundenVon, s.sekundenBis);
|
||||
if (zeit) badges.push(`<span class="badge badge-neutral">${esc(zeit)}</span>`);
|
||||
if (s.level != null) badges.push(`<span class="badge">Level ${esc(String(s.level))}</span>`);
|
||||
@@ -853,6 +856,7 @@
|
||||
const actionBtns = type === 'user' ? `
|
||||
<div class="item-action-btns">
|
||||
<button class="btn-item-edit" onclick="openEditItemModal('${esc(s.strafeId)}',event)">✎ Bearbeiten</button>
|
||||
<button class="btn-item-copy" onclick="duplicateItem('strafe','${esc(s.strafeId)}','${esc(gruppenId)}',event)">⧉ Duplizieren</button>
|
||||
<button class="btn-item-delete" onclick="deleteItem('strafe','${esc(s.strafeId)}','${esc(gruppenId)}',event)">✕ Löschen</button>
|
||||
</div>` : '';
|
||||
|
||||
@@ -868,8 +872,10 @@
|
||||
function renderZeitstrafe(z, type, gruppenId) {
|
||||
_itemData[z.sperreId] = { ...z, _kind: 'zeitstrafe', _gruppenId: gruppenId };
|
||||
const badges = [];
|
||||
(z.benoetigteToys || []).forEach(t => badges.push(`<span class="item-detail-chip item-detail-chip-toy">${esc(t.name || t)}</span>`));
|
||||
const zeit = formatMin(z.minutenVon, z.minutenBis);
|
||||
if (zeit) badges.push(`<span class="badge badge-neutral">${esc(zeit)}</span>`);
|
||||
if (z.level != null) badges.push(`<span class="badge">Level ${esc(String(z.level))}</span>`);
|
||||
|
||||
const detailRows = [];
|
||||
if (z.text) detailRows.push(`<div class="item-detail-text">${esc(z.text)}</div>`);
|
||||
@@ -879,6 +885,7 @@
|
||||
const actionBtns = type === 'user' ? `
|
||||
<div class="item-action-btns">
|
||||
<button class="btn-item-edit" onclick="openEditItemModal('${esc(z.sperreId)}',event)">✎ Bearbeiten</button>
|
||||
<button class="btn-item-copy" onclick="duplicateItem('zeitstrafe','${esc(z.sperreId)}','${esc(gruppenId)}',event)">⧉ Duplizieren</button>
|
||||
<button class="btn-item-delete" onclick="deleteItem('zeitstrafe','${esc(z.sperreId)}','${esc(gruppenId)}',event)">✕ Löschen</button>
|
||||
</div>` : '';
|
||||
|
||||
@@ -896,6 +903,7 @@
|
||||
function renderFinisher(f, type, gruppenId) {
|
||||
_itemData[f.finisherId] = { ...f, _kind: 'finisher', _gruppenId: gruppenId };
|
||||
const badges = [];
|
||||
(f.benoetigteToys || []).forEach(t => badges.push(`<span class="item-detail-chip item-detail-chip-toy">${esc(t.name || t)}</span>`));
|
||||
if (f.geschlecht) badges.push(`<span class="badge badge-neutral">${esc(GESCHLECHT_LABEL[f.geschlecht] || f.geschlecht)}</span>`);
|
||||
|
||||
const detailRows = [];
|
||||
@@ -906,6 +914,7 @@
|
||||
const actionBtns = type === 'user' ? `
|
||||
<div class="item-action-btns">
|
||||
<button class="btn-item-edit" onclick="openEditItemModal('${esc(f.finisherId)}',event)">✎ Bearbeiten</button>
|
||||
<button class="btn-item-copy" onclick="duplicateItem('finisher','${esc(f.finisherId)}','${esc(gruppenId)}',event)">⧉ Duplizieren</button>
|
||||
<button class="btn-item-delete" onclick="deleteItem('finisher','${esc(f.finisherId)}','${esc(gruppenId)}',event)">✕ Löschen</button>
|
||||
</div>` : '';
|
||||
|
||||
@@ -944,10 +953,36 @@
|
||||
finisher: apiUrl('/finisher')
|
||||
};
|
||||
const ITEM_DELETE_FIELD = { aufgabe: 'aufgabeId', strafe: 'strafeId', zeitstrafe: 'sperreId', finisher: 'finisherId' };
|
||||
const ITEM_COPY_URL = {
|
||||
aufgabe: apiUrl('/aufgabe/copy'),
|
||||
strafe: '/strafe/copy',
|
||||
zeitstrafe: '/sperre/copy',
|
||||
finisher: apiUrl('/finisher/copy')
|
||||
};
|
||||
|
||||
function duplicateItem(kind, itemId, gruppenId, event) {
|
||||
event.stopPropagation();
|
||||
const copyUrl = ITEM_COPY_URL[kind];
|
||||
if (!copyUrl) return;
|
||||
fetch(`${copyUrl}/${itemId}`, { method: 'POST' }).then(r => {
|
||||
if (r.ok) {
|
||||
pendingExpandId = gruppenId;
|
||||
pendingExpandType = 'user';
|
||||
_notifyOnLoad = true; loadUserGruppen();
|
||||
} else {
|
||||
document.getElementById('userActionError').textContent = 'Fehler beim Duplizieren (HTTP ' + r.status + ').';
|
||||
}
|
||||
}).catch(() => {
|
||||
document.getElementById('userActionError').textContent = 'Verbindungsfehler.';
|
||||
});
|
||||
}
|
||||
|
||||
function deleteItem(kind, itemId, gruppenId, event) {
|
||||
event.stopPropagation();
|
||||
if (!confirm('Eintrag wirklich löschen?')) return;
|
||||
openConfirmModal('Eintrag wirklich löschen?', () => _doDeleteItem(kind, itemId, gruppenId));
|
||||
}
|
||||
|
||||
function _doDeleteItem(kind, itemId, gruppenId) {
|
||||
const deleteUrl = ITEM_DELETE_URL[kind];
|
||||
if (!deleteUrl) return;
|
||||
const body = { [ITEM_DELETE_FIELD[kind]]: itemId };
|
||||
@@ -1430,7 +1465,7 @@
|
||||
const lbl = document.querySelector(`#iSperreFuer input[value="${v}"]`)?.closest('label');
|
||||
if (lbl) lbl.style.display = isChastity ? 'none' : '';
|
||||
});
|
||||
document.getElementById('iTempUnlockRow').style.display = (isZeit && isChastity) ? 'block' : 'none';
|
||||
document.getElementById('iTempUnlockRow').style.display = ((isZeit || isFinisher) && isChastity) ? 'block' : 'none';
|
||||
document.getElementById('iReleaseTextRow').style.display = isZeit ? 'block' : 'none';
|
||||
}
|
||||
|
||||
@@ -1449,8 +1484,7 @@
|
||||
document.querySelectorAll('#iWerkzeugFinisherPassiv input').forEach(cb => cb.checked = false);
|
||||
document.querySelectorAll('#iSperreFuer input').forEach(cb => cb.checked = false);
|
||||
document.querySelectorAll('#iGeschlecht input').forEach(rb => rb.checked = false);
|
||||
document.getElementById('iTempUnlockBefore').checked = false;
|
||||
document.getElementById('iTempUnlockAfter').checked = false;
|
||||
document.getElementById('iTempUnlockRequired').checked = false;
|
||||
_selectedToys = [];
|
||||
renderSelectedToys();
|
||||
document.getElementById('itemModalError').style.display = 'none';
|
||||
@@ -1495,6 +1529,9 @@
|
||||
const rb = document.querySelector(`#iGeschlecht input[value="${d.geschlecht}"]`);
|
||||
if (rb) rb.checked = true;
|
||||
}
|
||||
if (_isChastityMode) {
|
||||
document.getElementById('iTempUnlockRequired').checked = d.tempUnlockRequired === true;
|
||||
}
|
||||
} else {
|
||||
document.getElementById('iMinVon').value = d.minutenVon != null ? d.minutenVon : '';
|
||||
document.getElementById('iMinBis').value = d.minutenBis != null ? d.minutenBis : '';
|
||||
@@ -1502,8 +1539,7 @@
|
||||
(d.sperreFuer || []).forEach(w => { const cb = document.querySelector(`#iSperreFuer input[value="${w}"]`); if (cb) cb.checked = true; });
|
||||
if (_isChastityMode) {
|
||||
document.getElementById('iLevel').value = d.level != null ? d.level : '';
|
||||
document.getElementById('iTempUnlockBefore').checked = d.tempUnlockBeforeRequired === true;
|
||||
document.getElementById('iTempUnlockAfter').checked = d.tempUnlockAfterRequired === true;
|
||||
document.getElementById('iTempUnlockRequired').checked = d.tempUnlockRequired === true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1670,7 +1706,8 @@
|
||||
gruppeId: isEdit ? undefined : currentItemGruppeId,
|
||||
benoetigtAktiv: _isChastityMode ? [] : checkedValues('iWerkzeugFinisherAktiv'),
|
||||
benoetigtPassiv: _isChastityMode ? [] : checkedValues('iWerkzeugFinisherPassiv'),
|
||||
benoetigteToys: _selectedToys.map(t => ({ toyId: t.toyId }))
|
||||
benoetigteToys: _selectedToys.map(t => ({ toyId: t.toyId })),
|
||||
tempUnlockRequired: _isChastityMode ? document.getElementById('iTempUnlockRequired').checked : null
|
||||
};
|
||||
url = isEdit ? apiUrl(`/finisher/${currentItemEditId}`) : apiUrl('/finisher');
|
||||
method = isEdit ? 'PUT' : 'POST';
|
||||
@@ -1698,8 +1735,7 @@
|
||||
releaseText: document.getElementById('iReleaseText').value.trim() || null,
|
||||
sperreFuer,
|
||||
level: zeitLevel,
|
||||
tempUnlockBeforeRequired: _isChastityMode ? document.getElementById('iTempUnlockBefore').checked : null,
|
||||
tempUnlockAfterRequired: _isChastityMode ? document.getElementById('iTempUnlockAfter').checked : null,
|
||||
tempUnlockRequired: _isChastityMode ? document.getElementById('iTempUnlockRequired').checked : null,
|
||||
benoetigteToys: _selectedToys.map(t => ({ toyId: t.toyId }))
|
||||
};
|
||||
url = isEdit ? `/sperre/${currentItemEditId}` : '/sperre'; // BDSM-only
|
||||
|
||||
@@ -679,6 +679,7 @@
|
||||
|
||||
// ── Gruppe lists ──
|
||||
function renderGruppeList(containerId, gruppen) {
|
||||
gruppen = gruppen.filter(g => g.availableIn !== 'CHASTITY_ONLY');
|
||||
const ul = document.getElementById(containerId);
|
||||
const section = ul.closest('[id^="section"]');
|
||||
const selectAllWrap = section?.querySelector('.select-all-label');
|
||||
|
||||
@@ -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; }
|
||||
|
||||
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
|
||||
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);
|
||||
document.getElementById('tempOpeningTask').textContent = _state.activeTask || '';
|
||||
const code = _state.tempOpeningCode;
|
||||
if (code) {
|
||||
document.getElementById('tempOpeningCode').textContent = code;
|
||||
show('tempOpeningCodeRow');
|
||||
} else {
|
||||
hide('tempOpeningCodeRow');
|
||||
}
|
||||
show('tempOpeningBox');
|
||||
}
|
||||
} catch (_) { /* ignorieren */ }
|
||||
|
||||
// 2. Finisher laden und Zeit messen
|
||||
const finisherStartTime = Date.now();
|
||||
let finisher = null;
|
||||
async function doEndTempOpening() {
|
||||
try {
|
||||
const r = await fetch('/lock-game/finisher');
|
||||
if (r.ok) finisher = await r.json();
|
||||
} 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'); }
|
||||
}
|
||||
|
||||
document.getElementById('finisherTitle').textContent = finisher?.kurzText || '';
|
||||
document.getElementById('finisherText').textContent = finisher?.text || 'Glückwunsch – du hast Level 6 erreicht!';
|
||||
function showFinisherUI() {
|
||||
show('gameBox');
|
||||
hide('gameCard');
|
||||
hide('lockReleaseBox');
|
||||
|
||||
// 3. Warten bis Nutzer OK drückt
|
||||
await new Promise(resolve => {
|
||||
document.getElementById('btnFinisherOk').onclick = resolve;
|
||||
let finisher = {};
|
||||
try { finisher = JSON.parse(_state.finisher); } catch (_) {}
|
||||
document.getElementById('finisherTitle').textContent = finisher.kurzText || '';
|
||||
document.getElementById('finisherText').textContent = finisher.text || '';
|
||||
|
||||
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();
|
||||
|
||||
Binary file not shown.
@@ -127,12 +127,12 @@ public class BdsmGameService {
|
||||
newLock.setLockee(lockeeUserId);
|
||||
newLock.setKeyholder(keyholderUserId);
|
||||
newLock.setInitialCards(template.getInitialCards());
|
||||
newLock.setPickEveryMinute(template.getPickEveryMinute());
|
||||
newLock.setPickEverySeconds(template.getPickEverySeconds());
|
||||
newLock.setAccumulatePicks(template.isAccumulatePicks());
|
||||
newLock.setShowRemainingCards(template.isShowRemainingCards());
|
||||
newLock.setLatestOpeningtime(template.getLatestOpeningtime());
|
||||
newLock.setHygineOpeningDurationMinutes(template.getHygineOpeningDurationMinutes());
|
||||
newLock.setHygineOpeningEveryMinites(template.getHygineOpeningEveryMinites());
|
||||
newLock.setHygineOpeningDurationSeconds(template.getHygineOpeningDurationSeconds());
|
||||
newLock.setHygineOpeningEverySeconds(template.getHygineOpeningEverySeconds());
|
||||
newLock.setTasks(template.getTasks());
|
||||
newLock.setRequiresVerification(template.isRequiresVerification());
|
||||
newLock.setTestLock(false);
|
||||
@@ -149,10 +149,10 @@ public class BdsmGameService {
|
||||
newLock.setAvailableCards(template.getInitialCards() != null
|
||||
? new ArrayList<>(template.getInitialCards()) : new ArrayList<>());
|
||||
newLock.setOpenPicks(0);
|
||||
if (template.getPickEveryMinute() != null) {
|
||||
newLock.setNextCardIn(now.plusMinutes(template.getPickEveryMinute()));
|
||||
if (template.getPickEverySeconds() != null) {
|
||||
newLock.setNextCardIn(now.plusSeconds(template.getPickEverySeconds()));
|
||||
}
|
||||
if (template.getHygineOpeningEveryMinites() != null) {
|
||||
if (template.getHygineOpeningEverySeconds() != null) {
|
||||
newLock.setLastHygineOpening(now);
|
||||
}
|
||||
cardlockRepository.save(newLock);
|
||||
|
||||
@@ -103,6 +103,28 @@ public class AufgabeController {
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@PostMapping("/copy/{aufgabeId}")
|
||||
public ResponseEntity<Void> copy(@PathVariable("aufgabeId") UUID aufgabeId, Principal principal) {
|
||||
AufgabeEntity source = aufgabeRepository.findById(aufgabeId).orElse(null);
|
||||
if (source == null) return ResponseEntity.notFound().build();
|
||||
AufgabenGruppeEntity gruppe = source.getAufgabenGruppe();
|
||||
int limit = limitService.maxTasksPerGroup(userService.requireUser(principal).getUserId());
|
||||
if (gruppe.getAufgaben().size() >= limit) return ResponseEntity.status(409).build();
|
||||
AufgabeEntity copy = new AufgabeEntity();
|
||||
copy.setAufgabeId(UUID.randomUUID());
|
||||
copy.setAufgabenGruppe(gruppe);
|
||||
copy.setKurzText(source.getKurzText() + " (Kopie)");
|
||||
copy.setText(source.getText());
|
||||
copy.setLevel(source.getLevel());
|
||||
copy.setSekundenVon(source.getSekundenVon());
|
||||
copy.setSekundenBis(source.getSekundenBis());
|
||||
copy.setBenoetigtAktiv(source.getBenoetigtAktiv());
|
||||
copy.setBenoetigtPassiv(source.getBenoetigtPassiv());
|
||||
copy.setBenoetigteToys(new ArrayList<>(source.getBenoetigteToys() != null ? source.getBenoetigteToys() : List.of()));
|
||||
aufgabeRepository.save(copy);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@DeleteMapping
|
||||
public ResponseEntity<Void> delete(@RequestBody Aufgabe aufgabe) {
|
||||
try {
|
||||
|
||||
@@ -538,7 +538,7 @@ public class BdsmGameController extends BaseController {
|
||||
Map<String, Object> item = new LinkedHashMap<>();
|
||||
item.put("lockId", l.getLockId());
|
||||
item.put("name", l.getName() != null ? l.getName() : "Unbenanntes Lock");
|
||||
item.put("pickEveryMinute", l.getPickEveryMinute());
|
||||
item.put("pickEveryMinute", l.getPickEverySeconds() / 60);
|
||||
item.put("totalCards", l.getInitialCards() != null ? l.getInitialCards().size() : 0);
|
||||
item.put("active", l.getStartTime() != null && l.getUnlockTime() == null);
|
||||
return item;
|
||||
|
||||
@@ -88,11 +88,32 @@ public class FinisherController {
|
||||
entity.setBenoetigtAktiv(finisher.getBenoetigtAktiv());
|
||||
entity.setBenoetigtPassiv(finisher.getBenoetigtPassiv());
|
||||
entity.setBenoetigteToys(resolveToys(finisher.getBenoetigteToys()));
|
||||
entity.setTempUnlockRequired(finisher.getTempUnlockRequired());
|
||||
finisherRepository.save(entity);
|
||||
LOGGER.debug("Finisher {} aktualisiert", finisherId);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@PostMapping("/copy/{finisherId}")
|
||||
public ResponseEntity<Void> copy(@PathVariable("finisherId") UUID finisherId) {
|
||||
FinisherEntity source = finisherRepository.findById(finisherId).orElse(null);
|
||||
if (source == null) return ResponseEntity.notFound().build();
|
||||
AufgabenGruppeEntity gruppe = source.getAufgabenGruppe();
|
||||
if (gruppe.getFinisher().size() >= 100) return ResponseEntity.status(409).build();
|
||||
FinisherEntity copy = new FinisherEntity();
|
||||
copy.setFinisherId(UUID.randomUUID());
|
||||
copy.setAufgabenGruppe(gruppe);
|
||||
copy.setKurzText(source.getKurzText() + " (Kopie)");
|
||||
copy.setText(source.getText());
|
||||
copy.setGeschlecht(source.getGeschlecht());
|
||||
copy.setBenoetigtAktiv(source.getBenoetigtAktiv());
|
||||
copy.setBenoetigtPassiv(source.getBenoetigtPassiv());
|
||||
copy.setTempUnlockRequired(source.getTempUnlockRequired());
|
||||
copy.setBenoetigteToys(new ArrayList<>(source.getBenoetigteToys() != null ? source.getBenoetigteToys() : List.of()));
|
||||
finisherRepository.save(copy);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@DeleteMapping
|
||||
public ResponseEntity<Void> delete(@RequestBody Finisher finisher) {
|
||||
try {
|
||||
|
||||
@@ -56,7 +56,7 @@ public class SperreController {
|
||||
@PostMapping
|
||||
public ResponseEntity<Void> create(@RequestBody Sperre sperre) {
|
||||
if (sperre.getKurzText() == null || sperre.getText() == null || sperre.getMinutenVon() == null
|
||||
|| sperre.getGruppeId() == null || sperre.getSperreFuer() == null || sperre.getSperreFuer().isEmpty()) {
|
||||
|| sperre.getGruppeId() == null) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
AufgabenGruppeEntity gruppeEntity = gruppeRepository.findById(sperre.getGruppeId()).orElse(null);
|
||||
@@ -77,8 +77,7 @@ public class SperreController {
|
||||
|
||||
@PutMapping("/{sperreId}")
|
||||
public ResponseEntity<Void> update(@PathVariable("sperreId") UUID sperreId, @RequestBody Sperre sperre) {
|
||||
if (sperre.getKurzText() == null || sperre.getText() == null || sperre.getMinutenVon() == null
|
||||
|| sperre.getSperreFuer() == null || sperre.getSperreFuer().isEmpty()) {
|
||||
if (sperre.getKurzText() == null || sperre.getText() == null || sperre.getMinutenVon() == null) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
SperreEntity entity = sperreRepository.findById(sperreId).orElse(null);
|
||||
@@ -89,8 +88,7 @@ public class SperreController {
|
||||
entity.setMinutenVon(sperre.getMinutenVon());
|
||||
entity.setMinutenBis(sperre.getMinutenBis());
|
||||
entity.setLevel(sperre.getLevel());
|
||||
entity.setTempUnlockBeforeRequired(sperre.getTempUnlockBeforeRequired());
|
||||
entity.setTempUnlockAfterRequired(sperre.getTempUnlockAfterRequired());
|
||||
entity.setTempUnlockRequired(sperre.getTempUnlockRequired());
|
||||
entity.setSperreFuer(sperre.getSperreFuer());
|
||||
entity.setBenoetigteToys(resolveToys(sperre.getBenoetigteToys()));
|
||||
sperreRepository.save(entity);
|
||||
@@ -98,6 +96,28 @@ public class SperreController {
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@PostMapping("/copy/{sperreId}")
|
||||
public ResponseEntity<Void> copy(@PathVariable("sperreId") UUID sperreId) {
|
||||
SperreEntity source = sperreRepository.findById(sperreId).orElse(null);
|
||||
if (source == null) return ResponseEntity.notFound().build();
|
||||
AufgabenGruppeEntity gruppe = source.getAufgabenGruppe();
|
||||
if (gruppe.getSperren().size() >= 100) return ResponseEntity.status(409).build();
|
||||
SperreEntity copy = new SperreEntity();
|
||||
copy.setSperreId(UUID.randomUUID());
|
||||
copy.setAufgabenGruppe(gruppe);
|
||||
copy.setKurzText(source.getKurzText() + " (Kopie)");
|
||||
copy.setText(source.getText());
|
||||
copy.setReleaseText(source.getReleaseText());
|
||||
copy.setLevel(source.getLevel());
|
||||
copy.setMinutenVon(source.getMinutenVon());
|
||||
copy.setMinutenBis(source.getMinutenBis());
|
||||
copy.setSperreFuer(source.getSperreFuer());
|
||||
copy.setTempUnlockRequired(source.getTempUnlockRequired());
|
||||
copy.setBenoetigteToys(new ArrayList<>(source.getBenoetigteToys() != null ? source.getBenoetigteToys() : List.of()));
|
||||
sperreRepository.save(copy);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@DeleteMapping
|
||||
public ResponseEntity<Void> delete(@RequestBody Sperre sperre) {
|
||||
try {
|
||||
|
||||
@@ -94,6 +94,27 @@ public class StrafeController {
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@PostMapping("/copy/{strafeId}")
|
||||
public ResponseEntity<Void> copy(@PathVariable("strafeId") UUID strafeId) {
|
||||
StrafeEntity source = strafeRepository.findById(strafeId).orElse(null);
|
||||
if (source == null) return ResponseEntity.notFound().build();
|
||||
AufgabenGruppeEntity gruppe = source.getAufgabenGruppe();
|
||||
if (gruppe.getStrafen().size() >= 100) return ResponseEntity.status(409).build();
|
||||
StrafeEntity copy = new StrafeEntity();
|
||||
copy.setStrafeId(UUID.randomUUID());
|
||||
copy.setAufgabenGruppe(gruppe);
|
||||
copy.setKurzText(source.getKurzText() + " (Kopie)");
|
||||
copy.setText(source.getText());
|
||||
copy.setLevel(source.getLevel());
|
||||
copy.setSekundenVon(source.getSekundenVon());
|
||||
copy.setSekundenBis(source.getSekundenBis());
|
||||
copy.setBenoetigtAktiv(source.getBenoetigtAktiv());
|
||||
copy.setBenoetigtPassiv(source.getBenoetigtPassiv());
|
||||
copy.setBenoetigteToys(new ArrayList<>(source.getBenoetigteToys() != null ? source.getBenoetigteToys() : List.of()));
|
||||
strafeRepository.save(copy);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@DeleteMapping
|
||||
public ResponseEntity<Void> delete(@RequestBody Strafe strafe) {
|
||||
try {
|
||||
|
||||
@@ -125,7 +125,8 @@ public class CardLockController {
|
||||
List<CardEnum> initialCards, Integer pickEveryMinute, boolean accumulatePicks, boolean showRemainingCards,
|
||||
LocalDateTime latestOpeningtime, Integer hygineOpeningDurationMinutes, Integer hygineOpeningEveryMinites,
|
||||
List<Task> tasks, boolean requiresVerification, boolean testLock, Integer unlockCodeLines,
|
||||
TaskMode taskMode, LockControllType controllType, UUID gameSetId, Integer gameSpieldauerIdx) {
|
||||
TaskMode taskMode, LockControllType controllType, UUID gameSetId, Integer gameSpieldauerIdx,
|
||||
Integer speedFactor) {
|
||||
}
|
||||
|
||||
private static final SecureRandom RNG = new SecureRandom();
|
||||
@@ -166,12 +167,12 @@ public class CardLockController {
|
||||
lock.setLockee(lockee.getUserId());
|
||||
lock.setKeyholder(myId);
|
||||
lock.setInitialCards(req.initialCards());
|
||||
lock.setPickEveryMinute(req.pickEveryMinute());
|
||||
lock.setPickEverySeconds(req.pickEveryMinute() * 60);
|
||||
lock.setAccumulatePicks(req.accumulatePicks());
|
||||
lock.setShowRemainingCards(req.showRemainingCards());
|
||||
lock.setLatestOpeningtime(req.latestOpeningtime());
|
||||
lock.setHygineOpeningDurationMinutes(req.hygineOpeningDurationMinutes());
|
||||
lock.setHygineOpeningEveryMinites(req.hygineOpeningEveryMinites());
|
||||
lock.setHygineOpeningDurationSeconds(req.hygineOpeningDurationMinutes() != null ? req.hygineOpeningDurationMinutes() * 60 : null);
|
||||
lock.setHygineOpeningEverySeconds(req.hygineOpeningEveryMinites() != null ? req.hygineOpeningEveryMinites() * 60 : null);
|
||||
lock.setTasks(req.tasks() != null ? req.tasks() : List.of());
|
||||
lock.setRequiresVerification(req.requiresVerification());
|
||||
lock.setTestLock(false);
|
||||
@@ -206,21 +207,23 @@ public class CardLockController {
|
||||
}
|
||||
|
||||
int codeLines = (req.unlockCodeLines() != null && req.unlockCodeLines() >= 1) ? req.unlockCodeLines() : 5;
|
||||
int sf = (req.testLock() && req.speedFactor() != null && req.speedFactor() > 1) ? req.speedFactor() : 1;
|
||||
|
||||
CardLockEntity lock = new CardLockEntity();
|
||||
lock.setName(req.name());
|
||||
lock.setLockee(myId);
|
||||
lock.setKeyholder(null); // set only after invitation is confirmed
|
||||
lock.setInitialCards(req.initialCards());
|
||||
lock.setPickEveryMinute(req.pickEveryMinute());
|
||||
lock.setPickEverySeconds(req.pickEveryMinute() * 60);
|
||||
lock.setAccumulatePicks(req.accumulatePicks());
|
||||
lock.setShowRemainingCards(req.showRemainingCards());
|
||||
lock.setLatestOpeningtime(req.latestOpeningtime());
|
||||
lock.setHygineOpeningDurationMinutes(req.hygineOpeningDurationMinutes());
|
||||
lock.setHygineOpeningEveryMinites(req.hygineOpeningEveryMinites());
|
||||
lock.setHygineOpeningDurationSeconds(req.hygineOpeningDurationMinutes() != null ? req.hygineOpeningDurationMinutes() * 60 : null);
|
||||
lock.setHygineOpeningEverySeconds(req.hygineOpeningEveryMinites() != null ? req.hygineOpeningEveryMinites() * 60 : null);
|
||||
lock.setTasks(req.tasks() != null ? req.tasks() : List.of());
|
||||
lock.setRequiresVerification(req.requiresVerification());
|
||||
lock.setTestLock(req.testLock());
|
||||
lock.setSpeedFactor(sf > 1 ? sf : null);
|
||||
lock.setTaskMode(req.taskMode() != null ? req.taskMode() : TaskMode.RANDOM);
|
||||
lock.setUnlockCodeLength(codeLines);
|
||||
lock.setControllType(controllType);
|
||||
@@ -231,8 +234,9 @@ public class CardLockController {
|
||||
lock.setStartTime(now);
|
||||
lock.setAvailableCards(new ArrayList<>(req.initialCards()));
|
||||
lock.setOpenPicks(0);
|
||||
lock.setNextCardIn(now.plusMinutes(req.pickEveryMinute()));
|
||||
if (req.hygineOpeningEveryMinites() != null) {
|
||||
long firstCardSeconds = sf > 1 ? Math.max(6L, req.pickEveryMinute() * 60L / sf) : req.pickEveryMinute() * 60L;
|
||||
lock.setNextCardIn(now.plusSeconds(firstCardSeconds));
|
||||
if (req.hygineOpeningEveryMinites() != null) { // stored as seconds already above
|
||||
lock.setLastHygineOpening(now);
|
||||
}
|
||||
cardlockRepository.save(lock); // erst speichern, damit Lock-ID vorhanden ist
|
||||
@@ -243,7 +247,7 @@ public class CardLockController {
|
||||
"🃏 Deine erste Karte ist bereit – jetzt ziehen!",
|
||||
"/games/chastity/activelock.html?lockId=" + lock.getLockId(),
|
||||
de.oaa.xxx.social.entity.MessageCause.GAME_STATE,
|
||||
now.plusMinutes(req.pickEveryMinute()));
|
||||
now.plusSeconds(firstCardSeconds));
|
||||
|
||||
// Initialen Unlock-Code / TTLock-PIN via LockControl setzen
|
||||
CardLockService initService = cardLockServiceFactory.create(lock);
|
||||
@@ -307,9 +311,10 @@ public class CardLockController {
|
||||
result.put("taskPending", taskPending);
|
||||
|
||||
// Nächste Karte: geplante Benachrichtigung anlegen (echte nextCardIn aus Entity)
|
||||
int drawSf = (l.getSpeedFactor() != null && l.getSpeedFactor() > 1) ? l.getSpeedFactor() : 1;
|
||||
LocalDateTime nextCard = l.getNextCardIn() != null
|
||||
? l.getNextCardIn()
|
||||
: LocalDateTime.now().plusMinutes(l.getPickEveryMinute());
|
||||
: LocalDateTime.now().plusSeconds(drawSf > 1 ? Math.max(6L, l.getPickEverySeconds() / drawSf) : l.getPickEverySeconds());
|
||||
systemMessageService.sendScheduled(
|
||||
myId, myId,
|
||||
"🃏 Deine nächste Karte ist bereit – jetzt ziehen!",
|
||||
@@ -345,7 +350,7 @@ public class CardLockController {
|
||||
|
||||
cardLockServiceFactory.create(l).startHygieneOpening();
|
||||
int actualDuration = l.getTempOpeningDuration() != null ? l.getTempOpeningDuration()
|
||||
: (l.getHygineOpeningDurationMinutes() != null ? l.getHygineOpeningDurationMinutes() : 30);
|
||||
: (l.getHygineOpeningDurationSeconds() != null ? l.getHygineOpeningDurationSeconds() / 60 : 30);
|
||||
return ResponseEntity.ok(Map.of("unlockCode", l.getUnlockCode(), "durationMinutes", actualDuration));
|
||||
}
|
||||
|
||||
@@ -471,13 +476,15 @@ public class CardLockController {
|
||||
long totalCards = l.getAvailableCards() != null ? l.getAvailableCards().size() : 0;
|
||||
|
||||
// Hygiene-Berechnung
|
||||
boolean hygieneEnabled = l.getHygineOpeningEveryMinites() != null;
|
||||
boolean hygieneEnabled = l.getHygineOpeningEverySeconds() != null;
|
||||
boolean hygieneOpeningDue = false;
|
||||
long hygieneSecondsRemaining = 0;
|
||||
if (hygieneEnabled) {
|
||||
int lockSf = (l.getSpeedFactor() != null && l.getSpeedFactor() > 1) ? l.getSpeedFactor() : 1;
|
||||
long hygineSeconds = lockSf > 1 ? Math.max(6L, l.getHygineOpeningEverySeconds() / lockSf) : l.getHygineOpeningEverySeconds();
|
||||
LocalDateTime base = l.getLastHygineOpening() != null ? l.getLastHygineOpening() : l.getStartTime();
|
||||
if (base != null) {
|
||||
LocalDateTime nextHygiene = base.plusMinutes(l.getHygineOpeningEveryMinites());
|
||||
LocalDateTime nextHygiene = base.plusSeconds(hygineSeconds);
|
||||
hygieneSecondsRemaining = ChronoUnit.SECONDS.between(LocalDateTime.now(), nextHygiene);
|
||||
hygieneOpeningDue = hygieneSecondsRemaining <= 0;
|
||||
}
|
||||
@@ -507,7 +514,7 @@ public class CardLockController {
|
||||
result.put("hygieneOpeningStarted",
|
||||
l.getTempOpeningTime() != null ? l.getTempOpeningTime().toString() : null);
|
||||
result.put("hygieneDurationMinutes",
|
||||
l.getHygineOpeningDurationMinutes() != null ? l.getHygineOpeningDurationMinutes() : 0);
|
||||
l.getHygineOpeningDurationSeconds() != null ? l.getHygineOpeningDurationSeconds() / 60 : 0);
|
||||
result.put("hasKeyholder", l.getKeyholder() != null);
|
||||
result.put("keyholderInvitationPending",
|
||||
l.getKeyholder() == null && !invitationRepository.findByLockId(l.getLockId()).isEmpty());
|
||||
@@ -680,7 +687,9 @@ public class CardLockController {
|
||||
UUID gameSetId = l.getGameSetId();
|
||||
l.setGameCardParkedAt(null);
|
||||
l.setFrozenUntil(null);
|
||||
l.setNextCardIn(LocalDateTime.now().plusMinutes(l.getPickEveryMinute() != null ? l.getPickEveryMinute() : 60));
|
||||
int pickSecs = l.getPickEverySeconds() != null ? l.getPickEverySeconds() : 3600;
|
||||
int gameSf = (l.getSpeedFactor() != null && l.getSpeedFactor() > 1) ? l.getSpeedFactor() : 1;
|
||||
l.setNextCardIn(LocalDateTime.now().plusSeconds(gameSf > 1 ? Math.max(6L, pickSecs / gameSf) : pickSecs));
|
||||
l.setGameActive(true);
|
||||
cardlockRepository.save(l);
|
||||
|
||||
@@ -976,13 +985,13 @@ public class CardLockController {
|
||||
l.getAvailableCards().forEach(c -> cardCounts.merge(c.name(), 1L, Long::sum));
|
||||
}
|
||||
|
||||
boolean hygieneEnabled = l.getHygineOpeningEveryMinites() != null;
|
||||
boolean hygieneEnabled = l.getHygineOpeningEverySeconds() != null;
|
||||
boolean hygieneOpeningDue = false;
|
||||
long hygieneSecondsRemaining = 0;
|
||||
if (hygieneEnabled) {
|
||||
LocalDateTime base = l.getLastHygineOpening() != null ? l.getLastHygineOpening() : l.getStartTime();
|
||||
if (base != null) {
|
||||
LocalDateTime nextHygiene = base.plusMinutes(l.getHygineOpeningEveryMinites());
|
||||
LocalDateTime nextHygiene = base.plusSeconds(l.getHygineOpeningEverySeconds());
|
||||
hygieneSecondsRemaining = ChronoUnit.SECONDS.between(LocalDateTime.now(), nextHygiene);
|
||||
hygieneOpeningDue = hygieneSecondsRemaining <= 0;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public class CardLockEntity extends BaseLockEntity {
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private List<CardEnum> initialCards;
|
||||
@Column
|
||||
private Integer pickEveryMinute;
|
||||
private Integer pickEverySeconds;
|
||||
@Column
|
||||
private boolean accumulatePicks;
|
||||
@Column
|
||||
|
||||
@@ -98,12 +98,12 @@ public class CardLockService extends BaseLockService implements LockControlCallb
|
||||
|
||||
@Override
|
||||
protected void applyHygieneOvertime(Long overtime) {
|
||||
long penalty = Math.round(overtime * 4 * getTimeMultiplier());
|
||||
LOGGER.debug("Apply {} Minutes Overtime (penalty: {})", overtime, penalty);
|
||||
long penaltySeconds = Math.round(overtime * 4 * 60.0 * getTimeMultiplier());
|
||||
LOGGER.debug("Apply {} Minutes Overtime (penalty: {} seconds)", overtime, penaltySeconds);
|
||||
if (lock.getFrozenUntil() != null) {
|
||||
lock.setFrozenUntil(lock.getFrozenUntil().plusMinutes(penalty));
|
||||
lock.setFrozenUntil(lock.getFrozenUntil().plusSeconds(penaltySeconds));
|
||||
} else {
|
||||
lock.setFrozenUntil(LocalDateTime.now().plusMinutes(penalty));
|
||||
lock.setFrozenUntil(LocalDateTime.now().plusSeconds(penaltySeconds));
|
||||
}
|
||||
LOGGER.debug("Frozen until {}", lock.getFrozenUntil());
|
||||
}
|
||||
@@ -128,7 +128,7 @@ public class CardLockService extends BaseLockService implements LockControlCallb
|
||||
}
|
||||
} else {
|
||||
if (lock.getNextCardIn().isBefore(LocalDateTime.now())) {
|
||||
lock.setNextCardIn(LocalDateTime.now().plusMinutes(Math.round(lock.getPickEveryMinute() * getTimeMultiplier())));
|
||||
lock.setNextCardIn(LocalDateTime.now().plusSeconds(Math.round(lock.getPickEverySeconds() * 1.0 * getTimeMultiplier())));
|
||||
card = getRandomCard();
|
||||
}
|
||||
}
|
||||
@@ -175,16 +175,16 @@ public class CardLockService extends BaseLockService implements LockControlCallb
|
||||
}
|
||||
|
||||
public String freeze() {
|
||||
var multiplier = lock.getPickEveryMinute() * new Random().nextDouble(1.0, 4.0) * getTimeMultiplier();
|
||||
var multiplier = lock.getPickEverySeconds() * 1.0 * new Random().nextDouble(1.0, 4.0) * getTimeMultiplier();
|
||||
freeze(multiplier);
|
||||
return "";
|
||||
}
|
||||
|
||||
private String freeze(double multiplier) {
|
||||
LocalDateTime frozenTill = LocalDateTime.now().plus((long) multiplier, ChronoUnit.MINUTES);
|
||||
private String freeze(double seconds) {
|
||||
LocalDateTime frozenTill = LocalDateTime.now().plus((long) seconds, ChronoUnit.SECONDS);
|
||||
lock.setFrozenUntil(frozenTill);
|
||||
lock.setNextCardIn(frozenTill);
|
||||
LOGGER.info("[CardLock {}] FREEZE: eingefroren für {} Minuten (bis {})", lock.getLockee(), (long) multiplier, frozenTill);
|
||||
LOGGER.info("[CardLock {}] FREEZE: eingefroren für {} Sekunden (bis {})", lock.getLockee(), (long) seconds, frozenTill);
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -247,9 +247,9 @@ public class CardLockService extends BaseLockService implements LockControlCallb
|
||||
}
|
||||
|
||||
public void startHygieneOpening() {
|
||||
int base = lock.getHygineOpeningDurationMinutes() != null ? lock.getHygineOpeningDurationMinutes() : 30;
|
||||
int duration = (int) Math.round(base * getTimeMultiplier());
|
||||
startTempOpening(TempOpeningReason.HYGIENE, duration);
|
||||
int baseSecs = lock.getHygineOpeningDurationSeconds() != null ? lock.getHygineOpeningDurationSeconds() : (30 * 60);
|
||||
int durationMinutes = (int) Math.round(baseSecs / 60.0 * getTimeMultiplier());
|
||||
startTempOpening(TempOpeningReason.HYGIENE, durationMinutes);
|
||||
}
|
||||
|
||||
// ── Cum cards ─────────────────────────────────────────────────────────────
|
||||
@@ -313,23 +313,24 @@ public class CardLockService extends BaseLockService implements LockControlCallb
|
||||
|
||||
private double getTimeMultiplier() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
double sf = (lock.getSpeedFactor() != null && lock.getSpeedFactor() > 1) ? lock.getSpeedFactor() : 1.0;
|
||||
if (lock.getSpeedupUntil() != null && lock.getSpeedupUntil().isAfter(now)) {
|
||||
return 0.25;
|
||||
return 0.25 / sf;
|
||||
}
|
||||
if (lock.getSlowmoUntil() != null && lock.getSlowmoUntil().isAfter(now)) {
|
||||
return 4.0;
|
||||
return 4.0 / sf;
|
||||
}
|
||||
return 1.0;
|
||||
return 1.0 / sf;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleLockGameFinished(int timeInMinutes) {
|
||||
int freezeTime = (int) (timeInMinutes * new Random().nextDouble(1.0, 4.0));
|
||||
freeze(freezeTime);
|
||||
protected void handleLockGameFinished(int timeInSeconds) {
|
||||
double freezeSeconds = timeInSeconds * new Random().nextDouble(1.0, 4.0);
|
||||
freeze(freezeSeconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void penaltyLockGame() {
|
||||
handleLockGameFinished(60);
|
||||
handleLockGameFinished(3600);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,9 +64,9 @@ public class BaseLockEntity {
|
||||
@Column
|
||||
private LocalDateTime lastHygineOpening;
|
||||
@Column
|
||||
private Integer hygineOpeningDurationMinutes;
|
||||
private Integer hygineOpeningDurationSeconds;
|
||||
@Column
|
||||
private Integer hygineOpeningEveryMinites;
|
||||
private Integer hygineOpeningEverySeconds;
|
||||
@Column
|
||||
private LocalDateTime tempOpeningTime; // If null, not while hygine opening
|
||||
@Column
|
||||
@@ -90,6 +90,9 @@ public class BaseLockEntity {
|
||||
@Column(nullable = false)
|
||||
private TaskMode taskMode = TaskMode.RANDOM;
|
||||
|
||||
@Column
|
||||
private Integer speedFactor;
|
||||
|
||||
// --- Notfall- & Keyholder-Status ---
|
||||
@Column(nullable = false)
|
||||
private boolean keyholderRequestedUnlock = false;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package de.oaa.xxx.games.chastity.common;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
public class BaseLockHelper {
|
||||
|
||||
private BaseLockHelper() {}
|
||||
|
||||
public static Long calcOvertime(BaseLockEntity lock) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (lock.getTempOpeningTime() != null && lock.getTempOpeningDuration() != null) {
|
||||
LocalDateTime dueTime = lock.getTempOpeningTime().plusMinutes(lock.getTempOpeningDuration());
|
||||
if (now.isAfter(dueTime)) {
|
||||
return ChronoUnit.MINUTES.between(dueTime, now);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package de.oaa.xxx.games.chastity.common;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
@@ -17,13 +16,13 @@ import de.oaa.xxx.games.chastity.community.CommunityTaskVoteEntity;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityTaskVoteRepository;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityVerificationRepository;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityVerificationVoteRepository;
|
||||
import de.oaa.xxx.games.chastity.timelock.TimeLockRepository;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderNotificationEntity;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderNotificationRepository;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderTaskChoiceEntity;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderTaskChoiceRepository;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderVerificationRepository;
|
||||
import de.oaa.xxx.games.chastity.tasks.Task;
|
||||
import de.oaa.xxx.games.chastity.timelock.TimeLockRepository;
|
||||
import de.oaa.xxx.games.chastity.unlock.TempOpeningReason;
|
||||
import de.oaa.xxx.games.chastity.unlock.UnlockCodeHistoryService;
|
||||
import de.oaa.xxx.games.history.GameHistoryEntity;
|
||||
@@ -70,7 +69,7 @@ public abstract class BaseLockService {
|
||||
/** TimeLock: lockControl.lock() nach dem Schließen der Hygiene-Öffnung aufrufen. */
|
||||
protected void afterHygieneClosing() {}
|
||||
|
||||
protected abstract void handleLockGameFinished(int timeInMinutes);
|
||||
protected abstract void handleLockGameFinished(int timeInSeconds);
|
||||
|
||||
public abstract void penaltyLockGame();
|
||||
|
||||
@@ -111,18 +110,6 @@ public abstract class BaseLockService {
|
||||
|
||||
// ── Gemeinsame Hilfsmethoden ──────────────────────────────────────────────
|
||||
|
||||
protected Long calcOvertime() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
BaseLockEntity lock = getLock();
|
||||
if (lock.getTempOpeningTime() != null && lock.getTempOpeningDuration() != null) {
|
||||
LocalDateTime dueTime = lock.getTempOpeningTime().plusMinutes(lock.getTempOpeningDuration());
|
||||
if (now.isAfter(dueTime)) {
|
||||
return ChronoUnit.MINUTES.between(dueTime, now);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void reportKeyholder(Long overtime) {
|
||||
BaseLockEntity lock = getLock();
|
||||
KeyholderNotificationEntity notification = new KeyholderNotificationEntity();
|
||||
@@ -147,9 +134,9 @@ public abstract class BaseLockService {
|
||||
|
||||
// ── Lock-Game Abschluss ───────────────────────────────────────────────────
|
||||
|
||||
public void lockGameFinished(int timeInMinutes) {
|
||||
LOGGER.info("[Lock {}] lockGameFinished nach {} Minuten", getLock().getLockee(), timeInMinutes);
|
||||
handleLockGameFinished(timeInMinutes);
|
||||
public void lockGameFinished(int timeInSeconds) {
|
||||
LOGGER.info("[Lock {}] lockGameFinished nach {} Sekunden Overtime", getLock().getLockee(), timeInSeconds);
|
||||
handleLockGameFinished(timeInSeconds);
|
||||
}
|
||||
|
||||
|
||||
@@ -227,7 +214,7 @@ public abstract class BaseLockService {
|
||||
public String endTempOpening() {
|
||||
var lock = getLock();
|
||||
var now = LocalDateTime.now();
|
||||
var overtime = calcOvertime();
|
||||
var overtime = BaseLockHelper.calcOvertime(lock);
|
||||
if (overtime != null) {
|
||||
if (lock.getKeyholder() != null) {
|
||||
reportKeyholder(overtime);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.oaa.xxx.games.chastity.common;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public record GameState(
|
||||
@@ -9,7 +10,13 @@ public record GameState(
|
||||
Integer level,
|
||||
String activeTask,
|
||||
LocalDateTime activeTaskEnd,
|
||||
List<String> activeTaskBenoetigtAktiv,
|
||||
String taskInQueue,
|
||||
String lockInQueue) {
|
||||
String lockInQueue,
|
||||
LocalDateTime tempOpeningTime,
|
||||
Integer tempOpeningDuration,
|
||||
String tempOpeningCode,
|
||||
LocalDateTime finisherStartedAt,
|
||||
String finisher) {
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ 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.chastity.lockcontroll.LockControlFactory;
|
||||
import de.oaa.xxx.games.chastity.unlock.UnlockCodeHistoryService;
|
||||
import de.oaa.xxx.games.common.aufgaben.AufgabenList;
|
||||
import de.oaa.xxx.games.common.aufgaben.AvailableIn;
|
||||
import de.oaa.xxx.games.common.entity.AufgabeEntity;
|
||||
@@ -50,6 +52,9 @@ public class LockGameController {
|
||||
private final ObjectMapper objectMapper;
|
||||
private final CardlockRepository cardlockRepository;
|
||||
private final CardLockServiceFactory cardLockServiceFactory;
|
||||
private BaseLockRepository baseLockRepository;
|
||||
private UnlockCodeHistoryService unlockCodeHistoryService;
|
||||
private LockControlFactory lockControlFactory;
|
||||
|
||||
public LockGameController(LockGameRepository lockGameRepository,
|
||||
LockGameLockRepository lockGameLockRepository,
|
||||
@@ -60,7 +65,10 @@ public class LockGameController {
|
||||
UserService userService,
|
||||
ObjectMapper objectMapper,
|
||||
CardlockRepository cardlockRepository,
|
||||
CardLockServiceFactory cardLockServiceFactory) {
|
||||
CardLockServiceFactory cardLockServiceFactory,
|
||||
BaseLockRepository baseLockRepository,
|
||||
UnlockCodeHistoryService unlockCodeHistoryService,
|
||||
LockControlFactory lockControlFactory) {
|
||||
this.lockGameRepository = lockGameRepository;
|
||||
this.lockGameLockRepository = lockGameLockRepository;
|
||||
this.aufgabenGruppeRepository = aufgabenGruppeRepository;
|
||||
@@ -71,6 +79,9 @@ public class LockGameController {
|
||||
this.objectMapper = objectMapper;
|
||||
this.cardlockRepository = cardlockRepository;
|
||||
this.cardLockServiceFactory = cardLockServiceFactory;
|
||||
this.baseLockRepository = baseLockRepository;
|
||||
this.unlockCodeHistoryService = unlockCodeHistoryService;
|
||||
this.lockControlFactory = lockControlFactory;
|
||||
}
|
||||
|
||||
/** Verfügbare CHASTITY_ONLY-Gruppen des angemeldeten Users. */
|
||||
@@ -136,6 +147,7 @@ public class LockGameController {
|
||||
@PostMapping("/init")
|
||||
public ResponseEntity<?> init(
|
||||
@RequestParam UUID aufgabenGruppeId,
|
||||
@RequestParam UUID lockId,
|
||||
@RequestParam(required = false) List<UUID> excludedToyIds,
|
||||
Principal principal) {
|
||||
UUID userId = userService.requireUser(principal).getUserId();
|
||||
@@ -180,6 +192,10 @@ public class LockGameController {
|
||||
));
|
||||
}
|
||||
|
||||
aufgaben.forEach(a -> { if (a.getBenoetigteToys() != null) a.getBenoetigteToys().forEach(t -> t.setBild(null)); });
|
||||
sperren.forEach(s -> { if (s.getBenoetigteToys() != null) s.getBenoetigteToys().forEach(t -> t.setBild(null)); });
|
||||
finisher.forEach(f -> { if (f.getBenoetigteToys() != null) f.getBenoetigteToys().forEach(t -> t.setBild(null)); });
|
||||
|
||||
AufgabenList list = new AufgabenList();
|
||||
list.setAufgaben(aufgaben);
|
||||
list.setSperren(sperren);
|
||||
@@ -196,11 +212,15 @@ public class LockGameController {
|
||||
return g;
|
||||
});
|
||||
|
||||
game.setLockId(lockId);
|
||||
game.setAufgaben(aufgabenJson);
|
||||
game.setLevel(1);
|
||||
game.setAufgabenProLevel(AUFGABEN_PRO_LEVEL);
|
||||
game.setAufgabenAufAktuellemLevel(0);
|
||||
game.setZeitfaktorZeitstrafen(1.0);
|
||||
double zeitfaktor = baseLockRepository.findById(lockId)
|
||||
.map(l -> l.getSpeedFactor() != null && l.getSpeedFactor() > 1 ? 1.0 / l.getSpeedFactor() : 1.0)
|
||||
.orElse(1.0);
|
||||
game.setZeitfaktorZeitstrafen(zeitfaktor);
|
||||
game.setSetupId(aufgabenGruppeId);
|
||||
game.setActiveTask(null);
|
||||
game.setActiveTaskEnd(null);
|
||||
@@ -290,17 +310,55 @@ public class LockGameController {
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/finisher")
|
||||
public ResponseEntity<?> getFinisher(Principal principal) {
|
||||
@PostMapping("/start-finisher")
|
||||
public ResponseEntity<?> startFinisher(Principal principal) {
|
||||
UUID userId = userService.requireUser(principal).getUserId();
|
||||
var opt = lockGameRepository.findByUserId(userId);
|
||||
if (opt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
try {
|
||||
var finisher = buildService(opt.get()).getFinisher();
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("kurzText", finisher.getKurzText());
|
||||
result.put("text", finisher.getText());
|
||||
return ResponseEntity.ok(result);
|
||||
buildService(opt.get()).startFinisher();
|
||||
return ResponseEntity.noContent().build();
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/end-finisher")
|
||||
public ResponseEntity<?> endFinisher(Principal principal) {
|
||||
UUID userId = userService.requireUser(principal).getUserId();
|
||||
var opt = lockGameRepository.findByUserId(userId);
|
||||
if (opt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
try {
|
||||
buildService(opt.get()).endFinisher();
|
||||
return ResponseEntity.noContent().build();
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@PostMapping("/start-temp-opening")
|
||||
public ResponseEntity<?> startTempOpening(Principal principal) {
|
||||
UUID userId = userService.requireUser(principal).getUserId();
|
||||
var opt = lockGameRepository.findByUserId(userId);
|
||||
if (opt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
try {
|
||||
buildService(opt.get()).startTempOpening();
|
||||
return ResponseEntity.noContent().build();
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@PostMapping("/end-temp-opening")
|
||||
public ResponseEntity<?> endTempOpening(Principal principal) {
|
||||
UUID userId = userService.requireUser(principal).getUserId();
|
||||
var opt = lockGameRepository.findByUserId(userId);
|
||||
if (opt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
try {
|
||||
buildService(opt.get()).endTempOpening();
|
||||
return ResponseEntity.noContent().build();
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()));
|
||||
}
|
||||
@@ -324,23 +382,20 @@ public class LockGameController {
|
||||
@PostMapping("/complete")
|
||||
public ResponseEntity<?> completeGame(
|
||||
@RequestParam(required = false) UUID lockId,
|
||||
@RequestParam(required = false, defaultValue = "0") int timeInMinutes,
|
||||
Principal principal) {
|
||||
UUID userId = userService.requireUser(principal).getUserId();
|
||||
var opt = lockGameRepository.findByUserId(userId);
|
||||
if (opt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
LockGameEntity game = opt.get();
|
||||
int overtimeInSeconds = game.getOvertimeInSeconds() != null ? game.getOvertimeInSeconds() : 0;
|
||||
lockGameLockRepository.deleteAll(lockGameLockRepository.findByGameId(game.getGameId()));
|
||||
lockGameRepository.delete(game);
|
||||
|
||||
if (lockId != null) {
|
||||
cardlockRepository.findById(lockId).ifPresent(l -> {
|
||||
if (l.getLockee().equals(userId)) {
|
||||
cardLockServiceFactory.create(l).lockGameFinished(timeInMinutes);
|
||||
cardLockServiceFactory.create(l).lockGameFinished(overtimeInSeconds);
|
||||
l.setGameActive(false);
|
||||
l.setFrozenUntil(null);
|
||||
l.setNextCardIn(LocalDateTime.now()
|
||||
.plusMinutes(l.getPickEveryMinute() != null ? l.getPickEveryMinute() : 60));
|
||||
cardlockRepository.save(l);
|
||||
}
|
||||
});
|
||||
@@ -349,6 +404,6 @@ public class LockGameController {
|
||||
}
|
||||
|
||||
private LockGameService buildService(LockGameEntity entity) throws Exception {
|
||||
return new LockGameService(entity, lockGameRepository, lockGameLockRepository);
|
||||
return new LockGameService(entity, lockGameRepository, lockGameLockRepository, baseLockRepository, unlockCodeHistoryService, lockControlFactory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,13 +18,15 @@ import lombok.Setter;
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "lock_game")
|
||||
public class LockGameEntity {
|
||||
|
||||
public class LockGameEntity {
|
||||
@Id
|
||||
@Column
|
||||
private UUID gameId;
|
||||
@Column(unique = true)
|
||||
private UUID userId;
|
||||
@Column
|
||||
private UUID lockId;
|
||||
@OneToMany(mappedBy = "gameId", fetch = FetchType.EAGER)
|
||||
private List<LockGameLockEntity> activeLocks = new ArrayList<>();
|
||||
@Column
|
||||
@@ -42,9 +44,23 @@ public class LockGameEntity {
|
||||
@Column
|
||||
private LocalDateTime activeTaskEnd;
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String activeTaskBenoetigtAktiv;
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String taskInQueue;
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String lockInQueue;
|
||||
@Column
|
||||
private UUID setupId;
|
||||
@Column
|
||||
private LocalDateTime tempOpeningTime;
|
||||
@Column
|
||||
private Integer tempOpeningDurationInMinutes;
|
||||
@Column
|
||||
private String tempUnlockCode;
|
||||
@Column
|
||||
private Integer overtimeInSeconds;
|
||||
@Column
|
||||
private LocalDateTime finisherStartedAt;
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String finisher;
|
||||
}
|
||||
|
||||
@@ -39,4 +39,6 @@ public class LockGameLockEntity {
|
||||
private String releaseText;
|
||||
@Column
|
||||
private LocalDateTime releaseTime;
|
||||
@Column
|
||||
private Boolean tempUnlockRequired;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package de.oaa.xxx.games.chastity.common;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -14,39 +15,60 @@ import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import de.oaa.xxx.games.bdsm.AufgabeAnzeige;
|
||||
import de.oaa.xxx.games.chastity.lockcontroll.LockControlCallback;
|
||||
import de.oaa.xxx.games.chastity.lockcontroll.LockControlFactory;
|
||||
import de.oaa.xxx.games.chastity.unlock.TempOpeningReason;
|
||||
import de.oaa.xxx.games.chastity.unlock.UnlockCodeHistoryService;
|
||||
import de.oaa.xxx.games.common.aufgaben.Aufgabe;
|
||||
import de.oaa.xxx.games.common.aufgaben.AufgabenList;
|
||||
import de.oaa.xxx.games.common.aufgaben.Finisher;
|
||||
import de.oaa.xxx.games.common.aufgaben.Sperre;
|
||||
|
||||
public class LockGameService {
|
||||
public class LockGameService implements LockControlCallback {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(LockGameService.class);
|
||||
|
||||
private LockGameEntity gamestate;
|
||||
private LockGameRepository lockGameRepository;
|
||||
private LockGameLockRepository lockGameLockRepository;
|
||||
private BaseLockRepository baseLockRepository;
|
||||
private UnlockCodeHistoryService unlockCodeHistoryService;
|
||||
private LockControlFactory lockControlFactory;
|
||||
|
||||
private LockGameEntity gamestate;
|
||||
private AufgabenList aufgabenList;
|
||||
|
||||
public LockGameService(LockGameEntity gamestate, LockGameRepository lockGameRepository,
|
||||
LockGameLockRepository lockGameLockRepository) throws JsonMappingException, JsonProcessingException {
|
||||
LockGameLockRepository lockGameLockRepository, BaseLockRepository baseLockRepository,
|
||||
UnlockCodeHistoryService unlockCodeHistoryService, LockControlFactory lockControlFactory) throws JsonMappingException, JsonProcessingException {
|
||||
this.gamestate = gamestate;
|
||||
this.lockGameRepository = lockGameRepository;
|
||||
this.lockGameLockRepository = lockGameLockRepository;
|
||||
this.baseLockRepository = baseLockRepository;
|
||||
this.unlockCodeHistoryService = unlockCodeHistoryService;
|
||||
this.lockControlFactory = lockControlFactory;
|
||||
|
||||
this.aufgabenList = new ObjectMapper().readValue(gamestate.getAufgaben(), AufgabenList.class);
|
||||
|
||||
}
|
||||
|
||||
public GameState getGameState() {
|
||||
List<String> benoetigtAktiv = null;
|
||||
if (gamestate.getActiveTaskBenoetigtAktiv() != null) {
|
||||
try {
|
||||
benoetigtAktiv = new ObjectMapper().readValue(gamestate.getActiveTaskBenoetigtAktiv(),
|
||||
new com.fasterxml.jackson.core.type.TypeReference<List<String>>() {});
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
return new GameState(gamestate.getGameId(), gamestate.getUserId(), gamestate.getLevel(),
|
||||
gamestate.getActiveTask(), gamestate.getActiveTaskEnd(), gamestate.getTaskInQueue(),
|
||||
gamestate.getLockInQueue());
|
||||
gamestate.getActiveTask(), gamestate.getActiveTaskEnd(), benoetigtAktiv,
|
||||
gamestate.getTaskInQueue(), gamestate.getLockInQueue(), gamestate.getTempOpeningTime(),
|
||||
gamestate.getTempOpeningDurationInMinutes(), gamestate.getTempUnlockCode(),
|
||||
gamestate.getFinisherStartedAt(), gamestate.getFinisher());
|
||||
}
|
||||
|
||||
public void initNextTask() throws JsonProcessingException {
|
||||
gamestate.setActiveTask(null);
|
||||
gamestate.setActiveTaskEnd(null);
|
||||
gamestate.setActiveTaskBenoetigtAktiv(null);
|
||||
checkLevel();
|
||||
lockGameRepository.save(gamestate);
|
||||
pickNextTask();
|
||||
@@ -55,6 +77,7 @@ public class LockGameService {
|
||||
public void abandonActiveTask() throws JsonProcessingException {
|
||||
gamestate.setActiveTask(null);
|
||||
gamestate.setActiveTaskEnd(null);
|
||||
gamestate.setActiveTaskBenoetigtAktiv(null);
|
||||
lockGameRepository.save(gamestate);
|
||||
pickNextTask();
|
||||
}
|
||||
@@ -136,6 +159,9 @@ public class LockGameService {
|
||||
var aufgabe = mapper.readValue(gamestate.getTaskInQueue(), Aufgabe.class);
|
||||
gamestate.setActiveTask(aufgabe.getText());
|
||||
gamestate.setTaskInQueue(null);
|
||||
var benoetigtAktiv = aufgabe.getBenoetigtAktiv();
|
||||
gamestate.setActiveTaskBenoetigtAktiv(
|
||||
(benoetigtAktiv != null && !benoetigtAktiv.isEmpty()) ? mapper.writeValueAsString(benoetigtAktiv) : null);
|
||||
var time = getAufgabeTime(aufgabe);
|
||||
gamestate.setActiveTaskEnd(time > 0 ? LocalDateTime.now().plusSeconds(time) : null);
|
||||
LOGGER.info("[LockGame {}] AUFGABE aktiv: kurzText='{}', berechnete Zeit={}s (Range: {}s-{}s)",
|
||||
@@ -147,6 +173,7 @@ public class LockGameService {
|
||||
var lock = mapper.readValue(gamestate.getLockInQueue(), Sperre.class);
|
||||
String displayText = lock.getText() != null ? lock.getText() : lock.getKurzText();
|
||||
gamestate.setActiveTask(displayText != null ? displayText : "Zeitstrafe aktiv");
|
||||
gamestate.setActiveTaskBenoetigtAktiv(null);
|
||||
gamestate.setLockInQueue(null);
|
||||
applyLock(lock);
|
||||
}
|
||||
@@ -168,11 +195,14 @@ public class LockGameService {
|
||||
return (int) (time * gamestate.getZeitfaktorZeitstrafen());
|
||||
}
|
||||
|
||||
protected void checkLevel() {
|
||||
protected void checkLevel() throws JsonProcessingException {
|
||||
var aufgabenAufAktuellemLevel = gamestate.getAufgabenAufAktuellemLevel();
|
||||
if (++aufgabenAufAktuellemLevel >= 1 + gamestate.getAufgabenProLevel()) {
|
||||
aufgabenAufAktuellemLevel = 0;
|
||||
gamestate.setLevel(gamestate.getLevel() + 1);
|
||||
if (gamestate.getLevel() >= 6) {
|
||||
initFinisher();
|
||||
}
|
||||
}
|
||||
gamestate.setAufgabenAufAktuellemLevel(aufgabenAufAktuellemLevel);
|
||||
}
|
||||
@@ -190,6 +220,7 @@ public class LockGameService {
|
||||
entity.setGameId(gamestate.getGameId());
|
||||
entity.setLockFor(lock.getSperreFuer());
|
||||
entity.setReleaseText(lock.getReleaseText());
|
||||
entity.setTempUnlockRequired(lock.getTempUnlockRequired());
|
||||
int lockMinutes = getLockTime(lock);
|
||||
entity.setReleaseTime(LocalDateTime.now().plusMinutes(lockMinutes));
|
||||
LOGGER.info("[LockGame {}] ZEITSTRAFE aktiv: kurzText='{}', berechnete Zeit={}min (Range: {}min-{}min), sperreFuer={}",
|
||||
@@ -201,9 +232,13 @@ public class LockGameService {
|
||||
public List<String> checkLocks() {
|
||||
var result = new ArrayList<String>();
|
||||
for (LockGameLockEntity entity : lockGameLockRepository.findByGameId(gamestate.getGameId())) {
|
||||
if (entity.getReleaseTime().isAfter(LocalDateTime.now())) {
|
||||
if (entity.getReleaseTime().isBefore(LocalDateTime.now())) {
|
||||
result.add(entity.getReleaseText());
|
||||
if (Boolean.TRUE.equals(entity.getTempUnlockRequired())) {
|
||||
startTempOpening();
|
||||
}
|
||||
lockGameLockRepository.delete(entity);
|
||||
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -218,7 +253,77 @@ public class LockGameService {
|
||||
return result;
|
||||
}
|
||||
|
||||
public Finisher getFinisher() {
|
||||
return aufgabenList.getFinisher().get(new Random().nextInt(aufgabenList.getFinisher().size()));
|
||||
public void initFinisher() throws JsonProcessingException {
|
||||
var finisher = aufgabenList.getFinisher().get(new Random().nextInt(aufgabenList.getFinisher().size()));
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
gamestate.setFinisher(mapper.writeValueAsString(finisher));
|
||||
gamestate.setActiveTask(null);
|
||||
gamestate.setActiveTaskBenoetigtAktiv(null);
|
||||
lockGameLockRepository.deleteAll(lockGameLockRepository.findByGameId(gamestate.getGameId()));
|
||||
lockGameRepository.save(gamestate);
|
||||
}
|
||||
|
||||
public void startFinisher() {
|
||||
gamestate.setFinisherStartedAt(LocalDateTime.now());
|
||||
lockGameRepository.save(gamestate);
|
||||
}
|
||||
|
||||
public void endFinisher() {
|
||||
var seconds = Duration.between(gamestate.getFinisherStartedAt(), LocalDateTime.now()).toSeconds();
|
||||
addOvertime(seconds);
|
||||
}
|
||||
|
||||
private void addOvertime(Long seconds) {
|
||||
var currentOvertime = gamestate.getOvertimeInSeconds() != null ? gamestate.getOvertimeInSeconds() : 0;
|
||||
gamestate.setOvertimeInSeconds(currentOvertime + seconds.intValue());
|
||||
lockGameRepository.save(gamestate);
|
||||
}
|
||||
|
||||
public void startTempOpening() {
|
||||
startTempOpening(5);
|
||||
}
|
||||
|
||||
private void startTempOpening(Integer duration) {
|
||||
assert duration != null;
|
||||
var lock = baseLockRepository.findById(gamestate.getLockId()).get();
|
||||
gamestate.setTempOpeningTime(LocalDateTime.now());
|
||||
gamestate.setTempOpeningDurationInMinutes(duration);
|
||||
gamestate.setTempUnlockCode(lock.getUnlockCode());
|
||||
lockGameRepository.save(gamestate);
|
||||
unlockCodeHistoryService.save(lock.getLockee(), lock.getLockId(), lock.getName(), lock.getUnlockCode(), TempOpeningReason.TASK.toString());
|
||||
}
|
||||
|
||||
public void endTempOpening() {
|
||||
var lock = baseLockRepository.findById(gamestate.getLockId()).get();
|
||||
var overtime = BaseLockHelper.calcOvertime(lock);
|
||||
if (overtime != null) {
|
||||
addOvertime(overtime);
|
||||
}
|
||||
|
||||
gamestate.setTempOpeningTime(null);
|
||||
gamestate.setTempOpeningDurationInMinutes(null);
|
||||
gamestate.setTempUnlockCode(null);
|
||||
lockGameRepository.save(gamestate);
|
||||
|
||||
if (lock.getControllType() != null) {
|
||||
var lockControl = lockControlFactory.create(lock.getControllType(), this, lock.getLockee());
|
||||
if (lockControl != null
|
||||
&& lock.getControllType() != de.oaa.xxx.games.chastity.lockcontroll.LockControllType.UNLOCK_CODE) {
|
||||
lockControl.lock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUnlockCode(String code) {
|
||||
var lock = baseLockRepository.findById(gamestate.getLockId()).get();
|
||||
lock.setUnlockCode(code);
|
||||
baseLockRepository.save(lock);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUnlockcodeLenght() {
|
||||
var lock = baseLockRepository.findById(gamestate.getLockId()).get();
|
||||
return lock.getUnlockCodeLength();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@ public class KeyholderOfferController {
|
||||
|
||||
if (template instanceof TimeLockTemplateEntity tl) {
|
||||
TimeLockAdditionalSettings settings = new TimeLockAdditionalSettings(
|
||||
controllType, myId, keyholderIdIfDirect, false, codeLen);
|
||||
controllType, myId, keyholderIdIfDirect, false, codeLen, 1);
|
||||
TimeLockEntity lock = new TimeLockEntity();
|
||||
timeLockServiceFactory.create(lock).init(tl, settings);
|
||||
timeLockRepository.save(lock);
|
||||
@@ -283,11 +283,11 @@ public class KeyholderOfferController {
|
||||
lock.setLockee(myId);
|
||||
lock.setKeyholder(keyholderIdIfDirect);
|
||||
lock.setInitialCards(cards);
|
||||
lock.setPickEveryMinute(cl.getPickEveryMinute() != null ? cl.getPickEveryMinute() : 60);
|
||||
lock.setPickEverySeconds((cl.getPickEveryMinute() != null ? cl.getPickEveryMinute() : 60) * 60);
|
||||
lock.setAccumulatePicks(cl.isAccumulatePicks());
|
||||
lock.setShowRemainingCards(cl.isShowRemainingCards());
|
||||
lock.setHygineOpeningDurationMinutes(cl.getHygineOpeningDurationMinutes());
|
||||
lock.setHygineOpeningEveryMinites(cl.getHygineOpeningEveryMinites());
|
||||
lock.setHygineOpeningDurationSeconds(cl.getHygineOpeningDurationMinutes() != null ? cl.getHygineOpeningDurationMinutes() * 60 : null);
|
||||
lock.setHygineOpeningEverySeconds(cl.getHygineOpeningEveryMinites() != null ? cl.getHygineOpeningEveryMinites() * 60 : null);
|
||||
lock.setTasks(cl.getTasks() != null ? cl.getTasks() : List.of());
|
||||
lock.setRequiresVerification(cl.isRequiresVerification());
|
||||
lock.setTestLock(false);
|
||||
@@ -299,7 +299,7 @@ public class KeyholderOfferController {
|
||||
lock.setStartTime(now);
|
||||
lock.setAvailableCards(new ArrayList<>(cards));
|
||||
lock.setOpenPicks(0);
|
||||
lock.setNextCardIn(now.plusMinutes(lock.getPickEveryMinute()));
|
||||
lock.setNextCardIn(now.plusSeconds(lock.getPickEverySeconds()));
|
||||
if (cl.getHygineOpeningEveryMinites() != null) {
|
||||
lock.setLastHygineOpening(now);
|
||||
}
|
||||
|
||||
@@ -198,11 +198,11 @@ public class LockeeInvitationController {
|
||||
.collect(java.util.stream.Collectors.groupingBy(
|
||||
c -> c.name(), java.util.stream.Collectors.counting()));
|
||||
result.put("cardCounts", cardCounts);
|
||||
result.put("pickEveryMinute", cardLock.getPickEveryMinute());
|
||||
result.put("pickEveryMinute", cardLock.getPickEverySeconds() != null ? cardLock.getPickEverySeconds() / 60 : null);
|
||||
result.put("accumulatePicks", cardLock.isAccumulatePicks());
|
||||
result.put("showRemainingCards", cardLock.isShowRemainingCards());
|
||||
result.put("hygineOpeningEveryMinites", cardLock.getHygineOpeningEveryMinites());
|
||||
result.put("hygineOpeningDurationMinutes", cardLock.getHygineOpeningDurationMinutes());
|
||||
result.put("hygineOpeningEveryMinites", cardLock.getHygineOpeningEverySeconds() != null ? cardLock.getHygineOpeningEverySeconds() / 60 : null);
|
||||
result.put("hygineOpeningDurationMinutes", cardLock.getHygineOpeningDurationSeconds() != null ? cardLock.getHygineOpeningDurationSeconds() / 60 : null);
|
||||
result.put("requiresVerification", cardLock.isRequiresVerification());
|
||||
result.put("taskCount", cardLock.getTasks() != null ? cardLock.getTasks().size() : 0);
|
||||
}
|
||||
@@ -246,8 +246,8 @@ public class LockeeInvitationController {
|
||||
cardLock.setUnlockCodeLength(codeLines);
|
||||
cardLock.setAvailableCards(new ArrayList<>(cardLock.getInitialCards()));
|
||||
cardLock.setOpenPicks(0);
|
||||
cardLock.setNextCardIn(now.plusMinutes(cardLock.getPickEveryMinute()));
|
||||
if (cardLock.getHygineOpeningEveryMinites() != null) {
|
||||
cardLock.setNextCardIn(now.plusSeconds(cardLock.getPickEverySeconds()));
|
||||
if (cardLock.getHygineOpeningEverySeconds() != null) {
|
||||
cardLock.setLastHygineOpening(now);
|
||||
}
|
||||
cardlockRepository.save(cardLock);
|
||||
@@ -258,7 +258,7 @@ public class LockeeInvitationController {
|
||||
timeLock.setEstimatedUnlockTime(now.plusMinutes(unlockMinutes));
|
||||
timeLock.setUnlockCode(unlockCode);
|
||||
timeLock.setUnlockCodeLength(codeLines);
|
||||
if (timeLock.getHygineOpeningEveryMinites() != null) {
|
||||
if (timeLock.getHygineOpeningEverySeconds() != null) {
|
||||
timeLock.setLastHygineOpening(now);
|
||||
}
|
||||
timeLockRepository.save(timeLock);
|
||||
|
||||
@@ -4,6 +4,6 @@ import java.util.UUID;
|
||||
|
||||
import de.oaa.xxx.games.chastity.lockcontroll.LockControllType;
|
||||
|
||||
public record TimeLockAdditionalSettings(LockControllType controllType, UUID lockee, UUID keyholder, boolean testlock, Integer unlockCodeLength) {
|
||||
public record TimeLockAdditionalSettings(LockControllType controllType, UUID lockee, UUID keyholder, boolean testlock, Integer unlockCodeLength, Integer speedFactor) {
|
||||
|
||||
}
|
||||
|
||||
@@ -100,7 +100,8 @@ public class TimeLockController {
|
||||
UUID keyholder,
|
||||
boolean testLock,
|
||||
Integer unlockCodeLength,
|
||||
LockControllType controllType
|
||||
LockControllType controllType,
|
||||
Integer speedFactor
|
||||
) {}
|
||||
|
||||
@PostMapping("/timelock")
|
||||
@@ -155,9 +156,10 @@ public class TimeLockController {
|
||||
return ResponseEntity.status(403).body(Map.of("error", "subscription_required"));
|
||||
}
|
||||
|
||||
int speedFactor = (req.testLock() && req.speedFactor() != null && req.speedFactor() > 1) ? req.speedFactor() : 1;
|
||||
TimeLockAdditionalSettings settings = new TimeLockAdditionalSettings(
|
||||
req.controllType() != null ? req.controllType() : LockControllType.UNLOCK_CODE,
|
||||
myId, req.keyholder(), req.testLock(), codeLen);
|
||||
myId, req.keyholder(), req.testLock(), codeLen, speedFactor);
|
||||
TimeLockEntity lock = new TimeLockEntity();
|
||||
timeLockServiceFactory.create(lock).init(template, settings);
|
||||
timeLockRepository.save(lock); // Sicherstellen dass auch TRUST-Locks persistiert sind
|
||||
@@ -234,7 +236,7 @@ public class TimeLockController {
|
||||
&& (l.getFrozenUntil() == null || l.getFrozenUntil().isAfter(now));
|
||||
|
||||
// Hygiene state
|
||||
boolean hygieneEnabled = l.getHygineOpeningEveryMinites() != null;
|
||||
boolean hygieneEnabled = l.getHygineOpeningEverySeconds() != null;
|
||||
boolean hygieneOpeningDue = false;
|
||||
long hygieneSecondsRemaining = 0;
|
||||
boolean hygieneOpeningActive = l.getTempOpeningTime() != null
|
||||
@@ -244,7 +246,9 @@ public class TimeLockController {
|
||||
if (lastH == null) {
|
||||
hygieneOpeningDue = true;
|
||||
} else {
|
||||
LocalDateTime nextH = lastH.plusMinutes(l.getHygineOpeningEveryMinites());
|
||||
int tlSf = (l.getSpeedFactor() != null && l.getSpeedFactor() > 1) ? l.getSpeedFactor() : 1;
|
||||
long hygineSeconds = tlSf > 1 ? Math.max(6L, l.getHygineOpeningEverySeconds() / tlSf) : l.getHygineOpeningEverySeconds();
|
||||
LocalDateTime nextH = lastH.plusSeconds(hygineSeconds);
|
||||
long secs = ChronoUnit.SECONDS.between(now, nextH);
|
||||
if (secs <= 0) hygieneOpeningDue = true;
|
||||
else hygieneSecondsRemaining = secs;
|
||||
@@ -252,7 +256,7 @@ public class TimeLockController {
|
||||
}
|
||||
|
||||
// Spin wheel state
|
||||
boolean spinEnabled = l.getSpinsEveryMinutes() != null
|
||||
boolean spinEnabled = l.getSpinsEverySeconds() != null
|
||||
&& l.getSpinningWheelEntries() != null && !l.getSpinningWheelEntries().isEmpty();
|
||||
boolean spinDue = false;
|
||||
String nextSpinIn = null;
|
||||
@@ -261,23 +265,23 @@ public class TimeLockController {
|
||||
if (times == null || times.isEmpty()) {
|
||||
spinDue = true;
|
||||
} else {
|
||||
LocalDateTime next = times.get(times.size() - 1).plusMinutes(l.getSpinsEveryMinutes());
|
||||
LocalDateTime next = times.get(times.size() - 1).plusSeconds(l.getSpinsEverySeconds());
|
||||
if (next.isBefore(now)) spinDue = true;
|
||||
else nextSpinIn = next.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// Task timing state
|
||||
boolean taskTimingEnabled = l.getTaskEveryMinutes() != null;
|
||||
boolean taskTimingEnabled = l.getTaskEverySeconds() != null;
|
||||
String nextTaskIn = null;
|
||||
if (taskTimingEnabled && l.getCurrentTask() == null) {
|
||||
List<LocalDateTime> times = l.getTaskTimes();
|
||||
LocalDateTime next;
|
||||
if (times == null || times.isEmpty()) {
|
||||
next = l.getStartTime() != null
|
||||
? l.getStartTime().plusMinutes(l.getTaskEveryMinutes()) : null;
|
||||
? l.getStartTime().plusSeconds(l.getTaskEverySeconds()) : null;
|
||||
} else {
|
||||
next = times.get(times.size() - 1).plusMinutes(l.getTaskEveryMinutes());
|
||||
next = times.get(times.size() - 1).plusSeconds(l.getTaskEverySeconds());
|
||||
}
|
||||
if (next != null && next.isAfter(now)) nextTaskIn = next.toString();
|
||||
}
|
||||
@@ -347,7 +351,7 @@ public class TimeLockController {
|
||||
result.put("hygieneSecondsRemaining", hygieneSecondsRemaining);
|
||||
result.put("hygieneOpeningActive", hygieneOpeningActive);
|
||||
result.put("hygieneOpeningStarted", l.getTempOpeningTime() != null ? l.getTempOpeningTime().toString() : null);
|
||||
result.put("hygieneDurationMinutes", l.getHygineOpeningDurationMinutes() != null ? l.getHygineOpeningDurationMinutes() : 0);
|
||||
result.put("hygieneDurationMinutes", l.getHygineOpeningDurationSeconds() != null ? l.getHygineOpeningDurationSeconds() / 60 : 0);
|
||||
|
||||
result.put("verificationRequired", l.isRequiresVerification());
|
||||
result.put("verificationDue", verificationDue);
|
||||
@@ -402,8 +406,8 @@ public class TimeLockController {
|
||||
|
||||
// Check spin is due
|
||||
List<LocalDateTime> spinTimes = l.getSpinningWheelTimes();
|
||||
if (spinTimes != null && !spinTimes.isEmpty() && l.getSpinsEveryMinutes() != null) {
|
||||
LocalDateTime next = spinTimes.get(spinTimes.size() - 1).plusMinutes(l.getSpinsEveryMinutes());
|
||||
if (spinTimes != null && !spinTimes.isEmpty() && l.getSpinsEverySeconds() != null) {
|
||||
LocalDateTime next = spinTimes.get(spinTimes.size() - 1).plusSeconds(l.getSpinsEverySeconds());
|
||||
if (next.isAfter(now)) return ResponseEntity.status(409).body(Map.of("error", "not_due"));
|
||||
}
|
||||
|
||||
@@ -477,7 +481,7 @@ public class TimeLockController {
|
||||
if (lockOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
var l = lockOpt.get();
|
||||
if (!l.getLockee().equals(myId)) return ResponseEntity.status(403).build();
|
||||
if (l.getHygineOpeningEveryMinites() == null) return ResponseEntity.status(409).build();
|
||||
if (l.getHygineOpeningEverySeconds() == null) return ResponseEntity.status(409).build();
|
||||
if (l.getTempOpeningTime() != null) return ResponseEntity.status(409).body(Map.of("error", "already_open"));
|
||||
|
||||
TimeLockService service = timeLockServiceFactory.create(l);
|
||||
@@ -485,7 +489,7 @@ public class TimeLockController {
|
||||
|
||||
return ResponseEntity.ok(Map.of(
|
||||
"unlockCode", l.getUnlockCode() != null ? l.getUnlockCode() : "",
|
||||
"durationMinutes", l.getHygineOpeningDurationMinutes() != null ? l.getHygineOpeningDurationMinutes() : 0,
|
||||
"durationMinutes", l.getHygineOpeningDurationSeconds() != null ? l.getHygineOpeningDurationSeconds() / 60 : 0,
|
||||
"openedAt", l.getTempOpeningTime() != null ? l.getTempOpeningTime().toString() : ""));
|
||||
}
|
||||
|
||||
@@ -840,13 +844,13 @@ public class TimeLockController {
|
||||
lock.setEndTimeVisible(template.isEndTimeVisible());
|
||||
lock.setTasks(template.getTasks());
|
||||
lock.setTaskMode(template.getTaskCardMode());
|
||||
lock.setTaskEveryMinutes(template.getTaskEveryMinutes());
|
||||
lock.setTaskEverySeconds(template.getTaskEveryMinutes() != null ? template.getTaskEveryMinutes() * 60 : null);
|
||||
lock.setMinTasksPerDay(template.getMinTasksPerDay());
|
||||
lock.setSpinningWheelEntries(template.getSpinningWheelEntries());
|
||||
lock.setSpinsEveryMinutes(template.getSpinsEveryMinutes());
|
||||
lock.setSpinsEverySeconds(template.getSpinsEveryMinutes() != null ? template.getSpinsEveryMinutes() * 60 : null);
|
||||
lock.setMinSpinsPerDay(template.getMinSpinsPerDay());
|
||||
lock.setHygineOpeningDurationMinutes(template.getHygineOpeningDurationMinutes());
|
||||
lock.setHygineOpeningEveryMinites(template.getHygineOpeningEveryMinites());
|
||||
lock.setHygineOpeningDurationSeconds(template.getHygineOpeningDurationMinutes() != null ? template.getHygineOpeningDurationMinutes() * 60 : null);
|
||||
lock.setHygineOpeningEverySeconds(template.getHygineOpeningEveryMinites() != null ? template.getHygineOpeningEveryMinites() * 60 : null);
|
||||
lock.setPenaltyType(template.getPenaltyType());
|
||||
lock.setPenaltyValue(template.getPenaltyValue());
|
||||
lock.setMinTimeInMinutes(template.getMinTimeInMinutes());
|
||||
|
||||
@@ -29,7 +29,7 @@ public class TimeLockEntity extends BaseLockEntity {
|
||||
private Integer maxTimeInMinutes;
|
||||
|
||||
@Column
|
||||
private Integer taskEveryMinutes;
|
||||
private Integer taskEverySeconds;
|
||||
@Column
|
||||
private Integer minTasksPerDay;
|
||||
|
||||
@@ -37,7 +37,7 @@ public class TimeLockEntity extends BaseLockEntity {
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private List<SpinningWheelEntry> spinningWheelEntries;
|
||||
@Column
|
||||
private Integer spinsEveryMinutes;
|
||||
private Integer spinsEverySeconds;
|
||||
@Column
|
||||
private Integer minSpinsPerDay;
|
||||
|
||||
|
||||
@@ -132,24 +132,26 @@ public class TimeLockService extends BaseLockService implements LockControlCallb
|
||||
lock.setTestLock(settings.testlock());
|
||||
lock.setUnlockCodeLength(settings.unlockCodeLength() != null ? settings.unlockCodeLength() : 5);
|
||||
|
||||
int sf = (settings.speedFactor() != null && settings.speedFactor() > 1) ? settings.speedFactor() : 1;
|
||||
lock.setSpeedFactor(sf > 1 ? sf : null);
|
||||
Integer minMinutes = template.getMinTimeInMinutes();
|
||||
Integer maxMinutes = template.getMaxTimeInMinutes() != null ? template.getMaxTimeInMinutes() : 60;
|
||||
int unlockTimeMinutes = (minMinutes != null && minMinutes < maxMinutes)
|
||||
? minMinutes + new Random().nextInt(maxMinutes - minMinutes)
|
||||
: maxMinutes;
|
||||
lock.setEstimatedUnlockTime(now.plusMinutes(unlockTimeMinutes));
|
||||
lock.setEstimatedUnlockTime(now.plusSeconds(Math.max(6L, unlockTimeMinutes * 60L / sf)));
|
||||
lock.setEndTimeVisible(template.isEndTimeVisible());
|
||||
|
||||
lock.setTasks(template.getTasks());
|
||||
lock.setTaskEveryMinutes(template.getTaskEveryMinutes());
|
||||
lock.setTaskEverySeconds(template.getTaskEveryMinutes() != null ? Math.max(6, template.getTaskEveryMinutes() * 60 / sf) : null);
|
||||
lock.setMinTasksPerDay(template.getMinTasksPerDay());
|
||||
|
||||
lock.setSpinningWheelEntries(template.getSpinningWheelEntries());
|
||||
lock.setSpinsEveryMinutes(template.getSpinsEveryMinutes());
|
||||
lock.setSpinsEverySeconds(template.getSpinsEveryMinutes() != null ? Math.max(6, template.getSpinsEveryMinutes() * 60 / sf) : null);
|
||||
lock.setMinSpinsPerDay(template.getMinSpinsPerDay());
|
||||
|
||||
lock.setHygineOpeningDurationMinutes(template.getHygineOpeningDurationMinutes());
|
||||
lock.setHygineOpeningEveryMinites(template.getHygineOpeningEveryMinites());
|
||||
lock.setHygineOpeningDurationSeconds(template.getHygineOpeningDurationMinutes() != null ? template.getHygineOpeningDurationMinutes() * 60 : null);
|
||||
lock.setHygineOpeningEverySeconds(template.getHygineOpeningEveryMinites() != null ? template.getHygineOpeningEveryMinites() * 60 : null);
|
||||
if (template.getHygineOpeningEveryMinites() != null) {
|
||||
lock.setLastHygineOpening(now);
|
||||
}
|
||||
@@ -360,7 +362,7 @@ public class TimeLockService extends BaseLockService implements LockControlCallb
|
||||
// ── Hygiene opening ───────────────────────────────────────────────────────
|
||||
|
||||
public void startHygieneOpening() {
|
||||
startTempOpening(TempOpeningReason.HYGIENE, lock.getHygineOpeningDurationMinutes());
|
||||
startTempOpening(TempOpeningReason.HYGIENE, lock.getHygineOpeningDurationSeconds() != null ? lock.getHygineOpeningDurationSeconds() / 60 : 0);
|
||||
}
|
||||
|
||||
// ── LockControlCallback ───────────────────────────────────────────────────
|
||||
@@ -377,13 +379,13 @@ public class TimeLockService extends BaseLockService implements LockControlCallb
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleLockGameFinished(int timeInMinutes) {
|
||||
int freezeTime = (int) (timeInMinutes * new Random().nextDouble(1.0, 4.0));
|
||||
addTime(freezeTime);
|
||||
protected void handleLockGameFinished(int timeInSeconds) {
|
||||
int freezeMinutes = (int) (timeInSeconds / 60.0 * new Random().nextDouble(1.0, 4.0));
|
||||
addTime(Math.max(1, freezeMinutes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void penaltyLockGame() {
|
||||
handleLockGameFinished(60);
|
||||
handleLockGameFinished(3600);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package de.oaa.xxx.games.chastity.unlock;
|
||||
|
||||
public enum TempOpeningReason {
|
||||
HYGIENE, CARD, TASK, TTLOCK_UNAUTHORIZED;
|
||||
HYGIENE, CARD, TASK, TTLOCK_UNAUTHORIZED, TEMPORARY;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ public class Finisher {
|
||||
private List<Werkzeug> benoetigtPassiv;
|
||||
private List<Toy> benoetigteToys;
|
||||
private UUID gruppeId;
|
||||
private Boolean tempUnlockRequired;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
@@ -20,8 +20,7 @@ public class Sperre {
|
||||
private Integer minutenVon;
|
||||
private Integer minutenBis;
|
||||
private Integer level;
|
||||
private Boolean tempUnlockBeforeRequired;
|
||||
private Boolean tempUnlockAfterRequired;
|
||||
private Boolean tempUnlockRequired;
|
||||
private List<Toy> benoetigteToys;
|
||||
|
||||
@Override
|
||||
|
||||
@@ -55,6 +55,8 @@ public class FinisherEntity {
|
||||
@ManyToMany(cascade = CascadeType.DETACH)
|
||||
@JoinTable(name = "finisherToy", joinColumns = {@JoinColumn(name = "finisherId")}, inverseJoinColumns = {@JoinColumn(name = "toyId")})
|
||||
private List<ToyEntity> benoetigteToys;
|
||||
@Column
|
||||
private Boolean tempUnlockRequired;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
@@ -71,6 +73,7 @@ public class FinisherEntity {
|
||||
finisher.setBenoetigtPassiv(benoetigtPassiv != null ? new ArrayList<>(benoetigtPassiv) : new ArrayList<>());
|
||||
finisher.setBenoetigteToys(benoetigteToys != null ? benoetigteToys.stream().map(ToyEntity::toToy).toList() : new ArrayList<>());
|
||||
finisher.setGruppeId(aufgabenGruppe.getGruppenId());
|
||||
finisher.setTempUnlockRequired(tempUnlockRequired);
|
||||
return finisher;
|
||||
}
|
||||
|
||||
@@ -84,6 +87,7 @@ public class FinisherEntity {
|
||||
entity.setBenoetigtAktiv(finisher.getBenoetigtAktiv());
|
||||
entity.setBenoetigtPassiv(finisher.getBenoetigtPassiv());
|
||||
entity.setBenoetigteToys(toys != null ? toys : new ArrayList<>());
|
||||
entity.setTempUnlockRequired(finisher.getTempUnlockRequired());
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,9 +53,7 @@ public class SperreEntity {
|
||||
@Column
|
||||
private Integer level;
|
||||
@Column
|
||||
private Boolean tempUnlockBeforeRequired;
|
||||
@Column
|
||||
private Boolean tempUnlockAfterRequired;
|
||||
private Boolean tempUnlockRequired;
|
||||
@ManyToMany(cascade = CascadeType.DETACH)
|
||||
@JoinTable(name = "sperreToy", joinColumns = {@JoinColumn(name = "sperreId")}, inverseJoinColumns = {@JoinColumn(name = "toyId")})
|
||||
private List<ToyEntity> benoetigteToys;
|
||||
@@ -74,8 +72,7 @@ public class SperreEntity {
|
||||
sperre.setMinutenBis(minutenBis);
|
||||
sperre.setMinutenVon(minutenVon);
|
||||
sperre.setLevel(level);
|
||||
sperre.setTempUnlockBeforeRequired(tempUnlockBeforeRequired);
|
||||
sperre.setTempUnlockAfterRequired(tempUnlockAfterRequired);
|
||||
sperre.setTempUnlockRequired(tempUnlockRequired);
|
||||
sperre.setReleaseText(releaseText);
|
||||
sperre.setSperreFuer(sperreFuer != null ? new ArrayList<>(sperreFuer) : new ArrayList<>());
|
||||
sperre.setText(text);
|
||||
@@ -92,8 +89,7 @@ public class SperreEntity {
|
||||
entity.setMinutenBis(sperre.getMinutenBis());
|
||||
entity.setMinutenVon(sperre.getMinutenVon());
|
||||
entity.setLevel(sperre.getLevel());
|
||||
entity.setTempUnlockBeforeRequired(sperre.getTempUnlockBeforeRequired());
|
||||
entity.setTempUnlockAfterRequired(sperre.getTempUnlockAfterRequired());
|
||||
entity.setTempUnlockRequired(sperre.getTempUnlockRequired());
|
||||
entity.setReleaseText(sperre.getReleaseText());
|
||||
entity.setSperreFuer(sperre.getSperreFuer());
|
||||
entity.setText(sperre.getText());
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE lock_game
|
||||
MODIFY COLUMN aufgaben TEXT NULL;
|
||||
@@ -263,6 +263,12 @@
|
||||
cursor: pointer; transition: border-color 0.15s, color 0.15s;
|
||||
}
|
||||
.btn-item-edit:hover { border-color: var(--color-text); color: var(--color-text); }
|
||||
.btn-item-copy {
|
||||
background: none; border: 1px solid rgba(100,160,255,0.4); border-radius: 5px;
|
||||
color: var(--color-muted); font-size: 0.75rem; padding: 0.2rem 0.6rem;
|
||||
cursor: pointer; transition: border-color 0.15s, color 0.15s;
|
||||
}
|
||||
.btn-item-copy:hover { border-color: rgba(100,160,255,0.9); color: var(--color-text); }
|
||||
.btn-item-delete {
|
||||
background: none; border: 1px solid rgba(233,69,96,0.4); border-radius: 5px;
|
||||
color: var(--color-primary); font-size: 0.75rem; padding: 0.2rem 0.6rem;
|
||||
@@ -533,17 +539,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="iTempUnlockRow">
|
||||
<label>Temporäre Öffnungen</label>
|
||||
<div style="display:flex; flex-direction:column; gap:0.5rem; margin-top:0.5rem;">
|
||||
<label style="display:flex; align-items:center; gap:0.6rem; font-size:0.85rem; cursor:pointer;">
|
||||
<input type="checkbox" id="iTempUnlockBefore" style="accent-color:var(--color-primary); width:1rem; height:1rem;">
|
||||
Temporäre Öffnung <em>vor</em> der Zeitstrafe erforderlich
|
||||
<label class="toggle-switch" style="display:flex; align-items:center; gap:0.75rem; cursor:pointer; margin-top:0.25rem;">
|
||||
<input type="checkbox" id="iTempUnlockRequired">
|
||||
<span class="toggle-track"></span>
|
||||
<span style="font-size:0.9rem;">Temporäre Öffnung erforderlich</span>
|
||||
</label>
|
||||
<label style="display:flex; align-items:center; gap:0.6rem; font-size:0.85rem; cursor:pointer;">
|
||||
<input type="checkbox" id="iTempUnlockAfter" style="accent-color:var(--color-primary); width:1rem; height:1rem;">
|
||||
Temporäre Öffnung <em>nach</em> der Zeitstrafe erforderlich
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="iReleaseTextRow">
|
||||
<label for="iReleaseText">Text bei Aufhebung</label>
|
||||
@@ -765,7 +765,7 @@
|
||||
${g.beschreibung ? `<div class="gruppe-desc">${esc(g.beschreibung)}</div>` : ''}
|
||||
${renderSubSection('Aufgaben', sortByLevelThenName(g.aufgaben || []), 'aufgabe', renderAufgabe, g.gruppenId, type)}
|
||||
${g.availableIn !== 'CHASTITY_ONLY' ? renderSubSection('Strafen', sortByLevelThenName(g.strafen || []), 'strafe', renderStrafe, g.gruppenId, type) : ''}
|
||||
${renderSubSection('Zeitstrafen',sortByName(g.sperren || []), 'zeitstrafe',renderZeitstrafe, g.gruppenId, type)}
|
||||
${renderSubSection('Zeitstrafen',sortByLevelThenName(g.sperren || []), 'zeitstrafe',renderZeitstrafe, g.gruppenId, type)}
|
||||
${renderSubSection('Finisher', sortByGeschlecht(g.finisher || []), 'finisher', renderFinisher, g.gruppenId, type)}
|
||||
</div>
|
||||
</div>`;
|
||||
@@ -814,6 +814,7 @@
|
||||
function renderAufgabe(a, type, gruppenId) {
|
||||
_itemData[a.aufgabeId] = { ...a, _kind: 'aufgabe', _gruppenId: gruppenId };
|
||||
const badges = [];
|
||||
(a.benoetigteToys || []).forEach(t => badges.push(`<span class="item-detail-chip item-detail-chip-toy">${esc(t.name || t)}</span>`));
|
||||
const zeit = formatSek(a.sekundenVon, a.sekundenBis);
|
||||
if (zeit) badges.push(`<span class="badge badge-neutral">${esc(zeit)}</span>`);
|
||||
if (a.level != null) badges.push(`<span class="badge">Level ${esc(String(a.level))}</span>`);
|
||||
@@ -826,6 +827,7 @@
|
||||
const actionBtns = type === 'user' ? `
|
||||
<div class="item-action-btns">
|
||||
<button class="btn-item-edit" onclick="openEditItemModal('${esc(a.aufgabeId)}',event)">✎ Bearbeiten</button>
|
||||
<button class="btn-item-copy" onclick="duplicateItem('aufgabe','${esc(a.aufgabeId)}','${esc(gruppenId)}',event)">⧉ Duplizieren</button>
|
||||
<button class="btn-item-delete" onclick="deleteItem('aufgabe','${esc(a.aufgabeId)}','${esc(gruppenId)}',event)">✕ Löschen</button>
|
||||
</div>` : '';
|
||||
|
||||
@@ -841,6 +843,7 @@
|
||||
function renderStrafe(s, type, gruppenId) {
|
||||
_itemData[s.strafeId] = { ...s, _kind: 'strafe', _gruppenId: gruppenId };
|
||||
const badges = [];
|
||||
(s.benoetigteToys || []).forEach(t => badges.push(`<span class="item-detail-chip item-detail-chip-toy">${esc(t.name || t)}</span>`));
|
||||
const zeit = formatSek(s.sekundenVon, s.sekundenBis);
|
||||
if (zeit) badges.push(`<span class="badge badge-neutral">${esc(zeit)}</span>`);
|
||||
if (s.level != null) badges.push(`<span class="badge">Level ${esc(String(s.level))}</span>`);
|
||||
@@ -853,6 +856,7 @@
|
||||
const actionBtns = type === 'user' ? `
|
||||
<div class="item-action-btns">
|
||||
<button class="btn-item-edit" onclick="openEditItemModal('${esc(s.strafeId)}',event)">✎ Bearbeiten</button>
|
||||
<button class="btn-item-copy" onclick="duplicateItem('strafe','${esc(s.strafeId)}','${esc(gruppenId)}',event)">⧉ Duplizieren</button>
|
||||
<button class="btn-item-delete" onclick="deleteItem('strafe','${esc(s.strafeId)}','${esc(gruppenId)}',event)">✕ Löschen</button>
|
||||
</div>` : '';
|
||||
|
||||
@@ -868,8 +872,10 @@
|
||||
function renderZeitstrafe(z, type, gruppenId) {
|
||||
_itemData[z.sperreId] = { ...z, _kind: 'zeitstrafe', _gruppenId: gruppenId };
|
||||
const badges = [];
|
||||
(z.benoetigteToys || []).forEach(t => badges.push(`<span class="item-detail-chip item-detail-chip-toy">${esc(t.name || t)}</span>`));
|
||||
const zeit = formatMin(z.minutenVon, z.minutenBis);
|
||||
if (zeit) badges.push(`<span class="badge badge-neutral">${esc(zeit)}</span>`);
|
||||
if (z.level != null) badges.push(`<span class="badge">Level ${esc(String(z.level))}</span>`);
|
||||
|
||||
const detailRows = [];
|
||||
if (z.text) detailRows.push(`<div class="item-detail-text">${esc(z.text)}</div>`);
|
||||
@@ -879,6 +885,7 @@
|
||||
const actionBtns = type === 'user' ? `
|
||||
<div class="item-action-btns">
|
||||
<button class="btn-item-edit" onclick="openEditItemModal('${esc(z.sperreId)}',event)">✎ Bearbeiten</button>
|
||||
<button class="btn-item-copy" onclick="duplicateItem('zeitstrafe','${esc(z.sperreId)}','${esc(gruppenId)}',event)">⧉ Duplizieren</button>
|
||||
<button class="btn-item-delete" onclick="deleteItem('zeitstrafe','${esc(z.sperreId)}','${esc(gruppenId)}',event)">✕ Löschen</button>
|
||||
</div>` : '';
|
||||
|
||||
@@ -896,6 +903,7 @@
|
||||
function renderFinisher(f, type, gruppenId) {
|
||||
_itemData[f.finisherId] = { ...f, _kind: 'finisher', _gruppenId: gruppenId };
|
||||
const badges = [];
|
||||
(f.benoetigteToys || []).forEach(t => badges.push(`<span class="item-detail-chip item-detail-chip-toy">${esc(t.name || t)}</span>`));
|
||||
if (f.geschlecht) badges.push(`<span class="badge badge-neutral">${esc(GESCHLECHT_LABEL[f.geschlecht] || f.geschlecht)}</span>`);
|
||||
|
||||
const detailRows = [];
|
||||
@@ -906,6 +914,7 @@
|
||||
const actionBtns = type === 'user' ? `
|
||||
<div class="item-action-btns">
|
||||
<button class="btn-item-edit" onclick="openEditItemModal('${esc(f.finisherId)}',event)">✎ Bearbeiten</button>
|
||||
<button class="btn-item-copy" onclick="duplicateItem('finisher','${esc(f.finisherId)}','${esc(gruppenId)}',event)">⧉ Duplizieren</button>
|
||||
<button class="btn-item-delete" onclick="deleteItem('finisher','${esc(f.finisherId)}','${esc(gruppenId)}',event)">✕ Löschen</button>
|
||||
</div>` : '';
|
||||
|
||||
@@ -944,10 +953,36 @@
|
||||
finisher: apiUrl('/finisher')
|
||||
};
|
||||
const ITEM_DELETE_FIELD = { aufgabe: 'aufgabeId', strafe: 'strafeId', zeitstrafe: 'sperreId', finisher: 'finisherId' };
|
||||
const ITEM_COPY_URL = {
|
||||
aufgabe: apiUrl('/aufgabe/copy'),
|
||||
strafe: '/strafe/copy',
|
||||
zeitstrafe: '/sperre/copy',
|
||||
finisher: apiUrl('/finisher/copy')
|
||||
};
|
||||
|
||||
function duplicateItem(kind, itemId, gruppenId, event) {
|
||||
event.stopPropagation();
|
||||
const copyUrl = ITEM_COPY_URL[kind];
|
||||
if (!copyUrl) return;
|
||||
fetch(`${copyUrl}/${itemId}`, { method: 'POST' }).then(r => {
|
||||
if (r.ok) {
|
||||
pendingExpandId = gruppenId;
|
||||
pendingExpandType = 'user';
|
||||
_notifyOnLoad = true; loadUserGruppen();
|
||||
} else {
|
||||
document.getElementById('userActionError').textContent = 'Fehler beim Duplizieren (HTTP ' + r.status + ').';
|
||||
}
|
||||
}).catch(() => {
|
||||
document.getElementById('userActionError').textContent = 'Verbindungsfehler.';
|
||||
});
|
||||
}
|
||||
|
||||
function deleteItem(kind, itemId, gruppenId, event) {
|
||||
event.stopPropagation();
|
||||
if (!confirm('Eintrag wirklich löschen?')) return;
|
||||
openConfirmModal('Eintrag wirklich löschen?', () => _doDeleteItem(kind, itemId, gruppenId));
|
||||
}
|
||||
|
||||
function _doDeleteItem(kind, itemId, gruppenId) {
|
||||
const deleteUrl = ITEM_DELETE_URL[kind];
|
||||
if (!deleteUrl) return;
|
||||
const body = { [ITEM_DELETE_FIELD[kind]]: itemId };
|
||||
@@ -1430,7 +1465,7 @@
|
||||
const lbl = document.querySelector(`#iSperreFuer input[value="${v}"]`)?.closest('label');
|
||||
if (lbl) lbl.style.display = isChastity ? 'none' : '';
|
||||
});
|
||||
document.getElementById('iTempUnlockRow').style.display = (isZeit && isChastity) ? 'block' : 'none';
|
||||
document.getElementById('iTempUnlockRow').style.display = ((isZeit || isFinisher) && isChastity) ? 'block' : 'none';
|
||||
document.getElementById('iReleaseTextRow').style.display = isZeit ? 'block' : 'none';
|
||||
}
|
||||
|
||||
@@ -1449,8 +1484,7 @@
|
||||
document.querySelectorAll('#iWerkzeugFinisherPassiv input').forEach(cb => cb.checked = false);
|
||||
document.querySelectorAll('#iSperreFuer input').forEach(cb => cb.checked = false);
|
||||
document.querySelectorAll('#iGeschlecht input').forEach(rb => rb.checked = false);
|
||||
document.getElementById('iTempUnlockBefore').checked = false;
|
||||
document.getElementById('iTempUnlockAfter').checked = false;
|
||||
document.getElementById('iTempUnlockRequired').checked = false;
|
||||
_selectedToys = [];
|
||||
renderSelectedToys();
|
||||
document.getElementById('itemModalError').style.display = 'none';
|
||||
@@ -1495,6 +1529,9 @@
|
||||
const rb = document.querySelector(`#iGeschlecht input[value="${d.geschlecht}"]`);
|
||||
if (rb) rb.checked = true;
|
||||
}
|
||||
if (_isChastityMode) {
|
||||
document.getElementById('iTempUnlockRequired').checked = d.tempUnlockRequired === true;
|
||||
}
|
||||
} else {
|
||||
document.getElementById('iMinVon').value = d.minutenVon != null ? d.minutenVon : '';
|
||||
document.getElementById('iMinBis').value = d.minutenBis != null ? d.minutenBis : '';
|
||||
@@ -1502,8 +1539,7 @@
|
||||
(d.sperreFuer || []).forEach(w => { const cb = document.querySelector(`#iSperreFuer input[value="${w}"]`); if (cb) cb.checked = true; });
|
||||
if (_isChastityMode) {
|
||||
document.getElementById('iLevel').value = d.level != null ? d.level : '';
|
||||
document.getElementById('iTempUnlockBefore').checked = d.tempUnlockBeforeRequired === true;
|
||||
document.getElementById('iTempUnlockAfter').checked = d.tempUnlockAfterRequired === true;
|
||||
document.getElementById('iTempUnlockRequired').checked = d.tempUnlockRequired === true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1670,7 +1706,8 @@
|
||||
gruppeId: isEdit ? undefined : currentItemGruppeId,
|
||||
benoetigtAktiv: _isChastityMode ? [] : checkedValues('iWerkzeugFinisherAktiv'),
|
||||
benoetigtPassiv: _isChastityMode ? [] : checkedValues('iWerkzeugFinisherPassiv'),
|
||||
benoetigteToys: _selectedToys.map(t => ({ toyId: t.toyId }))
|
||||
benoetigteToys: _selectedToys.map(t => ({ toyId: t.toyId })),
|
||||
tempUnlockRequired: _isChastityMode ? document.getElementById('iTempUnlockRequired').checked : null
|
||||
};
|
||||
url = isEdit ? apiUrl(`/finisher/${currentItemEditId}`) : apiUrl('/finisher');
|
||||
method = isEdit ? 'PUT' : 'POST';
|
||||
@@ -1698,8 +1735,7 @@
|
||||
releaseText: document.getElementById('iReleaseText').value.trim() || null,
|
||||
sperreFuer,
|
||||
level: zeitLevel,
|
||||
tempUnlockBeforeRequired: _isChastityMode ? document.getElementById('iTempUnlockBefore').checked : null,
|
||||
tempUnlockAfterRequired: _isChastityMode ? document.getElementById('iTempUnlockAfter').checked : null,
|
||||
tempUnlockRequired: _isChastityMode ? document.getElementById('iTempUnlockRequired').checked : null,
|
||||
benoetigteToys: _selectedToys.map(t => ({ toyId: t.toyId }))
|
||||
};
|
||||
url = isEdit ? `/sperre/${currentItemEditId}` : '/sperre'; // BDSM-only
|
||||
|
||||
@@ -679,6 +679,7 @@
|
||||
|
||||
// ── Gruppe lists ──
|
||||
function renderGruppeList(containerId, gruppen) {
|
||||
gruppen = gruppen.filter(g => g.availableIn !== 'CHASTITY_ONLY');
|
||||
const ul = document.getElementById(containerId);
|
||||
const section = ul.closest('[id^="section"]');
|
||||
const selectAllWrap = section?.querySelector('.select-all-label');
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,9 @@
|
||||
margin-top: 1rem;
|
||||
height: 2.75rem;
|
||||
}
|
||||
#confirmModal { display:none; }
|
||||
#confirmModal.open { display:flex; }
|
||||
|
||||
|
||||
.level-display {
|
||||
display: flex;
|
||||
@@ -205,13 +208,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 +362,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 +440,8 @@
|
||||
await loadAndShowToys(sel.value);
|
||||
}
|
||||
|
||||
// ── Benötigt-Checkboxen ───────────────────────────────────────────────────
|
||||
|
||||
// ── Game Loop ─────────────────────────────────────────────────────────────
|
||||
|
||||
function setGameCard(label, text, action, btnLabel) {
|
||||
@@ -432,10 +457,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 +491,14 @@
|
||||
let sperre;
|
||||
try { sperre = JSON.parse(_state.lockInQueue); } catch { sperre = {}; }
|
||||
setGameCard('🔒 Neue Sperre', sperre.text || sperre.kurzText || '', 'queue-start', '▶ Starten');
|
||||
|
||||
} 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');
|
||||
|
||||
}
|
||||
show('gameBox');
|
||||
show('gameCard');
|
||||
@@ -497,19 +530,38 @@
|
||||
switch (_gameAction) {
|
||||
case 'queue-start': doQueueStart(); break;
|
||||
case 'queue-done': doQueueDone(); break;
|
||||
case 'active-running': doCancelCountdown(); break;
|
||||
case 'active-running': openConfirmModal('Aufgabe wirklich abbrechen?', () => doCancelCountdown()); break;
|
||||
case 'active-done': doErledigt(); break;
|
||||
}
|
||||
}
|
||||
|
||||
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; }
|
||||
|
||||
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 +578,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 +621,87 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function showFinisherFlow() {
|
||||
function showTempOpeningDialog() {
|
||||
show('gameBox');
|
||||
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);
|
||||
document.getElementById('tempOpeningTask').textContent = _state.activeTask || '';
|
||||
const code = _state.tempOpeningCode;
|
||||
if (code) {
|
||||
document.getElementById('tempOpeningCode').textContent = code;
|
||||
show('tempOpeningCodeRow');
|
||||
} else {
|
||||
hide('tempOpeningCodeRow');
|
||||
}
|
||||
show('tempOpeningBox');
|
||||
}
|
||||
} catch (_) { /* ignorieren */ }
|
||||
|
||||
// 2. Finisher laden und Zeit messen
|
||||
const finisherStartTime = Date.now();
|
||||
let finisher = null;
|
||||
async function doEndTempOpening() {
|
||||
try {
|
||||
const r = await fetch('/lock-game/finisher');
|
||||
if (r.ok) finisher = await r.json();
|
||||
} 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'); }
|
||||
}
|
||||
|
||||
document.getElementById('finisherTitle').textContent = finisher?.kurzText || '';
|
||||
document.getElementById('finisherText').textContent = finisher?.text || 'Glückwunsch – du hast Level 6 erreicht!';
|
||||
function showFinisherUI() {
|
||||
show('gameBox');
|
||||
hide('gameCard');
|
||||
hide('lockReleaseBox');
|
||||
|
||||
// 3. Warten bis Nutzer OK drückt
|
||||
await new Promise(resolve => {
|
||||
document.getElementById('btnFinisherOk').onclick = resolve;
|
||||
let finisher = {};
|
||||
try { finisher = JSON.parse(_state.finisher); } catch (_) {}
|
||||
document.getElementById('finisherTitle').textContent = finisher.kurzText || '';
|
||||
document.getElementById('finisherText').textContent = finisher.text || '';
|
||||
|
||||
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();
|
||||
@@ -658,7 +758,37 @@
|
||||
box.style.display = '';
|
||||
}
|
||||
|
||||
// ── Confirm Modal ─────────────────────────────────────────────────────────
|
||||
|
||||
const _confirmModal = document.getElementById('confirmModal');
|
||||
document.getElementById('confirmModalCancel').addEventListener('click', closeConfirmModal);
|
||||
document.getElementById('confirmModalOk').addEventListener('click', closeConfirmModal);
|
||||
_confirmModal.addEventListener('click', e => { if (e.target === _confirmModal) closeConfirmModal(); });
|
||||
document.addEventListener('keydown', e => { if (e.key === 'Escape' && _confirmModal.classList.contains('open')) closeConfirmModal(); });
|
||||
|
||||
function closeConfirmModal() { _confirmModal.classList.remove('open'); }
|
||||
|
||||
function openConfirmModal(text, onConfirm) {
|
||||
document.getElementById('confirmModalText').textContent = text;
|
||||
const okBtn = document.getElementById('confirmModalOk');
|
||||
const newOk = okBtn.cloneNode(true);
|
||||
okBtn.parentNode.replaceChild(newOk, okBtn);
|
||||
newOk.addEventListener('click', () => { closeConfirmModal(); onConfirm(); });
|
||||
_confirmModal.classList.add('open');
|
||||
}
|
||||
|
||||
boot();
|
||||
</script>
|
||||
|
||||
<div class="modal-backdrop" id="confirmModal">
|
||||
<div class="modal" style="max-width:420px;">
|
||||
<h2>Bestätigung</h2>
|
||||
<p id="confirmModalText" style="color:var(--color-text);margin-bottom:1.25rem;line-height:1.5;"></p>
|
||||
<div class="modal-actions">
|
||||
<button class="btn-cancel" id="confirmModalCancel">Nein</button>
|
||||
<button class="btn-save" id="confirmModalOk" style="background:var(--color-danger,#e74c3c);">Ja, abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -29,7 +29,7 @@ class CardLockServiceTest {
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
lock = new CardLockEntity();
|
||||
lock.setPickEveryMinute(60);
|
||||
lock.setPickEverySeconds(60);
|
||||
lock.setNextCardIn(LocalDateTime.now().minusMinutes(1));
|
||||
// controllType bleibt null → lockControlFactory.create() wird nicht aufgerufen
|
||||
|
||||
|
||||
Reference in New Issue
Block a user