เก็บ flow
This commit is contained in:
@@ -263,6 +263,19 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: fill;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* card-back.png — ชั้นกลางทับ card-bg ทันเฟรม (โผล่เฉพาะตอน is-ready) */
|
||||
#testimony-overlay .es3-ps-card .card-back {
|
||||
position: absolute;
|
||||
inset: 6px;
|
||||
width: calc(100% - 12px);
|
||||
height: calc(100% - 12px);
|
||||
object-fit: contain;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
filter: drop-shadow(0 0 6px rgba(83, 224, 255, 0.35));
|
||||
}
|
||||
|
||||
#testimony-overlay .es3-ps-card .status-icon {
|
||||
@@ -272,7 +285,7 @@
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 2;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
#testimony-overlay .es3-ps-card .loading-icon {
|
||||
@@ -528,29 +541,9 @@
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* โซนคลิกทับปุ่ม "ส่งหลักฐาน" ใน tab-01.png — ไม่วาด btn-send ซ้ำ (เดิม 920×944 ทับผิดจุด) */
|
||||
#testimony-overlay .es3-btn-submit {
|
||||
left: calc(708 / var(--es3-w) * 100%);
|
||||
top: calc(727 / var(--es3-h) * 100%);
|
||||
width: calc(180 / var(--es3-w) * 100%);
|
||||
height: calc(77 / var(--es3-h) * 100%);
|
||||
max-width: 180px;
|
||||
max-height: 77px;
|
||||
min-width: 96px;
|
||||
min-height: 40px;
|
||||
background: transparent;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
#testimony-overlay .es3-btn-submit:not(:disabled):hover {
|
||||
filter: brightness(1.08);
|
||||
}
|
||||
|
||||
#testimony-overlay .es3-btn-submit:not(:disabled):active {
|
||||
filter: brightness(0.92);
|
||||
}
|
||||
|
||||
#testimony-overlay .es3-btn-open {
|
||||
/* ปุ่มหลัก bottom-right — สลับรูประหว่าง btn-send.png ↔ btn-open-card.png */
|
||||
#testimony-overlay .es3-btn-action {
|
||||
position: absolute;
|
||||
left: calc(1329 / var(--es3-w) * 100%);
|
||||
top: calc(944 / var(--es3-h) * 100%);
|
||||
width: calc(400 / var(--es3-w) * 100%);
|
||||
@@ -558,15 +551,42 @@
|
||||
max-width: 400px;
|
||||
max-height: 120px;
|
||||
z-index: 5;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
display: block;
|
||||
transition: filter 0.15s ease, transform 0.1s ease;
|
||||
}
|
||||
|
||||
#testimony-overlay .es3-btn-open img {
|
||||
#testimony-overlay .es3-btn-action img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#testimony-overlay .es3-btn-open.is-hidden {
|
||||
#testimony-overlay .es3-btn-action:hover {
|
||||
filter: brightness(1.1) drop-shadow(0 0 8px rgba(109, 240, 255, 0.6));
|
||||
}
|
||||
|
||||
#testimony-overlay .es3-btn-action:active {
|
||||
transform: scale(0.97);
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
|
||||
#testimony-overlay .es3-btn-action.is-disabled {
|
||||
cursor: not-allowed;
|
||||
filter: grayscale(0.7) opacity(0.45);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#testimony-overlay .es3-btn-action.is-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* legacy hidden — ไม่ใช้แล้วแต่กันโค้ดเก่าอ้างอิง */
|
||||
#testimony-overlay .es3-btn-submit-legacy,
|
||||
#testimony-overlay .es3-btn-open-legacy {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@@ -943,19 +963,41 @@
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
#testimony-overlay .es-reveal-wrap .es-ready .ready-active {
|
||||
#testimony-overlay .es-reveal-wrap .es-ready .ready-active,
|
||||
#testimony-overlay .es-reveal-wrap .es-ready .ready-start-vote {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
#testimony-overlay .es-reveal-wrap .es-ready.is-active .ready-default,
|
||||
#testimony-overlay .es-reveal-wrap .es-ready:disabled .ready-default {
|
||||
/* state: ready (กดแล้ว / disabled) → แสดง btn-ready-active.png */
|
||||
#testimony-overlay .es-reveal-wrap .es-ready.is-active:not(.is-start-vote) .ready-default,
|
||||
#testimony-overlay .es-reveal-wrap .es-ready:disabled:not(.is-start-vote) .ready-default {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#testimony-overlay .es-reveal-wrap .es-ready.is-active .ready-active,
|
||||
#testimony-overlay .es-reveal-wrap .es-ready:disabled .ready-active {
|
||||
#testimony-overlay .es-reveal-wrap .es-ready.is-active:not(.is-start-vote) .ready-active,
|
||||
#testimony-overlay .es-reveal-wrap .es-ready:disabled:not(.is-start-vote) .ready-active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* state: start-vote (host เมื่อทุกคน READY แล้ว) → แสดง btn-start-vote.png */
|
||||
#testimony-overlay .es-reveal-wrap .es-ready.is-start-vote .ready-default,
|
||||
#testimony-overlay .es-reveal-wrap .es-ready.is-start-vote .ready-active {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#testimony-overlay .es-reveal-wrap .es-ready.is-start-vote .ready-start-vote {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#testimony-overlay .es-reveal-wrap .es-ready.is-start-vote {
|
||||
filter: drop-shadow(0 0 12px rgba(83, 224, 255, 0.5));
|
||||
}
|
||||
|
||||
#testimony-overlay .es-reveal-wrap .es-ready.is-start-vote:hover {
|
||||
filter: drop-shadow(0 0 18px rgba(83, 224, 255, 0.85)) brightness(1.1);
|
||||
transform: scale(1.02);
|
||||
transition: filter 0.15s ease, transform 0.1s ease;
|
||||
}
|
||||
|
||||
@@ -813,8 +813,8 @@
|
||||
const raw = mapData.gauntletCrownRunwayBg && typeof mapData.gauntletCrownRunwayBg === 'object' ? mapData.gauntletCrownRunwayBg : {};
|
||||
let lineInArt = Number(raw.finishLineInFinishArtFrac);
|
||||
if (!Number.isFinite(lineInArt)) {
|
||||
/* mno9kb07: จบที่ "แถบ FINISH" สีเหลือง (กลาง-ซ้ายของ finish art) — ไม่ใช่ตู้เซฟ */
|
||||
lineInArt = gauntletCrownRunwayBgMapEligiblePlay() ? 0.22 : 0.12;
|
||||
/* mno9kb07: จบที่ "แถบ FINISH" สีเหลือง — หยุดทันทีหลังผ่านแถบ (ขอบขวาของแถบ FINISH) */
|
||||
lineInArt = gauntletCrownRunwayBgMapEligiblePlay() ? 0.34 : 0.12;
|
||||
}
|
||||
lineInArt = Math.max(0, Math.min(0.92, lineInArt));
|
||||
const finishLineWorldX = g.finishStart + g.w4Safe * lineInArt;
|
||||
@@ -831,8 +831,8 @@
|
||||
legacy = Math.max(0.08, Math.min(0.96, legacy));
|
||||
return Math.max(0.12, Math.min(0.62, 0.14 + legacy * 0.42));
|
||||
}
|
||||
/* mno9kb07: หยุดเลื่อนที่ ~0.42 (เส้น FINISH เลื่อนมาอยู่กลางจอ) */
|
||||
return gauntletCrownRunwayBgMapEligiblePlay() ? 0.42 : 0.34;
|
||||
/* mno9kb07: หยุดเลื่อนเร็วขึ้น (เส้น FINISH หยุดที่ ~0.55 จากซ้าย — ขอบขวาของแถบ FINISH ใกล้กลางจอ) */
|
||||
return gauntletCrownRunwayBgMapEligiblePlay() ? 0.55 : 0.34;
|
||||
}
|
||||
|
||||
function gauntletCrownRunwayObstacleFadeStartViewportFracPlay() {
|
||||
@@ -16462,6 +16462,13 @@
|
||||
others.forEach((o, id) => {
|
||||
if (!isPreviewBotId(id)) return;
|
||||
o.botIsWalking = false;
|
||||
/* บอทที่ตอบผิดไปแล้วในแมป mng8a80o → เป็นผีเดินมั่ว ไม่ต้องไปยืนรอที่โซนตอบ */
|
||||
const ghostWanderBot = isQuizQuestionMissionUiMapPlay() && isQuizWrongGhostActiveForEnt(id);
|
||||
if (ghostWanderBot) {
|
||||
/* ข้าม path-following → ตกไป wander logic ด้านล่าง */
|
||||
o.botPath = [];
|
||||
o.botAnswerWander = true;
|
||||
}
|
||||
if (inAnswerPhase && o.botPath && o.botPath.length > 0 && !o.botAnswerWander) {
|
||||
stepPreviewBotAlongPath(o, w, h);
|
||||
return;
|
||||
@@ -16469,7 +16476,7 @@
|
||||
if (inAnswerPhase && o.botPath && o.botPath.length === 0 && !o.botAnswerWander) {
|
||||
return;
|
||||
}
|
||||
/* ก่อนตอบ / พัก / บอทสับสน: เดินทุกเฟรมตามทิศ (เหมือนผู้เล่นกดค้าง) — เดิมรอเป็นจังหวะแล้วก้าวทีละครั้งเลยดูกระตุก */
|
||||
/* ก่อนตอบ / พัก / บอทสับสน / ผี (ตอบผิดไปแล้ว): เดินทุกเฟรมตามทิศ */
|
||||
if (o.botWanderDx == null || o.botWanderDy == null || (o.botWanderDx === 0 && o.botWanderDy === 0)) {
|
||||
const d = pickRandomPreviewBotWanderDir();
|
||||
o.botWanderDx = d[0];
|
||||
|
||||
@@ -1840,6 +1840,12 @@
|
||||
preplayOverlay.addEventListener('click', function (ev) {
|
||||
var startBtn = ev.target.closest('.lobby-case-start');
|
||||
if (!startBtn || !preplayOverlay.contains(startBtn)) return;
|
||||
/* กันกดซ้ำ — ถ้าปุ่มไหน loading อยู่ (รวมปุ่มอื่นในกลุ่ม) → ไม่รับ */
|
||||
if (startBtn.classList.contains('is-loading') || startBtn.disabled) return;
|
||||
var allCaseBtns = preplayOverlay.querySelectorAll('.lobby-case-start');
|
||||
var anyLoading = false;
|
||||
allCaseBtns.forEach(function (b) { if (b.classList.contains('is-loading')) anyLoading = true; });
|
||||
if (anyLoading) return;
|
||||
var cid = (startBtn.getAttribute('data-case') || '').trim();
|
||||
var level = (preplaySelectedLevel && String(preplaySelectedLevel).trim())
|
||||
|| (preplayOverlay.dataset.preplayLevel || '').trim();
|
||||
@@ -1848,9 +1854,19 @@
|
||||
try { alert('กรุณาเลือกระดับความท้าทายก่อน (กลับไปขั้นตอนก่อนหน้าแล้วเลือกระดับ)'); } catch (e0) { /* ignore */ }
|
||||
return;
|
||||
}
|
||||
/* loading state: lock ทุกปุ่มเริ่มคดี + ใส่ class is-loading */
|
||||
allCaseBtns.forEach(function (b) { b.disabled = true; });
|
||||
startBtn.classList.add('is-loading');
|
||||
function clearLoadingState() {
|
||||
allCaseBtns.forEach(function (b) {
|
||||
b.disabled = false;
|
||||
b.classList.remove('is-loading');
|
||||
});
|
||||
}
|
||||
var acked = false;
|
||||
var t = setTimeout(function () {
|
||||
if (!acked) {
|
||||
clearLoadingState();
|
||||
try { alert('ไม่ได้รับตอบจากเซิร์ฟเวอร์ — ลองใหม่หรือรีเฟรชหน้า'); } catch (eT) { /* ignore */ }
|
||||
}
|
||||
}, 12000);
|
||||
@@ -1863,8 +1879,10 @@
|
||||
acked = true;
|
||||
clearTimeout(t);
|
||||
if (res && res.ok === false && res.error) {
|
||||
clearLoadingState();
|
||||
try { alert(res.error); } catch (e2) { /* ignore */ }
|
||||
}
|
||||
/* ถ้าสำเร็จ — คงสถานะ loading ไว้จนกว่าเซิร์ฟจะส่ง event นำเข้าฉาก (กันกดซ้ำระหว่างรอเปลี่ยน scene) */
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3949,6 +3967,8 @@
|
||||
setSuspectProgress([0, 1, 2].map(function (i) { return Math.min(3, (myPlayerEvidence[i] || []).length); }));
|
||||
}
|
||||
applySuspectProgressVisual();
|
||||
/* อัปเดต UI host เร็วทันที — ปุ่ม "ชี้ตัวคนร้าย" ต้องโผล่ทันทีเมื่อสืบครบ */
|
||||
if (suspectPickOverlayOpen) updateSuspectHostUi();
|
||||
if (typeof lobbyEvidenceSuspectIdx === 'number') renderLobbyEvidenceCards(lobbyEvidenceSuspectIdx);
|
||||
}
|
||||
|
||||
@@ -4615,7 +4635,7 @@
|
||||
}
|
||||
|
||||
/* ===== ห้องสรุปหลักฐาน — การไต่สวน (เลือกหลักฐาน 2 ใบ/ปากคำ ก่อนโหวต) ===== */
|
||||
var tmState = { round: 0, members: [], picks: [], submitted: false, mode: 'select', submittedPicksById: {}, ownedSlots: [], requiredPicks: 2 };
|
||||
var tmState = { round: 0, members: [], picks: [], submitted: false, mode: 'select', submittedPicksById: {}, ownedSlots: [], requiredPicks: 2, hostId: '', submittedIds: [], readyIds: [], iReadied: false };
|
||||
|
||||
/** สล็อตหลักฐานที่ผู้เล่นเก็บได้สำหรับ suspect ตามรอบ (เช่น [0,2]) */
|
||||
function tmOwnedSlotsForRound(round) {
|
||||
@@ -4736,10 +4756,13 @@
|
||||
'<div class="layer es3-counter es3-mode-select" id="es3-count">เลือกหลักฐาน 0/2</div>' +
|
||||
'<div class="layer es3-cards es3-mode-select" id="es3-cards"></div>' +
|
||||
'<div class="layer es3-status-text es3-mode-select" id="es3-status">Status : กำลังรอ..ผู้เล่นเลือกหลักฐาน</div>' +
|
||||
'<button type="button" class="layer es3-btn-submit btn es3-mode-select" id="es3BtnSubmit" disabled aria-label="ส่งหลักฐาน"></button>' +
|
||||
'<div class="layer es3-btn-open btn es3-mode-select" id="es3BtnOpen" role="button" tabindex="0" aria-label="เปิดหลักฐานทั้งหมด">' +
|
||||
'<img src="' + SC + 'btn-open-card.png" alt="">' +
|
||||
/* ปุ่มหลักที่ bottom-right — สลับรูปตามสถานะ: btn-send (ทุกคน) → btn-open-card (host เมื่อทุกคนส่งครบ) */
|
||||
'<div class="layer es3-btn-action es3-mode-select" id="es3BtnAction" role="button" tabindex="0" aria-label="ส่งหลักฐาน" data-state="send">' +
|
||||
'<img id="es3BtnActionImg" src="' + SC + 'btn-send.png" alt="">' +
|
||||
'</div>' +
|
||||
/* legacy hidden — เก็บไว้กันโค้ดเก่าอ้างอิง id (ไม่แสดงผล) */
|
||||
'<button type="button" class="es3-btn-submit-legacy" id="es3BtnSubmit" hidden disabled aria-hidden="true"></button>' +
|
||||
'<div class="es3-btn-open-legacy" id="es3BtnOpen" hidden aria-hidden="true"></div>' +
|
||||
'<div class="es-reveal-wrap" id="es-reveal-wrap" hidden>' +
|
||||
'<div class="layer es-bg" aria-hidden="true"><img src="' + SC + 'bg-showcard.png" alt=""></div>' +
|
||||
'<div class="layer es-popup"><img src="' + SC + 'popup-0.png" alt=""></div>' +
|
||||
@@ -4753,9 +4776,10 @@
|
||||
'<p class="es-subtitle" id="es-popup-sub">[วิเคราะห์พยานหลักฐานกับทีม ก่อนไปปากคำถัดไป]</p>' +
|
||||
'</div>' +
|
||||
'<div class="layer es-players" id="es-players"></div>' +
|
||||
'<button type="button" class="layer es-ready btn" id="esBtnReady" aria-label="READY">' +
|
||||
'<button type="button" class="layer es-ready btn" id="esBtnReady" aria-label="READY" data-state="ready">' +
|
||||
'<img class="ready-default" src="' + SC + 'btn-ready.png" alt="">' +
|
||||
'<img class="ready-active" src="' + SC + 'btn-ready-active.png" alt="">' +
|
||||
'<img class="ready-start-vote" src="' + SC + 'btn-start-vote.png" alt="">' +
|
||||
'</button>' +
|
||||
'</div>' +
|
||||
'<button type="button" class="es3-mic btn" id="es3-mic" aria-label="ไมค์">🎤</button>' +
|
||||
@@ -4773,8 +4797,8 @@
|
||||
else { if (tmState.picks.length >= maxPicks) return; tmState.picks.push(idx); card.classList.add('is-selected'); }
|
||||
es3UpdateCount();
|
||||
});
|
||||
var submitEl = ov.querySelector('#es3BtnSubmit');
|
||||
if (submitEl) submitEl.addEventListener('click', function () {
|
||||
/* ปุ่มเดียวจัดการทั้ง submit (ทุกคน) และ reveal (host เมื่อทุกคนส่งครบ) */
|
||||
function doSubmitMyPicks() {
|
||||
var req = Math.max(1, Math.min(2, tmState.requiredPicks || 2));
|
||||
if (tmState.picks.length !== req || tmState.submitted) return;
|
||||
socket.emit('testimony-submit', { picks: tmState.picks.slice(0, req) }, function (res) {
|
||||
@@ -4782,43 +4806,57 @@
|
||||
tmState.submitted = true;
|
||||
if (!tmState.submittedPicksById) tmState.submittedPicksById = {};
|
||||
tmState.submittedPicksById[socket.id] = tmState.picks.slice(0, req);
|
||||
if (tmState.submittedIds.indexOf(socket.id) < 0) tmState.submittedIds.push(socket.id);
|
||||
es3SetStatus('ส่งหลักฐานแล้ว — รอผู้เล่นอื่น...');
|
||||
var sub = document.getElementById('es3BtnSubmit'); if (sub) sub.disabled = true;
|
||||
} else appendLobbySystemChat('— ส่งหลักฐานไม่สำเร็จ' + (res && res.error ? (' · ' + res.error) : ''));
|
||||
es3RenderMembers(tmState.submittedIds, [], false);
|
||||
es3UpdateActionButton();
|
||||
} else {
|
||||
appendLobbySystemChat('— ส่งหลักฐานไม่สำเร็จ' + (res && res.error ? (' · ' + res.error) : ''));
|
||||
}
|
||||
});
|
||||
});
|
||||
var openEl = ov.querySelector('#es3BtnOpen');
|
||||
function autoSubmitThenReveal() {
|
||||
var req = Math.max(1, Math.min(2, tmState.requiredPicks || 2));
|
||||
var doReveal = function () {
|
||||
socket.emit('testimony-reveal', {}, function (res) { if (!(res && res.ok)) appendLobbySystemChat('— เปิดเผยไม่สำเร็จ' + (res && res.error ? (' · ' + res.error) : '')); });
|
||||
};
|
||||
/* ถ้ายังไม่ submit แต่เลือกครบตาม required แล้ว — submit ให้ก่อน reveal */
|
||||
if (!tmState.submitted && tmState.ownedSlots.length > 0 && tmState.picks.length === req) {
|
||||
socket.emit('testimony-submit', { picks: tmState.picks.slice(0, req) }, function (res) {
|
||||
if (res && res.ok) {
|
||||
tmState.submitted = true;
|
||||
tmState.submittedPicksById[socket.id] = tmState.picks.slice(0, req);
|
||||
}
|
||||
doReveal();
|
||||
});
|
||||
} else {
|
||||
doReveal();
|
||||
}
|
||||
}
|
||||
if (openEl) {
|
||||
openEl.addEventListener('click', autoSubmitThenReveal);
|
||||
openEl.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); autoSubmitThenReveal(); }
|
||||
function doRevealAll() {
|
||||
socket.emit('testimony-reveal', {}, function (res) {
|
||||
if (!(res && res.ok)) appendLobbySystemChat('— เปิดเผยไม่สำเร็จ' + (res && res.error ? (' · ' + res.error) : ''));
|
||||
});
|
||||
}
|
||||
function onActionBtnTrigger() {
|
||||
var btn = document.getElementById('es3BtnAction');
|
||||
if (!btn || btn.classList.contains('is-disabled')) return;
|
||||
var state = btn.getAttribute('data-state');
|
||||
if (state === 'open') doRevealAll();
|
||||
else doSubmitMyPicks();
|
||||
}
|
||||
var actionEl = ov.querySelector('#es3BtnAction');
|
||||
if (actionEl) {
|
||||
actionEl.addEventListener('click', onActionBtnTrigger);
|
||||
actionEl.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onActionBtnTrigger(); }
|
||||
});
|
||||
}
|
||||
var readyEl = ov.querySelector('#esBtnReady');
|
||||
if (readyEl) readyEl.addEventListener('click', function () {
|
||||
if (this.disabled) return;
|
||||
var state = this.getAttribute('data-state') || 'ready';
|
||||
if (state === 'start-vote') {
|
||||
/* host กดเริ่มพิจารณาคดี — ไปปากคำถัดไป/หน้าโหวต */
|
||||
this.disabled = true;
|
||||
socket.emit('testimony-start-vote', {}, function (res) {
|
||||
if (!(res && res.ok)) {
|
||||
var rd = document.getElementById('esBtnReady');
|
||||
if (rd) rd.disabled = false;
|
||||
appendLobbySystemChat('— เริ่มพิจารณาคดีไม่สำเร็จ' + (res && res.error ? (' · ' + res.error) : ''));
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
/* state === 'ready' — กดยืนยัน READY ของตัวเอง */
|
||||
this.classList.add('is-active');
|
||||
this.disabled = true;
|
||||
tmState.iReadied = true;
|
||||
socket.emit('testimony-ready-next', {}, function (res) {
|
||||
if (!(res && res.ok)) {
|
||||
tmState.iReadied = false;
|
||||
var rd = document.getElementById('esBtnReady');
|
||||
if (rd) { rd.disabled = false; rd.classList.remove('is-active'); }
|
||||
appendLobbySystemChat('— READY ไม่สำเร็จ' + (res && res.error ? (' · ' + res.error) : ''));
|
||||
@@ -4836,7 +4874,48 @@
|
||||
function es3UpdateCount() {
|
||||
var req = Math.max(1, Math.min(2, tmState.requiredPicks || 2));
|
||||
var el = document.getElementById('es3-count'); if (el) el.textContent = 'เลือกหลักฐาน ' + tmState.picks.length + '/' + req;
|
||||
var sub = document.getElementById('es3BtnSubmit'); if (sub) sub.disabled = (tmState.picks.length !== req || tmState.submitted);
|
||||
es3UpdateActionButton();
|
||||
}
|
||||
|
||||
/* คุมรูป + สถานะของปุ่มเดียวที่ bottom-right
|
||||
- state="send" → btn-send.png (ทุกคนเห็น; กดส่งหลักฐานของตัวเอง)
|
||||
- state="open" → btn-open-card.png (เฉพาะ host เห็น/กดได้; กดเปิดเผยทั้งหมด) */
|
||||
function es3UpdateActionButton() {
|
||||
var btn = document.getElementById('es3BtnAction');
|
||||
var img = document.getElementById('es3BtnActionImg');
|
||||
if (!btn || !img) return;
|
||||
var SC = '/Main-Lobby/IMAGE/Showcard/';
|
||||
var req = Math.max(1, Math.min(2, tmState.requiredPicks || 2));
|
||||
var totalMembers = (tmState.members || []).length;
|
||||
var submittedCount = (tmState.submittedIds || []).length;
|
||||
var allDone = totalMembers > 0 && submittedCount >= totalMembers;
|
||||
var isHost = !!(tmState.hostId && tmState.hostId === socket.id);
|
||||
|
||||
if (allDone) {
|
||||
btn.setAttribute('data-state', 'open');
|
||||
btn.setAttribute('aria-label', 'เปิดหลักฐานทั้งหมด');
|
||||
img.src = SC + 'btn-open-card.png';
|
||||
/* host เท่านั้นที่เห็น/กดได้ */
|
||||
if (isHost) {
|
||||
btn.classList.remove('is-hidden', 'is-disabled');
|
||||
btn.style.display = '';
|
||||
btn.removeAttribute('aria-hidden');
|
||||
} else {
|
||||
btn.classList.add('is-hidden');
|
||||
btn.style.display = 'none';
|
||||
btn.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
} else {
|
||||
btn.setAttribute('data-state', 'send');
|
||||
btn.setAttribute('aria-label', 'ส่งหลักฐาน');
|
||||
img.src = SC + 'btn-send.png';
|
||||
btn.classList.remove('is-hidden');
|
||||
btn.style.display = '';
|
||||
btn.removeAttribute('aria-hidden');
|
||||
/* ใช้ได้เฉพาะเมื่อเลือกครบและยังไม่เคยส่ง */
|
||||
var canSend = (tmState.picks.length === req && !tmState.submitted && tmState.ownedSlots.length > 0);
|
||||
btn.classList.toggle('is-disabled', !canSend);
|
||||
}
|
||||
}
|
||||
|
||||
function esRevealCardEl(c, linkName, cardIdx) {
|
||||
@@ -4946,6 +5025,7 @@
|
||||
'<div class="es3-ps-name">' + crown + nick + '</div>' +
|
||||
'<div class="es3-ps-card is-ready">' +
|
||||
'<img class="card-bg" src="' + SC + 'card-bg.png" alt="">' +
|
||||
'<img class="card-back" src="' + SC + 'card-back.png" alt="">' +
|
||||
'<img class="status-icon" src="' + SC + 'ready-icon.png" alt="พร้อม">' +
|
||||
'</div>';
|
||||
} else {
|
||||
@@ -4971,6 +5051,10 @@
|
||||
tmState.picks = [];
|
||||
tmState.submitted = false;
|
||||
tmState.submittedPicksById = {};
|
||||
tmState.submittedIds = (data && Array.isArray(data.submitted)) ? data.submitted.slice() : [];
|
||||
tmState.hostId = (data && data.hostId) ? data.hostId : '';
|
||||
tmState.readyIds = [];
|
||||
tmState.iReadied = false;
|
||||
tmState.mode = 'select';
|
||||
tmState.ownedSlots = tmOwnedSlotsForRound(tmState.round);
|
||||
/* ต้องเลือกหลักฐาน = min(2, จำนวนที่เก็บได้) — ถ้าได้ 1 ใบ ส่ง 1 ใบพอ */
|
||||
@@ -4989,12 +5073,6 @@
|
||||
if (readyBtn) { readyBtn.disabled = false; readyBtn.classList.remove('is-active'); }
|
||||
var tabImg = document.querySelector('#es3-bottom-box img');
|
||||
if (tabImg) tabImg.src = SC + 'tab-0' + (tmState.round + 1) + '.png';
|
||||
var btnOpen = document.getElementById('es3BtnOpen');
|
||||
if (btnOpen) {
|
||||
var isHost = (data && data.hostId) === socket.id;
|
||||
btnOpen.classList.toggle('is-hidden', !isHost);
|
||||
btnOpen.style.display = isHost ? '' : 'none';
|
||||
}
|
||||
es3SetStatus('กำลังรอ..ผู้เล่นเลือกหลักฐาน');
|
||||
var cardsRoot = document.getElementById('es3-cards'); cardsRoot.textContent = '';
|
||||
/* แสดงเฉพาะการ์ดที่ผู้เล่นเก็บได้จริง (ตามสล็อตจาก myPlayerEvidence) */
|
||||
@@ -5008,7 +5086,9 @@
|
||||
if (res && res.ok) {
|
||||
tmState.submitted = true;
|
||||
tmState.submittedPicksById[socket.id] = [];
|
||||
if (tmState.submittedIds.indexOf(socket.id) < 0) tmState.submittedIds.push(socket.id);
|
||||
es3SetStatus('ไม่มีหลักฐาน — ข้ามอัตโนมัติ');
|
||||
es3UpdateActionButton();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -5018,7 +5098,8 @@
|
||||
});
|
||||
}
|
||||
es3UpdateCount();
|
||||
es3RenderMembers([], [], false);
|
||||
es3RenderMembers(tmState.submittedIds || [], [], false);
|
||||
es3UpdateActionButton();
|
||||
ov.classList.remove('is-hidden');
|
||||
ov.setAttribute('aria-hidden', 'false');
|
||||
requestAnimationFrame(syncTestimonyScale);
|
||||
@@ -5026,8 +5107,19 @@
|
||||
|
||||
function testimonyStatusUpdate(data) {
|
||||
if (!data) return;
|
||||
es3RenderMembers(data.submitted || [], [], false);
|
||||
if (data.submitted && data.submitted.length) es3SetStatus('กำลังรอ..ผู้เล่นเลือกหลักฐาน');
|
||||
tmState.submittedIds = Array.isArray(data.submitted) ? data.submitted.slice() : [];
|
||||
if (data.hostId) tmState.hostId = data.hostId;
|
||||
es3RenderMembers(tmState.submittedIds, [], false);
|
||||
es3UpdateActionButton();
|
||||
/* อัปเดต status text */
|
||||
var total = (tmState.members || []).length;
|
||||
if (total > 0 && tmState.submittedIds.length >= total) {
|
||||
es3SetStatus('ทุกคนเลือกหลักฐานแล้ว — รอ host เปิดเผยผล');
|
||||
} else if (tmState.submittedIds.length > 0) {
|
||||
es3SetStatus('กำลังรอ..ผู้เล่นเลือกหลักฐาน (' + tmState.submittedIds.length + '/' + total + ')');
|
||||
} else {
|
||||
es3SetStatus('กำลังรอ..ผู้เล่นเลือกหลักฐาน');
|
||||
}
|
||||
}
|
||||
|
||||
function testimonyReveal(data) {
|
||||
@@ -5051,15 +5143,65 @@
|
||||
var susName = document.getElementById('es-popup-sus-name');
|
||||
if (susName) susName.textContent = sd.linkName || '';
|
||||
var rd = document.getElementById('esBtnReady');
|
||||
if (rd) { rd.disabled = false; rd.classList.remove('is-active'); }
|
||||
if (rd) { rd.disabled = false; rd.classList.remove('is-active', 'is-start-vote'); rd.setAttribute('data-state', 'ready'); }
|
||||
tmState.readyIds = (data && Array.isArray(data.ready)) ? data.ready.slice() : [];
|
||||
tmState.iReadied = false;
|
||||
if (data && data.hostId) tmState.hostId = data.hostId;
|
||||
var picks = Object.assign({}, tmState.submittedPicksById || {}, (data && data.picks) || {});
|
||||
esRenderRevealPlayers(tmState.members, picks, sd);
|
||||
es3RenderMembers([], (data && data.ready) || [], true);
|
||||
es3RenderMembers([], tmState.readyIds, true);
|
||||
esUpdateRevealActionButton();
|
||||
requestAnimationFrame(syncTestimonyScale);
|
||||
}
|
||||
|
||||
function testimonyReadyUpdate(data) {
|
||||
es3RenderMembers([], (data && data.ready) || [], true);
|
||||
var readyArr = (data && data.ready) || [];
|
||||
tmState.readyIds = readyArr.slice();
|
||||
if (data && data.hostId) tmState.hostId = data.hostId;
|
||||
es3RenderMembers([], readyArr, true);
|
||||
esUpdateRevealActionButton();
|
||||
}
|
||||
|
||||
/* คุมสถานะปุ่มใน reveal mode:
|
||||
- ก่อนผู้เล่นกด READY: btn-ready.png (ทุกคน)
|
||||
- หลังกด READY แต่ยังรอคนอื่น: btn-ready-active.png (disabled)
|
||||
- ทุกคน READY แล้ว: host เห็น btn-start-vote.png (กดได้); คนอื่น disabled */
|
||||
function esUpdateRevealActionButton() {
|
||||
var btn = document.getElementById('esBtnReady');
|
||||
if (!btn) return;
|
||||
var isHost = !!(tmState.hostId && tmState.hostId === socket.id);
|
||||
var total = (tmState.members || []).length;
|
||||
var readyCount = (tmState.readyIds || []).length;
|
||||
var allReady = total > 0 && readyCount >= total;
|
||||
var meReadied = !!tmState.iReadied || ((tmState.readyIds || []).indexOf(socket.id) >= 0);
|
||||
|
||||
btn.classList.remove('is-start-vote', 'is-active');
|
||||
btn.disabled = false;
|
||||
|
||||
if (allReady && isHost) {
|
||||
/* host เห็นปุ่มเริ่มพิจารณาคดี */
|
||||
btn.setAttribute('data-state', 'start-vote');
|
||||
btn.setAttribute('aria-label', 'เริ่มพิจารณาคดี');
|
||||
btn.classList.add('is-start-vote');
|
||||
btn.disabled = false;
|
||||
} else if (allReady && !isHost) {
|
||||
/* non-host: รอ host */
|
||||
btn.setAttribute('data-state', 'ready');
|
||||
btn.setAttribute('aria-label', 'รอ host เริ่มพิจารณาคดี');
|
||||
btn.classList.add('is-active');
|
||||
btn.disabled = true;
|
||||
} else if (meReadied) {
|
||||
/* ยังรอคนอื่น */
|
||||
btn.setAttribute('data-state', 'ready');
|
||||
btn.setAttribute('aria-label', 'รอผู้เล่นอื่น');
|
||||
btn.classList.add('is-active');
|
||||
btn.disabled = true;
|
||||
} else {
|
||||
/* ยังไม่ได้กด */
|
||||
btn.setAttribute('data-state', 'ready');
|
||||
btn.setAttribute('aria-label', 'READY');
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function hideTestimonyOverlay() {
|
||||
@@ -5395,13 +5537,34 @@
|
||||
if (!suspectPickOverlayOpen || hostId !== socket.id) return;
|
||||
const idx = suspectSelectedIndex;
|
||||
if (idx < 0 || idx > 2) return;
|
||||
const startBtn = document.getElementById('suspect-btn-start');
|
||||
/* กันกดซ้ำ */
|
||||
if (startBtn && (startBtn.classList.contains('is-loading') || startBtn.disabled)) return;
|
||||
if (!canInvestigateSuspect(idx)) {
|
||||
appendLobbySystemChat('— สืบผู้ต้องสงสัยคนนี้ครบ 3 หลักฐานแล้ว — เลือกคนอื่น');
|
||||
updateSuspectHostUi();
|
||||
return;
|
||||
}
|
||||
if (startBtn) {
|
||||
startBtn.classList.add('is-loading');
|
||||
startBtn.disabled = true;
|
||||
}
|
||||
const clearLoading = () => {
|
||||
const b = document.getElementById('suspect-btn-start');
|
||||
if (b) { b.classList.remove('is-loading'); b.disabled = false; }
|
||||
};
|
||||
let acked = false;
|
||||
const timeoutT = setTimeout(() => {
|
||||
if (!acked) {
|
||||
clearLoading();
|
||||
appendLobbySystemChat('— เซิร์ฟเวอร์ไม่ตอบ ลองอีกครั้ง');
|
||||
}
|
||||
}, 12000);
|
||||
socket.emit('suspect-pick-start', {}, function (res) {
|
||||
if (res && res.ok) return;
|
||||
acked = true;
|
||||
clearTimeout(timeoutT);
|
||||
if (res && res.ok) return; /* คง loading ต่อจนเข้า play scene */
|
||||
clearLoading();
|
||||
const err = (res && res.error) ? String(res.error) : (sourceLabel || 'เริ่มสืบสวนไม่สำเร็จ');
|
||||
appendLobbySystemChat('— ' + err);
|
||||
try { console.warn('suspect-pick-start', res); } catch (e2) { /* ignore */ }
|
||||
@@ -5435,16 +5598,50 @@
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('suspect-pick-close')?.addEventListener('click', () => {
|
||||
if (!suspectPickOverlayOpen) return;
|
||||
closeSuspectOverlay();
|
||||
});
|
||||
/* ลบปุ่ม X ปิดออกตาม design — ใช้ปุ่ม "เริ่มสืบสวน/ชี้ตัวคนร้าย" เท่านั้น */
|
||||
const __sustClose = document.getElementById('suspect-pick-close');
|
||||
if (__sustClose && __sustClose.parentNode) __sustClose.parentNode.removeChild(__sustClose);
|
||||
|
||||
document.getElementById('btn-suspect-reopen')?.addEventListener('click', () => {
|
||||
if (!serverSuspectPhaseActive || suspectPickOverlayOpen) return;
|
||||
openSuspectOverlay(suspectSelectedIndex);
|
||||
});
|
||||
|
||||
/**
|
||||
* Hotkey ลัดสำหรับเทสต์ (เฉพาะตอน suspect overlay เปิด): Ctrl+1 / Ctrl+2 / Ctrl+3
|
||||
* → "เล่นเสร็จ 1 รอบ" ของ suspect คนนั้น (เก็บหลักฐาน 1 ใบให้ทันที)
|
||||
* กดครบ 3 ปุ่ม → "ชี้ตัวคนร้าย" โผล่อัตโนมัติ
|
||||
* โหมดโหวต/เปิดเผยผล: ปิด hotkey ป้องกันการกระทบเกม
|
||||
*/
|
||||
document.addEventListener('keydown', (ev) => {
|
||||
if (!ev.ctrlKey || ev.altKey || ev.metaKey || ev.shiftKey) return;
|
||||
if (!suspectPickOverlayOpen) return;
|
||||
/* ขณะอยู่ใน input/textarea/ที่แก้ไขได้ — ข้าม กันชน UX */
|
||||
const ae = document.activeElement;
|
||||
if (ae && (ae.tagName === 'INPUT' || ae.tagName === 'TEXTAREA' || ae.isContentEditable)) return;
|
||||
let idx = -1;
|
||||
if (ev.key === '1' || ev.code === 'Digit1' || ev.code === 'Numpad1') idx = 0;
|
||||
else if (ev.key === '2' || ev.code === 'Digit2' || ev.code === 'Numpad2') idx = 1;
|
||||
else if (ev.key === '3' || ev.code === 'Digit3' || ev.code === 'Numpad3') idx = 2;
|
||||
if (idx < 0) return;
|
||||
/* โหมด trial — ไม่ให้ hotkey ใช้ (กันชนกับการโหวต) */
|
||||
if (trialMode === 'voting' || trialMode === 'revealed') return;
|
||||
ev.preventDefault();
|
||||
/* แสดงสถานะกำลังโกง (toast) */
|
||||
appendLobbySystemChat('— [DEBUG] เก็บหลักฐานให้ผู้ต้องสงสัย ' + (idx + 1) + '...');
|
||||
socket.emit('detective-debug-award-evidence', { suspectIndex: idx }, function (res) {
|
||||
if (res && res.ok) {
|
||||
appendLobbySystemChat('— [DEBUG] เก็บหลักฐาน suspect ' + (idx + 1) + ' สำเร็จ');
|
||||
/* อัปเดต state ฝั่ง client ทันที (server จะ broadcast lobby-evidence-sync มาด้วย แต่ apply ทันทีเพื่อ UX ลื่น) */
|
||||
if (Array.isArray(res.myPlayerEvidence)) applyMyPlayerEvidence(res.myPlayerEvidence);
|
||||
if (Array.isArray(res.suspectProgress)) setSuspectProgress(res.suspectProgress);
|
||||
if (suspectPickOverlayOpen) updateSuspectHostUi();
|
||||
} else {
|
||||
appendLobbySystemChat('— [DEBUG] ล้มเหลว: ' + ((res && res.error) || 'unknown'));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function applyRoomLobbyBTransition(data) {
|
||||
const mid = (data && data.mapId) ? String(data.mapId).trim() : '';
|
||||
if (!mid) return;
|
||||
|
||||
@@ -3214,7 +3214,7 @@
|
||||
<script src="/app-base.js?v=2"></script>
|
||||
<script src="/Game/socket.io/socket.io.js"></script>
|
||||
<script src="js/version.js?v=0.0306"></script>
|
||||
<script src="js/play.js?v=0.0472"></script>
|
||||
<script src="js/play.js?v=0.0474"></script>
|
||||
<div class="version-tag">v —</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<link rel="stylesheet" href="css/profile-popup.css?v=6">
|
||||
<link rel="stylesheet" href="css/customize-popup.css?v=42">
|
||||
<link rel="stylesheet" href="css/leaderboard-popup.css?v=2">
|
||||
<link rel="stylesheet" href="css/testimony-overlay.css?v=11">
|
||||
<link rel="stylesheet" href="css/testimony-overlay.css?v=14">
|
||||
<link rel="stylesheet" href="css/evidence-view-overlay.css?v=12">
|
||||
<style>
|
||||
html, body.room-lobby-page { margin: 0; padding: 0; overflow: hidden; width: 100%; height: 100%; min-height: 100%; }
|
||||
@@ -253,6 +253,41 @@
|
||||
transform: none;
|
||||
filter: grayscale(0.6);
|
||||
}
|
||||
/* loading state — กันกดซ้ำ + แสดง spinner */
|
||||
.suspect-btn-start.is-loading,
|
||||
.lobby-case-start.is-loading {
|
||||
position: relative;
|
||||
cursor: wait !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
.suspect-btn-start.is-loading::after,
|
||||
.lobby-case-start.is-loading::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
margin: auto;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 50%;
|
||||
border: 4px solid rgba(255, 255, 255, 0.18);
|
||||
border-top-color: #6ff0ff;
|
||||
animation: zd-spin 0.8s linear infinite;
|
||||
pointer-events: none;
|
||||
z-index: 5;
|
||||
}
|
||||
.suspect-btn-start.is-loading > *,
|
||||
.lobby-case-start.is-loading > * {
|
||||
opacity: 0.32;
|
||||
filter: blur(0.8px);
|
||||
}
|
||||
.lobby-case-start:disabled:not(.is-loading) {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
@keyframes zd-spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.suspect-card-ui {
|
||||
position: absolute;
|
||||
left: 61%;
|
||||
@@ -1086,9 +1121,6 @@
|
||||
</div>
|
||||
<div id="suspect-pick-overlay" class="is-hidden" role="dialog" aria-modal="true" aria-labelledby="suspect-pick-sr-title" aria-hidden="true">
|
||||
<h2 id="suspect-pick-sr-title" class="visually-hidden" style="position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);">เลือกผู้ต้องสงสัย</h2>
|
||||
<button type="button" id="suspect-pick-close" class="suspect-pick-close" title="ปิดหน้าต่าง (กดปุ่มด้านล่างเพื่อเปิดอีกครั้ง)" aria-label="ปิดหน้าเลือกผู้ต้องสงสัย">
|
||||
<img src="/Main-Lobby/IMAGE/Choose%20a%20suspect/btn-close_1.png" alt="" width="56" height="56" decoding="async">
|
||||
</button>
|
||||
<div id="suspect-pick-scale-wrap" class="suspect-pick-scale-wrap">
|
||||
<div class="suspect-pick-inner">
|
||||
<img class="suspect-pick-title-img" src="/Main-Lobby/IMAGE/Choose%20a%20suspect/txt-suspect.png" alt="เลือกผู้ต้องสงสัย" width="1149" height="198" decoding="async">
|
||||
@@ -1576,7 +1608,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.0248"></script>
|
||||
<script src="js/room-lobby.js?v=0.0253"></script>
|
||||
<div class="version-tag">v —</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+71
-10
@@ -2192,6 +2192,7 @@ function emitTestimonyOpen(sid, space) {
|
||||
space.testimonyRevealed = false;
|
||||
/* บอทส่งหลักฐานทันทีเมื่อเปิด round */
|
||||
autoSubmitBotTestimonyPicks(space);
|
||||
const submittedIds = Object.keys(space.testimonySubmitted || {});
|
||||
const base = {
|
||||
round: space.testimonyRound,
|
||||
suspectIndex: space.testimonyRound,
|
||||
@@ -2199,6 +2200,7 @@ function emitTestimonyOpen(sid, space) {
|
||||
totalPlayers: testimonyTotalParticipants(space),
|
||||
hostId: space.hostId,
|
||||
cardMinigames: space.suspectCardMinigames || [],
|
||||
submitted: submittedIds,
|
||||
};
|
||||
space.peers.forEach((_p, pid) => {
|
||||
const sock = io.sockets.sockets.get(pid);
|
||||
@@ -2209,7 +2211,8 @@ function emitTestimonyOpen(sid, space) {
|
||||
suspectProgress: playerSuspectProgressFromEvidence(space, pid),
|
||||
});
|
||||
});
|
||||
/* ถ้ามีแต่บอท (ไม่มีผู้เล่นจริง) ก็เปิด reveal เลย — แต่ปกติจะมีคนกด reveal เอง */
|
||||
/* sync สถานะปุ่ม (เผื่อบอทส่งครบแล้ว — host ต้องเห็นปุ่ม "เปิดหลักฐานทั้งหมด" ทันที) */
|
||||
emitTestimonyStatus(sid, space);
|
||||
}
|
||||
|
||||
function emitTestimonyStatus(sid, space) {
|
||||
@@ -4674,9 +4677,25 @@ io.on('connection', (socket) => {
|
||||
const spawnJoinOrder = space.peers.size;
|
||||
const spawnPt = pickSpawnForJoin(mdJoin, spawnJoinOrder);
|
||||
const bbStartBalloons = Math.max(1, Math.min(12, Math.floor(Number(mdJoin.balloonBossBalloonsPerPlayer)) || 3));
|
||||
/* ใช้สีที่ client เลือกไว้ใน Main-Lobby ถ้าผ่านการตรวจสอบ + ยังไม่ถูกใช้ในห้องนี้ */
|
||||
/* ใช้สีที่ client เลือกไว้ใน Main-Lobby — คนจริงสำคัญกว่าบอท */
|
||||
let chosenThemeIdx = parseInt(desiredLobbyColorThemeIndex, 10);
|
||||
if (!(chosenThemeIdx >= 1 && chosenThemeIdx <= LOBBY_THEME_COUNT) || getTakenLobbyThemeIndices(space).has(chosenThemeIdx)) {
|
||||
if (chosenThemeIdx >= 1 && chosenThemeIdx <= LOBBY_THEME_COUNT) {
|
||||
/* ถ้ามี "ผู้เล่นจริงคนอื่น" ถือสีนี้แล้ว → fallback สุ่มใหม่ */
|
||||
let takenByHuman = false;
|
||||
space.peers.forEach((p) => {
|
||||
const ti = parseInt(p.lobbyColorThemeIndex, 10);
|
||||
if (ti === chosenThemeIdx) takenByHuman = true;
|
||||
});
|
||||
if (takenByHuman) {
|
||||
chosenThemeIdx = pickFreeLobbyThemeIndex(space);
|
||||
} else if (space.lobbyBotThemeBySlot && typeof space.lobbyBotThemeBySlot === 'object') {
|
||||
/* ถ้าบอทใช้สีนี้ → ดันบอทไปสีอื่น (เคลียร์ slot.themeIndex; syncLobbyBotThemeSlots ใน joinCb จะหาสีใหม่ที่ไม่ชนกับ peer ใหม่) */
|
||||
Object.keys(space.lobbyBotThemeBySlot).forEach((k) => {
|
||||
const slot = space.lobbyBotThemeBySlot[k];
|
||||
if (slot && parseInt(slot.themeIndex, 10) === chosenThemeIdx) slot.themeIndex = 0;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
chosenThemeIdx = pickFreeLobbyThemeIndex(space);
|
||||
}
|
||||
let chosenSkinIdx = parseInt(desiredLobbySkinToneIndex, 10);
|
||||
@@ -4979,6 +4998,30 @@ io.on('connection', (socket) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* DEBUG/TEST hotkey — เก็บหลักฐาน 1 ใบให้ผู้ต้องสงสัย (suspectIndex 0–2) ทันที
|
||||
* (ใช้สำหรับเทสต์ Ctrl+1/2/3 — เทียบเท่าเล่นมินิเกมจบ 1 ครั้ง)
|
||||
*/
|
||||
socket.on('detective-debug-award-evidence', (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 (!serverMapIsPostCaseLobbyB(space)) return reply({ ok: false, error: 'ต้องอยู่โถง LobbyB' });
|
||||
const idx = Math.floor(Number(data && data.suspectIndex));
|
||||
if (!(idx >= 0 && idx <= 2)) return reply({ ok: false, error: 'suspectIndex ต้องเป็น 0–2' });
|
||||
const card = awardRandomEvidenceCardToPlayer(space, socket.id, idx);
|
||||
if (card == null) return reply({ ok: false, error: 'เก็บครบ 3 ใบแล้วสำหรับ suspect นี้' });
|
||||
emitLobbyEvidenceSync(sid, space);
|
||||
reply({
|
||||
ok: true,
|
||||
suspectIndex: idx,
|
||||
awardedCard: card,
|
||||
myPlayerEvidence: clonePlayerEvidence(space, socket.id),
|
||||
suspectProgress: playerSuspectProgressFromEvidence(space, socket.id),
|
||||
});
|
||||
});
|
||||
|
||||
// Host เปิดขั้นพิจารณาคดี (ต้องสืบครบ 3 ผู้ต้องสงสัย)
|
||||
socket.on('suspect-accuse-open', (_data, cb) => {
|
||||
const reply = typeof cb === 'function' ? cb : () => {};
|
||||
@@ -5018,8 +5061,7 @@ io.on('connection', (socket) => {
|
||||
if (!space.testimonySubmitted) space.testimonySubmitted = {};
|
||||
space.testimonySubmitted[socket.id] = [];
|
||||
emitTestimonyStatus(sid, space);
|
||||
const totalP0 = testimonyTotalParticipants(space);
|
||||
if (Object.keys(space.testimonySubmitted).length >= totalP0 && totalP0 > 0) doTestimonyReveal(sid, space);
|
||||
/* ไม่ auto-reveal — รอ host กดปุ่ม "เปิดหลักฐานทั้งหมด" เองตาม design */
|
||||
return reply({ ok: true, skipped: true });
|
||||
}
|
||||
if (picks.length !== required) {
|
||||
@@ -5028,8 +5070,8 @@ io.on('connection', (socket) => {
|
||||
if (!space.testimonySubmitted) space.testimonySubmitted = {};
|
||||
space.testimonySubmitted[socket.id] = picks.slice(0, required);
|
||||
emitTestimonyStatus(sid, space);
|
||||
const totalP = testimonyTotalParticipants(space);
|
||||
if (Object.keys(space.testimonySubmitted).length >= totalP && totalP > 0) doTestimonyReveal(sid, space);
|
||||
/* ไม่ auto-reveal — รอ host กดปุ่ม "เปิดหลักฐานทั้งหมด" เองตาม design
|
||||
(UX: host ต้องเห็นปุ่มเปลี่ยนรูปจาก btn-send → btn-open-card แล้วค่อยกดเอง) */
|
||||
reply({ ok: true });
|
||||
});
|
||||
|
||||
@@ -5045,7 +5087,7 @@ io.on('connection', (socket) => {
|
||||
reply({ ok: true });
|
||||
});
|
||||
|
||||
// ผู้เล่นกด READY ไปปากคำถัดไป (หลังเปิดเผยผล)
|
||||
// ผู้เล่นกด READY ไปปากคำถัดไป (หลังเปิดเผยผล) — ไม่ auto-advance รอ host กด "เริ่มพิจารณาคดี"
|
||||
socket.on('testimony-ready-next', (_data, cb) => {
|
||||
const reply = typeof cb === 'function' ? cb : () => {};
|
||||
const sid = socket.data.spaceId;
|
||||
@@ -5055,8 +5097,27 @@ io.on('connection', (socket) => {
|
||||
if (!space.testimonyReadyNext) space.testimonyReadyNext = {};
|
||||
space.testimonyReadyNext[socket.id] = true;
|
||||
const totalRN = testimonyTotalParticipants(space);
|
||||
io.to(sid).emit('testimony-ready-update', { ready: Object.keys(space.testimonyReadyNext), totalPlayers: totalRN });
|
||||
if (Object.keys(space.testimonyReadyNext).length >= totalRN && totalRN > 0) advanceTestimony(sid, space);
|
||||
io.to(sid).emit('testimony-ready-update', {
|
||||
ready: Object.keys(space.testimonyReadyNext),
|
||||
totalPlayers: totalRN,
|
||||
hostId: space.hostId,
|
||||
});
|
||||
/* ไม่ auto-advance — รอ host กดปุ่ม "เริ่มพิจารณาคดี" (btn-start-vote.png) เอง */
|
||||
reply({ ok: true });
|
||||
});
|
||||
|
||||
// Host กดปุ่ม "เริ่มพิจารณาคดี" (btn-start-vote) — ไปปากคำถัดไป/เข้าโหวต
|
||||
socket.on('testimony-start-vote', (_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: 'ยังไม่ถึงเวลา' });
|
||||
/* host อาจกดก่อนทุกคน READY — บังคับให้ทุกคน (รวมบอท) ready ก่อน advance เพื่อให้ flow consistent */
|
||||
if (!space.testimonyReadyNext) space.testimonyReadyNext = {};
|
||||
testimonyBotIds(space).forEach((bid) => { space.testimonyReadyNext[bid] = true; });
|
||||
advanceTestimony(sid, space);
|
||||
reply({ ok: true });
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user