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