update sync game 1-7

This commit is contained in:
2026-06-22 08:47:26 +00:00
parent a474f0e736
commit cedfad8ff7
6 changed files with 794 additions and 273 deletions
+37 -13
View File
@@ -394,14 +394,14 @@
{
"id": "0bf4718eeec4434c080d6057",
"email": "",
"displayName": "Guest",
"displayName": "MONE",
"loginType": "guest",
"providerUserId": "p_1781851275186_szcmp5pu3s",
"notes": "auto: player-coins",
"blocked": false,
"coins": 10,
"coins": 158,
"createdAt": "2026-06-19T06:41:15+00:00",
"updatedAt": "2026-06-19T07:00:09+00:00",
"updatedAt": "2026-06-21T17:18:58+00:00",
"daily": {
"anchorMs": 1781802000000,
"claimedDays": [
@@ -413,7 +413,20 @@
false,
false
],
"lockUntilMs": 1781888400000
"lockUntilMs": 0
},
"score": 138,
"scoreByCase": {
"10": 20,
"13": 36,
"12": 54,
"4": 28
},
"lbName": "MONE",
"achievements": {
"d1_minigame_solver": 13,
"a1_first_deduction": 1,
"a5_truth_hunter": 1
}
},
{
@@ -541,33 +554,44 @@
"providerUserId": "p_1781862203558_jhynnz90qrg",
"notes": "auto: player-coins",
"blocked": false,
"coins": 18,
"coins": 182,
"createdAt": "2026-06-19T09:43:23+00:00",
"updatedAt": "2026-06-21T09:15:13+00:00",
"updatedAt": "2026-06-22T07:38:20+00:00",
"lobbyColorThemeIndex": 6,
"lobbySkinToneIndex": 1,
"achievements": {
"d1_minigame_solver": 2
"d1_minigame_solver": 17
},
"score": 18,
"score": 162,
"scoreByCase": {
"10": 18
"10": 44,
"13": 38,
"12": 54,
"4": 26
},
"lbName": "ผู้เล่น"
},
{
"id": "fbb262e6c2aa47bd97aef554",
"email": "",
"displayName": "Guest",
"displayName": "MONE",
"loginType": "guest",
"providerUserId": "p_1781938677448_8j2wnr5ha1b",
"notes": "auto: player-coins",
"blocked": false,
"coins": 0,
"coins": 28,
"createdAt": "2026-06-20T06:57:57+00:00",
"updatedAt": "2026-06-20T06:58:53+00:00",
"updatedAt": "2026-06-22T07:38:20+00:00",
"lobbyColorThemeIndex": 7,
"lobbySkinToneIndex": 1
"lobbySkinToneIndex": 1,
"achievements": {
"d1_minigame_solver": 3
},
"lbName": "MONE",
"score": 18,
"scoreByCase": {
"13": 18
}
},
{
"id": "8f0741532038e900e759c5ef",
File diff suppressed because it is too large Load Diff
+18 -1
View File
@@ -174,6 +174,10 @@
let suspectSelectedIndex = 0;
/** ตรงกับเซิร์ฟ — เฟสเลือกผู้ต้องสงสัยยังไม่จบ (ปิด overlay แล้วยังเปิดได้จากปุ่มโถง) */
let serverSuspectPhaseActive = false;
/** จำนวนผู้เล่นที่กลับเข้า LobbyB แล้ว/ทั้งหมด — host เริ่มมินิเกมรอบถัดไปได้เมื่อครบ (allHere) */
let lobbyBPresentCount = 0;
let lobbyBTotalCount = 0;
let lobbyBAllHere = true;
/** มินิเกม 3 แบบที่สุ่มแล้ว — ล็อกกับการ์ด 0–2 จนกว่าสร้างห้องใหม่ */
let suspectCardMinigames = [];
/** หลักฐาน (ติกถูก) ที่เก็บได้ต่อผู้ต้องสงสัย 0–2 (เล่นมินิเกมจบ +1 ต่อช่อง สูงสุด 3) */
@@ -6274,7 +6278,9 @@
const accuseBtn = document.getElementById('suspect-btn-accuse');
const startBtn = document.getElementById('suspect-btn-start');
const hint = document.getElementById('suspect-pick-hint');
const canStartInvestigation = isHost && canInvestigateSuspect(suspectSelectedIndex);
/* ต้องรอผู้เล่นทุกคนกลับเข้า LobbyB ครบก่อน host ถึงเริ่มได้ (กันคนยังโหลดไม่เสร็จหลุดรอบ) */
const waitingForPlayers = !lobbyBAllHere;
const canStartInvestigation = isHost && canInvestigateSuspect(suspectSelectedIndex) && !waitingForPlayers;
if (startBtn) {
startBtn.disabled = !canStartInvestigation;
startBtn.classList.toggle('suspect-btn-start--disabled', !canStartInvestigation);
@@ -6283,6 +6289,8 @@
if (hint) {
if (!isHost) {
hint.textContent = 'รอ Host เลือกการ์ดและกดเริ่มสืบสวน';
} else if (waitingForPlayers) {
hint.textContent = 'รอผู้เล่นกลับเข้าห้องให้ครบก่อน (' + lobbyBPresentCount + '/' + lobbyBTotalCount + ') แล้วจึงเริ่มได้';
} else if (!canInvestigateSuspect(suspectSelectedIndex)) {
hint.textContent = 'สืบผู้ต้องสงสัยคนนี้ครบ 3 หลักฐานแล้ว — เลือกคนอื่นเพื่อสืบต่อ';
} else if (allSuspectsInvestigated()) {
@@ -6409,6 +6417,15 @@
applySuspectSelectionVisual(idx);
});
/* เซิร์ฟแจ้งจำนวนผู้เล่นที่กลับเข้า LobbyB แล้ว — ใช้ล็อกปุ่มเริ่มจนกว่าจะครบทุกคน */
socket.on('detective-lobbyb-presence', (data) => {
if (!data) return;
lobbyBPresentCount = Number(data.present) || 0;
lobbyBTotalCount = Number(data.total) || 0;
lobbyBAllHere = (data.allHere !== false) && (lobbyBTotalCount <= 0 || lobbyBPresentCount >= lobbyBTotalCount);
if (suspectPickOverlayOpen) updateSuspectHostUi();
});
// ===== ห้องสรุปหลักฐาน (ไต่สวน) =====
socket.on('testimony-open', (data) => { openTestimony(data || {}); });
socket.on('testimony-status', (data) => { testimonyStatusUpdate(data || {}); });
+1 -1
View File
@@ -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.006131850"></script>
<script src="js/play.js?v=0.006131863"></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)
+1 -1
View File
@@ -1617,7 +1617,7 @@
<script src="js/version.js?v=0.0122"></script>
<script src="js/customize-popup.js?v=40" 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.0298"></script>
<script src="js/room-lobby.js?v=0.0299"></script>
<div class="version-tag">v —</div>
</body>
</html>
+206 -19
View File
@@ -3202,6 +3202,7 @@ function buildPeersSnapForSpace(space, extraFields) {
spawnJoinOrder: typeof p.spawnJoinOrder === 'number' ? p.spawnJoinOrder : null,
lobbyColorThemeIndex: p.lobbyColorThemeIndex || null,
lobbySkinToneIndex: p.lobbySkinToneIndex || null,
bannedSpectator: !!p.bannedSpectator,
};
if (extraFields && extraFields.gauntlet) {
row.gauntletJumpTicks = p.gauntletJumpTicks || 0;
@@ -3215,11 +3216,42 @@ function buildPeersSnapForSpace(space, extraFields) {
/* payload quiz-carry-lobby-sync peers snapshot
client (โดยเฉพาะ host) upsert others ขาดหายใหครบ host ไมเหนตวละคร client
เม join/reconnect ไมนรอบ (race ตอนเปลยนหน lobbyBplay.html) */
/* true peer (Card 3 Ban) Ready gate
(เชคท id เดมตอนโหวต และ flag bannedSpectator ของ peer reconnect เข play.html) */
function isBannedPeerForRun(space, id) {
if (!space) return false;
if (space.bannedThisRunPlayerId && id === space.bannedThisRunPlayerId) return true;
const pp = space.peers && space.peers.get(id);
return !!(pp && pp.bannedSpectator);
}
/* สำเนา ready map โดยตัด id ผู้ถูกแบนออก — กัน client นับ banned เป็นผู้เล่นที่ต้องรอ Ready (host ค้าง N/N+1) */
function readyMapWithoutBanned(space, srcMap) {
const out = {};
if (srcMap && typeof srcMap === 'object') {
Object.keys(srcMap).forEach((id) => {
if (!isBannedPeerForRun(space, id)) out[id] = srcMap[id];
});
}
return out;
}
function quizCarryLobbySyncPayload(space) {
const readyMap = (space && space.quizCarryLobbyReady && typeof space.quizCarryLobbyReady === 'object')
? { ...space.quizCarryLobbyReady }
: {};
return { readyMap, peers: buildPeersSnapForSpace(space) };
return {
readyMap: readyMapWithoutBanned(space, space && space.quizCarryLobbyReady),
peers: buildPeersSnapForSpace(space),
expectedActive: (space && space.minigameExpectedActiveCount) || 0,
};
}
/* payload gauntlet-crown-lobby-sync banned readyMap
แนบ peers snapshot วย (เหมอน quiz_carry) client/host เต others ขาด เหนจำนวนผเลนเทาก */
function crownLobbySyncPayload(space) {
return {
readyMap: readyMapWithoutBanned(space, space && space.gauntletCrownLobbyReady),
expectedActive: (space && space.minigameExpectedActiveCount) || 0,
peers: buildPeersSnapForSpace(space),
};
}
/** สุ่ม 3 minigame จาก pool 7 แบบ — เรียกครั้งเดียวตอนเข้า LobbyB (ล็อกบน space) */
@@ -3244,6 +3276,34 @@ function normalizeDetectiveCardEntry(entry) {
return entry;
}
/* (caseParticipantNicknames) LobbyB
ใชตรวจวาครบหรอยงกอนให host เรมมเกมรอบถดไป */
function detectiveLobbyBMissingParticipants(space) {
const roster = space && space.caseParticipantNicknames;
if (!roster || roster.size === 0) return { missing: 0, present: 0, total: 0 };
const present = new Set();
space.peers.forEach((p) => {
if (!p || !p.nickname) return;
const n = normalizeLobbyNickname(p.nickname);
if (roster.has(n)) present.add(n);
});
const total = roster.size;
return { missing: Math.max(0, total - present.size), present: present.size, total };
}
/* แจ้งทุกคนใน LobbyB ว่ามีผู้เล่นกลับเข้ามาแล้วกี่คน/ทั้งหมดกี่คน → host ใช้เปิด/ปิดปุ่มเริ่ม + แสดงสถานะรอ */
function emitDetectiveLobbyBPresence(sid, space) {
if (!sid || !space) return;
if (!serverMapIsPostCaseLobbyB(space)) return;
const info = detectiveLobbyBMissingParticipants(space);
if (info.total <= 0) return;
io.to(sid).emit('detective-lobbyb-presence', {
present: info.present,
total: info.total,
allHere: info.missing === 0,
});
}
function assignDetectiveSuspectCardMinigames(space) {
const available = DETECTIVE_MINIGAME_POOL.filter((e) => maps.has(e.mapId));
/** โหมดเทสต์ (admin game-timing): เลือกเกมต่อการ์ด 3 ใบ — ช่องที่ตั้ง key = เจาะจง, ช่องว่าง = สุ่ม */
@@ -3458,6 +3518,9 @@ function beginDetectiveSuspectMinigame(sid, space, cardEntry, selectedIndex) {
space.bannedThisRunPlayerId = space.bannedPlayerId || null;
console.log('[ban-debug] startMinigame: bannedThisRunPlayerId=', space.bannedThisRunPlayerId);
space.bannedPlayerId = null;
/* + Ready host ()
สแนปจาก peers ใน LobbyB ตอนน host เรมกอน client โหลด/กด Ready ครบ (เกมไม sync/จบไมพรอมก) */
space.minigameExpectedActiveCount = [...space.peers.keys()].filter((id) => !isBannedPeerForRun(space, id)).length;
space.detectiveMinigameCardIndex = cardEntry.cardIndex;
// จำว่ากำลังสืบผู้ต้องสงสัยคนไหน เพื่อเติมหลักฐาน (ติกถูก) ตอนเล่นจบ
space.suspectActiveIndex = (typeof selectedIndex === 'number' && selectedIndex >= 0 && selectedIndex <= 2)
@@ -7439,6 +7502,21 @@ io.on('connection', (socket) => {
space.hostId = socket.id; /* ยังไม่มี host → คนนี้เป็น host (ชั่วคราวถ้ามี hostPlayerKey อยู่แล้ว) */
if (!space.hostPlayerKey && joinPlayerKey) space.hostPlayerKey = joinPlayerKey;
}
/* reconnect/: (playerKey ) socket.id peer space.peers
จนกว disconnect เกาจะมา (อาจช) กคนเห "ตัวละครซ้ำ 2 ตัว" (ghost)
แก: ลบ ghost peer playerKey ตรงแต socket.id าง อนเพมตวใหม + แจ user-left ให client ลบท */
if (joinPlayerKey) {
const staleIds = [];
space.peers.forEach((p, pid) => {
if (pid !== socket.id && p && p.playerKey && p.playerKey === joinPlayerKey) staleIds.push(pid);
});
staleIds.forEach((pid) => {
space.peers.delete(pid);
if (space.quizCarryLobbyReady) delete space.quizCarryLobbyReady[pid];
if (space.gauntletCrownLobbyReady) delete space.gauntletCrownLobbyReady[pid];
io.to(spaceId).emit('user-left', { id: pid });
});
}
if (serverMapIsPostCaseLobbyB(space)) initTroublesomeState(space);
ensurePeerLobbyThemes(space);
const mdJoin = (space.mapId && maps.get(space.mapId)) || space.mapData;
@@ -7512,14 +7590,14 @@ io.on('connection', (socket) => {
peer.gauntletEliminated = false;
if (!space.gauntletCrownLobbyReady || typeof space.gauntletCrownLobbyReady !== 'object') space.gauntletCrownLobbyReady = {};
space.gauntletCrownLobbyReady[socket.id] = false;
io.to(spaceId).emit('gauntlet-crown-lobby-sync', { readyMap: { ...space.gauntletCrownLobbyReady } });
io.to(spaceId).emit('gauntlet-crown-lobby-sync', crownLobbySyncPayload(space));
}
startGauntletTicker(spaceId, space);
} else if (isBalloonBossMissionShellSpace(space) || isQuizQuestionMissionShellSpace(space) || isStackTowerMissionShellSpace(space) || isJumpSurviveMissionShellSpace(space) || isSpaceShooterMissionShellSpace(space)) {
if (!space.gauntletCrownLobbyReady || typeof space.gauntletCrownLobbyReady !== 'object') space.gauntletCrownLobbyReady = {};
space.gauntletCrownLobbyReady[socket.id] = false;
if (!space.gauntletRun) space.gauntletRun = newBalloonBossShellGauntletRunState();
io.to(spaceId).emit('gauntlet-crown-lobby-sync', { readyMap: { ...space.gauntletCrownLobbyReady } });
io.to(spaceId).emit('gauntlet-crown-lobby-sync', crownLobbySyncPayload(space));
}
const peersList = [...space.peers.values()];
const mapDataOut = (space.mapId && maps.get(space.mapId)) || space.mapData || mdJoin;
@@ -7593,6 +7671,8 @@ io.on('connection', (socket) => {
/* Card 3 Ban: LobbyB + Ban
(คน rejoin คนแรกเปนคนเปดโหวต; คนท rejoin หลงขณะโหวตเปดอย งสถานะใหเหนดวย) */
if (serverMapIsPostCaseLobbyB(space)) {
/* ผู้เล่นกลับเข้า LobbyB แล้ว → อัปเดตจำนวนคนที่พร้อมให้ทุกคน (host ใช้เปิดปุ่มเริ่มเมื่อครบ) */
emitDetectiveLobbyBPresence(spaceId, space);
console.log('[ban-debug] join-space rejoin: postLobbyB=true pendingBan=', !!space.pendingBanVoteCard, 'pickVote=', !!space.pickVote, 'by', socket.id);
if (space.pendingBanVoteCard && !space.pickVote) {
const banCard = space.pendingBanVoteCard;
@@ -7780,6 +7860,57 @@ io.on('connection', (socket) => {
socket.to(sid).emit('fill-bot-state', { bots: data.bots });
});
/* MG7 balloon_boss relay / ( host)
payload: { sx, sy, vx, vy, ownerId, t0 } t0 = serverNow ตอนย ให fast-forward ตำแหนงใหตรง */
socket.on('balloon-boss-shot', (data) => {
const sid = socket.data.spaceId;
const space = sid ? spaces.get(sid) : null;
if (!space || !data) return;
const ok = ['sx', 'sy', 'vx', 'vy'].every((k) => typeof data[k] === 'number' && Number.isFinite(data[k]));
if (!ok) return;
socket.to(sid).emit('balloon-boss-shot', {
sx: data.sx, sy: data.sy, vx: data.vx, vy: data.vy,
ownerId: data.ownerId, t0: (typeof data.t0 === 'number' ? data.t0 : Date.now()),
});
});
/* MG7 balloon_boss — host เป็นคนตัดสินจบเกม แล้ว relay ให้ทุกคนจบพร้อมกัน */
socket.on('balloon-boss-over', (data) => {
const sid = socket.data.spaceId;
const space = sid ? spaces.get(sid) : null;
if (!space || !data) return;
const reason = (data.reason === 'victory' || data.reason === 'all_dead') ? data.reason : 'all_dead';
const scores = Array.isArray(data.scores)
? data.scores.filter((s) => s && s.id != null).map((s) => ({ id: s.id, score: Math.max(0, Number(s.score) || 0) }))
: [];
socket.to(sid).emit('balloon-boss-over', { reason, scores });
});
/* MG6 space_shooter — host ตัดสินจบเกม + แนบคะแนนสุดท้าย → ทุกคนจบ/อันดับตรงกัน */
socket.on('space-shooter-over', (data) => {
const sid = socket.data.spaceId;
const space = sid ? spaces.get(sid) : null;
if (!space || !data) return;
const kind = (data.kind === 'all_dead') ? 'all_dead' : 'time_up';
const scores = Array.isArray(data.scores)
? data.scores.filter((s) => s && s.id != null).map((s) => ({ id: s.id, score: Math.max(0, Number(s.score) || 0), hits: Math.max(0, Number(s.hits) || 0), eliminated: !!s.eliminated }))
: [];
socket.to(sid).emit('space-shooter-over', { kind, scores });
});
/* MG6 space_shooter — relay กระสุนผู้เล่น/บอท ให้ทุกคนเห็นตรงกัน (host เห็นยังไง client เห็นแบบนั้น) */
socket.on('space-shooter-shot', (data) => {
const sid = socket.data.spaceId;
const space = sid ? spaces.get(sid) : null;
if (!space || !data) return;
const ok = ['x', 'y', 'vy'].every((k) => typeof data[k] === 'number' && Number.isFinite(data[k]));
if (!ok) return;
socket.to(sid).emit('space-shooter-shot', {
x: data.x, y: data.y, vy: data.vy,
ownerId: data.ownerId, t0: (typeof data.t0 === 'number' ? data.t0 : Date.now()),
});
});
/** เปลี่ยนชื่อที่แสดงใน lobby / เกม (ซิงก์ทุก client ในห้อง) */
socket.on('update-display-name', ({ nickname }, cb) => {
const reply = typeof cb === 'function' ? cb : () => {};
@@ -7881,6 +8012,18 @@ io.on('connection', (socket) => {
if (space.hostId !== socket.id) {
return reply({ ok: false, error: 'เฉพาะ host กดเริ่มสืบสวนได้' });
}
/* LobbyB host
(เด: หลงจบเกม client บางคนยงโหลด LobbyB ไมเสร แต host กดเรมไดเลย คนนนหลดรอบ) */
const waitInfo = detectiveLobbyBMissingParticipants(space);
if (waitInfo.missing > 0) {
return reply({
ok: false,
waitingPlayers: true,
present: waitInfo.present,
total: waitInfo.total,
error: 'รอผู้เล่นกลับเข้าห้องให้ครบก่อน (' + waitInfo.present + '/' + waitInfo.total + ')',
});
}
if (!space.suspectCardMinigames || space.suspectCardMinigames.length < 3) {
assignDetectiveSuspectCardMinigames(space);
}
@@ -8402,7 +8545,7 @@ io.on('connection', (socket) => {
if (space.gauntletCrownLobbyReady && typeof space.gauntletCrownLobbyReady === 'object') {
delete space.gauntletCrownLobbyReady[socket.id];
if (isCrownLobbyShellSpace(space)) {
io.to(sid).emit('gauntlet-crown-lobby-sync', { readyMap: { ...space.gauntletCrownLobbyReady } });
io.to(sid).emit('gauntlet-crown-lobby-sync', crownLobbySyncPayload(space));
}
}
if (space.qbwSolved) space.qbwSolved.delete(socket.id); // Quiz Battle (ฉากเดิน) — เอาคะแนนคนออกแล้ว sync ใหม่ด้านล่าง
@@ -8437,6 +8580,7 @@ io.on('connection', (socket) => {
}
io.to(sid).emit('user-left', { id: socket.id });
if (space.qbwSolved) qbwBroadcastScores(sid, space); // Quiz Battle ฉากเดิน — อัปเดตอันดับหลังมีคนออก
emitDetectiveLobbyBPresence(sid, space); /* คนออกจาก LobbyB → อัปเดตจำนวนที่พร้อมให้ host */
}
});
@@ -8547,7 +8691,12 @@ io.on('connection', (socket) => {
if (!spaceAllowsQuizCarryLobbyRelaxed(space)) return reply({ ok: false, error: 'ห้องนี้ไม่ใช่โหมด lobby หยิบมาวาง / พรีวิว' });
if (space.hostId !== socket.id) return reply({ ok: false, error: 'เฉพาะโฮสต์กด START' });
const rm = space.quizCarryLobbyReady || {};
const humanIds = [...space.peers.keys()];
const humanIds = [...space.peers.keys()].filter((id) => !isBannedPeerForRun(space, id));
/* ต้องเข้าเกมครบก่อน (กัน host เริ่มก่อน client โหลดเสร็จ) */
const expectedActive = space.minigameExpectedActiveCount || 0;
if (expectedActive > 0 && humanIds.length < expectedActive) {
return reply({ ok: false, waitingPlayers: true, present: humanIds.length, total: expectedActive, error: 'รอผู้เล่นเข้าเกมให้ครบก่อน (' + humanIds.length + '/' + expectedActive + ')' });
}
const allReady = humanIds.length > 0 && humanIds.every((id) => rm[id]);
if (!allReady) return reply({ ok: false, error: 'ยังมีผู้เล่นที่ยังไม่ Ready' });
const liveBeginsAt = Date.now() + LIVE_COUNTDOWN_MS;
@@ -8590,7 +8739,7 @@ io.on('connection', (socket) => {
for (const id of space.peers.keys()) {
if (space.gauntletCrownLobbyReady[id] === undefined) space.gauntletCrownLobbyReady[id] = false;
}
socket.emit('gauntlet-crown-lobby-sync', { readyMap: { ...space.gauntletCrownLobbyReady } });
socket.emit('gauntlet-crown-lobby-sync', crownLobbySyncPayload(space));
});
socket.on('gauntlet-crown-lobby-ready', (data) => {
@@ -8603,7 +8752,7 @@ io.on('connection', (socket) => {
if (space.gauntletCrownLobbyReady[id] === undefined) space.gauntletCrownLobbyReady[id] = false;
}
space.gauntletCrownLobbyReady[socket.id] = !!(data && data.ready);
io.to(sid).emit('gauntlet-crown-lobby-sync', { readyMap: { ...space.gauntletCrownLobbyReady } });
io.to(sid).emit('gauntlet-crown-lobby-sync', crownLobbySyncPayload(space));
});
socket.on('gauntlet-crown-lobby-start', (_data, cb) => {
@@ -8617,7 +8766,12 @@ io.on('connection', (socket) => {
const gr = space.gauntletRun;
if (!gr || !gr.crownRunHeld) return reply({ ok: false, error: 'เริ่มแล้ว' });
const rm = space.gauntletCrownLobbyReady || {};
const humanIds = [...space.peers.keys()];
const humanIds = [...space.peers.keys()].filter((id) => !isBannedPeerForRun(space, id));
/* ต้องเข้าเกมครบก่อน (กัน host เริ่มก่อน client โหลดเสร็จ → เกมไม่ sync/จบไม่พร้อมกัน) */
const expectedActive = space.minigameExpectedActiveCount || 0;
if (expectedActive > 0 && humanIds.length < expectedActive) {
return reply({ ok: false, waitingPlayers: true, present: humanIds.length, total: expectedActive, error: 'รอผู้เล่นเข้าเกมให้ครบก่อน (' + humanIds.length + '/' + expectedActive + ')' });
}
const allReady = humanIds.length > 0 && humanIds.every((id) => rm[id]);
if (!allReady) return reply({ ok: false, error: 'ยังมีผู้เล่นที่ยังไม่ Ready' });
/* เวลาเริ่มเล่นจริงร่วมกัน (server epoch) → ทุก client นับ 3-2-1 ไปหาจุดนี้ → GO พร้อมกัน */
@@ -8693,11 +8847,27 @@ io.on('connection', (socket) => {
ny = sn.y;
}
}
if (p && md && md.gameType === 'space_shooter' && data && data.spaceShooterScore != null) {
const ns = Math.floor(Number(data.spaceShooterScore));
if (Number.isFinite(ns) && ns >= 0) {
const prev = Math.max(0, p.spaceShooterScore | 0);
if (ns <= prev + 35) p.spaceShooterScore = Math.max(prev, ns);
let moveForceEmit = false;
if (p && md && md.gameType === 'space_shooter' && data) {
if (data.spaceShooterScore != null) {
const ns = Math.floor(Number(data.spaceShooterScore));
if (Number.isFinite(ns) && ns >= 0) {
const prev = Math.max(0, p.spaceShooterScore | 0);
if (ns <= prev + 35) p.spaceShooterScore = Math.max(prev, ns);
}
}
if (data.spaceShooterHits != null) {
const nh = Math.floor(Number(data.spaceShooterHits));
if (Number.isFinite(nh) && nh >= 0) {
const prevH = Math.max(0, p.spaceShooterHits | 0);
if (nh > prevH) { p.spaceShooterHits = nh; moveForceEmit = true; }
}
}
/* / relay ()
ไมนยานคนตายจะคางนงบนจออกฝ (เหมอนขยบไมได) + ตายไมตรงก */
if (data.spaceShooterEliminated && !p.spaceShooterEliminated) {
p.spaceShooterEliminated = true;
moveForceEmit = true;
}
}
if (p && md && md.gameType === 'balloon_boss' && data) {
@@ -8725,7 +8895,17 @@ io.on('connection', (socket) => {
if (nb <= prevB && nb >= Math.max(0, prevB - 2)) p.balloonBossBalloons = nb;
}
}
if (data.balloonBossEliminated) p.balloonBossEliminated = true;
if (data.balloonBossEliminated && !p.balloonBossEliminated) { p.balloonBossEliminated = true; moveForceEmit = true; }
else if (data.balloonBossEliminated) p.balloonBossEliminated = true;
}
if (p && md && md.gameType === 'quiz_carry' && data && data.quizCarryHeld !== undefined) {
/* relay ของที่ผู้เล่น (มนุษย์) ถืออยู่ → client อื่นเห็น "ใครถืออะไร"; force emit แม้ยืนนิ่ง (หยิบ/วางขณะไม่ขยับ) */
const prevHeld = (p.quizCarryHeld === undefined ? null : p.quizCarryHeld);
const rawHeld = data.quizCarryHeld;
const nh = (rawHeld == null) ? null : Math.floor(Number(rawHeld));
const newHeld = (nh == null || !Number.isFinite(nh)) ? null : nh;
if (newHeld !== prevHeld) moveForceEmit = true;
p.quizCarryHeld = newHeld;
}
if (p) {
const prevX = Number(p.x);
@@ -8735,9 +8915,13 @@ io.on('connection', (socket) => {
p.y = ny;
p.direction = data.direction || p.direction;
const posUnchanged = Math.abs(nx - prevX) < 1e-5 && Math.abs(ny - prevY) < 1e-5 && p.direction === prevDir;
if (!posUnchanged) {
if (!posUnchanged || moveForceEmit) {
const out = { id: socket.id, x: nx, y: ny, direction: p.direction, characterId: p.characterId };
if (md && md.gameType === 'space_shooter') out.spaceShooterScore = Math.max(0, p.spaceShooterScore | 0);
if (md && md.gameType === 'space_shooter') {
out.spaceShooterScore = Math.max(0, p.spaceShooterScore | 0);
out.spaceShooterHits = Math.max(0, p.spaceShooterHits | 0);
out.spaceShooterEliminated = !!p.spaceShooterEliminated;
}
if (md && md.gameType === 'balloon_boss') {
const bbDef = balloonBossBalloonsForMap(md);
out.balloonBossScore = Math.max(0, p.balloonBossScore | 0);
@@ -8747,6 +8931,9 @@ io.on('connection', (socket) => {
: bbDef;
out.balloonBossEliminated = !!p.balloonBossEliminated;
}
if (md && md.gameType === 'quiz_carry') {
out.quizCarryHeld = (p.quizCarryHeld === undefined ? null : p.quizCarryHeld);
}
if (nx !== data.x || ny !== data.y) socket.emit('user-move', out);
socket.to(sid).emit('user-move', out);
}