vote and bot on lobby
This commit is contained in:
@@ -1782,9 +1782,10 @@
|
||||
function add(u) {
|
||||
if (out.indexOf(u) === -1) out.push(u);
|
||||
}
|
||||
// idle เฟรมเดียว — ชื่อจริงของ char-* คือ *_idle_layer_*.png (ไม่มี frame index) ลองตัวนี้ก่อนกัน 404 รัว
|
||||
add(base + '_layer_' + layerName + '.png');
|
||||
add(base + '_' + frameIndex + '_layer_' + layerName + '.png');
|
||||
if (frameIndex !== 0) add(base + '_0_layer_' + layerName + '.png');
|
||||
add(base + '_layer_' + layerName + '.png');
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -140,6 +140,12 @@
|
||||
let serverSuspectPhaseActive = false;
|
||||
/** มินิเกม 3 แบบที่สุ่มแล้ว — ล็อกกับการ์ด 0–2 จนกว่าสร้างห้องใหม่ */
|
||||
let suspectCardMinigames = [];
|
||||
/** หลักฐาน (ติกถูก) ที่เก็บได้ต่อผู้ต้องสงสัย 0–2 (เล่นมินิเกมจบ +1 ต่อช่อง สูงสุด 3) */
|
||||
let suspectProgress = [0, 0, 0];
|
||||
/** ขั้นพิจารณาคดี: null | 'voting' | 'revealed' */
|
||||
let trialMode = null;
|
||||
let myTrialVote = null;
|
||||
let trialVoteCounts = [0, 0, 0];
|
||||
let mapBackgroundImg = null;
|
||||
let hostIconImg = null;
|
||||
const peers = new Map();
|
||||
@@ -2371,6 +2377,7 @@
|
||||
ensureReadyControlEnabled();
|
||||
|
||||
if (res.cardMinigames) setSuspectCardMinigames(res.cardMinigames);
|
||||
if (Array.isArray(res.suspectProgress)) setSuspectProgress(res.suspectProgress);
|
||||
serverSuspectPhaseActive = !!res.suspectPhaseActive;
|
||||
if (res.suspectPhaseActive) {
|
||||
openSuspectOverlay(res.suspectPickIndex != null ? res.suspectPickIndex : 0);
|
||||
@@ -3624,6 +3631,426 @@
|
||||
suspectCardMinigames = cards;
|
||||
}
|
||||
|
||||
var SUSPECT_EVID_FULL = '/Main-Lobby/IMAGE/Choose%20a%20suspect/symbol-card.png'; // เก็บหลักฐานแล้ว (ติกถูก)
|
||||
var SUSPECT_EVID_EMPTY = '/Main-Lobby/IMAGE/Choose%20a%20suspect/symbol-card-bg.png'; // ช่องว่าง (?)
|
||||
|
||||
/** อัปเดต suspectProgress จากเซิร์ฟ แล้ววาดติกถูกใหม่ */
|
||||
function setSuspectProgress(arr) {
|
||||
if (!Array.isArray(arr)) return;
|
||||
suspectProgress = [0, 1, 2].map(function (i) {
|
||||
var v = Math.floor(Number(arr[i]));
|
||||
return (Number.isFinite(v) && v > 0) ? Math.min(3, v) : 0;
|
||||
});
|
||||
applySuspectProgressVisual();
|
||||
if (suspectPickOverlayOpen) updateSuspectHostUi(); // อัปเดตปุ่มชี้ตัว (โชว์เมื่อสืบครบ)
|
||||
}
|
||||
|
||||
/** วางติกถูกในช่อง "หลักฐาน" (.suspect-symbol-list) ตามจำนวนที่เก็บได้ */
|
||||
function applySuspectProgressVisual() {
|
||||
for (var i = 0; i <= 2; i++) {
|
||||
var card = document.querySelector('.suspect-card[data-index="' + i + '"]');
|
||||
if (!card) continue;
|
||||
var slots = card.querySelectorAll('.suspect-symbol-list img');
|
||||
var done = Math.max(0, Math.min(3, (suspectProgress && suspectProgress[i]) || 0));
|
||||
for (var s = 0; s < slots.length; s++) {
|
||||
var want = (s < done) ? SUSPECT_EVID_FULL : SUSPECT_EVID_EMPTY;
|
||||
if (slots[s].getAttribute('src') !== want) slots[s].src = want;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== ขั้นพิจารณาคดี — โหวตชี้ตัวคนร้าย (ใช้ overlay เดิม) ===== */
|
||||
function injectTrialStyle() {
|
||||
if (document.getElementById('trial-style')) return;
|
||||
var st = document.createElement('style');
|
||||
st.id = 'trial-style';
|
||||
st.textContent =
|
||||
'.suspect-pick--trial .suspect-pick-actions{display:none!important}' +
|
||||
'.suspect-pick--trial #suspect-btn-accuse{display:none!important}' + /* #1 ปุ่มชี้ตัวต้องหายตอนหน้าโหวต */
|
||||
'#trial-vote-counter{position:absolute;right:22px;top:14px;color:#7fe9ff;font:800 24px/1 Kanit,system-ui,sans-serif;display:none;z-index:62;text-shadow:0 0 12px rgba(34,211,238,.7)}' +
|
||||
'#trial-vote-counter b{color:#fff}' +
|
||||
'.suspect-pick--trial #trial-vote-counter{display:block}' +
|
||||
'.suspect-pick--trial .suspect-pick-title-img{display:none!important}' +
|
||||
'#trial-title{display:none;text-align:center;color:#7fe9ff;font:800 40px/1.1 Kanit,system-ui,sans-serif;text-shadow:0 0 18px rgba(34,211,238,.7);margin:0 0 2px}' +
|
||||
'#trial-title small{display:block;color:#cfe9ff;font:600 18px/1.3 Kanit,system-ui,sans-serif;text-shadow:none;margin-top:6px}' +
|
||||
'.suspect-pick--trial #trial-title{display:block}' +
|
||||
'.suspect-pick--trial .suspect-card{cursor:pointer;position:relative}' +
|
||||
// badge อยู่กลางล่างการ์ด (เลียนแบบปุ่ม "เลือกแล้ว" ใน mockup)
|
||||
'.suspect-card .trial-vote-badge{position:absolute;left:50%;bottom:12px;transform:translateX(-50%);color:#06121f;font:700 15px/1 Kanit,system-ui,sans-serif;padding:7px 16px;border-radius:999px;display:none;z-index:6;white-space:nowrap;box-shadow:0 2px 10px rgba(0,0,0,.45)}' +
|
||||
// ตอนโหวต: โชว์เฉพาะใบที่เราเลือก = "เลือกแล้ว" (เขียว)
|
||||
'.suspect-pick--trial .suspect-card--myvote .trial-vote-badge{display:block;background:rgba(34,197,94,.97)}' +
|
||||
// ตอนเปิดเผยผล: โชว์คะแนนโหวตทุกใบ (ฟ้า)
|
||||
'.suspect-pick--trial.trial-revealed .trial-vote-badge{display:block;background:rgba(34,211,238,.97)}' +
|
||||
'.suspect-card--myvote{outline:3px solid #22c55e;outline-offset:-3px;border-radius:16px}' +
|
||||
'.suspect-card--culprit{outline:4px solid #f7768e;outline-offset:-4px;border-radius:16px;box-shadow:0 0 34px rgba(247,118,142,.85)}' +
|
||||
'#trial-result-banner{position:absolute;left:50%;top:5%;transform:translateX(-50%);background:rgba(6,12,24,.96);border:2px solid #22d3ee;border-radius:16px;padding:14px 26px;color:#e7ecff;font:600 20px/1.5 Kanit,system-ui,sans-serif;text-align:center;z-index:60;max-width:82%;box-shadow:0 8px 40px rgba(0,0,0,.6)}' +
|
||||
'#trial-reveal-btn{position:absolute;left:50%;bottom:5%;transform:translateX(-50%);background:linear-gradient(180deg,#22d3ee,#0e7490);color:#06121f;border:none;border-radius:14px;padding:14px 30px;font:700 20px/1 Kanit,system-ui,sans-serif;cursor:pointer;z-index:60;box-shadow:0 6px 22px rgba(34,211,238,.5)}';
|
||||
document.head.appendChild(st);
|
||||
}
|
||||
|
||||
function ensureTrialBadges() {
|
||||
for (var i = 0; i <= 2; i++) {
|
||||
var card = document.querySelector('.suspect-card[data-index="' + i + '"]');
|
||||
if (!card) continue;
|
||||
if (!card.querySelector('.trial-vote-badge')) {
|
||||
var b = document.createElement('span');
|
||||
b.className = 'trial-vote-badge';
|
||||
b.textContent = '';
|
||||
card.appendChild(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applyTrialBadges() {
|
||||
for (var i = 0; i <= 2; i++) {
|
||||
var card = document.querySelector('.suspect-card[data-index="' + i + '"]');
|
||||
if (!card) continue;
|
||||
var b = card.querySelector('.trial-vote-badge');
|
||||
if (b) b.textContent = 'โหวต ' + ((trialVoteCounts && trialVoteCounts[i]) || 0);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureTrialBanner() {
|
||||
var ov = document.getElementById('suspect-pick-overlay');
|
||||
var el = document.getElementById('trial-result-banner');
|
||||
if (!el && ov) {
|
||||
el = document.createElement('div');
|
||||
el.id = 'trial-result-banner';
|
||||
el.style.display = 'none';
|
||||
ov.appendChild(el);
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
function ensureTrialCounter() {
|
||||
var ov = document.getElementById('suspect-pick-overlay');
|
||||
var el = document.getElementById('trial-vote-counter');
|
||||
if (!el && ov) {
|
||||
el = document.createElement('div');
|
||||
el.id = 'trial-vote-counter';
|
||||
ov.appendChild(el);
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
function setTrialCounter(voted, total) {
|
||||
var el = ensureTrialCounter();
|
||||
if (el) el.innerHTML = 'ลงติกแล้ว : <b>' + (voted || 0) + '/' + (total || 0) + '</b>';
|
||||
}
|
||||
|
||||
function ensureTrialRevealBtn() {
|
||||
var ov = document.getElementById('suspect-pick-overlay');
|
||||
var btn = document.getElementById('trial-reveal-btn');
|
||||
if (!btn && ov) {
|
||||
btn = document.createElement('button');
|
||||
btn.id = 'trial-reveal-btn';
|
||||
btn.type = 'button';
|
||||
btn.textContent = 'เปิดเผยผล';
|
||||
btn.addEventListener('click', function () {
|
||||
socket.emit('trial-reveal', {}, function (res) {
|
||||
if (!(res && res.ok)) appendLobbySystemChat('— เปิดเผยผลไม่สำเร็จ' + (res && res.error ? (' · ' + res.error) : ''));
|
||||
});
|
||||
});
|
||||
ov.appendChild(btn);
|
||||
}
|
||||
if (btn) btn.style.display = (trialMode === 'voting' && hostId === socket.id) ? 'block' : 'none';
|
||||
}
|
||||
|
||||
/** หัวข้อ "พิจารณาคดี" (แทนรูป title เลือกผู้ต้องสงสัยตอนอยู่โหมดโหวต) */
|
||||
function ensureTrialTitle() {
|
||||
var row = document.getElementById('suspect-cards-row');
|
||||
var t = document.getElementById('trial-title');
|
||||
if (!t && row && row.parentNode) {
|
||||
t = document.createElement('div');
|
||||
t.id = 'trial-title';
|
||||
t.innerHTML = 'พิจารณาคดี<small>เลือกผู้ร้ายตัวจริง และส่งตัวไปดำเนินคดี เพื่อทำภารกิจให้สำเร็จ</small>';
|
||||
row.parentNode.insertBefore(t, row);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
function setTrialSubtitle(txt) {
|
||||
var t = document.getElementById('trial-title');
|
||||
if (!t) return;
|
||||
var s = t.querySelector('small');
|
||||
if (s) s.textContent = txt;
|
||||
}
|
||||
|
||||
function castTrialVote(idx) {
|
||||
if (trialMode !== 'voting') return;
|
||||
myTrialVote = idx;
|
||||
document.querySelectorAll('.suspect-card').forEach(function (c) {
|
||||
var sel = parseInt(c.getAttribute('data-index'), 10) === idx;
|
||||
c.classList.toggle('suspect-card--myvote', sel);
|
||||
var b = c.querySelector('.trial-vote-badge');
|
||||
if (b && sel) b.textContent = '✓ เลือกแล้ว';
|
||||
});
|
||||
socket.emit('trial-vote', { index: idx }, function (res) {
|
||||
if (!(res && res.ok)) appendLobbySystemChat('— โหวตไม่สำเร็จ' + (res && res.error ? (' · ' + res.error) : ''));
|
||||
});
|
||||
}
|
||||
|
||||
function openTrialVoteUI(data) {
|
||||
trialMode = 'voting';
|
||||
myTrialVote = null;
|
||||
trialVoteCounts = [0, 0, 0];
|
||||
if (data && data.cardMinigames) setSuspectCardMinigames(data.cardMinigames);
|
||||
if (data && Array.isArray(data.suspectProgress)) suspectProgress = [0, 1, 2].map(function (i) { return Math.min(3, Math.max(0, Math.floor(Number(data.suspectProgress[i])) || 0)); });
|
||||
injectTrialStyle();
|
||||
var ov = document.getElementById('suspect-pick-overlay');
|
||||
if (!ov) return;
|
||||
bindSuspectPickScaleListeners();
|
||||
suspectPickOverlayOpen = true;
|
||||
serverSuspectPhaseActive = false;
|
||||
ov.classList.remove('is-hidden', 'trial-revealed');
|
||||
ov.classList.add('suspect-pick--trial');
|
||||
ov.setAttribute('aria-hidden', 'false');
|
||||
document.querySelectorAll('.suspect-card').forEach(function (c) {
|
||||
c.classList.remove('suspect-card--myvote', 'suspect-card--culprit', 'suspect-card--selected');
|
||||
});
|
||||
applySuspectProgressVisual();
|
||||
ensureTrialTitle();
|
||||
ensureTrialBadges();
|
||||
document.querySelectorAll('.trial-vote-badge').forEach(function (b) { b.textContent = ''; });
|
||||
setTrialCounter(0, (data && data.totalPlayers) || (peers ? peers.size : 0) || 1);
|
||||
// กันปุ่มชี้ตัวค้าง: ใส่ is-hidden ตอนเข้าหน้าโหวต
|
||||
var accuseBtn0 = document.getElementById('suspect-btn-accuse');
|
||||
if (accuseBtn0) accuseBtn0.classList.add('is-hidden');
|
||||
ensureTrialRevealBtn();
|
||||
var banner = ensureTrialBanner();
|
||||
if (banner) banner.style.display = 'none';
|
||||
var actions = document.getElementById('suspect-pick-actions');
|
||||
if (actions) actions.classList.remove('suspect-pick-actions--visible');
|
||||
var hint = document.getElementById('suspect-pick-hint');
|
||||
if (hint) { hint.classList.remove('is-hidden'); hint.textContent = 'คลิกการ์ดที่คิดว่าเป็นคนร้าย'; }
|
||||
updateSuspectFloatingOpenBtn();
|
||||
scheduleSuspectPickScale();
|
||||
}
|
||||
|
||||
function showTrialResult(data) {
|
||||
trialMode = 'revealed';
|
||||
var culprit = (data && typeof data.culpritIndex === 'number') ? data.culpritIndex : 0;
|
||||
if (data && Array.isArray(data.counts)) trialVoteCounts = data.counts;
|
||||
var ov = document.getElementById('suspect-pick-overlay');
|
||||
if (ov) ov.classList.add('trial-revealed');
|
||||
// โชว์คะแนนโหวตทุกใบ + ไฮไลต์คนร้าย
|
||||
document.querySelectorAll('.suspect-card').forEach(function (c) {
|
||||
var ci = parseInt(c.getAttribute('data-index'), 10);
|
||||
c.classList.remove('suspect-card--myvote');
|
||||
c.classList.toggle('suspect-card--culprit', ci === culprit);
|
||||
var b = c.querySelector('.trial-vote-badge');
|
||||
if (b) b.textContent = 'โหวต ' + ((trialVoteCounts && trialVoteCounts[ci]) || 0);
|
||||
});
|
||||
setTrialSubtitle('ผลการพิจารณาคดี');
|
||||
var names = (data && data.winnerNames && data.winnerNames.length) ? data.winnerNames.join(', ') : 'ไม่มีใครตอบถูก';
|
||||
var iWon = !!(data && Array.isArray(data.winners) && data.winners.indexOf(socket.id) >= 0);
|
||||
var banner = ensureTrialBanner();
|
||||
if (banner) {
|
||||
banner.innerHTML = 'คนร้ายตัวจริงคือ <b>ผู้ต้องสงสัยหมายเลข ' + (culprit + 1) + '</b><br>' +
|
||||
'ผู้ตอบถูก: ' + names + (iWon ? '<br>🎉 คุณตอบถูก! +100' : '');
|
||||
banner.style.display = 'block';
|
||||
}
|
||||
var rb = document.getElementById('trial-reveal-btn');
|
||||
if (rb) rb.style.display = 'none';
|
||||
var hint = document.getElementById('suspect-pick-hint');
|
||||
if (hint) hint.textContent = 'ปิดหน้าต่างเพื่อกลับโถง';
|
||||
appendLobbySystemChat('— เปิดเผยผล: คนร้ายคือผู้ต้องสงสัยหมายเลข ' + (culprit + 1) + ' · ผู้ตอบถูก: ' + names);
|
||||
}
|
||||
|
||||
/* ===== ห้องสรุปหลักฐาน — การไต่สวน (เลือกหลักฐาน 2 ใบ/ปากคำ ก่อนโหวต) ===== */
|
||||
var tmState = { round: 0, members: [], picks: [], submitted: false, mode: 'select' };
|
||||
var SUSPECT_NAMES_EN = ['SOMCHAI', 'ARIN', 'JIRAPHA'];
|
||||
|
||||
function injectTestimonyStyle() {
|
||||
if (document.getElementById('testimony-style')) return;
|
||||
var st = document.createElement('style');
|
||||
st.id = 'testimony-style';
|
||||
st.textContent =
|
||||
'#testimony-overlay{position:fixed;inset:0;z-index:9000;background:rgba(4,8,18,.92);display:flex;flex-direction:column;align-items:center;gap:14px;padding:24px 16px;overflow:auto;font-family:Kanit,system-ui,sans-serif;color:#e7ecff}' +
|
||||
'#testimony-overlay.is-hidden{display:none}' +
|
||||
'.tm-head{text-align:center}.tm-head h2{margin:0;color:#fff;font:800 30px/1.2 Kanit;text-shadow:0 0 16px rgba(34,211,238,.6)}.tm-head .tm-sub{margin:4px 0 0;color:#9fe7ff;font:600 16px/1.3 Kanit}' +
|
||||
'.tm-members{display:flex;gap:12px;flex-wrap:wrap;justify-content:center;max-width:1100px}' +
|
||||
'.tm-member{min-width:120px;border:2px solid #1e6f8c;border-radius:12px;padding:8px 10px;text-align:center;background:rgba(10,22,40,.7)}' +
|
||||
'.tm-member .tm-mname{font:700 14px/1 Kanit;color:#bfe9ff;margin-bottom:6px;white-space:nowrap}' +
|
||||
'.tm-member .tm-mstat{font:600 13px/1.2 Kanit;color:#8aa0c0}' +
|
||||
'.tm-member.tm-ready{border-color:#22c55e;box-shadow:0 0 14px rgba(34,197,94,.5)}.tm-member.tm-ready .tm-mstat{color:#22c55e}' +
|
||||
'.tm-member.tm-me{outline:2px solid #22d3ee;outline-offset:2px}' +
|
||||
'.tm-crown{color:#ffd54a}' +
|
||||
'.tm-count{color:#ffd54a;font:700 18px/1 Kanit}' +
|
||||
'.tm-cards{display:flex;gap:14px;flex-wrap:wrap;justify-content:center;max-width:1100px}' +
|
||||
'.tm-cards .lobby-evidence-card{cursor:pointer;transition:transform .12s,box-shadow .12s}' +
|
||||
'.tm-cards .lobby-evidence-card.tm-sel{outline:3px solid #22d3ee;outline-offset:2px;transform:translateY(-4px);box-shadow:0 8px 26px rgba(34,211,238,.5)}' +
|
||||
'.tm-actions{display:flex;gap:14px;align-items:center}' +
|
||||
'.tm-btn{background:linear-gradient(180deg,#22d3ee,#0e7490);color:#06121f;border:none;border-radius:14px;padding:13px 30px;font:700 19px/1 Kanit;cursor:pointer;box-shadow:0 6px 20px rgba(34,211,238,.45)}' +
|
||||
'.tm-btn:disabled{opacity:.45;cursor:not-allowed;filter:grayscale(.5)}' +
|
||||
'.tm-btn--ghost{background:transparent;color:#7fe9ff;border:2px solid #2a7e9c;box-shadow:none}' +
|
||||
'.tm-reveal-grid{display:flex;gap:16px;flex-wrap:wrap;justify-content:center;max-width:1200px}' +
|
||||
'.tm-rev-col{display:flex;flex-direction:column;align-items:center;gap:8px}' +
|
||||
'.tm-rev-col .tm-rev-name{font:700 15px/1 Kanit;color:#bfe9ff}' +
|
||||
'.tm-rev-col .lobby-evidence-card{transform:scale(.82);margin:-14px 0}' +
|
||||
'.tm-status{color:#ffd54a;font:600 15px/1.3 Kanit;text-align:center}';
|
||||
document.head.appendChild(st);
|
||||
}
|
||||
|
||||
function tmEvidenceCardEl(c, linkName, cardIndex, selectable) {
|
||||
var rar = String(c.rarity || 'common').toLowerCase();
|
||||
if (!LOBBY_EVIDENCE_RARITY[rar]) rar = 'common';
|
||||
var nStar = Math.max(1, Math.min(3, Number(c.stars) || 1));
|
||||
var stars = ''; for (var st = 0; st < nStar; st++) stars += '★';
|
||||
var card = document.createElement('article');
|
||||
card.className = 'lobby-evidence-card lobby-evidence-card--' + rar;
|
||||
if (selectable) card.setAttribute('data-card-index', String(cardIndex));
|
||||
card.innerHTML =
|
||||
'<div class="lobby-evidence-card-head"><span class="lobby-evidence-card-icon" aria-hidden="true">🔎</span>' +
|
||||
'<div class="lobby-evidence-card-titles"><p class="lobby-evidence-card-title-th"></p><p class="lobby-evidence-card-title-en"></p></div></div>' +
|
||||
'<div class="lobby-evidence-card-art" aria-hidden="true">◆</div>' +
|
||||
'<p class="lobby-evidence-card-body"></p>' +
|
||||
'<div class="lobby-evidence-card-foot"><div class="lobby-evidence-link"><span class="lobby-evidence-avatar"></span><span class="lobby-evidence-link-name"></span></div>' +
|
||||
'<div class="lobby-evidence-foot-meta"><span class="lobby-evidence-rarity">(' + LOBBY_EVIDENCE_RARITY[rar] + ')</span><div class="lobby-evidence-stars">' + stars + '</div></div></div>';
|
||||
card.querySelector('.lobby-evidence-card-title-th').textContent = c.titleTh || '';
|
||||
card.querySelector('.lobby-evidence-card-title-en').textContent = c.titleEn ? '(' + c.titleEn + ')' : '';
|
||||
card.querySelector('.lobby-evidence-card-body').textContent = c.body || '';
|
||||
card.querySelector('.lobby-evidence-avatar').textContent = (linkName || '?').charAt(0);
|
||||
card.querySelector('.lobby-evidence-link-name').textContent = linkName || '';
|
||||
return card;
|
||||
}
|
||||
|
||||
function tmSuspectData(round) {
|
||||
var payload = getLobbyEvidenceCasePayload();
|
||||
var suspects = payload.suspects || [];
|
||||
var si = Math.max(0, Math.min(suspects.length - 1, round));
|
||||
return suspects[si] || { linkName: '?', cards: [] };
|
||||
}
|
||||
|
||||
function ensureTestimonyOverlay() {
|
||||
var ov = document.getElementById('testimony-overlay');
|
||||
if (ov) return ov;
|
||||
injectTestimonyStyle();
|
||||
ov = document.createElement('div');
|
||||
ov.id = 'testimony-overlay';
|
||||
ov.className = 'is-hidden';
|
||||
ov.innerHTML =
|
||||
'<div class="tm-head"><h2 id="tm-title"></h2><p class="tm-sub" id="tm-sub"></p></div>' +
|
||||
'<div class="tm-members" id="tm-members"></div>' +
|
||||
'<div id="tm-select-wrap" style="display:flex;flex-direction:column;align-items:center;gap:12px;width:100%">' +
|
||||
'<div style="display:flex;gap:18px;align-items:center;flex-wrap:wrap;justify-content:center"><span class="tm-sub">[เลือกพยานหลักฐาน 2 ชิ้น เพื่อประกอบสำนวนคดีนี้]</span><span class="tm-count" id="tm-count">เลือกหลักฐาน 0/2</span></div>' +
|
||||
'<div class="tm-cards" id="tm-cards"></div>' +
|
||||
'<div class="tm-status" id="tm-status"></div>' +
|
||||
'<div class="tm-actions"><button type="button" class="tm-btn" id="tm-submit" disabled>ส่งหลักฐาน</button><button type="button" class="tm-btn tm-btn--ghost" id="tm-force-reveal" style="display:none">เปิดหลักฐานทั้งหมด</button></div>' +
|
||||
'</div>' +
|
||||
'<div id="tm-reveal-wrap" style="display:none;flex-direction:column;align-items:center;gap:14px;width:100%">' +
|
||||
'<div class="tm-reveal-grid" id="tm-reveal-grid"></div>' +
|
||||
'<button type="button" class="tm-btn" id="tm-ready">READY</button>' +
|
||||
'</div>';
|
||||
document.body.appendChild(ov);
|
||||
ov.querySelector('#tm-cards').addEventListener('click', function (e) {
|
||||
var card = e.target.closest('.lobby-evidence-card'); if (!card || tmState.submitted) return;
|
||||
var idx = parseInt(card.getAttribute('data-card-index'), 10);
|
||||
if (!(idx >= 0)) return;
|
||||
var pos = tmState.picks.indexOf(idx);
|
||||
if (pos >= 0) { tmState.picks.splice(pos, 1); card.classList.remove('tm-sel'); }
|
||||
else { if (tmState.picks.length >= 2) return; tmState.picks.push(idx); card.classList.add('tm-sel'); }
|
||||
tmUpdateCount();
|
||||
});
|
||||
ov.querySelector('#tm-submit').addEventListener('click', function () {
|
||||
if (tmState.picks.length !== 2 || tmState.submitted) return;
|
||||
socket.emit('testimony-submit', { picks: tmState.picks.slice(0, 2) }, function (res) {
|
||||
if (res && res.ok) { tmState.submitted = true; tmSetStatus('ส่งหลักฐานแล้ว — รอผู้เล่นอื่น...'); document.getElementById('tm-submit').disabled = true; }
|
||||
else appendLobbySystemChat('— ส่งหลักฐานไม่สำเร็จ' + (res && res.error ? (' · ' + res.error) : ''));
|
||||
});
|
||||
});
|
||||
ov.querySelector('#tm-force-reveal').addEventListener('click', function () {
|
||||
socket.emit('testimony-reveal', {}, function (res) { if (!(res && res.ok)) appendLobbySystemChat('— เปิดเผยไม่สำเร็จ' + (res && res.error ? (' · ' + res.error) : '')); });
|
||||
});
|
||||
ov.querySelector('#tm-ready').addEventListener('click', function () {
|
||||
this.disabled = true;
|
||||
socket.emit('testimony-ready-next', {}, function (res) { if (!(res && res.ok)) { document.getElementById('tm-ready').disabled = false; appendLobbySystemChat('— READY ไม่สำเร็จ' + (res && res.error ? (' · ' + res.error) : '')); } });
|
||||
});
|
||||
return ov;
|
||||
}
|
||||
|
||||
function tmSetStatus(txt) { var el = document.getElementById('tm-status'); if (el) el.textContent = txt || ''; }
|
||||
function tmUpdateCount() {
|
||||
var el = document.getElementById('tm-count'); if (el) el.textContent = 'เลือกหลักฐาน ' + tmState.picks.length + '/2';
|
||||
var sub = document.getElementById('tm-submit'); if (sub) sub.disabled = (tmState.picks.length !== 2 || tmState.submitted);
|
||||
}
|
||||
|
||||
function tmRenderMembers(submittedIds, readyIds, revealMode) {
|
||||
var root = document.getElementById('tm-members'); if (!root) return;
|
||||
root.textContent = '';
|
||||
(tmState.members || []).forEach(function (m) {
|
||||
var d = document.createElement('div');
|
||||
d.className = 'tm-member' + (m.id === socket.id ? ' tm-me' : '');
|
||||
var done = revealMode ? (readyIds && readyIds.indexOf(m.id) >= 0) : (submittedIds && submittedIds.indexOf(m.id) >= 0);
|
||||
if (done) d.classList.add('tm-ready');
|
||||
d.innerHTML = '<div class="tm-mname">' + (m.isHost ? '<span class="tm-crown">♔</span> ' : '') + (m.nickname || '') + '</div><div class="tm-mstat">' + (done ? (revealMode ? 'พร้อม' : 'พร้อม ✓') : (revealMode ? 'กำลังดู..' : 'กำลังเลือก..')) + '</div>';
|
||||
root.appendChild(d);
|
||||
});
|
||||
}
|
||||
|
||||
function openTestimony(data) {
|
||||
if (typeof closeSuspectOverlay === 'function') closeSuspectOverlay();
|
||||
var ov = ensureTestimonyOverlay();
|
||||
tmState.round = (data && typeof data.round === 'number') ? data.round : 0;
|
||||
tmState.members = (data && Array.isArray(data.members)) ? data.members : [];
|
||||
tmState.picks = [];
|
||||
tmState.submitted = false;
|
||||
tmState.mode = 'select';
|
||||
var sd = tmSuspectData(tmState.round);
|
||||
document.getElementById('tm-title').textContent = 'การไต่สวน : ปากคำที่ ' + (tmState.round + 1) + ' - ' + (sd.linkName || ('ผู้ต้องสงสัย ' + (tmState.round + 1)));
|
||||
document.getElementById('tm-sub').textContent = 'ห้องสรุปหลักฐาน';
|
||||
document.getElementById('tm-select-wrap').style.display = 'flex';
|
||||
document.getElementById('tm-reveal-wrap').style.display = 'none';
|
||||
var fr = document.getElementById('tm-force-reveal');
|
||||
if (fr) fr.style.display = ((data && data.hostId) === socket.id) ? 'inline-block' : 'none';
|
||||
tmSetStatus('');
|
||||
// การ์ดของผู้ต้องสงสัยปัจจุบัน (ทุกคนเห็นครบ)
|
||||
var cardsRoot = document.getElementById('tm-cards'); cardsRoot.textContent = '';
|
||||
(sd.cards || []).forEach(function (c, i) { cardsRoot.appendChild(tmEvidenceCardEl(c, sd.linkName, i, true)); });
|
||||
tmUpdateCount();
|
||||
tmRenderMembers([], [], false);
|
||||
ov.classList.remove('is-hidden');
|
||||
}
|
||||
|
||||
function testimonyStatusUpdate(data) {
|
||||
if (!data) return;
|
||||
tmRenderMembers(data.submitted || [], [], false);
|
||||
}
|
||||
|
||||
function testimonyReveal(data) {
|
||||
tmState.mode = 'reveal';
|
||||
var ov = ensureTestimonyOverlay();
|
||||
ov.classList.remove('is-hidden');
|
||||
if (data && Array.isArray(data.members)) tmState.members = data.members;
|
||||
document.getElementById('tm-select-wrap').style.display = 'none';
|
||||
document.getElementById('tm-reveal-wrap').style.display = 'flex';
|
||||
document.getElementById('tm-sub').textContent = 'วิเคราะห์พยานหลักฐานกับทีม ก่อนไปปากคำถัดไป';
|
||||
var rd = document.getElementById('tm-ready'); if (rd) rd.disabled = false;
|
||||
var sd = tmSuspectData(tmState.round);
|
||||
var grid = document.getElementById('tm-reveal-grid'); grid.textContent = '';
|
||||
var picks = (data && data.picks) || {};
|
||||
(tmState.members || []).forEach(function (m) {
|
||||
var col = document.createElement('div'); col.className = 'tm-rev-col';
|
||||
var nm = document.createElement('div'); nm.className = 'tm-rev-name';
|
||||
nm.innerHTML = (m.isHost ? '<span class="tm-crown">♔</span> ' : '') + (m.nickname || '');
|
||||
col.appendChild(nm);
|
||||
var sel = picks[m.id] || [];
|
||||
if (!sel.length) { var none = document.createElement('div'); none.className = 'tm-status'; none.textContent = '(ไม่ได้ส่ง)'; col.appendChild(none); }
|
||||
sel.forEach(function (ci) { var c = (sd.cards || [])[ci]; if (c) col.appendChild(tmEvidenceCardEl(c, sd.linkName, ci, false)); });
|
||||
grid.appendChild(col);
|
||||
});
|
||||
tmRenderMembers([], [], true);
|
||||
}
|
||||
|
||||
function testimonyReadyUpdate(data) {
|
||||
tmRenderMembers([], (data && data.ready) || [], true);
|
||||
}
|
||||
|
||||
function hideTestimonyOverlay() {
|
||||
var ov = document.getElementById('testimony-overlay');
|
||||
if (ov) ov.classList.add('is-hidden');
|
||||
}
|
||||
|
||||
function applySuspectSelectionVisual(idx) {
|
||||
let i = Math.floor(Number(idx));
|
||||
if (Number.isNaN(i) || i < 0 || i > 2) i = 0;
|
||||
@@ -3645,6 +4072,7 @@
|
||||
const reopenSuspect = !!(data && data.suspectPhaseActive);
|
||||
const pickIdx = (data && typeof data.suspectPickIndex === 'number') ? data.suspectPickIndex : suspectSelectedIndex;
|
||||
if (data && data.cardMinigames) setSuspectCardMinigames(data.cardMinigames);
|
||||
if (data && Array.isArray(data.suspectProgress)) setSuspectProgress(data.suspectProgress);
|
||||
applyRoomLobbyBTransition({
|
||||
mapId: mid,
|
||||
peersSnap: (data && data.peersSnap) ? data.peersSnap : [],
|
||||
@@ -3752,15 +4180,24 @@
|
||||
btn.classList.toggle('is-hidden', !show);
|
||||
}
|
||||
|
||||
/** ชี้ตัวคนร้ายได้ต่อเมื่อสืบครบทั้ง 3 ผู้ต้องสงสัย (เล่นมินิเกมอย่างน้อยการ์ดละ 1 ครั้ง) */
|
||||
function allSuspectsInvestigated() {
|
||||
return [0, 1, 2].every(function (i) { return ((suspectProgress && suspectProgress[i]) || 0) >= 1; });
|
||||
}
|
||||
|
||||
function updateSuspectHostUi() {
|
||||
if (!suspectPickOverlayOpen) return;
|
||||
if (trialMode) { ensureTrialRevealBtn(); return; } // โหมดพิจารณาคดีคุม UI เอง
|
||||
const isHost = hostId === socket.id;
|
||||
const canAccuse = isHost && allSuspectsInvestigated();
|
||||
const actions = document.getElementById('suspect-pick-actions');
|
||||
const accuseBtn = document.getElementById('suspect-btn-accuse');
|
||||
const hint = document.getElementById('suspect-pick-hint');
|
||||
if (hint) {
|
||||
hint.textContent = isHost
|
||||
? 'เลือกการ์ดผู้ต้องสงสัยก่อน แล้วกดเริ่มสืบสวน'
|
||||
? (allSuspectsInvestigated()
|
||||
? 'สืบครบทุกผู้ต้องสงสัยแล้ว — กด "ชี้ตัวคนร้าย" เพื่อเข้าสู่การพิจารณาคดี'
|
||||
: 'เลือกการ์ดผู้ต้องสงสัยแล้วกดเริ่มสืบสวน (ต้องสืบให้ครบทั้ง 3 ก่อนชี้ตัวคนร้าย)')
|
||||
: 'รอ Host เลือกการ์ดและกดเริ่มสืบสวน';
|
||||
hint.classList.toggle('is-hidden', false);
|
||||
}
|
||||
@@ -3769,8 +4206,9 @@
|
||||
actions.setAttribute('aria-hidden', isHost ? 'false' : 'true');
|
||||
}
|
||||
if (accuseBtn) {
|
||||
accuseBtn.classList.toggle('is-hidden', !isHost);
|
||||
accuseBtn.setAttribute('aria-hidden', isHost ? 'false' : 'true');
|
||||
// โชว์ปุ่มชี้ตัวเฉพาะเมื่อ host + สืบครบ 3 ผู้ต้องสงสัยแล้ว
|
||||
accuseBtn.classList.toggle('is-hidden', !canAccuse);
|
||||
accuseBtn.setAttribute('aria-hidden', canAccuse ? 'false' : 'true');
|
||||
}
|
||||
document.querySelectorAll('.suspect-card').forEach((c) => {
|
||||
c.classList.toggle('suspect-card--host', isHost);
|
||||
@@ -3787,6 +4225,7 @@
|
||||
ov.classList.remove('is-hidden');
|
||||
ov.setAttribute('aria-hidden', 'false');
|
||||
applySuspectSelectionVisual(typeof selectedIndex === 'number' ? selectedIndex : 0);
|
||||
applySuspectProgressVisual();
|
||||
updateSuspectHostUi();
|
||||
updatePlayersHud();
|
||||
updateSuspectFloatingOpenBtn();
|
||||
@@ -3794,6 +4233,15 @@
|
||||
|
||||
function closeSuspectOverlay() {
|
||||
const ov = document.getElementById('suspect-pick-overlay');
|
||||
// รีเซ็ตสถานะพิจารณาคดี
|
||||
if (trialMode) {
|
||||
trialMode = null; myTrialVote = null; trialVoteCounts = [0, 0, 0];
|
||||
if (ov) ov.classList.remove('suspect-pick--trial', 'trial-revealed');
|
||||
document.querySelectorAll('.suspect-card').forEach(function (c) { c.classList.remove('suspect-card--myvote', 'suspect-card--culprit'); });
|
||||
document.querySelectorAll('.trial-vote-badge').forEach(function (b) { b.textContent = ''; });
|
||||
var banner = document.getElementById('trial-result-banner'); if (banner) banner.style.display = 'none';
|
||||
var rb = document.getElementById('trial-reveal-btn'); if (rb) rb.style.display = 'none';
|
||||
}
|
||||
const wrap = document.getElementById('suspect-pick-scale-wrap');
|
||||
const inner = wrap && wrap.querySelector('.suspect-pick-inner');
|
||||
if (inner) {
|
||||
@@ -3857,6 +4305,7 @@
|
||||
serverSuspectPhaseActive = true;
|
||||
if (data && data.hostId != null) hostId = data.hostId;
|
||||
if (data && data.cardMinigames) setSuspectCardMinigames(data.cardMinigames);
|
||||
if (data && Array.isArray(data.suspectProgress)) setSuspectProgress(data.suspectProgress);
|
||||
const idx = (data && typeof data.selectedIndex === 'number') ? data.selectedIndex : 0;
|
||||
openSuspectOverlay(idx);
|
||||
});
|
||||
@@ -3866,6 +4315,29 @@
|
||||
applySuspectSelectionVisual(idx);
|
||||
});
|
||||
|
||||
// ===== ห้องสรุปหลักฐาน (ไต่สวน) =====
|
||||
socket.on('testimony-open', (data) => { openTestimony(data || {}); });
|
||||
socket.on('testimony-status', (data) => { testimonyStatusUpdate(data || {}); });
|
||||
socket.on('testimony-reveal', (data) => { testimonyReveal(data || {}); });
|
||||
socket.on('testimony-ready-update', (data) => { testimonyReadyUpdate(data || {}); });
|
||||
|
||||
// ===== ขั้นพิจารณาคดี =====
|
||||
socket.on('trial-open', (data) => {
|
||||
hideTestimonyOverlay(); // ปิดห้องสรุปหลักฐานก่อนเข้าโหวต
|
||||
openTrialVoteUI(data || {});
|
||||
});
|
||||
socket.on('trial-vote-update', (data) => {
|
||||
if (data && Array.isArray(data.counts)) trialVoteCounts = data.counts; // เก็บไว้โชว์ตอนเปิดเผยผล (ไม่โชว์ต่อใบตอนโหวต)
|
||||
if (trialMode === 'voting') setTrialCounter((data && data.voted) || 0, (data && data.totalPlayers) || 0);
|
||||
const hint = document.getElementById('suspect-pick-hint');
|
||||
if (hint && trialMode === 'voting') {
|
||||
hint.textContent = 'คลิกการ์ดที่คิดว่าเป็นคนร้าย';
|
||||
}
|
||||
});
|
||||
socket.on('trial-result', (data) => {
|
||||
showTrialResult(data || {});
|
||||
});
|
||||
|
||||
socket.on('suspect-investigation-start', (data) => {
|
||||
serverSuspectPhaseActive = false;
|
||||
closeSuspectOverlay();
|
||||
@@ -3901,9 +4373,13 @@
|
||||
|
||||
document.getElementById('suspect-cards-row')?.addEventListener('click', (ev) => {
|
||||
const card = ev.target.closest('.suspect-card');
|
||||
if (!card || !suspectPickOverlayOpen || hostId !== socket.id) return;
|
||||
if (!card || !suspectPickOverlayOpen) return;
|
||||
const idx = parseInt(card.getAttribute('data-index'), 10);
|
||||
if (idx >= 0 && idx <= 2) selectSuspectCard(idx);
|
||||
if (!(idx >= 0 && idx <= 2)) return;
|
||||
if (trialMode === 'voting') { castTrialVote(idx); return; } // โหมดโหวต: ทุกคนคลิกได้
|
||||
if (trialMode === 'revealed') return;
|
||||
if (hostId !== socket.id) return;
|
||||
selectSuspectCard(idx);
|
||||
});
|
||||
|
||||
document.getElementById('suspect-btn-start')?.addEventListener('click', () => {
|
||||
@@ -3912,9 +4388,14 @@
|
||||
|
||||
document.getElementById('suspect-btn-accuse')?.addEventListener('click', () => {
|
||||
if (!suspectPickOverlayOpen || hostId !== socket.id) return;
|
||||
const pickNumber = Number.isFinite(suspectSelectedIndex) ? (suspectSelectedIndex + 1) : 1;
|
||||
appendLobbySystemChat('— Host ชี้ตัวคนร้ายหมายเลข ' + pickNumber);
|
||||
beginSuspectInvestigation('ชี้ตัวคนร้ายไม่สำเร็จ');
|
||||
if (!allSuspectsInvestigated()) {
|
||||
appendLobbySystemChat('— ต้องสืบให้ครบทั้ง 3 ผู้ต้องสงสัยก่อน จึงจะชี้ตัวคนร้ายได้');
|
||||
return;
|
||||
}
|
||||
// เข้าสู่ขั้นพิจารณาคดี/โหวต (ไม่ใช่เริ่มมินิเกม)
|
||||
socket.emit('suspect-accuse-open', {}, function (res) {
|
||||
if (!(res && res.ok)) appendLobbySystemChat('— เปิดพิจารณาคดีไม่สำเร็จ' + (res && res.error ? (' · ' + res.error) : ''));
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('suspect-pick-close')?.addEventListener('click', () => {
|
||||
@@ -3984,6 +4465,7 @@
|
||||
renderPeers();
|
||||
ensureReadyControlEnabled();
|
||||
if (data.cardMinigames) setSuspectCardMinigames(data.cardMinigames);
|
||||
if (Array.isArray(data.suspectProgress)) setSuspectProgress(data.suspectProgress);
|
||||
if (lobbyBotSlotCount > 0) syncLobbyCaseBots();
|
||||
appendLobbySystemChat('— ย้ายไป LobbyB แล้ว · ห้องนี้ไม่รับผู้เล่นใหม่');
|
||||
tryTroublesomeLobbyGate();
|
||||
@@ -4056,6 +4538,7 @@
|
||||
closeSuspectOverlay();
|
||||
closeLobbyPreplayWizard();
|
||||
if (data && data.cardMinigames) setSuspectCardMinigames(data.cardMinigames);
|
||||
if (data && Array.isArray(data.suspectProgress)) setSuspectProgress(data.suspectProgress);
|
||||
if (data && data.quizMode && data.stayInRoomLobby) {
|
||||
const qMid = (data.mapId != null) ? String(data.mapId).trim() : '';
|
||||
applyLobbyPeersSnapFromServer(data.peersSnap);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1665,6 +1665,13 @@ function assignDetectiveSuspectCardMinigames(space) {
|
||||
gameType: (md && md.gameType) || '',
|
||||
};
|
||||
});
|
||||
// ชุดการ์ดใหม่ = เริ่มนับหลักฐาน (ติกถูก) ใหม่ทั้งหมด
|
||||
space.suspectProgress = [0, 0, 0];
|
||||
// สุ่มคนร้ายตัวจริง 1 ใน 3 ใบ (เก็บฝั่ง server ไม่ส่งให้ client จนกว่าจะเปิดเผย)
|
||||
space.culpritIndex = Math.floor(Math.random() * 3);
|
||||
space.trialPhase = null; // null | 'voting' | 'revealed'
|
||||
space.trialVotes = {}; // socketId -> suspectIndex
|
||||
space.trialScores = space.trialScores || {};
|
||||
}
|
||||
|
||||
/** เริ่มมินิเกมตามการ์ดที่เลือก (หลังกดเริ่มสืบสวน) */
|
||||
@@ -1676,6 +1683,10 @@ function beginDetectiveSuspectMinigame(sid, space, cardEntry, selectedIndex) {
|
||||
space.suspectPhaseActive = false;
|
||||
space.detectiveMinigameActive = true;
|
||||
space.detectiveMinigameCardIndex = cardEntry.cardIndex;
|
||||
// จำว่ากำลังสืบผู้ต้องสงสัยคนไหน เพื่อเติมหลักฐาน (ติกถูก) ตอนเล่นจบ
|
||||
space.suspectActiveIndex = (typeof selectedIndex === 'number' && selectedIndex >= 0 && selectedIndex <= 2)
|
||||
? selectedIndex
|
||||
: (cardEntry && typeof cardEntry.cardIndex === 'number' ? cardEntry.cardIndex : 0);
|
||||
space.gauntletReturnMapId = POST_CASE_LOBBY_SPACE_ID;
|
||||
clearSpaceQuizTimers(space);
|
||||
if (space.quizSession) space.quizSession.active = false;
|
||||
@@ -1768,6 +1779,13 @@ function returnDetectiveSpaceToLobbyB(sid, space, message) {
|
||||
space.gauntletReturnMapId = null;
|
||||
stopGauntletTicker(space);
|
||||
space.gauntletRun = null;
|
||||
// เล่นมินิเกมจบ → เติมหลักฐาน (ติกถูก) 1 ช่องให้ผู้ต้องสงสัยที่กำลังสืบ (สะสมได้สูงสุด 3)
|
||||
if (!Array.isArray(space.suspectProgress) || space.suspectProgress.length !== 3) space.suspectProgress = [0, 0, 0];
|
||||
const _ai = (typeof space.suspectActiveIndex === 'number') ? space.suspectActiveIndex : -1;
|
||||
if (_ai >= 0 && _ai <= 2) {
|
||||
space.suspectProgress[_ai] = Math.min(3, (space.suspectProgress[_ai] || 0) + 1);
|
||||
}
|
||||
space.suspectActiveIndex = null;
|
||||
const lb = maps.get(POST_CASE_LOBBY_SPACE_ID);
|
||||
if (!lb) {
|
||||
io.to(sid).emit('quiz-ended', { message: message || 'จบมินิเกม — ไม่พบ LobbyB', returnToLobbyB: true });
|
||||
@@ -1795,6 +1813,7 @@ function returnDetectiveSpaceToLobbyB(sid, space, message) {
|
||||
suspectPhaseActive: true,
|
||||
suspectPickIndex: typeof space.suspectPickIndex === 'number' ? space.suspectPickIndex : 0,
|
||||
cardMinigames: space.suspectCardMinigames || [],
|
||||
suspectProgress: space.suspectProgress || [0, 0, 0],
|
||||
detectiveReturn: true,
|
||||
};
|
||||
io.to(sid).emit('quiz-ended', { message: message || 'จบมินิเกม — กลับ LobbyB', returnToLobbyB: true });
|
||||
@@ -1802,6 +1821,104 @@ function returnDetectiveSpaceToLobbyB(sid, space, message) {
|
||||
io.to(sid).emit('game-start', payload);
|
||||
}
|
||||
|
||||
/* ===== ขั้นพิจารณาคดี — โหวตชี้ตัวคนร้าย ===== */
|
||||
function emitTrialVoteUpdate(sid, space) {
|
||||
const counts = [0, 0, 0];
|
||||
const votes = space.trialVotes || {};
|
||||
Object.keys(votes).forEach((id) => { const v = votes[id]; if (v >= 0 && v <= 2) counts[v]++; });
|
||||
io.to(sid).emit('trial-vote-update', {
|
||||
counts,
|
||||
voted: Object.keys(votes).length,
|
||||
totalPlayers: space.peers ? space.peers.size : 0,
|
||||
});
|
||||
}
|
||||
|
||||
function computeAndEmitTrialResult(sid, space) {
|
||||
space.trialPhase = 'revealed';
|
||||
const counts = [0, 0, 0];
|
||||
const votes = space.trialVotes || {};
|
||||
const culprit = (typeof space.culpritIndex === 'number') ? space.culpritIndex : 0;
|
||||
const winners = [];
|
||||
if (!space.trialScores) space.trialScores = {};
|
||||
Object.keys(votes).forEach((id) => {
|
||||
const v = votes[id];
|
||||
if (v >= 0 && v <= 2) counts[v]++;
|
||||
if (v === culprit) {
|
||||
winners.push(id);
|
||||
space.trialScores[id] = (space.trialScores[id] || 0) + 100;
|
||||
}
|
||||
});
|
||||
const winnerNames = winners.map((id) => {
|
||||
const p = space.peers.get(id);
|
||||
return (p && p.nickname) ? p.nickname : String(id).slice(0, 6);
|
||||
});
|
||||
io.to(sid).emit('trial-result', {
|
||||
culpritIndex: culprit,
|
||||
counts,
|
||||
winners,
|
||||
winnerNames,
|
||||
cardMinigames: space.suspectCardMinigames || [],
|
||||
});
|
||||
}
|
||||
|
||||
/* ===== ห้องสรุปหลักฐาน — การไต่สวน (ปากคำ 3 รอบ ก่อนโหวต) ===== */
|
||||
function buildTestimonyMembers(space) {
|
||||
const arr = [];
|
||||
space.peers.forEach((p, id) => {
|
||||
arr.push({ id, nickname: (p && p.nickname) ? p.nickname : String(id).slice(0, 6), isHost: id === space.hostId });
|
||||
});
|
||||
return arr;
|
||||
}
|
||||
|
||||
function emitTestimonyOpen(sid, space) {
|
||||
space.testimonySubmitted = {};
|
||||
space.testimonyReadyNext = {};
|
||||
space.testimonyRevealed = false;
|
||||
io.to(sid).emit('testimony-open', {
|
||||
round: space.testimonyRound,
|
||||
suspectIndex: space.testimonyRound,
|
||||
members: buildTestimonyMembers(space),
|
||||
totalPlayers: space.peers.size,
|
||||
hostId: space.hostId,
|
||||
cardMinigames: space.suspectCardMinigames || [],
|
||||
suspectProgress: space.suspectProgress || [0, 0, 0],
|
||||
});
|
||||
}
|
||||
|
||||
function emitTestimonyStatus(sid, space) {
|
||||
io.to(sid).emit('testimony-status', {
|
||||
round: space.testimonyRound,
|
||||
submitted: Object.keys(space.testimonySubmitted || {}),
|
||||
totalPlayers: space.peers.size,
|
||||
});
|
||||
}
|
||||
|
||||
function doTestimonyReveal(sid, space) {
|
||||
space.testimonyRevealed = true;
|
||||
io.to(sid).emit('testimony-reveal', {
|
||||
round: space.testimonyRound,
|
||||
picks: space.testimonySubmitted || {},
|
||||
members: buildTestimonyMembers(space),
|
||||
});
|
||||
}
|
||||
|
||||
function advanceTestimony(sid, space) {
|
||||
space.testimonyRound = (typeof space.testimonyRound === 'number' ? space.testimonyRound : 0) + 1;
|
||||
if (space.testimonyRound >= 3) {
|
||||
space.testimonyActive = false;
|
||||
space.trialPhase = 'voting';
|
||||
space.trialVotes = {};
|
||||
io.to(sid).emit('trial-open', {
|
||||
hostId: space.hostId,
|
||||
cardMinigames: space.suspectCardMinigames || [],
|
||||
suspectProgress: space.suspectProgress || [0, 0, 0],
|
||||
});
|
||||
emitTrialVoteUpdate(sid, space);
|
||||
} else {
|
||||
emitTestimonyOpen(sid, space);
|
||||
}
|
||||
}
|
||||
|
||||
function endQuizGame(sid, space, message) {
|
||||
clearSpaceQuizTimers(space);
|
||||
if (space.quizSession) space.quizSession.active = false;
|
||||
@@ -4239,6 +4356,7 @@ io.on('connection', (socket) => {
|
||||
suspectPhaseActive: !!space.suspectPhaseActive && serverMapIsPostCaseLobbyB(space),
|
||||
suspectPickIndex: typeof space.suspectPickIndex === 'number' ? space.suspectPickIndex : 0,
|
||||
cardMinigames: space.suspectCardMinigames || [],
|
||||
suspectProgress: space.suspectProgress || [0, 0, 0],
|
||||
botSlotCount: effectiveBotSlotCount(space),
|
||||
joinLocked: !!space.joinLocked,
|
||||
};
|
||||
@@ -4326,6 +4444,7 @@ io.on('connection', (socket) => {
|
||||
selectedIndex: space.suspectPickIndex,
|
||||
disruptorAccepted: accept,
|
||||
cardMinigames: space.suspectCardMinigames || [],
|
||||
suspectProgress: space.suspectProgress || [0, 0, 0],
|
||||
});
|
||||
socket.emit('troublesome-ack', { ok: true, accept });
|
||||
});
|
||||
@@ -4384,6 +4503,99 @@ io.on('connection', (socket) => {
|
||||
reply({ ok: true });
|
||||
});
|
||||
|
||||
// Host เปิดขั้นพิจารณาคดี (ต้องสืบครบ 3 ผู้ต้องสงสัย)
|
||||
socket.on('suspect-accuse-open', (_data, cb) => {
|
||||
const reply = typeof cb === 'function' ? cb : () => {};
|
||||
const sid = socket.data.spaceId;
|
||||
const space = sid ? spaces.get(sid) : null;
|
||||
if (!space || !serverMapIsPostCaseLobbyB(space)) return reply({ ok: false, error: 'ต้องอยู่โถง LobbyB' });
|
||||
if (space.hostId !== socket.id) return reply({ ok: false, error: 'เฉพาะ host เปิดพิจารณาคดีได้' });
|
||||
const prog = Array.isArray(space.suspectProgress) ? space.suspectProgress : [0, 0, 0];
|
||||
if (![0, 1, 2].every((i) => (prog[i] || 0) >= 1)) return reply({ ok: false, error: 'ต้องสืบครบทั้ง 3 ผู้ต้องสงสัยก่อน' });
|
||||
if (typeof space.culpritIndex !== 'number') space.culpritIndex = Math.floor(Math.random() * 3);
|
||||
space.suspectPhaseActive = false;
|
||||
// เริ่ม "ห้องสรุปหลักฐาน" (ไต่สวน 3 ปากคำ) ก่อนเข้าโหวต
|
||||
space.testimonyActive = true;
|
||||
space.testimonyRound = 0;
|
||||
emitTestimonyOpen(sid, space);
|
||||
reply({ ok: true });
|
||||
});
|
||||
|
||||
// ผู้เล่นส่งหลักฐาน 2 ใบในปากคำปัจจุบัน
|
||||
socket.on('testimony-submit', (data, cb) => {
|
||||
const reply = typeof cb === 'function' ? cb : () => {};
|
||||
const sid = socket.data.spaceId;
|
||||
const space = sid ? spaces.get(sid) : null;
|
||||
if (!space || !space.peers.has(socket.id)) return reply({ ok: false, error: 'ไม่อยู่ในห้อง' });
|
||||
if (!space.testimonyActive) return reply({ ok: false, error: 'ไม่อยู่ในขั้นไต่สวน' });
|
||||
if (space.testimonyRevealed) return reply({ ok: false, error: 'ปากคำนี้เปิดเผยแล้ว' });
|
||||
const raw = (data && Array.isArray(data.picks)) ? data.picks : [];
|
||||
const picks = [];
|
||||
raw.forEach((n) => { const v = Math.floor(Number(n)); if (v >= 0 && v <= 8 && picks.indexOf(v) === -1) picks.push(v); });
|
||||
if (picks.length !== 2) return reply({ ok: false, error: 'ต้องเลือกหลักฐาน 2 ใบ' });
|
||||
if (!space.testimonySubmitted) space.testimonySubmitted = {};
|
||||
space.testimonySubmitted[socket.id] = picks.slice(0, 2);
|
||||
emitTestimonyStatus(sid, space);
|
||||
if (Object.keys(space.testimonySubmitted).length >= space.peers.size && space.peers.size > 0) doTestimonyReveal(sid, space);
|
||||
reply({ ok: true });
|
||||
});
|
||||
|
||||
// Host บังคับเปิดเผยหลักฐานทั้งหมด (เผื่อมีคนยังไม่ส่ง)
|
||||
socket.on('testimony-reveal', (_data, cb) => {
|
||||
const reply = typeof cb === 'function' ? cb : () => {};
|
||||
const sid = socket.data.spaceId;
|
||||
const space = sid ? spaces.get(sid) : null;
|
||||
if (!space) return reply({ ok: false, error: 'ไม่อยู่ในห้อง' });
|
||||
if (space.hostId !== socket.id) return reply({ ok: false, error: 'เฉพาะ host' });
|
||||
if (!space.testimonyActive || space.testimonyRevealed) return reply({ ok: false, error: 'เปิดเผยไม่ได้ตอนนี้' });
|
||||
doTestimonyReveal(sid, space);
|
||||
reply({ ok: true });
|
||||
});
|
||||
|
||||
// ผู้เล่นกด READY ไปปากคำถัดไป (หลังเปิดเผยผล)
|
||||
socket.on('testimony-ready-next', (_data, cb) => {
|
||||
const reply = typeof cb === 'function' ? cb : () => {};
|
||||
const sid = socket.data.spaceId;
|
||||
const space = sid ? spaces.get(sid) : null;
|
||||
if (!space || !space.peers.has(socket.id)) return reply({ ok: false, error: 'ไม่อยู่ในห้อง' });
|
||||
if (!space.testimonyActive || !space.testimonyRevealed) return reply({ ok: false, error: 'ยังเปิดเผยไม่ได้' });
|
||||
if (!space.testimonyReadyNext) space.testimonyReadyNext = {};
|
||||
space.testimonyReadyNext[socket.id] = true;
|
||||
io.to(sid).emit('testimony-ready-update', { ready: Object.keys(space.testimonyReadyNext), totalPlayers: space.peers.size });
|
||||
if (Object.keys(space.testimonyReadyNext).length >= space.peers.size && space.peers.size > 0) advanceTestimony(sid, space);
|
||||
reply({ ok: true });
|
||||
});
|
||||
|
||||
// ผู้เล่นโหวตชี้ตัวคนร้าย
|
||||
socket.on('trial-vote', (data, cb) => {
|
||||
const reply = typeof cb === 'function' ? cb : () => {};
|
||||
const sid = socket.data.spaceId;
|
||||
const space = sid ? spaces.get(sid) : null;
|
||||
if (!space || !space.peers.has(socket.id)) return reply({ ok: false, error: 'ไม่อยู่ในห้อง' });
|
||||
if (space.trialPhase !== 'voting') return reply({ ok: false, error: 'ยังไม่ถึงเวลาโหวต' });
|
||||
let idx = Math.floor(Number(data && data.index));
|
||||
if (Number.isNaN(idx) || idx < 0 || idx > 2) return reply({ ok: false, error: 'เลือกผู้ต้องสงสัยไม่ถูกต้อง' });
|
||||
space.trialVotes = space.trialVotes || {};
|
||||
space.trialVotes[socket.id] = idx;
|
||||
emitTrialVoteUpdate(sid, space);
|
||||
if (Object.keys(space.trialVotes).length >= space.peers.size && space.peers.size > 0) {
|
||||
computeAndEmitTrialResult(sid, space);
|
||||
}
|
||||
reply({ ok: true, index: idx });
|
||||
});
|
||||
|
||||
// Host บังคับเปิดเผยผล (เช่นมีคนยังไม่โหวต)
|
||||
socket.on('trial-reveal', (_data, cb) => {
|
||||
const reply = typeof cb === 'function' ? cb : () => {};
|
||||
const sid = socket.data.spaceId;
|
||||
const space = sid ? spaces.get(sid) : null;
|
||||
if (!space) return reply({ ok: false, error: 'ไม่อยู่ในห้อง' });
|
||||
if (space.hostId !== socket.id) return reply({ ok: false, error: 'เฉพาะ host' });
|
||||
if (space.trialPhase !== 'voting') return reply({ ok: false, error: 'ไม่อยู่ในขั้นโหวต' });
|
||||
computeAndEmitTrialResult(sid, space);
|
||||
reply({ ok: true });
|
||||
});
|
||||
|
||||
/* พร้อมแล้ว — ไม่บังคับจำนวนคนในห้อง */
|
||||
socket.on('set-ready', ({ ready }) => {
|
||||
for (const [sid, space] of spaces) {
|
||||
@@ -4480,6 +4692,7 @@ io.on('connection', (socket) => {
|
||||
caseId,
|
||||
peersSnap,
|
||||
cardMinigames: space.suspectCardMinigames || [],
|
||||
suspectProgress: space.suspectProgress || [0, 0, 0],
|
||||
});
|
||||
return reply({ ok: true });
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user