fixgame 1 and 2 but 2 not finish
This commit is contained in:
@@ -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
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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
@@ -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');
|
||||
|
||||
@@ -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 */
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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 }))
|
||||
: [];
|
||||
|
||||
Reference in New Issue
Block a user