update join room

This commit is contained in:
2026-06-19 08:31:14 +00:00
parent 7c3c4c9d71
commit 57642f2d30
12 changed files with 430 additions and 46 deletions
+257 -12
View File
@@ -38,34 +38,36 @@
"providerUserId": "p_1775109142385_wq7wfy1p32j",
"notes": "auto: player-coins",
"blocked": false,
"coins": 815,
"coins": 897,
"createdAt": "2026-04-02T05:52:21+00:00",
"updatedAt": "2026-06-18T15:59:38+00:00",
"updatedAt": "2026-06-19T07:10:26+00:00",
"daily": {
"anchorMs": 1781197200000,
"claimedDays": [
true,
true,
false,
true,
false,
false,
false,
false
],
"lockUntilMs": 0
"lockUntilMs": 1781888400000
},
"score": 730,
"score": 792,
"scoreByCase": {
"1": 10,
"10": 310,
"1": 16,
"10": 320,
"8": 220,
"11": 20,
"13": 20,
"13": 40,
"14": 20,
"9": 40,
"12": 60,
"15": 20,
"4": 10
"4": 10,
"5": 20,
"7": 6
},
"lbName": "Q",
"achievements": {
@@ -158,14 +160,36 @@
{
"id": "2613daea57c76f13ac9197ec",
"email": "",
"displayName": "Guest",
"displayName": "Pear",
"loginType": "guest",
"providerUserId": "p_1780400804441_lsc3lqqvs0i",
"notes": "auto: player-coins",
"blocked": false,
"coins": 0,
"coins": 16,
"createdAt": "2026-06-02T11:46:46+00:00",
"updatedAt": "2026-06-02T11:46:46+00:00"
"updatedAt": "2026-06-19T07:10:26+00:00",
"daily": {
"anchorMs": 1781802000000,
"claimedDays": [
true,
false,
false,
false,
false,
false,
false
],
"lockUntilMs": 1781888400000
},
"score": 6,
"scoreByCase": {
"1": 4,
"7": 2
},
"lbName": "Pear",
"achievements": {
"d1_minigame_solver": 2
}
},
{
"id": "9404ea306bffed187b43a490",
@@ -193,6 +217,227 @@
},
"createdAt": "2026-06-17T08:22:51+00:00",
"updatedAt": "2026-06-17T08:22:51+00:00"
},
{
"id": "c3f2affa3fc67b2411992cdf",
"email": "",
"displayName": "Nutty",
"loginType": "guest",
"providerUserId": "p_1781849622720_33zppelly7r",
"notes": "auto: player-coins",
"blocked": false,
"coins": 12,
"createdAt": "2026-06-19T06:13:44+00:00",
"updatedAt": "2026-06-19T07:10:26+00:00",
"score": 12,
"scoreByCase": {
"1": 12
},
"lbName": "Nutty",
"achievements": {
"d1_minigame_solver": 3
}
},
{
"id": "71f99e98ffa2e824b225af7c",
"email": "",
"displayName": "tomato",
"loginType": "guest",
"providerUserId": "p_1781849642042_x2vt68wscm",
"notes": "auto: player-coins",
"blocked": false,
"coins": 136,
"createdAt": "2026-06-19T06:14:02+00:00",
"updatedAt": "2026-06-19T07:49:00+00:00",
"daily": {
"anchorMs": 1781802000000,
"claimedDays": [
true,
false,
false,
false,
false,
false,
false
],
"lockUntilMs": 1781888400000
},
"score": 126,
"scoreByCase": {
"1": 66,
"7": 10,
"5": 10,
"2": 40
},
"lbName": "tomato",
"achievements": {
"d1_minigame_solver": 14
}
},
{
"id": "cdf90c44df43f374c61afe24",
"email": "",
"displayName": "Guest",
"loginType": "guest",
"providerUserId": "p_1781849653038_swv8lg8jlm",
"notes": "auto: player-coins",
"blocked": false,
"coins": 10,
"createdAt": "2026-06-19T06:14:13+00:00",
"updatedAt": "2026-06-19T06:14:35+00:00",
"daily": {
"anchorMs": 1781802000000,
"claimedDays": [
true,
false,
false,
false,
false,
false,
false
],
"lockUntilMs": 1781888400000
}
},
{
"id": "237fe56525ecb6a41d2831ee",
"email": "",
"displayName": "Chupa Chup",
"loginType": "guest",
"providerUserId": "p_1781849770177_lylmhvm1lp",
"notes": "auto: player-coins",
"blocked": false,
"coins": 94,
"createdAt": "2026-06-19T06:16:09+00:00",
"updatedAt": "2026-06-19T07:33:03+00:00",
"daily": {
"anchorMs": 1781802000000,
"claimedDays": [
true,
false,
false,
false,
false,
false,
false
],
"lockUntilMs": 1781888400000
},
"score": 84,
"scoreByCase": {
"1": 76,
"7": 8
},
"lbName": "Zara Kissy",
"achievements": {
"d1_minigame_solver": 9
}
},
{
"id": "c762e08534c6c99a24a49dbe",
"email": "",
"displayName": "Guest",
"loginType": "guest",
"providerUserId": "p_1781850202249_347o67u9vhq",
"notes": "auto: player-coins",
"blocked": false,
"coins": 10,
"createdAt": "2026-06-19T06:23:24+00:00",
"updatedAt": "2026-06-19T06:24:28+00:00",
"daily": {
"anchorMs": 1781802000000,
"claimedDays": [
true,
false,
false,
false,
false,
false,
false
],
"lockUntilMs": 1781888400000
}
},
{
"id": "a5e5d250871a8fdc6988bb83",
"email": "",
"displayName": "Guest",
"loginType": "guest",
"providerUserId": "p_1781850663247_l0g0d1r7pcm",
"notes": "auto: player-coins",
"blocked": false,
"coins": 0,
"createdAt": "2026-06-19T06:31:03+00:00",
"updatedAt": "2026-06-19T06:31:03+00:00"
},
{
"id": "0bf4718eeec4434c080d6057",
"email": "",
"displayName": "Guest",
"loginType": "guest",
"providerUserId": "p_1781851275186_szcmp5pu3s",
"notes": "auto: player-coins",
"blocked": false,
"coins": 10,
"createdAt": "2026-06-19T06:41:15+00:00",
"updatedAt": "2026-06-19T07:00:09+00:00",
"daily": {
"anchorMs": 1781802000000,
"claimedDays": [
true,
false,
false,
false,
false,
false,
false
],
"lockUntilMs": 1781888400000
}
},
{
"id": "6976f001d7a7eb32c2fd0171",
"email": "",
"displayName": "MONE",
"loginType": "guest",
"providerUserId": "p_1781852432483_mfrw1xmw0j",
"notes": "auto: player-coins",
"blocked": false,
"coins": 4,
"createdAt": "2026-06-19T07:00:33+00:00",
"updatedAt": "2026-06-19T07:10:26+00:00",
"score": 4,
"scoreByCase": {
"7": 4
},
"lbName": "MONE",
"achievements": {
"d1_minigame_solver": 1
}
},
{
"id": "689a199e3291ae6cc2c8bc05",
"email": "",
"displayName": "Guest",
"loginType": "guest",
"providerUserId": "p_1781855252958_jds3qm3aebt",
"notes": "auto: player-coins",
"blocked": false,
"coins": 0,
"createdAt": "2026-06-19T07:47:33+00:00",
"updatedAt": "2026-06-19T07:47:33+00:00"
},
{
"id": "bb6f0d73632880703c833ad5",
"email": "",
"displayName": "Guest",
"loginType": "guest",
"providerUserId": "p_1781856969270_oupok32bkh9",
"notes": "auto: player-coins",
"blocked": false,
"coins": 0,
"createdAt": "2026-06-19T08:16:09+00:00",
"updatedAt": "2026-06-19T08:16:09+00:00"
}
]
}
+7 -13
View File
@@ -89,19 +89,13 @@
"balloonBossPlayerBalloonFallbackUrl": "/Game/img/MegaVirus/Artboard%209.png",
"balloonBossBalloonsPerPlayer": 3,
"forcedMinigameKeys": [
"quiz",
"gauntlet",
"stack"
"quiz_carry",
"space_shooter",
"balloon_boss"
],
"testSpecialCardByMap": {
"mng8a80o": 1,
"mno9kb07": 2,
"mnn93hpi": 3,
"mnorwqx1": 4,
"mnptfts2": 5,
"mnpz6rkp": 6,
"mnq1eml7": 7
},
"testSpecialCardByMap": {},
"troublesomeForceOffer": false,
"specialQuizIconExpireSec": 20
"specialQuizIconExpireSec": 20,
"pickVoteSec": 25,
"trialVoteSec": 30
}
@@ -1 +1 @@
{"qbroom1":[{"name":"Q","score":7,"characterId":"char-1777017632279","ts":1781171902962},{"name":"ผู้เล่น8916","score":4,"characterId":"char-1777017632279","ts":1781074083031},{"name":"ผู้เล่น2821","score":4,"characterId":"char-1777017632279","ts":1781079179108}],"qbroom2":[{"name":"Q","score":7,"characterId":"char-1777017632279","ts":1781175468983},{"name":"ผู้เล่น2612","score":1,"characterId":"char-1777017632279","ts":1781079252376}],"qbroom3":[{"name":"Q","score":9,"characterId":"char-1777017632279","ts":1781175654893}],"qbroom5":[{"name":"Q","score":6,"characterId":"char-1777017632279","ts":1781675299850}],"qbroom6":[{"name":"Q","score":3,"characterId":"char-1777017632279","ts":1781675383175}],"qbroom9":[{"name":"Q","score":7,"characterId":"char-1777017632279","ts":1781696345322}]}
{"qbroom1":[{"name":"ผู้เล่น9514","score":17,"characterId":"char-1777017632279","ts":1781850667717},{"name":"MONE","score":10,"characterId":"char-1777017632279","ts":1781850749437},{"name":"tomato","score":9,"characterId":"char-1777017632279","ts":1781850748552},{"name":"Q","score":7,"characterId":"char-1777017632279","ts":1781171902962},{"name":"ผู้เล่น1991","score":5,"characterId":"char-1777017632279","ts":1781850950078},{"name":"ผู้เล่น8916","score":4,"characterId":"char-1777017632279","ts":1781074083031},{"name":"ผู้เล่น2821","score":4,"characterId":"char-1777017632279","ts":1781079179108},{"name":"Weenwild","score":4,"characterId":"char-1777017632279","ts":1781849863580}],"qbroom2":[{"name":"ผู้เล่น4472","score":13,"characterId":"char-1777017632279","ts":1781851033914},{"name":"Q","score":7,"characterId":"char-1777017632279","ts":1781175468983},{"name":"ผู้เล่น2612","score":1,"characterId":"char-1777017632279","ts":1781079252376}],"qbroom3":[{"name":"Q","score":9,"characterId":"char-1777017632279","ts":1781175654893},{"name":"Weenwild","score":2,"characterId":"char-1777017632279","ts":1781849898452}],"qbroom5":[{"name":"Q","score":6,"characterId":"char-1777017632279","ts":1781675299850}],"qbroom6":[{"name":"Q","score":3,"characterId":"char-1777017632279","ts":1781675383175}],"qbroom9":[{"name":"Q","score":7,"characterId":"char-1777017632279","ts":1781696345322}]}
+44 -1
View File
@@ -566,9 +566,28 @@
function applyCustomizeSelection() {
var characterId = getStoredCharacterId();
if (!characterId) return Promise.resolve(null);
return new Promise(function (resolve) {
function runApply() {
// ไม่มีตัวละคร (เครื่องที่ยังไม่ได้เลือกตัวละคร) — ยังต้องซิงก์สีไปเซิร์ฟเวอร์
// ให้คนอื่นเห็น แต่ข้ามการ composite รูป preview เพราะไม่มี characterId
if (!characterId) {
try {
persistConfirmedState();
syncLobbyTintStorageKeys();
} catch (e0) { /* ignore */ }
try {
window.dispatchEvent(new CustomEvent('customize-popup:applied', {
detail: {
characterId: '',
activeColor: clampIndex(state.activeColor, 1, 8),
activeSkin: clampIndex(state.activeSkin, 1, 3),
avatarSrc: ''
}
}));
} catch (e1) { /* ignore */ }
resolve(null);
return;
}
var tint = getSelectedTint();
var domAvatar = resolveDomAvatarSource();
var fallbackSrc = domAvatar.src || '';
@@ -1068,6 +1087,12 @@
function closePopup() {
if (!refs.overlay) return;
stopScrollbarDrag();
/* ย้าย focus ออกจาก overlay ก่อนซ่อน — กันคำเตือน "aria-hidden on focused element" (เช่นปุ่มยืนยันที่เพิ่งกด) */
try {
if (refs.overlay.contains(document.activeElement) && document.activeElement.blur) {
document.activeElement.blur();
}
} catch (e) { /* ignore */ }
refs.overlay.classList.add('is-hidden');
refs.overlay.setAttribute('aria-hidden', 'true');
}
@@ -1124,9 +1149,27 @@
});
}
/** ซ่อมรูป avatar ที่ถูก "อบ" (bake) ไว้ผิดสีจากโค้ดเวอร์ชันเก่า (heuristic = ชุดขาว+ขอบสี)
→ คอมโพสิตใหม่จากเลเยอร์จริงด้วยสีที่บันทึกไว้ แล้วเขียนทับ bake เดิม (self-heal ทุกเครื่อง) */
function rebakeAvatarFromSavedColors() {
if (isRoomLobbyPage()) return; // room-lobby มีระบบ tint ของตัวเองอยู่แล้ว
var characterId = getStoredCharacterId();
if (!characterId) return;
var hasSavedColor = false;
try { hasSavedColor = !!localStorage.getItem('lobbyThemeColor'); } catch (e) { /* ignore */ }
if (!hasSavedColor) return; // ยังไม่เคยเลือกสี → คงรูป default ไว้ ไม่ไปอบทับ
readLobbyTintIntoState();
composeTintedCharacterPreview(characterId, getSelectedTint()).then(function (outSrc) {
if (!outSrc || typeof outSrc !== 'string' || outSrc.indexOf('data:image/') !== 0) return; // composite ไม่สำเร็จ → ไม่เขียนทับด้วย fallback
try { localStorage.setItem(LOBBY_IDLE_DOWN_PREFIX + characterId, outSrc); } catch (e) { /* ignore */ }
syncLiveAvatarElements(outSrc, 'ตัวละครที่ปรับแต่งแล้ว');
}).catch(function () { /* ignore */ });
}
function init() {
createPopupIfNeeded();
if (triggerSelectors.length) bindTriggers();
rebakeAvatarFromSavedColors();
}
if (document.readyState === 'loading') {
+36 -1
View File
@@ -2436,6 +2436,21 @@
} catch (e) { /* ignore */ }
}
/** วินิจฉัยสีในเกม: รันใน console บนแต่ละเครื่อง → เทียบสีที่ render ของทุกตัว */
window.__colorDebug = function () {
var rows = [];
rows.push({ who: '*ME*', id: String(myId == null ? '' : myId).slice(0, 6), renderTint: JSON.stringify(me && me.playTint) });
others.forEach(function (o, id) {
rows.push({ who: isPreviewBotId(id) ? 'BOT' : 'peer', id: String(id).slice(0, 10), name: o.nickname || '', renderTint: JSON.stringify(o.playTint) });
});
try { console.table(rows); } catch (e) { console.log(JSON.stringify(rows, null, 2)); }
var lsTheme = '?'; var lsTint = '?';
try { lsTheme = localStorage.getItem('lobbyThemeColor'); lsTint = localStorage.getItem(LOBBY_PLAYER_TINT_KEY); } catch (e) { /* ignore */ }
console.log('[color-dbg/game] myId=' + myId + ' playLobbyBotThemes=' + JSON.stringify(playLobbyBotThemes) +
' LS.lobbyThemeColor=' + lsTheme + ' LS.PLAYER_TINT=' + lsTint);
return rows;
};
function getPlayTintForMissionAvatar(peerId) {
const sid = peerId != null ? String(peerId) : '';
if (myId != null && sid === String(myId)) {
@@ -3194,6 +3209,24 @@
let playLiveQuizScores = {};
/** เกมถูก/ผิด (multiplayer): ใครเคยตอบผิดในเซสชันนี้ — ใช้โชว์แสตมป์ในสรุปภารกิจ mng8a80o */
let playQuizEverWrong = {};
/** MG1: กันสั่งจบซ้ำเมื่อตรวจพบ "ตายหมด" (รวมบอท ฝั่ง client) */
let mg1AllDeadEndScheduled = false;
/** MG1: ตรวจว่าทุกคน(เรา+บอท+ผู้เล่นอื่น)ถูกล็อกตอบผิดหมด → จบเกมทันที (เฉพาะโหมดสืบสวน) */
function checkMg1AllDeadAndEnd() {
if (mg1AllDeadEndScheduled || previewMode) return;
if (!isQuiz() || !isDetectiveMinigamePlay()) return;
const meLocked = !!(playQuizPlayerLocal && playQuizPlayerLocal.cannotTrue && playQuizPlayerLocal.cannotFalse);
if (!meLocked) return;
let anyAlive = false;
others.forEach(function (o, id) {
if (!o || o.bannedSpectator) return;
if (!playBotsEnabled() && isPreviewBotId(id)) return;
if (!(o.quizCannotTrue && o.quizCannotFalse)) anyAlive = true;
});
if (anyAlive) return;
mg1AllDeadEndScheduled = true;
setTimeout(function () { try { tryFinishDetectiveMinigameAndReturnLobby(); } catch (e) { /* ignore */ } }, 1600);
}
/** ผีตามตัวเมื่อตอบผิด — ใช้เฉพาะแมป mng8a80o (เป็นสัญลักษณ์/บทลงโทษเชิงสนุก) */
const QUIZ_WRONG_GHOST_URL = BASE + '/img/QUESTION/ghost/ghost-front.png';
let quizWrongGhostImg = null;
@@ -3588,7 +3621,7 @@
playLiveQuizScores = {};
playLiveQuizScores[myId] = 0;
others.forEach((_, id) => { playLiveQuizScores[id] = 0; });
if (isQuiz()) playQuizEverWrong = {};
if (isQuiz()) { playQuizEverWrong = {}; mg1AllDeadEndScheduled = false; }
renderPlayQuizScoreboard(playLiveQuizScores);
}
@@ -18876,6 +18909,7 @@
spawnQuizTrueFalseScorePopupPlay(!!r.correctTrue);
}
showPlayQuizFeedback(r);
checkMg1AllDeadAndEnd();
});
socket.on('quiz-player-state', (st) => {
@@ -18888,6 +18922,7 @@
score: typeof st.score === 'number' ? st.score : (playQuizPlayerLocal.score || 0),
};
if (quizGhostConfineActivePlay()) warpMeIntoQuizGhostZonePlay();
checkMg1AllDeadAndEnd();
});
socket.on('quiz-ended', () => {
+40 -6
View File
@@ -603,7 +603,7 @@
return parseLobbyThemeIndex(p.lobbyColorThemeIndex) || parseLobbyThemeIndex(p.colorThemeIndex);
}
function getLobbyTakenThemeIndices(excludePeerId) {
function getLobbyTakenThemeIndices(excludePeerId, humanOnly) {
var taken = [];
var seen = {};
peers.forEach(function (p, id) {
@@ -611,10 +611,12 @@
var ti = peerLobbyThemeIndex(p);
if (ti && !seen[ti]) { seen[ti] = true; taken.push(ti); }
});
lobbyBots.forEach(function (b) {
var ti = peerLobbyThemeIndex(b);
if (ti && !seen[ti]) { seen[ti] = true; taken.push(ti); }
});
if (!humanOnly) {
lobbyBots.forEach(function (b) {
var ti = peerLobbyThemeIndex(b);
if (ti && !seen[ti]) { seen[ti] = true; taken.push(ti); }
});
}
return taken;
}
@@ -623,7 +625,8 @@
if (!ti) return false;
var myIdx = getMyLobbyThemeIndex();
if (forSelfChange && myIdx === ti) return true;
return getLobbyTakenThemeIndices(forSelfChange ? socket.id : null).indexOf(ti) < 0;
/* บอทไม่บล็อก: ถ้าสีถูกบอทยึดอยู่ ยังเลือกได้ (server จะดันบอทไปสีอื่นให้) — บล็อกเฉพาะสีที่ "คนจริง" ใช้ */
return getLobbyTakenThemeIndices(forSelfChange ? socket.id : null, true).indexOf(ti) < 0;
}
function applyThemeIndexToOccupant(target, themeIndex, skinIndex, done) {
@@ -672,6 +675,29 @@
tick();
}
/** วินิจฉัยสี: รันใน console บนแต่ละเครื่อง → เทียบ index/สี ของทุกตัว เพื่อหาจุดที่ไม่ตรง */
window.__colorDebug = function () {
var rows = [];
peers.forEach(function (p, id) {
rows.push({
who: id === socket.id ? '*ME*' : 'peer',
id: String(id).slice(0, 6),
name: p.nickname || '',
serverIdx: p.lobbyColorThemeIndex || null,
skinIdx: p.lobbySkinToneIndex || null,
renderRGB: (id === socket.id ? (myTintTheme || '') : (p.colorTheme || '(none)')),
});
});
(lobbyBots || []).forEach(function (b, i) {
rows.push({ who: 'BOT', id: String(b.id || ('bot' + i)).slice(0, 10), name: b.nickname || '', serverIdx: peerLobbyThemeIndex(b), skinIdx: b.lobbySkinToneIndex || null, renderRGB: b.colorTheme || '(none)' });
});
try { console.table(rows); } catch (e) { console.log(JSON.stringify(rows, null, 2)); }
console.log('[color-dbg/lobby] mySocket=' + socket.id + ' myTintTheme=' + myTintTheme + ' myTintSkin=' + myTintSkin +
' botThemesFromServer=' + JSON.stringify(lobbyBotThemesFromServer) +
' LS.lobbyThemeColor=' + (function () { try { return localStorage.getItem('lobbyThemeColor'); } catch (e) { return '?'; } })());
return rows;
};
function applyLobbyTintSyncPayload(data) {
if (!data) return;
if (Array.isArray(data.lobbyBotThemes)) lobbyBotThemesFromServer = data.lobbyBotThemes;
@@ -4576,6 +4602,14 @@
socket.off('user-joined');
socket.on('user-joined', async (data) => {
peers.set(data.id, normalizeLobbyPeerFromServer(data, mapData));
/* peer : resolve RGB
(lobby-tint-sync emit อน user-joined งขาม peer เพราะยงไมอยใน map) */
applyPeerThemeFromServer(peers.get(data.id), data.lobbyColorThemeIndex, data.lobbySkinToneIndex, function () {
updatePlayersHud();
renderPeers();
redrawLobbyMap();
if (typeof window.refreshCustomizeColorRow === 'function') window.refreshCustomizeColorRow();
});
updatePlayersHud();
renderPeers();
ensureReadyControlEnabled();
+2 -2
View File
@@ -5124,7 +5124,7 @@
#special-quiz-overlay .sq-ov-result--fail { color: #fff; background: rgba(247,118,142,0.9); }
#special-quiz-overlay #sq-ov-quiz-body.is-hidden { display: none; }
/* ===== หน้ารับการ์ด (mock 10-Game-1-result) — เต็มจอ ไม่มีกรอบควิซ ===== */
#special-quiz-overlay.is-award .sq-ov-panel { background: none !important; padding: 2vh 4vw !important; min-height: 0 !important; box-shadow: none; }
#special-quiz-overlay.is-award .sq-ov-panel { background: none !important; padding: 2vh 4vw !important; min-height: 0 !important; width: auto !important; max-width: 96vw !important; aspect-ratio: auto !important; max-height: 96vh !important; overflow: visible !important; box-shadow: none; }
#special-quiz-overlay .sq-ov-award { display: flex; flex-direction: column; align-items: center; gap: 8px; text-align: center; }
#special-quiz-overlay .sq-ov-award.is-hidden { display: none; }
#special-quiz-overlay .sq-ov-award-stage { position: relative; display: flex; align-items: center; justify-content: center; }
@@ -5205,7 +5205,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.006131839"></script>
<script src="js/play.js?v=0.006131841"></script>
<div class="version-tag">v —</div>
<style id="qc-howto-mg2-fix">
/* HOW TO PLAY ของ quiz_carry (Minigame-4) -> ให้เหมือน Minigame-2 (gch-mg2-mock)
+2 -2
View File
@@ -1615,9 +1615,9 @@
<script src="/Game/socket.io/socket.io.js"></script>
<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/customize-popup.js?v=35" data-customize-triggers="" data-customize-asset-base="img/03-5-Customize"></script>
<script src="js/achievements.js?v=0.002" data-asset-base="img/03-6-Profile"></script>
<script src="js/room-lobby.js?v=0.0291"></script>
<script src="js/room-lobby.js?v=0.0293"></script>
<div class="version-tag">v —</div>
</body>
</html>
+37 -5
View File
@@ -1101,6 +1101,9 @@ function applySpecialCardImmediate(sid, space, card) {
}
/* ===== คลังการ์ดพิเศษที่สะสมในเคสนี้ (ใช้ได้พร้อมกันทุกใบ ไม่ทับกัน) ===== */
function dbgCards(space) {
return JSON.stringify((space && space.pendingSpecialCards || []).map((e) => e.cardId + (e.consumed ? 'x' : '')));
}
function pendingSpecialCardList(space) {
if (!Array.isArray(space.pendingSpecialCards)) space.pendingSpecialCards = [];
return space.pendingSpecialCards;
@@ -1110,9 +1113,10 @@ function addPendingSpecialCard(space, card) {
if (!card || !card.cardId) return null;
const list = pendingSpecialCardList(space);
const cid = Number(card.cardId);
if (list.some((e) => Number(e.cardId) === cid && !e.consumed)) return null; /* มีใบนี้อยู่แล้ว ไม่ซ้ำ */
if (list.some((e) => Number(e.cardId) === cid && !e.consumed)) { console.log('[card-dbg] add skip dup card' + cid + ' list=' + dbgCards(space)); return null; }
const entry = { cardId: cid, card: card, consumed: false };
list.push(entry);
console.log('[card-dbg] +card' + cid + ' list=' + dbgCards(space));
return entry;
}
/** หาการ์ดในคลังตาม cardId ที่ยังไม่ถูกใช้ */
@@ -1132,6 +1136,7 @@ function endSpecialQuizSession(sid, space, success) {
sq.done = true;
sq.session = null;
let cardOut = null;
console.log('[card-dbg] specialQuiz end: success=' + !!success + ' cardId=' + (sq.card && sq.card.cardId));
if (success && sq.card && (sq.card.cardId || sq.card.imageUrl)) {
space.specialCardAwardedThisRun = sq.card;
cardOut = specialCardClientPayload(sq.card);
@@ -3610,6 +3615,7 @@ function returnDetectiveSpaceToLobbyB(sid, space, message, awardOptions) {
/* Card 3 Ban (when: lobby_next): " LobbyB "
bannedPlayerId ใหเกมรอบถดไปกนไมใหคนทกโหวตเล */
const banPend = findPendingSpecialCard(space, 3);
console.log('[card-dbg] lobby return: ban(3)=' + !!banPend + ' pickVote=' + !!space.pickVote + ' list=' + dbgCards(space));
if (banPend && !space.pickVote) {
banPend.consumed = true;
/* ตั้ง flag — เริ่มโหวตตอนผู้เล่น "rejoin เข้า room-lobby" (ตอน return ผู้เล่นกำลัง navigate play.html→room-lobby.html อยู่ event จะหาย) */
@@ -4032,6 +4038,7 @@ function advanceTestimony(sid, space) {
space.silencedPlayerId = null;
/* Card 4 Silence — โหวตเลือกผู้เล่น 1 คนให้ห้ามโหวต ก่อนเข้าโหวตจริง */
const pend = findPendingSpecialCard(space, 4);
console.log('[card-dbg] trial pre-vote: silence(4)=' + !!pend + ' list=' + dbgCards(space));
if (pend) {
pend.consumed = true;
io.to(sid).emit('special-card-applied', { card: specialCardClientPayload(pend.card), context: 'silence' });
@@ -5781,7 +5788,7 @@ const server = http.createServer((req, res) => {
return;
}
if (url.startsWith(BASE_PATH + '/api/spaces')) {
const parts = url.slice((BASE_PATH + '/api/spaces').length).split('/').filter(Boolean);
const parts = url.slice((BASE_PATH + '/api/spaces').length).split('?')[0].split('#')[0].split('/').filter(Boolean);
const spaceId = parts[0];
if (req.method === 'GET' && !spaceId) {
const now = Date.now();
@@ -7381,7 +7388,16 @@ io.on('connection', (socket) => {
}
socket.join(spaceId);
socket.data.spaceId = spaceId;
if (!space.hostId) space.hostId = socket.id;
/* Host (lobbyplaylobbyB): host playerKey ( browser)
ไมใช socket.id (เปลยนทกครงทโหลดหนาใหม host สลบไปมาแบบส) */
const joinPlayerKey = (typeof playerKey === 'string' && /^[a-zA-Z0-9_-]{8,128}$/.test(playerKey)) ? playerKey : '';
const prevHostId = space.hostId;
if (joinPlayerKey && space.hostPlayerKey && joinPlayerKey === space.hostPlayerKey) {
space.hostId = socket.id; /* host ตัวจริงกลับเข้ามา → คืนสถานะ host เสมอ */
} else if (!space.hostId) {
space.hostId = socket.id; /* ยังไม่มี host → คนนี้เป็น host (ชั่วคราวถ้ามี hostPlayerKey อยู่แล้ว) */
if (!space.hostPlayerKey && joinPlayerKey) space.hostPlayerKey = joinPlayerKey;
}
if (serverMapIsPostCaseLobbyB(space)) initTroublesomeState(space);
ensurePeerLobbyThemes(space);
const mdJoin = (space.mapId && maps.get(space.mapId)) || space.mapData;
@@ -7524,6 +7540,7 @@ io.on('connection', (socket) => {
const sqIcon = specialQuizIconPayload(space);
if (sqIcon) joinCb.specialQuizIcon = sqIcon;
if (typeof cb === 'function') cb(joinCb);
if (space.hostId !== prevHostId) io.to(spaceId).emit('host-changed', { hostId: space.hostId }); /* host เปลี่ยน (เช่น host ตัวจริงกลับเข้ามา) → sync ทุกคน */
emitLobbyTintSync(spaceId, space);
socket.to(spaceId).emit('user-joined', peer);
if (space.voiceInits) {
@@ -7665,8 +7682,20 @@ io.on('connection', (socket) => {
if (!Number.isFinite(skinIndex) || skinIndex < 1 || skinIndex > LOBBY_SKIN_COUNT) {
skinIndex = peer.lobbySkinToneIndex || 1;
}
const taken = getTakenLobbyThemeIndices(space, socket.id);
if (taken.has(themeIndex)) return reply({ ok: false, error: 'สีเสื้อนี้มีคนใช้แล้ว' });
/* "" ; "" ( join)
นอาการ "เปลี่ยนสีไม่ได้" เมอบอทยดสไว */
let humanTaken = false;
space.peers.forEach((op, oid) => {
if (oid === socket.id) return;
if (parseInt(op.lobbyColorThemeIndex, 10) === themeIndex) humanTaken = true;
});
if (humanTaken) return reply({ ok: false, error: 'สีเสื้อนี้มีคนใช้แล้ว' });
if (space.lobbyBotThemeBySlot && typeof space.lobbyBotThemeBySlot === 'object') {
Object.keys(space.lobbyBotThemeBySlot).forEach((k) => {
const slot = space.lobbyBotThemeBySlot[k];
if (slot && parseInt(slot.themeIndex, 10) === themeIndex) slot.themeIndex = 0; /* syncLobbyBotThemeSlots จะหาสีใหม่ให้ */
});
}
peer.lobbyColorThemeIndex = themeIndex;
peer.lobbySkinToneIndex = skinIndex;
emitLobbyTintSync(sid, space);
@@ -7715,6 +7744,8 @@ io.on('connection', (socket) => {
|| (space.isPrivate && String(space.spaceName || '').toLowerCase() === 'preview'));
if (!isPreviewEditorRoom) return reply({ ok: false, error: 'ใช้ได้เฉพาะห้องทดสอบจากเอดิเตอร์' });
space.hostId = socket.id;
const claimPeer = space.peers.get(socket.id);
if (claimPeer && claimPeer.playerKey) space.hostPlayerKey = claimPeer.playerKey;
io.to(sid).emit('host-changed', { hostId: socket.id });
return reply({ ok: true });
});
@@ -8325,6 +8356,7 @@ io.on('connection', (socket) => {
const firstPeer = space.peers.values().next().value;
if (firstPeer) {
space.hostId = firstPeer.id;
space.hostPlayerKey = firstPeer.playerKey || space.hostPlayerKey; /* host ออกจริง (ไม่ใช่เปลี่ยนหน้า) → ย้าย host ให้คนถัดไปแบบถาวร */
io.to(sid).emit('host-changed', { hostId: firstPeer.id });
}
}
+1 -1
View File
@@ -82,6 +82,6 @@
<script src="../app-base.js?v=2"></script>
<script src="../Game/js/display-name.js?v=2"></script>
<script src="join.js?v=0.006131120"></script>
<script src="join.js?v=0.006131121"></script>
</body>
</html>
+2 -1
View File
@@ -191,7 +191,8 @@
});
btnPrivateJoin?.addEventListener('click', function () {
var code = (idPrivate?.value || '').trim();
/* รหัสห้องเป็นตัวพิมพ์ใหญ่เสมอ (ABCDEFGHJKLMNPQRSTUVWXYZ23456789) — บังคับ uppercase กันพิมพ์เล็กแล้วหาห้องไม่เจอ (404) */
var code = (idPrivate?.value || '').trim().toUpperCase();
var pass = (passPrivate?.value || '').trim();
if (!code) {
alert('กรุณากรอกรหัสห้อง');
+1 -1
View File
@@ -228,7 +228,7 @@
<script src="../app-base.js?v=2"></script>
<script src="../Game/js/display-name.js?v=2"></script>
<script src="daily-popup.js?v=26" data-daily-trigger="#btn-daily" data-daily-asset-base="IMAGE/Daily" data-daily-test-reset-seconds="0"></script>
<script src="../Game/js/customize-popup.js?v=31" data-customize-triggers="#btn-cloth" data-customize-asset-base="/Game/img/03-5-Customize"></script>
<script src="../Game/js/customize-popup.js?v=35" data-customize-triggers="#btn-cloth" data-customize-asset-base="/Game/img/03-5-Customize"></script>
<script src="../Game/js/achievements.js?v=0.002" data-asset-base="/Game/img/03-6-Profile"></script>
<script src="../Game/js/profile-popup.js?v=7" data-profile-trigger="#btn-myprofile" data-profile-asset-base="/Game/img/03-6-Profile"></script>
<script src="lobby.js?v=0.0192"></script>