update all flow
This commit is contained in:
+32
-1
@@ -4107,6 +4107,35 @@
|
||||
.then(function () { if (btn) btn.disabled = false; });
|
||||
}
|
||||
|
||||
function loadTroublesomeForcePanel() {
|
||||
var chk = el('troublesome-force-offer');
|
||||
if (!chk) return;
|
||||
gameTimingFetch('GET')
|
||||
.then(function (data) {
|
||||
chk.checked = !!(data && data.troublesomeForceOffer);
|
||||
setMsg('troublesome-force-msg', '', '');
|
||||
})
|
||||
.catch(function (e) { setMsg('troublesome-force-msg', e.message || 'โหลดไม่ได้', 'error'); });
|
||||
}
|
||||
function saveTroublesomeForcePanel() {
|
||||
var chk = el('troublesome-force-offer');
|
||||
if (!chk) return;
|
||||
var on = !!chk.checked;
|
||||
var btn = el('btn-troublesome-force-save');
|
||||
if (btn) btn.disabled = true;
|
||||
gameTimingFetch('GET')
|
||||
.then(function (data) {
|
||||
data.troublesomeForceOffer = on;
|
||||
return gameTimingFetch('PUT', data);
|
||||
})
|
||||
.then(function (res) {
|
||||
if (res && typeof res === 'object') chk.checked = !!res.troublesomeForceOffer;
|
||||
setMsg('troublesome-force-msg', on ? 'บันทึกแล้ว — บทตัวป่วนจะขึ้นเสมอ (เริ่มคดีใหม่เพื่อเทสต์)' : 'ปิดแล้ว — กลับเป็นปกติ (ขึ้นเฉพาะคนจริง > 4)', 'ok');
|
||||
})
|
||||
.catch(function (e) { setMsg('troublesome-force-msg', e.message || 'บันทึกไม่ได้', 'error'); })
|
||||
.then(function () { if (btn) btn.disabled = false; });
|
||||
}
|
||||
|
||||
function saveJumpSurviveTimingPanel() {
|
||||
var inp = el('jump-survive-height-mult');
|
||||
var jumpSurvMult = inp ? parseFloat(String(inp.value)) : 1.5;
|
||||
@@ -4625,7 +4654,7 @@
|
||||
if (name === 'game-timing') loadGameTimingPanel();
|
||||
if (name === 'stack-game') loadStackGamePanel();
|
||||
if (name === 'highscore') loadHighscore();
|
||||
if (name === 'test-mode') { loadForcedMinigamePanel(); loadSpecialCardByMapPanel(); }
|
||||
if (name === 'test-mode') { loadForcedMinigamePanel(); loadSpecialCardByMapPanel(); loadTroublesomeForcePanel(); }
|
||||
}
|
||||
|
||||
function boot() {
|
||||
@@ -4853,6 +4882,8 @@
|
||||
if (forcedMgSaveBtn) forcedMgSaveBtn.addEventListener('click', saveForcedMinigamePanel);
|
||||
var specialCardSaveBtn = el('btn-special-card-by-map-save');
|
||||
if (specialCardSaveBtn) specialCardSaveBtn.addEventListener('click', saveSpecialCardByMapPanel);
|
||||
var troublesomeForceSaveBtn = el('btn-troublesome-force-save');
|
||||
if (troublesomeForceSaveBtn) troublesomeForceSaveBtn.addEventListener('click', saveTroublesomeForcePanel);
|
||||
syncTestModeUi();
|
||||
/* ฟัง storage event — sync เมื่อเปิดหลายแท็บ */
|
||||
window.addEventListener('storage', function (e) {
|
||||
|
||||
@@ -1109,6 +1109,9 @@
|
||||
<li><kbd>Ctrl</kbd> + <kbd>Q</kbd> — ในเกมตอนคำถามพิเศษ (Special Quiz) เปิดอยู่: ตอบข้อปัจจุบันให้ถูกอัตโนมัติ<br>
|
||||
<span class="muted" style="font-size:13px;">เซิร์ฟเวอร์เป็นผู้เติมคำตอบที่ถูก (กันโกง) — ใช้ทดสอบ flow รับการ์ดพิเศษได้เร็ว</span>
|
||||
</li>
|
||||
<li><kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>1</kbd> / <kbd>2</kbd> / <kbd>3</kbd> — ที่หน้าห้อง LobbyB: พรีวิว "หน้าผลตัดสินคดี" โดยไม่ต้องเล่นจบ<br>
|
||||
<span class="muted" style="font-size:13px;"><kbd>1</kbd> = โหวตถูก (เรือนจำ → โพเดียมผู้ชนะ) · <kbd>2</kbd> = โหวตผิด (ตัวป่วนชนะ) · <kbd>3</kbd> = สลับ มี/ไม่มีตัวป่วน (ดูสถานะในแชต) · ตัวป่วนจำลองใช้ผู้เล่น/บอทคนแรก</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p id="test-mode-msg" class="msg" role="status" style="margin-top:14px;"></p>
|
||||
@@ -1161,11 +1164,20 @@
|
||||
<div id="special-card-by-map-rows" style="display:flex;flex-direction:column;gap:8px;max-width:640px;"></div>
|
||||
<div style="margin-top:12px;"><button type="button" class="btn" id="btn-special-card-by-map-save">บันทึก</button></div>
|
||||
<p id="special-card-by-map-msg" class="msg" role="status"></p>
|
||||
|
||||
<h3 class="admin-subheading" style="margin-top:28px;">บังคับเสนอบท "ตัวป่วน" (เทสต์)</h3>
|
||||
<p class="muted">ปกติบทตัวป่วนจะเสนอเฉพาะเมื่อมี<strong>คนจริงเกิน 4 คน</strong> · เปิดอันนี้เพื่อ<strong>บังคับให้ขึ้นเสมอ</strong>แม้เล่นคนเดียว (ไว้เทสต์)</p>
|
||||
<label style="display:flex;align-items:center;gap:10px;color:#cfd6ea;font-size:14px;cursor:pointer;">
|
||||
<input type="checkbox" id="troublesome-force-offer" style="width:18px;height:18px;cursor:pointer;">
|
||||
บังคับเสนอบทตัวป่วน (ข้ามเงื่อนไข > 4 คน)
|
||||
</label>
|
||||
<div style="margin-top:12px;"><button type="button" class="btn" id="btn-troublesome-force-save">บันทึก</button></div>
|
||||
<p id="troublesome-force-msg" class="msg" role="status"></p>
|
||||
</section>
|
||||
</div><!-- /.admin-content -->
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<script src="admin.js?v=82"></script>
|
||||
<script src="admin.js?v=83"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -38,9 +38,9 @@
|
||||
"providerUserId": "p_1775109142385_wq7wfy1p32j",
|
||||
"notes": "auto: player-coins",
|
||||
"blocked": false,
|
||||
"coins": 210,
|
||||
"coins": 230,
|
||||
"createdAt": "2026-04-02T05:52:21+00:00",
|
||||
"updatedAt": "2026-06-14T15:14:18+00:00",
|
||||
"updatedAt": "2026-06-15T13:38:26+00:00",
|
||||
"daily": {
|
||||
"anchorMs": 1781197200000,
|
||||
"claimedDays": [
|
||||
@@ -54,11 +54,11 @@
|
||||
],
|
||||
"lockUntilMs": 0
|
||||
},
|
||||
"score": 190,
|
||||
"score": 200,
|
||||
"scoreByCase": {
|
||||
"1": 10,
|
||||
"10": 70,
|
||||
"8": 90,
|
||||
"8": 100,
|
||||
"11": 20
|
||||
},
|
||||
"lbName": "Q"
|
||||
|
||||
@@ -101,5 +101,6 @@
|
||||
"mnptfts2": 5,
|
||||
"mnpz6rkp": 6,
|
||||
"mnq1eml7": 7
|
||||
}
|
||||
},
|
||||
"troublesomeForceOffer": true
|
||||
}
|
||||
@@ -4924,6 +4924,9 @@
|
||||
if (titleEl) titleEl.style.display = 'block';
|
||||
}
|
||||
var names = (data && data.winnerNames && data.winnerNames.length) ? data.winnerNames.join(', ') : 'ไม่มีใครตอบถูก';
|
||||
_lastTrialWinners = (data && Array.isArray(data.winners)) ? data.winners.slice() : [];
|
||||
_lastDisruptorId = (data && data.disruptorId) ? data.disruptorId : null;
|
||||
_lastHasDisruptor = !!(data && data.hasDisruptor && data.disruptorId);
|
||||
var iWon = !!(data && Array.isArray(data.winners) && data.winners.indexOf(socket.id) >= 0);
|
||||
var banner = ensureTrialBanner();
|
||||
if (banner) {
|
||||
@@ -4943,6 +4946,129 @@
|
||||
}
|
||||
|
||||
var trialFinalTimer = null;
|
||||
var winPodiumTimer = null;
|
||||
var _lastTrialWinners = [];
|
||||
var _lastDisruptorId = null;
|
||||
var _lastHasDisruptor = false;
|
||||
|
||||
/** id ผู้เล่น/บอททั้งหมดในห้อง (เรียง peers ก่อน bot) — ใช้กรณีตัวป่วน: โชว์ทุกคนยกเว้นตัวป่วน */
|
||||
function allOccupantIds() {
|
||||
var out = [];
|
||||
if (peers) peers.forEach(function (_p, id) { out.push(id); });
|
||||
if (lobbyBots) lobbyBots.forEach(function (_b, id) { out.push(id); });
|
||||
return out;
|
||||
}
|
||||
|
||||
/** ข้อมูลตัวละคร/ชื่อ/สี ของผู้เล่นหรือบอท ตาม id (ใช้วางบนโพเดียมผู้ชนะ)
|
||||
สี = colorTheme/colorSkin (rgb/hex) สำหรับระบบ tint — ไม่ใช่ index */
|
||||
function resolveOccupantCharInfo(id) {
|
||||
var isMe = !!(socket && id === socket.id);
|
||||
var p = (peers && peers.get(id)) || (lobbyBots && lobbyBots.get(id));
|
||||
if (p) {
|
||||
return {
|
||||
characterId: p.characterId || (isMe ? getStoredCharacterId() : null),
|
||||
name: (p.nickname || '').trim() || (isMe ? 'คุณ' : String(id).slice(0, 6)),
|
||||
colorTheme: (isMe ? (myTintTheme || p.colorTheme) : p.colorTheme) || '',
|
||||
colorSkin: (isMe ? (myTintSkin || p.colorSkin) : p.colorSkin) || '',
|
||||
};
|
||||
}
|
||||
if (isMe) {
|
||||
return { characterId: getStoredCharacterId(), name: 'คุณ', colorTheme: myTintTheme || '', colorSkin: myTintSkin || '' };
|
||||
}
|
||||
if (typeof id === 'string' && id.indexOf('__lobby_bot_') === 0) {
|
||||
var slot = parseInt(id.slice('__lobby_bot_'.length), 10);
|
||||
return { characterId: null, name: 'บอท ' + ((Number.isFinite(slot) ? slot : 0) + 1), colorTheme: '', colorSkin: '' };
|
||||
}
|
||||
return { characterId: null, name: String(id).slice(0, 6), colorTheme: '', colorSkin: '' };
|
||||
}
|
||||
|
||||
/** img ตัวละครเต็มตัว (down idle) — โหลด sprite ฐานก่อน แล้ว swap เป็นตัวที่ "ทาสีแล้ว" (tint) เมื่อพร้อม */
|
||||
function buildPodiumCharImg(characterId, colorTheme, colorSkin) {
|
||||
var img = document.createElement('img');
|
||||
img.className = 'av';
|
||||
img.alt = '';
|
||||
img.decoding = 'async';
|
||||
img.style.filter = 'drop-shadow(0 6px 12px rgba(0,0,0,.5))';
|
||||
if (!characterId) { img.src = defaultAvatarImg.src; return img; }
|
||||
var cands = characterSpriteUrlCandidates(characterId, 'down');
|
||||
var ci = 0;
|
||||
img.src = cands[0];
|
||||
img.onerror = function () {
|
||||
ci++;
|
||||
if (ci < cands.length) { img.src = cands[ci]; }
|
||||
else { img.onerror = null; img.src = defaultAvatarImg.src; }
|
||||
};
|
||||
if ((colorTheme || colorSkin) && typeof preloadLobbyTintedCharacter === 'function') {
|
||||
preloadLobbyTintedCharacter(characterId, colorTheme, colorSkin, function () {
|
||||
var ckey = characterId + '|' + (colorTheme || '') + '|' + (colorSkin || '') + '|down';
|
||||
var anim = tintedCharCache[ckey];
|
||||
var tinted = anim && anim.fallback;
|
||||
if (tinted && tinted.complete && tinted.naturalWidth) { img.onerror = null; img.src = tinted.src; }
|
||||
});
|
||||
}
|
||||
return img;
|
||||
}
|
||||
|
||||
/** หน้า2 ของผลชนะ — โพเดียมผู้ชนะบน win-bg + มงกุฎ · memberIds = รายชื่อที่จะโชว์ (ดีฟอลต์ = ผู้โหวตถูก) */
|
||||
function renderWinPodium(memberIds) {
|
||||
var R = '/Main-Lobby/IMAGE/Result/';
|
||||
var ov = document.getElementById('trial-final');
|
||||
if (!ov) return;
|
||||
ov.classList.add('trial-final--podium');
|
||||
ov.style.backgroundImage = 'url(' + R + 'win-bg.png)';
|
||||
var culpEl = ov.querySelector('#trial-final-culprit');
|
||||
if (culpEl) culpEl.style.display = 'none';
|
||||
var txtEl = ov.querySelector('#trial-final-txt');
|
||||
if (txtEl) txtEl.style.display = 'none';
|
||||
var congrats = ov.querySelector('#trial-final-congrats');
|
||||
if (!congrats) { congrats = document.createElement('img'); congrats.id = 'trial-final-congrats'; congrats.alt = 'ยินดีด้วย ทีมนักสืบ ชนะ'; ov.appendChild(congrats); }
|
||||
congrats.src = R + 'txt-congrats.png';
|
||||
var pod = ov.querySelector('#trial-final-podium');
|
||||
if (!pod) { pod = document.createElement('div'); pod.id = 'trial-final-podium'; ov.appendChild(pod); }
|
||||
pod.innerHTML = '';
|
||||
var left = document.createElement('div'); left.className = 'tf-pod-side tf-pod-left';
|
||||
var right = document.createElement('div'); right.className = 'tf-pod-side tf-pod-right';
|
||||
var ids = (Array.isArray(memberIds) && memberIds.length) ? memberIds.slice(0, 6)
|
||||
: ((_lastTrialWinners && _lastTrialWinners.length) ? _lastTrialWinners.slice(0, 6) : []);
|
||||
if (!ids.length && socket && socket.id) ids = [socket.id];
|
||||
ids.forEach(function (id, i) {
|
||||
var info = resolveOccupantCharInfo(id);
|
||||
var cell = document.createElement('div'); cell.className = 'tf-pod-char';
|
||||
if (i === 0) { var cr = document.createElement('div'); cr.className = 'crown'; cr.textContent = '👑'; cell.appendChild(cr); }
|
||||
var nm = document.createElement('div'); nm.className = 'nm'; nm.textContent = info.name || '';
|
||||
cell.appendChild(nm);
|
||||
cell.appendChild(buildPodiumCharImg(info.characterId, info.colorTheme, info.colorSkin));
|
||||
(i % 2 === 0 ? left : right).appendChild(cell);
|
||||
});
|
||||
pod.appendChild(left);
|
||||
pod.appendChild(right);
|
||||
var home = ov.querySelector('#trial-final-home');
|
||||
if (home) home.style.display = '';
|
||||
}
|
||||
|
||||
/** หน้าผลโหวตผิด "ตัวป่วนชนะ" (กรณีมีตัวป่วน) — โชว์เฉพาะตัวป่วน 1 ตัว บน troll-win-bg */
|
||||
function renderTrollWinDisruptor(disruptorId) {
|
||||
var R = '/Main-Lobby/IMAGE/Result/';
|
||||
var ov = document.getElementById('trial-final');
|
||||
if (!ov) return;
|
||||
ov.classList.remove('trial-final--podium');
|
||||
ov.classList.add('trial-final--trollwin');
|
||||
ov.style.backgroundImage = 'url(' + R + 'troll-win-bg.png)';
|
||||
var culpEl = ov.querySelector('#trial-final-culprit');
|
||||
if (culpEl) culpEl.style.display = 'none';
|
||||
var txtEl = ov.querySelector('#trial-final-txt');
|
||||
if (txtEl) { txtEl.src = R + 'txt-troll-win.png'; txtEl.className = ''; txtEl.style.display = 'block'; }
|
||||
var dWrap = ov.querySelector('#trial-final-disruptor');
|
||||
if (!dWrap) { dWrap = document.createElement('div'); dWrap.id = 'trial-final-disruptor'; ov.appendChild(dWrap); }
|
||||
dWrap.innerHTML = '';
|
||||
var info = resolveOccupantCharInfo(disruptorId);
|
||||
var nm = document.createElement('div'); nm.className = 'nm'; nm.textContent = info.name || '';
|
||||
dWrap.appendChild(nm);
|
||||
dWrap.appendChild(buildPodiumCharImg(info.characterId, info.colorTheme, info.colorSkin));
|
||||
var home = ov.querySelector('#trial-final-home');
|
||||
if (home) home.style.display = '';
|
||||
}
|
||||
|
||||
function injectFinalResultStyle() {
|
||||
if (document.getElementById('trial-final-style')) return;
|
||||
var R = '/Main-Lobby/IMAGE/Result/';
|
||||
@@ -4957,7 +5083,25 @@
|
||||
'#trial-final-txt{position:absolute;left:50%;top:7%;transform:translateX(-50%);width:780px;max-width:90vw;height:auto;z-index:2;display:none}' +
|
||||
/* ชนะ: ข้อความ "ยินดีด้วย ชนะ" วางช่วงล่างเหนือปุ่มกลับ (เลี่ยงป้าย "เรือนจำไซเบอร์" บนสุด) */
|
||||
'#trial-final-txt.final-txt-congrats{top:auto;bottom:21%;width:420px;filter:drop-shadow(0 2px 10px rgba(0,0,0,.7))}' +
|
||||
'#trial-final-home{position:absolute;left:50%;bottom:5%;transform:translateX(-50%);width:300px;height:104px;max-width:70vw;background:url(' + R + 'btn-gohome.png) center/contain no-repeat;border:none;cursor:pointer;font-size:0;color:transparent;z-index:3;filter:drop-shadow(0 4px 14px rgba(0,0,0,.6))}';
|
||||
'#trial-final-home{position:absolute;left:50%;bottom:5%;transform:translateX(-50%);width:300px;height:104px;max-width:70vw;background:url(' + R + 'btn-gohome.png) center/contain no-repeat;border:none;cursor:pointer;font-size:0;color:transparent;z-index:5;filter:drop-shadow(0 4px 14px rgba(0,0,0,.6))}' +
|
||||
/* ===== หน้า2 (ชนะ): โพเดียมผู้ชนะ win-bg + ตัวละคร + มงกุฎ ===== */
|
||||
'#trial-final.trial-final--podium{background-image:url(' + R + 'win-bg.png)!important}' +
|
||||
'#trial-final-congrats{position:absolute;left:50%;top:5%;transform:translateX(-50%);width:560px;max-width:82vw;height:auto;z-index:4;display:none;filter:drop-shadow(0 3px 14px rgba(0,0,0,.6))}' +
|
||||
'#trial-final.trial-final--podium #trial-final-congrats{display:block}' +
|
||||
'#trial-final-podium{position:absolute;left:0;right:0;bottom:9%;height:58%;display:none;z-index:3;pointer-events:none}' +
|
||||
'#trial-final.trial-final--podium #trial-final-podium{display:block}' +
|
||||
'.tf-pod-side{position:absolute;bottom:0;display:flex;align-items:flex-end;justify-content:center;gap:min(3vw,52px)}' +
|
||||
'.tf-pod-left{left:2%;right:56%}' +
|
||||
'.tf-pod-right{left:56%;right:2%}' +
|
||||
'.tf-pod-char{position:relative;display:flex;flex-direction:column;align-items:center;width:min(17vw,230px)}' +
|
||||
'.tf-pod-char .crown{font-size:min(7vw,62px);line-height:1;margin-bottom:-2px;filter:drop-shadow(0 0 10px rgba(255,200,60,.95))}' +
|
||||
'.tf-pod-char .nm{margin-bottom:8px;color:#eafcff;font:800 clamp(14px,1.7vw,26px)/1 Kanit,system-ui,sans-serif;text-shadow:0 0 10px rgba(0,0,0,.85),0 2px 4px rgba(0,0,0,.9);white-space:nowrap}' +
|
||||
'.tf-pod-char img.av{width:100%;height:auto;image-rendering:auto}' +
|
||||
/* ===== ตัวป่วนชนะ (โหวตผิด) — โชว์ตัวป่วนเดี่ยวกลางเวที ===== */
|
||||
'#trial-final-disruptor{position:absolute;left:50%;bottom:17%;transform:translateX(-50%);display:none;flex-direction:column;align-items:center;width:min(21vw,280px);z-index:3;pointer-events:none}' +
|
||||
'#trial-final.trial-final--trollwin #trial-final-disruptor{display:flex}' +
|
||||
'#trial-final-disruptor .nm{margin-bottom:10px;color:#ffd6e6;font:800 clamp(15px,2vw,30px)/1 Kanit,system-ui,sans-serif;text-shadow:0 0 12px rgba(0,0,0,.9),0 2px 5px rgba(0,0,0,.95);white-space:nowrap}' +
|
||||
'#trial-final-disruptor img.av{width:100%;height:auto}';
|
||||
document.head.appendChild(st);
|
||||
}
|
||||
function showFinalResult(correct, culprit, anyVotes) {
|
||||
@@ -4975,17 +5119,32 @@
|
||||
}
|
||||
var culpEl = ov.querySelector('#trial-final-culprit');
|
||||
var txtEl = ov.querySelector('#trial-final-txt');
|
||||
var homeEl = ov.querySelector('#trial-final-home');
|
||||
/* รีเซ็ตสถานะหน้าก่อน (เผื่อรอบก่อนเป็นหน้าโพเดียม/ตัวป่วน) */
|
||||
if (winPodiumTimer) { clearTimeout(winPodiumTimer); winPodiumTimer = null; }
|
||||
ov.classList.remove('trial-final--podium', 'trial-final--trollwin');
|
||||
var dWrap0 = ov.querySelector('#trial-final-disruptor'); if (dWrap0) dWrap0.innerHTML = '';
|
||||
if (homeEl) homeEl.style.display = '';
|
||||
if (correct) {
|
||||
/* ชนะ — จับคนร้ายจริง: เรือนจำไซเบอร์ + คนร้ายในคุก + ข้อความ "ยินดีด้วย ทีมนักสืบ ชนะ!" */
|
||||
/* ชนะ — 2 หน้า: (หน้า1) เรือนจำไซเบอร์ + คนร้ายในคุก → (หน้า2) โพเดียมผู้ชนะ + ตัวละคร
|
||||
มีตัวป่วน → โพเดียมโชว์ "ทุกคนยกเว้นตัวป่วน" · ไม่มี → โชว์ผู้โหวตถูก (ดีฟอลต์) */
|
||||
ov.style.backgroundImage = 'url(' + R + 'cyber-prison.png)';
|
||||
var culpIdx = (typeof culprit === 'number') ? culprit : 0;
|
||||
if (culpEl) {
|
||||
culpEl.src = getCulpritPrisonImageUrl(culpIdx);
|
||||
culpEl.style.display = 'block';
|
||||
}
|
||||
if (txtEl) { txtEl.src = R + 'txt-congrats.png'; txtEl.className = 'final-txt-congrats'; txtEl.style.display = 'block'; }
|
||||
if (txtEl) txtEl.style.display = 'none'; /* หน้า1 ไม่มีข้อความ congrats (อยู่หน้า2) */
|
||||
if (homeEl) homeEl.style.display = 'none'; /* หน้า1 ยังกลับไม่ได้ — auto เข้าหน้า2 */
|
||||
var podMembers = _lastHasDisruptor
|
||||
? allOccupantIds().filter(function (id) { return id !== _lastDisruptorId; })
|
||||
: null;
|
||||
winPodiumTimer = setTimeout(function () { renderWinPodium(podMembers); }, 3500);
|
||||
} else if (anyVotes && _lastHasDisruptor && _lastDisruptorId) {
|
||||
/* โหวตผิด + มีตัวป่วน → "ตัวป่วนชนะ" โชว์เฉพาะตัวป่วน */
|
||||
renderTrollWinDisruptor(_lastDisruptorId);
|
||||
} else if (anyVotes) {
|
||||
/* จับแพะ — โหวตผิดคน: "อุปส์..ตัวป่วนชนะ ความจริงถูกบิดเบือน!" (troll-win) */
|
||||
/* จับแพะ — โหวตผิดคน (ไม่มีตัวป่วน): "ตัวป่วนชนะ ความจริงถูกบิดเบือน!" (troll-win เปล่า) */
|
||||
ov.style.backgroundImage = 'url(' + R + 'troll-win-bg.png)';
|
||||
if (txtEl) { txtEl.src = R + 'txt-troll-win.png'; txtEl.className = ''; txtEl.style.display = 'block'; }
|
||||
if (culpEl) culpEl.style.display = 'none';
|
||||
@@ -4999,6 +5158,29 @@
|
||||
try { closeSuspectOverlay(); } catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
/* ===== Key ลัด (เฉพาะ Test Mode) — พรีวิวหน้าผลจบโดยไม่ต้องเล่นจบจริง =====
|
||||
Ctrl+Alt+1 = โหวตถูก · Ctrl+Alt+2 = โหวตผิด · Ctrl+Alt+3 = สลับมี/ไม่มีตัวป่วน */
|
||||
var _testDisruptorOn = true;
|
||||
function __previewTrialFinal(correct, anyVotes) {
|
||||
var occ = allOccupantIds();
|
||||
var disruptor = (_testDisruptorOn && occ.length) ? occ[0] : null;
|
||||
_lastDisruptorId = disruptor;
|
||||
_lastHasDisruptor = !!disruptor;
|
||||
_lastTrialWinners = occ.slice(0, Math.min(4, occ.length)); /* ผู้โหวตถูก (ใช้กรณีไม่มีตัวป่วน) */
|
||||
showFinalResult(correct, 0, anyVotes);
|
||||
}
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (!e.ctrlKey || !e.altKey || e.repeat) return;
|
||||
if (!__isJusticeTestModeOn()) return;
|
||||
if (e.code === 'Digit1') { e.preventDefault(); __previewTrialFinal(true, true); }
|
||||
else if (e.code === 'Digit2') { e.preventDefault(); __previewTrialFinal(false, true); }
|
||||
else if (e.code === 'Digit3') {
|
||||
e.preventDefault();
|
||||
_testDisruptorOn = !_testDisruptorOn;
|
||||
try { appendLobbySystemChat('— [Test] ตัวป่วน: ' + (_testDisruptorOn ? 'มี' : 'ไม่มี') + ' (Ctrl+Alt+1 ถูก / Ctrl+Alt+2 ผิด)'); } catch (eC) { /* ignore */ }
|
||||
}
|
||||
});
|
||||
|
||||
/* ===== ห้องสรุปหลักฐาน — การไต่สวน (เลือกหลักฐาน 2 ใบ/ปากคำ ก่อนโหวต) ===== */
|
||||
var tmState = { round: 0, members: [], picks: [], submitted: false, mode: 'select', submittedPicksById: {}, ownedSlots: [], requiredPicks: 2, hostId: '', submittedIds: [], readyIds: [], iReadied: false };
|
||||
|
||||
|
||||
@@ -426,6 +426,8 @@
|
||||
}
|
||||
#suspect-pick-close:hover { transform: scale(1.07); filter: brightness(1.12); }
|
||||
#suspect-pick-close img { display: block; width: min(52px, 11vw); height: auto; }
|
||||
/* หน้าโหวตชี้ตัวคนร้าย (trial) — ปิดไม่ได้ ต้องโหวต → ซ่อนปุ่มปิด */
|
||||
.suspect-pick--trial #suspect-pick-close { display: none !important; }
|
||||
/* ปุ่มบนโถง — เปิด overlay ใหม่เมื่อเฟสยัง active
|
||||
NOTE: ต้องอยู่ "หลัง" #testimony-overlay (z 9000) และ #suspect-pick-overlay (z 10045)
|
||||
เพื่อไม่ทับ overlay สำคัญ — ตั้งค่าต่ำกว่า 9000 (สูงพอเหนือ HUD โถง) */
|
||||
@@ -1613,7 +1615,7 @@
|
||||
<script src="js/display-name.js?v=2"></script>
|
||||
<script src="js/version.js?v=0.0122"></script>
|
||||
<script src="js/customize-popup.js?v=31" data-customize-triggers="" data-customize-asset-base="img/03-5-Customize"></script>
|
||||
<script src="js/room-lobby.js?v=0.0278"></script>
|
||||
<script src="js/room-lobby.js?v=0.0282"></script>
|
||||
<div class="version-tag">v —</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1953,6 +1953,8 @@ function defaultGameTiming() {
|
||||
forcedMinigameKeys: ['', '', ''],
|
||||
/** โหมดเทสต์: บังคับการ์ดพิเศษต่อเกม { mapId: cardId } */
|
||||
testSpecialCardByMap: {},
|
||||
/** โหมดเทสต์: บังคับเสนอบทตัวป่วน (ปกติเสนอเฉพาะเมื่อมีคนจริง > 4) */
|
||||
troublesomeForceOffer: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2341,6 +2343,7 @@ function loadGameTiming() {
|
||||
j.forcedMinigameKeys != null ? j.forcedMinigameKeys : j.forcedMinigameKey,
|
||||
),
|
||||
testSpecialCardByMap: normalizeTestSpecialCardByMap(j.testSpecialCardByMap),
|
||||
troublesomeForceOffer: !!j.troublesomeForceOffer,
|
||||
};
|
||||
}
|
||||
} catch (e) { console.error('loadGameTiming', e.message); }
|
||||
@@ -2554,6 +2557,9 @@ function saveGameTimingToFile(d) {
|
||||
testSpecialCardByMap: Object.prototype.hasOwnProperty.call(d, 'testSpecialCardByMap')
|
||||
? normalizeTestSpecialCardByMap(d.testSpecialCardByMap)
|
||||
: normalizeTestSpecialCardByMap(prev.testSpecialCardByMap),
|
||||
troublesomeForceOffer: Object.prototype.hasOwnProperty.call(d, 'troublesomeForceOffer')
|
||||
? !!d.troublesomeForceOffer
|
||||
: !!prev.troublesomeForceOffer,
|
||||
};
|
||||
fs.writeFileSync(GAME_TIMING_PATH, JSON.stringify(out, null, 2), 'utf8');
|
||||
runtimeGameTiming = out;
|
||||
@@ -3537,12 +3543,23 @@ function computeAndEmitTrialResult(sid, space) {
|
||||
}
|
||||
return String(id).slice(0, 6);
|
||||
});
|
||||
/* ตัวป่วน (ถ้ามีคนรับบท) — หา socket.id ปัจจุบันจาก playerKey/nickname (id เดิมตอนเสนออาจเปลี่ยนแล้ว) */
|
||||
let disruptorId = null;
|
||||
if (space.troublesomeAccept && (space.disruptorPlayerKey || space.disruptorNickname)) {
|
||||
for (const [pid, p] of space.peers) {
|
||||
const keyMatch = space.disruptorPlayerKey && p.playerKey === space.disruptorPlayerKey;
|
||||
const nickMatch = space.disruptorNickname && normalizeLobbyNickname(p.nickname) === space.disruptorNickname;
|
||||
if (keyMatch || nickMatch) { disruptorId = pid; break; }
|
||||
}
|
||||
}
|
||||
io.to(sid).emit('trial-result', {
|
||||
culpritIndex: culprit,
|
||||
counts,
|
||||
winners,
|
||||
winnerNames,
|
||||
cardMinigames: space.suspectCardMinigames || [],
|
||||
disruptorId,
|
||||
hasDisruptor: !!disruptorId,
|
||||
});
|
||||
space.silencedPlayerId = null;
|
||||
}
|
||||
@@ -6717,6 +6734,12 @@ function attemptTroublesomeRoll(sid, force) {
|
||||
const peerIds = [...space.peers.keys()];
|
||||
if (peerIds.length === 0) return;
|
||||
if (!force && space.troublesomeEligible.size < peerIds.length) return;
|
||||
/* เสนอบทตัวป่วนเฉพาะเมื่อมี "คนจริง > 4 คน" — ยกเว้น admin บังคับ (troublesomeForceOffer) */
|
||||
const forceOffer = !!(runtimeGameTiming && runtimeGameTiming.troublesomeForceOffer);
|
||||
if (peerIds.length <= 4 && !forceOffer) {
|
||||
space.troublesomeOfferSent = true; /* มาร์คว่าผ่านรอบนี้ไปแล้ว — ไม่มีตัวป่วน */
|
||||
return;
|
||||
}
|
||||
space.troublesomeOfferSent = true;
|
||||
if (space.troublesomeDebTimer) clearTimeout(space.troublesomeDebTimer);
|
||||
if (space.troublesomeMaxTimer) clearTimeout(space.troublesomeMaxTimer);
|
||||
@@ -7479,6 +7502,15 @@ io.on('connection', (socket) => {
|
||||
if (space.troublesomeResponseReceived) return;
|
||||
space.troublesomeResponseReceived = true;
|
||||
space.troublesomeAccept = accept;
|
||||
/* เก็บตัวตนตัวป่วนแบบถาวร (playerKey/nickname) — socket.id เปลี่ยนหลังเล่นมินิเกม ใช้ id ตรง ๆ ไม่ได้ */
|
||||
if (accept) {
|
||||
const dPeer = space.peers.get(socket.id);
|
||||
space.disruptorPlayerKey = (dPeer && dPeer.playerKey) ? dPeer.playerKey : '';
|
||||
space.disruptorNickname = dPeer ? normalizeLobbyNickname(dPeer.nickname) : '';
|
||||
} else {
|
||||
space.disruptorPlayerKey = '';
|
||||
space.disruptorNickname = '';
|
||||
}
|
||||
let idx = Math.floor(Number(space.suspectPickIndex));
|
||||
if (Number.isNaN(idx) || idx < 0 || idx > 2) idx = 0;
|
||||
space.suspectPickIndex = idx;
|
||||
@@ -7895,6 +7927,9 @@ io.on('connection', (socket) => {
|
||||
space.troublesomeOfferSent = false;
|
||||
space.troublesomeTargetId = null;
|
||||
space.troublesomeResponseReceived = false;
|
||||
space.troublesomeAccept = false;
|
||||
space.disruptorPlayerKey = '';
|
||||
space.disruptorNickname = '';
|
||||
space.suspectPhaseActive = false;
|
||||
space.suspectPickIndex = 0;
|
||||
space.detectiveLobbyLevel = Number(lobbyLevel) || 1;
|
||||
|
||||
Reference in New Issue
Block a user