fixgame 1 and 2 but 2 not finish

This commit is contained in:
2026-06-24 08:58:51 +00:00
parent 7d8a29b96c
commit a67fe4e408
7 changed files with 214 additions and 67 deletions
+26 -22
View File
@@ -51,37 +51,38 @@
"providerUserId": "p_1775109142385_wq7wfy1p32j",
"notes": "auto: player-coins",
"blocked": false,
"coins": 1233,
"coins": 1358,
"createdAt": "2026-04-02T05:52:21+00:00",
"updatedAt": "2026-06-23T15:32:42+00:00",
"updatedAt": "2026-06-24T08:45:00+00:00",
"daily": {
"anchorMs": 1781197200000,
"claimedDays": [
true,
true,
true,
false,
true,
false,
false,
false
],
"lockUntilMs": 0
"lockUntilMs": 1782320400000
},
"score": 1118,
"score": 1208,
"scoreByCase": {
"1": 16,
"10": 340,
"8": 230,
"10": 358,
"8": 238,
"11": 20,
"13": 96,
"14": 20,
"9": 40,
"12": 246,
"12": 272,
"15": 20,
"4": 10,
"5": 20,
"7": 6,
"3": 54
"3": 72,
"6": 20
},
"lbName": "Q",
"achievements": {
@@ -415,9 +416,9 @@
"providerUserId": "p_1781851275186_szcmp5pu3s",
"notes": "auto: player-coins",
"blocked": false,
"coins": 158,
"coins": 212,
"createdAt": "2026-06-19T06:41:15+00:00",
"updatedAt": "2026-06-21T17:18:58+00:00",
"updatedAt": "2026-06-24T05:16:27+00:00",
"daily": {
"anchorMs": 1781802000000,
"claimedDays": [
@@ -431,16 +432,17 @@
],
"lockUntilMs": 0
},
"score": 138,
"score": 192,
"scoreByCase": {
"10": 20,
"10": 28,
"13": 36,
"12": 54,
"4": 28
"12": 82,
"4": 28,
"3": 18
},
"lbName": "MONE",
"achievements": {
"d1_minigame_solver": 13,
"d1_minigame_solver": 19,
"a1_first_deduction": 1,
"a5_truth_hunter": 1
}
@@ -617,19 +619,21 @@
"providerUserId": "p_1782016990238_0u37bv7pxlxs",
"notes": "auto: player-coins",
"blocked": false,
"coins": 20,
"coins": 58,
"createdAt": "2026-06-21T04:43:11+00:00",
"updatedAt": "2026-06-21T05:01:35+00:00",
"updatedAt": "2026-06-24T08:45:00+00:00",
"lobbyColorThemeIndex": 8,
"lobbySkinToneIndex": 1,
"score": 20,
"score": 48,
"scoreByCase": {
"13": 10,
"10": 10
"10": 20,
"6": 8,
"8": 10
},
"lbName": "ผู้เล่น",
"lbName": "MONE",
"achievements": {
"d1_minigame_solver": 2
"d1_minigame_solver": 4
}
},
{
+3 -3
View File
@@ -89,9 +89,9 @@
"balloonBossPlayerBalloonFallbackUrl": "/Game/img/MegaVirus/Artboard%209.png",
"balloonBossBalloonsPerPlayer": 3,
"forcedMinigameKeys": [
"balloon_boss",
"jump_survive",
"space_shooter"
"quiz",
"gauntlet",
"stack"
],
"testSpecialCardByMap": {},
"troublesomeForceOffer": false,
+141 -31
View File
@@ -1704,7 +1704,10 @@
clearTimeout(gauntletCrownCountdownTimer);
gauntletCrownCountdownTimer = null;
}
showGauntletCrownMissionOverlay(gauntletCrownHeistBuildLocalCrownMissionPlay());
/* ตามกฎใหม่: เข้าเส้นชัย/รอด ≥1 คน (ไม่ถูกดันตกขอบ) = result-complete · ตายหมด = result-gameover (ไม่มี timeup) */
const mg2LocalMission = gauntletCrownHeistBuildLocalCrownMissionPlay();
const mg2LocalWin = missionSurvivorsPlay(mg2LocalMission) >= 1 || missionAnyScorePlay(mg2LocalMission);
beginSimpleResultFlashThenGcmPlay(mg2LocalMission, mg2LocalWin ? 'result-complete.png' : 'result-gameover.png');
} catch (_e) { /* ignore */ }
try {
draw();
@@ -6767,8 +6770,56 @@
}
}
/** "" 2 path ( MG2 local finish + server gauntlet-ended) flash 2
วท 2 กบลอก (เกบตวแรกไว). reset เมอเรมมเกมรอบใหม (resetMissionResultFlashGuardPlay) */
let _missionResultFlashAt = 0;
function missionResultFlashGuardPlay() {
const now = Date.now();
if (now - _missionResultFlashAt < 9500) return false; /* เพิ่งโชว์ไป → บล็อกตัวซ้ำ */
_missionResultFlashAt = now;
return true;
}
function resetMissionResultFlashGuardPlay() { _missionResultFlashAt = 0; }
/** มีใครได้คะแนน ≥1 ไหม (จาก mission.ranked) — ตัดสิน complete/gameover (MG1 quiz / MG4 quiz_carry) */
function missionAnyScorePlay(mission) {
const r = (mission && Array.isArray(mission.ranked)) ? mission.ranked : [];
return r.some(function (x) { return x && (Number(x.baseScore) || Number(x.finalScore) || Number(x.score) || 0) > 0; });
}
/** จำนวนผู้รอด (ไม่ eliminated) — ตัดสิน complete/gameover (MG2 เข้าเส้นชัย / MG5 / MG6) */
function missionSurvivorsPlay(mission) {
if (mission && typeof mission.survivorCount === 'number') return mission.survivorCount;
const r = (mission && Array.isArray(mission.ranked)) ? mission.ranked : [];
return r.filter(function (x) { return x && !x.eliminated; }).length;
}
/** จำนวนผู้เข้าร่วมทั้งหมด (คน+บอท) */
function missionParticipantsPlay(mission) {
if (mission && typeof mission.participantCount === 'number' && mission.participantCount > 0) return mission.participantCount;
return (mission && Array.isArray(mission.ranked)) ? mission.ranked.length : 0;
}
/** "รอดทั้งหมด" — ทุกคนรอด ไม่มีใคร eliminated (MG5/MG6) */
function missionAllSurvivedPlay(mission) {
const total = missionParticipantsPlay(mission);
return total > 0 && missionSurvivorsPlay(mission) >= total;
}
/** แฟลชรูปผลรูปเดียว (5s) แล้วเปิด GCM — เกมที่เดิมไม่มีแฟลช (MG1/MG2). asset ตาม skin ผ่าน gcmMissionMockAssetUrl */
function beginSimpleResultFlashThenGcmPlay(mission, file) {
if (!missionResultFlashGuardPlay()) return; /* กันโชว์ซ้ำ (MG2: local finish + server gauntlet-ended ยิงทั้งคู่) */
hideStackTowerResultFlashDomOnlyPlay();
const ov = document.getElementById('stack-tower-result-flash');
const imgEl = document.getElementById('stack-tower-result-flash-img');
if (!mission || !ov || !imgEl) { showGauntletCrownMissionOverlay(mission); return; }
const finishToGcm = function () { hideStackTowerResultFlashDomOnlyPlay(); showGauntletCrownMissionOverlay(mission); };
imgEl.onerror = function () { imgEl.onerror = null; finishToGcm(); };
imgEl.src = gcmMissionMockAssetUrl(mission, file);
ov.classList.remove('is-hidden');
ov.setAttribute('aria-hidden', 'false');
stackTowerResultFlashTimer = setTimeout(function () { stackTowerResultFlashTimer = null; finishToGcm(); }, STACK_TOWER_RESULT_FLASH_MS);
}
/** โชว์รูปผล Tower จาก TowerBlock 5 วิ แล้วเปิด GCM สรุปเดิม */
function beginStackTowerResultFlashThenGcm(mission) {
if (!missionResultFlashGuardPlay()) return; /* กันโชว์ผลซ้ำ */
if (!mission || mission.uiSkin !== 'stack_tower') {
showGauntletCrownMissionOverlay(mission);
return;
@@ -6802,11 +6853,10 @@
}, STACK_TOWER_RESULT_FLASH_MS);
}
/** Jumper mnptfts2: เกรด F = ภาพ gameover · อื่น = complete (ก่อนแผงสรุปผล) */
/** Jumper mnptfts2: มีคนรอด ≥1 = complete · ไม่รอดเลย = gameover (เกรดไม่เกี่ยว — ตามกฎใหม่) */
function jumpSurviveMissionOutcomeImageFile(mission) {
const g = mission && String(mission.grade || '').toUpperCase();
if (g === 'F') return 'result-gameover.png';
return 'result-complete.png';
/* รอดทั้งหมด = complete · มีคนตาย/ตายหมด = gameover (ตามกฎใหม่) */
return missionAllSurvivedPlay(mission) ? 'result-complete.png' : 'result-gameover.png';
}
/**
@@ -6814,6 +6864,7 @@
* all_dead = าม timeup แล outcome อยางเดยว แลวคอย GCM
*/
function beginJumpSurviveMissionResultFlashSequenceThenGcm(mission, endKind) {
if (!missionResultFlashGuardPlay()) return; /* กันโชว์ผลซ้ำ */
if (!mission || mission.uiSkin !== 'jumper') {
showGauntletCrownMissionOverlay(mission);
return;
@@ -6858,6 +6909,7 @@
* กคนตาย = แฟลช result-gameover อยางเดยวแล GCM
*/
function beginSpaceShooterMissionResultFlashSequenceThenGcm(mission, endKind) {
if (!missionResultFlashGuardPlay()) return; /* กันโชว์ผลซ้ำ */
if (!mission || mission.uiSkin !== 'violent_crime') {
showGauntletCrownMissionOverlay(mission);
return;
@@ -6889,10 +6941,11 @@
if (endKind === 'time_up') {
showOneFlash('result-timeup.png', function () {
hideStackTowerResultFlashDomOnlyPlay();
showOneFlash('result-gameover.png', finishToGcm);
/* หมดเวลา: รอดทั้งหมด = complete · มีคนตาย = gameover (ตามกฎใหม่) */
showOneFlash(missionAllSurvivedPlay(mission) ? 'result-complete.png' : 'result-gameover.png', finishToGcm);
});
} else {
showOneFlash('result-gameover.png', finishToGcm);
showOneFlash('result-gameover.png', finishToGcm); /* all_dead */
}
}
@@ -6902,6 +6955,7 @@
* ชนะ = end-victory-2.png ใน MegaVirus
*/
function beginBalloonBossMegaVirusResultFlashSequenceThenGcm(mission, endReason) {
if (!missionResultFlashGuardPlay()) return; /* กันโชว์ผลซ้ำ */
if (!mission || mission.uiSkin !== 'mega_virus') {
showGauntletCrownMissionOverlay(mission);
return;
@@ -6913,23 +6967,30 @@
showGauntletCrownMissionOverlay(mission);
return;
}
const lose = endReason === 'all_dead' || endReason === 'time';
const flashSrc = lose ? stackTowerAssetUrl('result-gameover.png') : megaVirusAssetUrl('end-victory-2.png');
const finishToGcm = function () {
hideStackTowerResultFlashDomOnlyPlay();
showGauntletCrownMissionOverlay(mission);
};
imgEl.onerror = function () {
imgEl.onerror = null;
finishToGcm();
const showOneFlash = function (url, onDone) {
imgEl.onerror = function () { imgEl.onerror = null; onDone(); };
imgEl.src = url;
ov.classList.remove('is-hidden');
ov.setAttribute('aria-hidden', 'false');
stackTowerResultFlashTimer = setTimeout(function () { stackTowerResultFlashTimer = null; onDone(); }, STACK_TOWER_RESULT_FLASH_MS);
};
imgEl.src = flashSrc;
ov.classList.remove('is-hidden');
ov.setAttribute('aria-hidden', 'false');
stackTowerResultFlashTimer = setTimeout(function () {
stackTowerResultFlashTimer = null;
finishToGcm();
}, STACK_TOWER_RESULT_FLASH_MS);
if (endReason === 'victory') {
/* ฆ่าบอส HP=0 → end-victory-2.png (MegaVirus) */
showOneFlash(megaVirusAssetUrl('end-victory-2.png'), finishToGcm);
} else if (endReason === 'time') {
/* หมดเวลา บอสยังไม่ตาย → result-timeup → result-gameover (MegaVirus ไม่มี timeup ใช้ TowerBlock) */
showOneFlash(stackTowerAssetUrl('result-timeup.png'), function () {
hideStackTowerResultFlashDomOnlyPlay();
showOneFlash(stackTowerAssetUrl('result-gameover.png'), finishToGcm);
});
} else {
/* ตายหมด → result-gameover */
showOneFlash(stackTowerAssetUrl('result-gameover.png'), finishToGcm);
}
}
function stackTowerMissionTimeLimitSecPlay() {
@@ -8434,7 +8495,10 @@
const n = ranked.length || 1;
const averageScore = Math.floor(totalSum / n);
let grade = averageScore >= 80 ? 'A' : averageScore >= 60 ? 'B' : 'C';
if (totalSum <= 0 && rows.length) grade = 'F';
/* F (= Lose stamp) "" playLiveQuizScores ()
ไมใช totalSum ของ ranked (me+others) อาจหายคนตอบถกทไมอยใน others ดก flash complete */
const mg1FullScoreSum = Object.keys(playLiveQuizScores).reduce(function (s, k) { return s + Math.max(0, playLiveQuizScores[k] | 0); }, 0);
if (mg1FullScoreSum <= 0 && rows.length) grade = 'F';
const rewardCard = grade === 'F' ? null : gauntletCrownRollRewardCardLocal(grade);
return {
ranked: ranked,
@@ -10286,7 +10350,8 @@
const splashN = splashRanks.length;
const splashAvg = splashN ? Math.min(100, Math.max(0, Math.round(splashTotal / splashN))) : 0;
const splashGrade = summaryOpts.forceGradeF ? 'F' : quizCarryGradeFromTeamAverage(splashAvg);
const useCompleteSplash = splashGrade !== 'F';
/* ตามกฎใหม่: มีใครได้คะแนน ≥1 = result-complete · ไม่มีใครได้เลย = result-gameover (ไม่อิงเกรด/timeup) */
const useCompleteSplash = splashParts.some(function (s) { return s > 0; });
showQuizCarryResultEndLayer(useCompleteSplash);
quizCarrySessionCompleteResultToSummaryT = setTimeout(function () {
quizCarrySessionCompleteResultToSummaryT = null;
@@ -16970,7 +17035,7 @@
if (e.ref && !e.ref.balloonBossEliminated) { e.ref.balloonBossBalloons = 0; e.ref.balloonBossEliminated = true; }
});
try { if (socket && myId != null) socket.emit('move', { x: me.x, y: me.y, direction: me.direction, balloonBossBalloons: 0, balloonBossEliminated: true }); } catch (eT) { /* ignore */ }
triggerBalloonBossOverPlay('all_dead');
triggerBalloonBossOverPlay('time'); /* หมดเวลา บอสยังไม่ตาย → timeup→gameover (แยกจาก all_dead = gameover) */
return;
}
if (teamBossDmg >= maxHp) {
@@ -19537,8 +19602,13 @@
const o = others.get(oid);
if (!o) return;
if (hostHumans.has(String(oid))) { o._notInHostRoster = 0; return; }
/* peer server ( gauntlet-sync = space.peers )
server-authoritative ชนะ host roster อาจตกหลนชวคราว = MG2 เลนหาย/ */
if (o._serverConfirmedAt && (Date.now() - o._serverConfirmedAt) < 3000) { o._notInHostRoster = 0; return; }
o._notInHostRoster = (o._notInHostRoster || 0) + 1;
if (o._notInHostRoster >= 3) others.delete(oid);
/* " roster host " (~1.3s = 16 broadcast) blip peer
แล gauntlet-sync ไม re-create องรอ snapshot re-add ตำแหนงเก = MG2 เลนมารวมเลนบน */
if (o._notInHostRoster >= 16) others.delete(oid);
});
}
/* เกมที่ render อ่านตำแหน่งจาก x/y หรือ Cx/Cy ตรงๆ (ไม่ผ่าน lerp ของ main tick) → สแนปตำแหน่งเลย */
@@ -19685,7 +19755,7 @@
/* MG7 — รับสัญญาณจบเกมจาก host → จบพร้อมกันทุกเครื่อง + ใช้คะแนนสุดท้ายของ host (อันดับตรงกัน) */
socket.on('balloon-boss-over', (d) => {
if (!mapData || mapData.gameType !== 'balloon_boss' || balloonBossGameEnded) return;
const reason = (d && d.reason === 'victory') ? 'victory' : 'all_dead';
const reason = (d && (d.reason === 'victory' || d.reason === 'time')) ? d.reason : 'all_dead';
if (d && Array.isArray(d.scores)) {
d.scores.forEach((s) => {
if (!s || s.id == null) return;
@@ -20269,8 +20339,16 @@
checkMg1AllDeadAndEnd();
});
socket.on('quiz-ended', () => {
socket.on('quiz-ended', (data) => {
if (previewMode && isQuiz()) return;
/* merge authoritative server mission complete/gameover +
( quiz-result อสดทายมาไมนบางจอ totalSum/anyScore าง = ผล 2 จอไมเหมอนก) */
if (data && data.scores && typeof data.scores === 'object') {
Object.keys(data.scores).forEach((pid) => {
const sv = Math.max(0, Number(data.scores[pid]) || 0);
if (playLiveQuizScores[pid] == null || sv > (playLiveQuizScores[pid] | 0)) playLiveQuizScores[pid] = sv;
});
}
let questionMissionEnd = null;
if (isQuizQuestionMissionUiMapPlay()) {
questionMissionEnd = quizQuestionMissionBuildPayload();
@@ -20296,7 +20374,11 @@
quizQuestionMissionPhase = 'ended';
quizQuestionMissionDeferredPhase = null;
applyQuizQuestionMissionPanelImages();
showGauntletCrownMissionOverlay(questionMissionEnd);
/* : 1 = result-complete · = result-gameover ( timeup)
ดสนจาก playLiveQuizScores ตรงๆ (ครบทกคน + merge authoritative แล) ไมใช ranked
(ranked สรางจาก me+others เทาน าคนตอบถกไมอยใน others ของจอน คะแนนหาย = ผลเพยน/2 จอไมตรง) */
const mg1AnyScore = Object.keys(playLiveQuizScores).some(function (k) { return (playLiveQuizScores[k] | 0) > 0; });
beginSimpleResultFlashThenGcmPlay(questionMissionEnd, mg1AnyScore ? 'result-complete.png' : 'result-gameover.png');
}
});
@@ -20376,15 +20458,41 @@
}
me.gauntletEliminated = !!p.gauntletEliminated;
clampGauntletCrownHeistEntityXInPlacePlay(me);
} else {
const o = others.get(p.id) ?? others.get(pid);
} else if (!isPreviewBotId(p.id)) {
let o = others.get(p.id) ?? others.get(pid);
const px = Number(p.x);
const py = Number(p.y);
if (!o && Number.isFinite(px) && Number.isFinite(py)) {
/* peer gauntlet-sync (server-authoritative = space.peers) others
(เชนถ fill-bot-state human-roster force-remove หร user-joined/snapshot มาไมครบ)
re-create "ตำแหน่ง sync" นท ไมองรอ snapshot re-add ดเก
= แก MG2 เลนมาไมครบ + มารวมเลนบน (เพราะ snapshot เก re-add spawn) */
o = {
x: px, y: py, tx: px, ty: py,
direction: p.direction || 'down',
nickname: p.nickname || 'ผู้เล่น',
characterId: p.characterId ?? null,
spawnJoinOrder: 0,
playTint: playTintForPreviewBotId(p.id) || resolvePlayTintForPeer(p.id, p),
gauntletJumpTicks: 0, gauntletJumpVis: 0, gauntletScore: 0,
gauntletEliminated: !!p.gauntletEliminated,
jumpSurviveEliminated: false,
spaceShooterScore: 0, balloonBossScore: 0, balloonBossBossDmg: 0,
balloonBossBalloons: balloonBossBalloonsStartPlay(), balloonBossEliminated: false,
quizCarryHeld: null, bannedSpectator: false,
_notInHostRoster: 0,
};
others.set(p.id, o);
if (o.characterId) { try { preloadPlayTintForAvatar(o.characterId, o.playTint); } catch (_e) { /* ignore */ } }
}
if (o) {
o._serverConfirmedAt = Date.now();
o._notInHostRoster = 0;
const prevSc = Math.max(0, Number(o.gauntletScore) || 0);
const px = Number(p.x);
const py = Number(p.y);
if (Number.isFinite(px)) o.tx = px;
if (Number.isFinite(py)) o.ty = py;
if (p.direction) o.direction = p.direction;
if (o.characterId == null && p.characterId != null) o.characterId = p.characterId;
const jt = Number(p.gauntletJumpTicks);
o.gauntletJumpTicks = Number.isFinite(jt) ? jt : 0;
if (o.gauntletJumpVis == null) o.gauntletJumpVis = o.gauntletJumpTicks;
@@ -20463,7 +20571,9 @@
if (hto) hto.classList.add('is-hidden');
gauntletCrownHowtoVisible = false;
if (data && data.crownMission) {
showGauntletCrownMissionOverlay(data.crownMission);
/* ตามกฎใหม่: เข้าเส้นชัย/รอด ≥1 คน = result-complete · ตายหมด = result-gameover (ไม่มี timeup) */
const mg2SrvWin = missionSurvivorsPlay(data.crownMission) >= 1 || missionAnyScorePlay(data.crownMission);
beginSimpleResultFlashThenGcmPlay(data.crownMission, mg2SrvWin ? 'result-complete.png' : 'result-gameover.png');
return;
}
const ov = document.getElementById('gauntlet-ended-overlay');
+8 -2
View File
@@ -27,6 +27,12 @@
}
const CREATE_ROOM_URL = typeof appPath === 'function' ? appPath('/Create%20Room/') : '/Create%20Room/';
const JOIN_ROOM_URL = typeof appPath === 'function' ? appPath('/Join%20Room/') : '/Join%20Room/';
/** กดออกจากห้อง (LobbyA/LobbyB): host → Create Room · client → Join Room */
function leaveRoomDest() {
try { if (hostId != null && socket && hostId === socket.id) return CREATE_ROOM_URL; } catch (e) { /* ignore */ }
return JOIN_ROOM_URL;
}
function wasPageReload() {
try {
@@ -4704,7 +4710,7 @@
openRoomCustomize();
});
document.getElementById('room-lobby-profile-logout')?.addEventListener('click', () => {
window.location.href = CREATE_ROOM_URL;
window.location.href = leaveRoomDest(); /* host→Create Room · client→Join Room */
});
document.getElementById('lobby-rank-backdrop')?.addEventListener('click', () => {
closeLobbyRankModal();
@@ -7329,7 +7335,7 @@
const btnLeaveLobby = document.getElementById('btn-leave-lobby');
if (btnLeaveLobby) {
btnLeaveLobby.addEventListener('click', () => {
window.location.href = CREATE_ROOM_URL;
window.location.href = leaveRoomDest(); /* host→Create Room · client→Join Room */
});
}
+1 -1
View File
@@ -5371,7 +5371,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.006232016"></script>
<script src="js/play.js?v=0.006232022"></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.0303"></script>
<script src="js/room-lobby.js?v=0.0304"></script>
<div class="version-tag">v —</div>
</body>
</html>
+34 -7
View File
@@ -4166,17 +4166,27 @@ function advanceTestimony(sid, space) {
function endQuizGame(sid, space, message) {
clearSpaceQuizTimers(space);
/* authoritative () quiz-ended client complete/gameover
(เดมไม client าน playLiveQuizScores local quiz-result อสดทายอาจมาไม ผล 2 จอไมตรง) */
const quizFinalScores = {};
const sessF = space.quizSession;
if (sessF && sessF.players) {
Object.keys(sessF.players).forEach((pid) => {
const st = sessF.players[pid];
quizFinalScores[pid] = (st && typeof st.score === 'number') ? Math.max(0, st.score) : 0;
});
}
if (space.quizSession) space.quizSession.active = false;
if (space.detectiveMinigameActive) {
/* MG1: auto-return/auto-grant quiz-ended client ( )
เด returnDetectiveSpaceToLobbyB = server แจกการ+เดงกลบเองทนท = "auto รับผล/การ์ด"
การแจกการ/กล LobbyB จะเกดเม client กดป detective-minigame-finished (เสนทางเดยวก MG2-7) */
io.to(sid).emit('quiz-ended', { message: message || 'จบเกม', returnToLobbyB: true });
io.to(sid).emit('quiz-ended', { message: message || 'จบเกม', returnToLobbyB: true, scores: quizFinalScores });
/* quizSession (active=false ) client finish returnDetectiveSpaceToLobbyB quiz
( null minigamePlayerScoreSurvived('quiz') = 0 หมด เหรยญเพยน) ; จะถกแทนเมอเรมมเกมรอบใหม */
return;
}
io.to(sid).emit('quiz-ended', { message: message || 'จบเกม' });
io.to(sid).emit('quiz-ended', { message: message || 'จบเกม', scores: quizFinalScores });
space.quizSession = null;
}
@@ -6531,9 +6541,20 @@ function initGauntletPeersAll(space, md) {
const list = [...space.peers.values()];
const pos = gauntletSpawnPositions(md, list.length);
const crown = isGauntletCrownHeistMapBySpace(space);
list.forEach((p, i) => {
const ord = typeof p.spawnJoinOrder === 'number' ? p.spawnJoinOrder : i;
const pt = pos[ord] || pos[i] || pos[pos.length - 1];
/* lane "": spawnJoinOrder (id tiebreak )
แลวแจกชอง pos แบบ unique ord ชนก/เกนจำนวนชอง (pos[ord] ) นไปชองวางถดไป
= แก MG2 user1/user2 spawn บก (เด pos[ord] ำเม ord ชนหร fallback องสดทาย) */
const ordered = list
.map((p, i) => ({ p, ord: (typeof p.spawnJoinOrder === 'number' ? p.spawnJoinOrder : i) }))
.sort((a, b) => (a.ord - b.ord) || (String(a.p.id) < String(b.p.id) ? -1 : 1));
const usedPosIdx = new Set();
ordered.forEach((entry, seq) => {
const p = entry.p;
let idx = Number.isFinite(entry.ord) ? Math.max(0, Math.min(entry.ord, pos.length - 1)) : seq;
let guard = 0;
while (usedPosIdx.has(idx) && guard < pos.length) { idx = (idx + 1) % pos.length; guard++; }
usedPosIdx.add(idx);
const pt = pos[idx] || pos[pos.length - 1];
p.x = pt.x;
p.y = pt.y;
p.gauntletJumpTicks = 0;
@@ -7624,7 +7645,13 @@ io.on('connection', (socket) => {
if (mdJoin.gameType === 'gauntlet') {
const ord = typeof peer.spawnJoinOrder === 'number' ? peer.spawnJoinOrder : [...space.peers.keys()].indexOf(socket.id);
const pos = gauntletSpawnPositions(mdJoin, space.peers.size);
const pt = pos[ord] != null ? pos[ord] : pos[pos.length - 1];
/* เลี่ยงเกิดทับ peer อื่นที่อยู่แล้ว (reconnect กลางเกม ord ชนช่องที่ถูกถือ) — ดันไปช่องว่างถัดไป */
const occupied = new Set();
space.peers.forEach((q) => { if (q && q.id !== socket.id) occupied.add(Math.floor(q.x) + ',' + Math.floor(q.y)); });
let pidx = Number.isFinite(ord) ? Math.max(0, Math.min(ord, pos.length - 1)) : pos.length - 1;
let pguard = 0;
while (pos[pidx] && occupied.has(pos[pidx].x + ',' + pos[pidx].y) && pguard < pos.length) { pidx = (pidx + 1) % pos.length; pguard++; }
const pt = pos[pidx] != null ? pos[pidx] : pos[pos.length - 1];
peer.x = pt.x;
peer.y = pt.y;
peer.gauntletJumpTicks = 0;
@@ -7930,7 +7957,7 @@ io.on('connection', (socket) => {
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 reason = (data.reason === 'victory' || data.reason === 'all_dead' || data.reason === 'time') ? 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), eliminated: !!s.eliminated }))
: [];