fixed game synce

This commit is contained in:
2026-06-21 09:46:01 +00:00
parent fc2ff30e50
commit a474f0e736
4 changed files with 344 additions and 85 deletions
+39 -10
View File
@@ -111,20 +111,21 @@
"providerUserId": "p_1777015659667_xqfjh247bgp",
"notes": "auto: player-coins",
"blocked": false,
"coins": 330,
"coins": 348,
"createdAt": "2026-04-24T07:28:02+00:00",
"updatedAt": "2026-06-20T17:54:37+00:00",
"score": 330,
"updatedAt": "2026-06-21T09:15:13+00:00",
"score": 348,
"scoreByCase": {
"8": 220,
"5": 30,
"7": 60,
"2": 10,
"9": 10
"9": 10,
"10": 18
},
"lbName": "MONE",
"achievements": {
"d1_minigame_solver": 2
"d1_minigame_solver": 4
},
"lobbyColorThemeIndex": 6,
"lobbySkinToneIndex": 1
@@ -535,19 +536,24 @@
{
"id": "80f31f73da0328dbf24e0206",
"email": "",
"displayName": "Guest",
"displayName": "ผู้เล่น",
"loginType": "guest",
"providerUserId": "p_1781862203558_jhynnz90qrg",
"notes": "auto: player-coins",
"blocked": false,
"coins": 0,
"coins": 18,
"createdAt": "2026-06-19T09:43:23+00:00",
"updatedAt": "2026-06-20T17:27:35+00:00",
"updatedAt": "2026-06-21T09:15:13+00:00",
"lobbyColorThemeIndex": 6,
"lobbySkinToneIndex": 1,
"achievements": {
"d1_minigame_solver": 1
}
"d1_minigame_solver": 2
},
"score": 18,
"scoreByCase": {
"10": 18
},
"lbName": "ผู้เล่น"
},
{
"id": "fbb262e6c2aa47bd97aef554",
@@ -562,6 +568,29 @@
"updatedAt": "2026-06-20T06:58:53+00:00",
"lobbyColorThemeIndex": 7,
"lobbySkinToneIndex": 1
},
{
"id": "8f0741532038e900e759c5ef",
"email": "",
"displayName": "MONE",
"loginType": "guest",
"providerUserId": "p_1782016990238_0u37bv7pxlxs",
"notes": "auto: player-coins",
"blocked": false,
"coins": 20,
"createdAt": "2026-06-21T04:43:11+00:00",
"updatedAt": "2026-06-21T05:01:35+00:00",
"lobbyColorThemeIndex": 8,
"lobbySkinToneIndex": 1,
"score": 20,
"scoreByCase": {
"13": 10,
"10": 10
},
"lbName": "ผู้เล่น",
"achievements": {
"d1_minigame_solver": 2
}
}
]
}
+265 -70
View File
@@ -145,6 +145,22 @@
const SPACE_SHOOTER_ASTEROID_RADIUS_AT_FULL = 48;
const SPACE_SHOOTER_ASTEROID_RADIUS_AT_MIN_HP = 11;
/* PRNG seed (mulberry32) deterministic
seed เดยวก ( liveBeginsAt ของ server) กเครองไดลำดบสมเดยวก = กาบาต/บอลลนชดเดยวก */
function makeSeededRng(seed) {
let t = (seed >>> 0) || 1;
return function () {
t = (t + 0x6D2B79F5) >>> 0;
let r = Math.imul(t ^ (t >>> 15), 1 | t);
r = (r + Math.imul(r ^ (r >>> 7), 61 | r)) ^ r;
return ((r ^ (r >>> 14)) >>> 0) / 4294967296;
};
}
/* seed ต่อ index ของ spawn — รวม anchor (liveBeginsAt) กับ index ให้ก้อนแต่ละลำดับสุ่มต่างกันแต่ตรงกันทุกเครื่อง */
function envSpawnSeed(anchorMs, index) {
return (((anchorMs >>> 0) ^ Math.imul((index | 0) + 1, 2654435761)) >>> 0);
}
function spaceShooterAsteroidRadiusFromHpPlay(hp) {
const cap = SPACE_SHOOTER_ASTEROID_MAX_HP;
const h = Math.max(0, Math.min(cap, Math.floor(Number(hp)) || 0));
@@ -3428,6 +3444,9 @@
let jumpSurviveEliminated = false;
/** จับเวลาโหมดกระโดดให้รอด (วินาทีบน HUD) */
let jumpSurviveSessionStartMs = 0;
/* server (epoch ms) live scroll/ serverNow()
กเครองเหนแพลตฟอรมเลอนเทาก + จบพรอมก (เดมสะสม dt local เลอน/จบไมตรงก = desync) */
let jumpSurviveLiveAnchorServerMs = 0;
let jumpSurvivePlatformScrollPx = 0;
let jumpSurviveVy = 0;
let jumpSurviveLastTickMs = 0;
@@ -3447,6 +3466,9 @@
let spaceShooterPopups = [];
let spaceShooterLastTickMs = 0;
let spaceShooterSpawnAccMs = 0;
/* เวลา server ที่ live เริ่ม + ตัวนับลำดับก้อนที่เกิดแล้ว — ใช้ทำ deterministic spawn ให้ทุกเครื่องตรงกัน */
let spaceShooterLiveAnchorServerMs = 0;
let spaceShooterSpawnCount = 0;
let spaceShooterFireCd = 0;
let spaceShooterSessionStartMs = 0;
let spaceShooterLastMoveEmit = 0;
@@ -3541,6 +3563,9 @@
/** Mega Virus — ป๊อปดาเมจต่อบอส (ข้อความ +2) เมื่อกระสุนโดนบอส */
let balloonBossScorePopups = [];
let balloonBossBossFireAcc = 0;
/* anchor เวลา server + ตัวนับกระสุนบอส — deterministic boss fire ให้ทุกเครื่องยิงทิศ/เวลาเดียวกัน */
let balloonBossLiveAnchorServerMs = 0;
let balloonBossBossShotCount = 0;
/** quiz_battle — โดม MCQ กด E (ข้อจาก battleQuizMcq) */
let quizBattleMcqPool = [];
let quizBattleAnsweredComps = new Set();
@@ -7092,7 +7117,10 @@
if (!isJumpSurviveMissionUiMapPlay() || jumpSurviveMissionPhase !== 'live' || jumpSurviveSessionStartMs <= 0) return null;
const lim = jumpSurviveTimeLimitSecForMission();
if (!(lim > 0)) return null;
const elapsed = (performance.now() - jumpSurviveSessionStartMs) / 1000;
/* นับเวลาจาก anchor server (serverNow) → จบพร้อมกันทุกเครื่อง; fallback local ถ้ายังไม่มี anchor */
const elapsed = jumpSurviveLiveAnchorServerMs > 0
? (serverNow() - jumpSurviveLiveAnchorServerMs) / 1000
: (performance.now() - jumpSurviveSessionStartMs) / 1000;
return Math.max(0, Math.ceil(lim - elapsed));
}
@@ -7182,6 +7210,9 @@
jumpSurviveMissionPhase = 'live';
jumpSurviveGameEnded = false;
jumpSurviveEliminated = false;
/* anchor เวลาเริ่ม live = liveBeginsAt ของ server (ถ้ามี) → scroll/timer ของทุกเครื่องอ้างอิงจุดเดียวกัน */
jumpSurviveLiveAnchorServerMs = playMissionLiveBeginsAt > 0 ? playMissionLiveBeginsAt : serverNow();
jumpSurvivePlatformScrollPx = 0;
applyJumpSurvivePreviewSpawnLayout(false);
};
if (!cd || !numEl) {
@@ -7564,6 +7595,9 @@
spaceShooterPopups = [];
spaceShooterLastTickMs = performance.now();
spaceShooterSpawnAccMs = 0;
spaceShooterSpawnCount = 0;
/* anchor = liveBeginsAt ของ server (ทุกเครื่องได้ค่าเดียวกัน) → spawn/timer อ้างอิงจุดเดียวกัน */
spaceShooterLiveAnchorServerMs = playMissionLiveBeginsAt > 0 ? playMissionLiveBeginsAt : serverNow();
spaceShooterFireCd = 0;
spaceShooterSessionStartMs = performance.now();
spaceShooterLastMoveEmit = 0;
@@ -8967,12 +9001,19 @@
}
function quizCarryPregameHumanIds() {
const ids = [];
if (myId != null && !isPreviewBotId(myId)) ids.push(String(myId));
const set = new Set();
if (myId != null && !isPreviewBotId(myId)) set.add(String(myId));
others.forEach((_, id) => {
if (!isPreviewBotId(id)) ids.push(String(id));
if (!isPreviewBotId(id)) set.add(String(id));
});
return ids;
/* id readyMap server host pregame client reconnect (others )
ทำให host /รอ client ครบจร ไมเรมคนเดยว และเหนจำนวนผเลนถกตอง */
if (quizCarryLobbyReadyMap && typeof quizCarryLobbyReadyMap === 'object') {
Object.keys(quizCarryLobbyReadyMap).forEach((id) => {
if (id && !isPreviewBotId(id)) set.add(String(id));
});
}
return [...set];
}
function quizCarryPregameBotCount() {
@@ -9126,14 +9167,16 @@
primaryImg.alt = humansReady ? 'START' : 'READY';
}
if (primary) {
/* READY phase: ทุกคน (รวม client) กดสลับ Ready ของตัวเองได้ | START phase: เฉพาะ host กดเริ่ม */
const canPress = humansReady ? isMePlayHost() : true;
primary.classList.toggle('is-start-phase', humansReady);
primary.classList.toggle('is-read-only', !isMePlayHost());
primary.classList.toggle('is-pressed', isMePlayHost() && meReady && !humansReady);
primary.disabled = !isMePlayHost();
primary.classList.toggle('is-read-only', !canPress);
primary.classList.toggle('is-pressed', meReady && !humansReady);
primary.disabled = !canPress;
primary.setAttribute('aria-pressed', humansReady ? 'false' : (meReady ? 'true' : 'false'));
primary.title = isMePlayHost()
? (humansReady ? 'START' : (meReady ? 'ยกเลิก READY' : 'READY'))
: (humansReady ? 'START (โฮสต์เท่านั้น)' : 'READY (โฮสต์เท่านั้น)');
primary.title = humansReady
? (isMePlayHost() ? 'START' : 'รอโฮสต์กด START')
: (meReady ? 'ยกเลิก READY' : 'READY');
}
}
@@ -9143,8 +9186,8 @@
quizCarryLobbyReadyMap = {};
quizCarryPregameHumanIds().forEach((id) => { quizCarryLobbyReadyMap[id] = false; });
showQuizCarryPregameOverlay();
/* ขอสถานะ ready ปัจจุบันจาก server (authoritative) — ทุกคนรวม host ต้องกด READY เองตาม flow */
if (socket && socket.connected) socket.emit('quiz-carry-lobby-sync-request');
quizCarrySyncGuestReadyIfNeeded();
updateQuizCarryPregameHud();
}
@@ -9361,6 +9404,43 @@
syncQuizCarryEmbedQuestionStrip();
}
/* networked mission (quiz_carry detective): host + broadcast
นแตละเครองสมคนละข (เด client Math.random() เอง host/client เหนคนละคำถาม) */
function quizCarryIsNetworkedMission() {
return quizCarryEmbedMissionFlowActive() && socket && socket.connected;
}
/* ตั้งคำถามถัดไป (embed/detective flow) จาก q ที่ "เลือกแล้ว" — ใช้ได้ทั้ง host (หลังสุ่ม) และ client (รับ broadcast) */
function applyQuizCarryEmbedNextQuestion(q) {
if (!q) return;
quizCarryEmbedPendingQuestion = q;
preloadQuizCarryChoiceImages(q);
quizCarryEmbedCountdownStartAt = Date.now();
quizCarryEmbedCountdownEndAt = quizCarryEmbedCountdownStartAt + 3000;
quizCarryEmbedPreOptionCountdownStartAt = 0;
quizCarryEmbedPreOptionCountdownEndAt = 0;
quizCarryCurrent = null;
playQuizText = '';
clearQuizMapQuestionCarryFeedback();
quizCarryOptionRevealAt = 0;
quizCarryAnswerCloseAt = 0;
me.quizCarryHeld = null;
others.forEach((o) => {
if (!o) return;
o.quizCarryHeld = null;
o.botPath = [];
o.botPathStuckTicks = 0;
/* ต้องใช้ performance.now() ช่วงกับ stepQuizCarryPreviewBots — ห้ามใช้ Date.now() epoch ไม่งั้นบอทค้าง pathfind ตลอด */
o.botQuizCarryPathfindAfter = 0;
});
const qEl = document.getElementById('quiz-game-question');
if (qEl) qEl.textContent = '…';
const phaseEl = document.getElementById('quiz-game-phase-label');
if (phaseEl) phaseEl.textContent = 'รอเริ่มคำถาม — นับถอยหลัง 3 · 2 · 1';
tickQuizCarryEmbedCountdown();
syncQuizCarryEmbedQuestionStrip();
}
function quizCarryPickNextQuestion() {
if (quizCarrySessionEnded) return;
if (quizCarryPregameActive) return;
@@ -9377,39 +9457,20 @@
playQuizText = 'ไม่มีคำถาม — ใส่ quizQuestions ในแมป (choices + correctIndex) หรือตั้งใน Admin';
return;
}
/* client ที่ไม่ใช่ host ในห้องจริง: ไม่สุ่มเอง รอ event 'quiz-carry-question' จาก host */
if (quizCarryIsNetworkedMission() && !isMePlayHost()) return;
let q;
let qIdx = 0;
let guard = 0;
do {
q = quizCarryPool[Math.floor(Math.random() * quizCarryPool.length)];
qIdx = Math.floor(Math.random() * quizCarryPool.length);
q = quizCarryPool[qIdx];
guard++;
} while (guard < 12 && quizCarryPool.length > 1 && quizCarryCurrent && q && q.text === quizCarryCurrent.text);
if (quizCarryEmbedMissionFlowActive()) {
quizCarryEmbedPendingQuestion = q;
preloadQuizCarryChoiceImages(q);
quizCarryEmbedCountdownStartAt = Date.now();
quizCarryEmbedCountdownEndAt = quizCarryEmbedCountdownStartAt + 3000;
quizCarryEmbedPreOptionCountdownStartAt = 0;
quizCarryEmbedPreOptionCountdownEndAt = 0;
quizCarryCurrent = null;
playQuizText = '';
clearQuizMapQuestionCarryFeedback();
quizCarryOptionRevealAt = 0;
quizCarryAnswerCloseAt = 0;
me.quizCarryHeld = null;
others.forEach((o) => {
if (!o) return;
o.quizCarryHeld = null;
o.botPath = [];
o.botPathStuckTicks = 0;
/* ต้องใช้ performance.now() ช่วงกับ stepQuizCarryPreviewBots — ห้ามใช้ Date.now() epoch ไม่งั้นบอทค้าง pathfind ตลอด */
o.botQuizCarryPathfindAfter = 0;
});
const qEl = document.getElementById('quiz-game-question');
if (qEl) qEl.textContent = '…';
const phaseEl = document.getElementById('quiz-game-phase-label');
if (phaseEl) phaseEl.textContent = 'รอเริ่มคำถาม — นับถอยหลัง 3 · 2 · 1';
tickQuizCarryEmbedCountdown();
syncQuizCarryEmbedQuestionStrip();
/* host แจ้ง index คำถามให้ทุกคนใช้ข้อเดียวกัน */
if (socket && socket.connected) socket.emit('quiz-carry-question', { idx: qIdx });
applyQuizCarryEmbedNextQuestion(q);
return;
}
quizCarryCurrent = q;
@@ -9433,6 +9494,9 @@
finishDetectiveMinigameAndReturnLobby();
return;
}
/* : host / broadcast client event
(นแตละเครองเลอนรอบคนละจงหวะ/คนละคำถาม) */
if (quizCarryIsNetworkedMission() && !isMePlayHost()) return;
if (!quizCarryEmbedMissionFlowActive() && !previewMode) {
quizCarryPickNextQuestion();
return;
@@ -9440,6 +9504,7 @@
quizCarryRoundsCompleted++;
const lim = quizCarrySessionLength;
if (lim > 0 && quizCarryRoundsCompleted >= lim) {
if (socket && socket.connected) socket.emit('quiz-carry-session-complete', {});
showQuizCarrySessionCompleteOverlay();
return;
}
@@ -12536,6 +12601,9 @@
function applyMegaBalloonLiveStartPlay() {
balloonBossSessionStartMs = performance.now();
balloonBossLastTickMs = performance.now();
/* anchor = liveBeginsAt server → boss fire + timer อ้างอิงจุดเดียวกันทุกเครื่อง */
balloonBossLiveAnchorServerMs = playMissionLiveBeginsAt > 0 ? playMissionLiveBeginsAt : serverNow();
balloonBossBossShotCount = 0;
balloonBossGameEnded = false;
balloonBossPendingShots = [];
balloonBossPlayerBullets = [];
@@ -15506,7 +15574,10 @@
if (isSpaceShooterMissionUiMapPlay() && spaceShooterMissionPhase !== 'live') return null;
const lim = spaceShooterTimeLimitSecPlay();
if (lim <= 0) return null;
const elapsed = (performance.now() - spaceShooterSessionStartMs) / 1000;
/* นับเวลาจาก anchor server → จบพร้อมกันทุกเครื่อง; fallback local ถ้ายังไม่มี anchor (preview) */
const elapsed = spaceShooterLiveAnchorServerMs > 0
? (serverNow() - spaceShooterLiveAnchorServerMs) / 1000
: (performance.now() - spaceShooterSessionStartMs) / 1000;
return Math.max(0, Math.ceil(lim - elapsed));
}
@@ -15654,19 +15725,46 @@
}
const interval = spaceShooterAsteroidSpawnIntervalMsPlay();
spaceShooterSpawnAccMs += dt * 1000;
while (spaceShooterSpawnAccMs >= interval) {
spaceShooterSpawnAccMs -= interval;
const maxHp = 1 + Math.floor(Math.random() * SPACE_SHOOTER_ASTEROID_MAX_HP);
const hp0 = maxHp;
spaceShooterAsteroids.push({
x: 36 + Math.random() * (mw - 72),
y: -45 - Math.random() * 90,
r: spaceShooterAsteroidRadiusFromHpPlay(hp0),
vy: 58 + Math.random() * 62,
maxHp: maxHp,
hp: hp0,
});
if (spaceShooterLiveAnchorServerMs > 0) {
/* deterministic spawn: k anchor + k*interval seed(anchor,k)
กเครองเกดกอนชดเดยวก ตำแหน/HP/ความเรวเทาก เวลา server เดยวก */
const targetCount = Math.floor((serverNow() - spaceShooterLiveAnchorServerMs) / interval) + 1;
let guard = 0;
while (spaceShooterSpawnCount < targetCount && guard < 200) {
guard++;
const k = spaceShooterSpawnCount++;
const rng = makeSeededRng(envSpawnSeed(spaceShooterLiveAnchorServerMs, k));
const maxHp = 1 + Math.floor(rng() * SPACE_SHOOTER_ASTEROID_MAX_HP);
const ax = 36 + rng() * (mw - 72);
const baseY = -45 - rng() * 90;
const avy = 58 + rng() * 62;
const spawnTimeMs = spaceShooterLiveAnchorServerMs + k * interval;
const elapsedSec = Math.max(0, (serverNow() - spawnTimeMs) / 1000);
spaceShooterAsteroids.push({
id: k,
x: ax,
y: baseY + avy * elapsedSec,
r: spaceShooterAsteroidRadiusFromHpPlay(maxHp),
vy: avy,
maxHp: maxHp,
hp: maxHp,
});
}
} else {
spaceShooterSpawnAccMs += dt * 1000;
while (spaceShooterSpawnAccMs >= interval) {
spaceShooterSpawnAccMs -= interval;
const maxHp = 1 + Math.floor(Math.random() * SPACE_SHOOTER_ASTEROID_MAX_HP);
const hp0 = maxHp;
spaceShooterAsteroids.push({
x: 36 + Math.random() * (mw - 72),
y: -45 - Math.random() * 90,
r: spaceShooterAsteroidRadiusFromHpPlay(hp0),
vy: 58 + Math.random() * 62,
maxHp: maxHp,
hp: hp0,
});
}
}
others.forEach((o, id) => {
@@ -16177,8 +16275,11 @@
const lim = balloonBossTimeLimitSecPlay();
if (lim <= 0) return null;
if (isMegaVirusMissionShellMapPlay() && isGauntletCrownPregameBlockingPlay()) return lim;
if (!balloonBossSessionStartMs) return lim;
const elapsed = (performance.now() - balloonBossSessionStartMs) / 1000;
if (!balloonBossSessionStartMs && !(balloonBossLiveAnchorServerMs > 0)) return lim;
/* นับเวลาจาก anchor server → จบพร้อมกันทุกเครื่อง; fallback local ถ้ายังไม่มี anchor */
const elapsed = balloonBossLiveAnchorServerMs > 0
? (serverNow() - balloonBossLiveAnchorServerMs) / 1000
: (performance.now() - balloonBossSessionStartMs) / 1000;
return Math.max(0, Math.ceil(lim - elapsed));
}
@@ -16436,18 +16537,40 @@
}
const bossFireEvery = 1.42;
balloonBossBossFireAcc += dt;
while (balloonBossBossFireAcc >= bossFireEvery) {
balloonBossBossFireAcc -= bossFireEvery;
if (aliveRefs.length) {
const ang = Math.random() * Math.PI * 2;
const spd = 88 + Math.random() * 72;
if (balloonBossLiveAnchorServerMs > 0) {
/* deterministic boss fire: k anchor + k*bossFireEvery seed(anchor,k)
กเครองเหนแพตเทนกระสนบอสเดยวก (เด Math.random แยกก = คนละลาย) */
const fireIntervalMs = bossFireEvery * 1000;
const targetShots = Math.floor((serverNow() - balloonBossLiveAnchorServerMs) / fireIntervalMs) + 1;
let bguard = 0;
while (balloonBossBossShotCount < targetShots && bguard < 200) {
bguard++;
const k = balloonBossBossShotCount++;
if (!aliveRefs.length) continue;
const rng = makeSeededRng(envSpawnSeed(balloonBossLiveAnchorServerMs, k));
const ang = rng() * Math.PI * 2;
const spd = 88 + rng() * 72;
const gy = 48 + rng() * 56;
balloonBossBossBullets.push({
x: bossC.cx, y: bossC.cy,
vx: Math.cos(ang) * spd, vy: Math.sin(ang) * spd,
gy: 48 + Math.random() * 56,
gy: gy,
});
}
} else {
balloonBossBossFireAcc += dt;
while (balloonBossBossFireAcc >= bossFireEvery) {
balloonBossBossFireAcc -= bossFireEvery;
if (aliveRefs.length) {
const ang = Math.random() * Math.PI * 2;
const spd = 88 + Math.random() * 72;
balloonBossBossBullets.push({
x: bossC.cx, y: bossC.cy,
vx: Math.cos(ang) * spd, vy: Math.sin(ang) * spd,
gy: 48 + Math.random() * 56,
});
}
}
}
function applyBalloonBossHitToEntity(ent) {
@@ -17827,8 +17950,14 @@
let zJumpSurviveWorld = zoom;
if (previewMode && editorEmbedReturn && mapData) zJumpSurviveWorld *= playEmbedUserZoomMul;
const halfViewH = canvas.height / (2 * zJumpSurviveWorld);
const rise = (Number(md.jumpSurviveRisePxPerSec) > 0 ? Number(md.jumpSurviveRisePxPerSec) : 42) * dt;
jumpSurvivePlatformScrollPx += rise;
const risePerSec = Number(md.jumpSurviveRisePxPerSec) > 0 ? Number(md.jumpSurviveRisePxPerSec) : 42;
if (jumpSurviveLiveAnchorServerMs > 0) {
/* scroll = ฟังก์ชันของเวลา server (ไม่สะสม dt local) → ทุกเครื่องเลื่อนเท่ากันที่เวลาเดียวกัน */
const scrollSec = Math.max(0, (serverNow() - jumpSurviveLiveAnchorServerMs) / 1000);
jumpSurvivePlatformScrollPx = scrollSec * risePerSec;
} else {
jumpSurvivePlatformScrollPx += risePerSec * dt;
}
const camTgt = jumpSurviveCameraTargetPx(md);
const ck = Math.min(1, dt * 7);
@@ -18714,8 +18843,35 @@
socket.on('quiz-carry-lobby-sync', (d) => {
if (!d || typeof d.readyMap !== 'object') return;
quizCarryLobbyReadyMap = { ...d.readyMap };
/* upsert others peers snapshot server
host ไมเหนตวละคร client เม user-joined/plist มาไมครบชวงเปลยนหน lobbyBplay */
if (Array.isArray(d.peers)) {
d.peers.forEach((p) => {
if (!p || p.id == null || isPreviewBotId(p.id)) return;
if (myId != null && String(p.id) === String(myId)) return;
if (others.has(p.id) || others.has(String(p.id))) return;
const px = Number(p.x);
const py = Number(p.y);
const x = Number.isFinite(px) ? px : 1;
const y = Number.isFinite(py) ? py : 1;
const joP = Number(p.spawnJoinOrder);
others.set(p.id, {
x, y, tx: x, ty: y,
direction: p.direction || 'down',
nickname: p.nickname,
characterId: p.characterId ?? null,
spawnJoinOrder: Number.isFinite(joP) ? Math.max(0, Math.floor(joP)) : 0,
playTint: playTintFromJoinPeer(p) || playTintForPreviewBotId(p.id) || resolvePlayTintForPeer(p.id, p),
gauntletJumpTicks: 0, gauntletJumpVis: 0, gauntletScore: 0,
jumpSurviveEliminated: false, spaceShooterScore: 0,
balloonBossScore: 0, balloonBossBossDmg: 0,
balloonBossBalloons: balloonBossBalloonsStartPlay(),
balloonBossEliminated: false, quizCarryHeld: null,
});
if (p.characterId) preloadPlayCharacterLayers(p.characterId);
});
}
if (quizCarryPregameActive && isQuizCarry()) {
quizCarrySyncGuestReadyIfNeeded();
updateQuizCarryPregameHud();
}
});
@@ -18724,6 +18880,18 @@
if (!quizCarryEmbedMissionFlowActive()) return;
endQuizCarryEmbedPregameAndStart();
});
/* host เลือกคำถาม → client อื่นใช้ข้อเดียวกัน (กันสุ่มไม่ตรงกัน) */
socket.on('quiz-carry-question', (d) => {
if (!d || isMePlayHost() || !isQuizCarry() || !quizCarryEmbedMissionFlowActive()) return;
const idx = Number(d.idx);
if (!Number.isInteger(idx) || idx < 0 || idx >= quizCarryPool.length) return;
applyQuizCarryEmbedNextQuestion(quizCarryPool[idx]);
});
/* host จบเซสชัน → client อื่นโชว์สรุปพร้อมกัน */
socket.on('quiz-carry-session-complete', () => {
if (isMePlayHost() || !isQuizCarry() || !quizCarryEmbedMissionFlowActive()) return;
showQuizCarrySessionCompleteOverlay();
});
socket.on('gauntlet-crown-lobby-sync', (d) => {
if (!d || typeof d.readyMap !== 'object') return;
gauntletCrownLobbyReadyMap = { ...d.readyMap };
@@ -18888,7 +19056,9 @@
} else {
o.tx = data.x;
o.ty = data.y;
if (mapData && isQuiz()) {
/* quiz + quiz_carry: / ( lerp )
host าง/หายจากจอเม move มาหางหรอมาไมอเนอง */
if (mapData && (isQuiz() || isQuizCarry())) {
const px = Number(data.x);
const py = Number(data.y);
if (Number.isFinite(px)) o.x = px;
@@ -19319,6 +19489,28 @@
o.gauntletEliminated = false;
});
}
/* quiz_carry: sync peersSnap
เด client apply peersSnap เฉพาะ gauntlet quiz_carry เลนยนนงตอบคำถาม ไม move
others างทตำแหนงเก (lobby) นอกแมป quiz_carry = host หายจากจอ client */
if (mapData.gameType === 'quiz_carry' && ev.peersSnap && Array.isArray(ev.peersSnap)) {
ev.peersSnap.forEach((p) => {
if (p.id == null) return;
const px = Number(p.x);
const py = Number(p.y);
if (myId != null && String(p.id) === String(myId)) {
if (Number.isFinite(px)) { me.x = px; me.tx = px; }
if (Number.isFinite(py)) { me.y = py; me.ty = py; }
if (p.direction) me.direction = p.direction;
} else {
const o = others.get(p.id) ?? others.get(String(p.id));
if (o) {
if (Number.isFinite(px)) { o.x = px; o.tx = px; }
if (Number.isFinite(py)) { o.y = py; o.ty = py; }
if (p.direction) o.direction = p.direction;
}
}
});
}
if (mapData.gameType === 'gauntlet') {
if (ev.gauntletEndsAt != null) {
const ge = Number(ev.gauntletEndsAt);
@@ -23696,12 +23888,14 @@
if (!primary) return;
primary.addEventListener('click', () => {
if (!quizCarryPregameActive || myId == null) return;
if (!isMePlayHost()) return;
const humans = quizCarryPregameHumanIds();
const humansReady = humans.length > 0 && humans.every((id) => !!quizCarryLobbyReadyMap[id]);
if (humansReady) {
/* ผู้เล่นจริงคนเดียว + พรีวิว: ไม่ต้องรอ ACK เซิร์ฟ (ห้องที่ map บนเซิร์ฟยังไม่ใช่ quiz_carry จะเคยล้มเหลว) */
if (humans.length === 1 && isMePlayHost()) {
/* ทุกคนพร้อมแล้ว → เฉพาะ host กด START */
if (!isMePlayHost()) return;
/* offline/ server
( host เรมคนเดยวท client ตอน others งวาง) */
if ((!socket || !socket.connected) && humans.length === 1) {
endQuizCarryEmbedPregameAndStart();
return;
}
@@ -23710,11 +23904,12 @@
});
return;
}
/* READY phase: ทุกคน (รวม client) กดสลับสถานะ Ready ของตัวเองได้ */
const sid = String(myId);
const next = !quizCarryLobbyReadyMap[sid];
quizCarryLobbyReadyMap[sid] = next;
updateQuizCarryPregameHud();
socket.emit('quiz-carry-lobby-ready', { ready: next });
if (socket && socket.connected) socket.emit('quiz-carry-lobby-ready', { ready: next });
});
})();
window.addEventListener('beforeunload', () => { try { saveQuizBattleProgress(); } catch (e) { /* ignore */ } socket.emit('leave-space'); });
+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.006131844"></script>
<script src="js/play.js?v=0.006131850"></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)
+39 -4
View File
@@ -3212,6 +3212,16 @@ function buildPeersSnapForSpace(space, extraFields) {
});
}
/* payload quiz-carry-lobby-sync peers snapshot
client (โดยเฉพาะ host) upsert others ขาดหายใหครบ host ไมเหนตวละคร client
เม join/reconnect ไมนรอบ (race ตอนเปลยนหน lobbyBplay.html) */
function quizCarryLobbySyncPayload(space) {
const readyMap = (space && space.quizCarryLobbyReady && typeof space.quizCarryLobbyReady === 'object')
? { ...space.quizCarryLobbyReady }
: {};
return { readyMap, peers: buildPeersSnapForSpace(space) };
}
/** สุ่ม 3 minigame จาก pool 7 แบบ — เรียกครั้งเดียวตอนเข้า LobbyB (ล็อกบน space) */
/** แมปเก่า (กบ frogger) → Minigame-2 Gauntlet ตาม Admin */
function normalizeDetectiveCardEntry(entry) {
@@ -3527,6 +3537,13 @@ function beginDetectiveSuspectMinigame(sid, space, cardEntry, selectedIndex) {
space.peers.forEach((_p, id) => { space.gauntletCrownLobbyReady[id] = false; });
space.gauntletRun = newBalloonBossShellGauntletRunState();
}
/* quiz_carry ready map ( crown) Ready client
host เหนจำนวนผเล/สถานะ ready ไมตรง และให flow READYSTART ทำงานเหมอนเกมอ */
if (md.gameType === 'quiz_carry') {
space.quizCarryLobbyReady = {};
space.peers.forEach((_p, id) => { space.quizCarryLobbyReady[id] = false; });
io.to(sid).emit('quiz-carry-lobby-sync', quizCarryLobbySyncPayload(space));
}
const gameStartPayload = {
mapId,
@@ -7481,7 +7498,7 @@ io.on('connection', (socket) => {
if (spaceAllowsQuizCarryLobbyRelaxed(space)) {
if (!space.quizCarryLobbyReady || typeof space.quizCarryLobbyReady !== 'object') space.quizCarryLobbyReady = {};
space.quizCarryLobbyReady[socket.id] = false;
io.to(spaceId).emit('quiz-carry-lobby-sync', { readyMap: { ...space.quizCarryLobbyReady } });
io.to(spaceId).emit('quiz-carry-lobby-sync', quizCarryLobbySyncPayload(space));
}
if (mdJoin.gameType === 'gauntlet') {
const ord = typeof peer.spawnJoinOrder === 'number' ? peer.spawnJoinOrder : [...space.peers.keys()].indexOf(socket.id);
@@ -8379,7 +8396,7 @@ io.on('connection', (socket) => {
if (space.quizCarryLobbyReady && typeof space.quizCarryLobbyReady === 'object') {
delete space.quizCarryLobbyReady[socket.id];
if (spaceAllowsQuizCarryLobbyRelaxed(space)) {
io.to(sid).emit('quiz-carry-lobby-sync', { readyMap: { ...space.quizCarryLobbyReady } });
io.to(sid).emit('quiz-carry-lobby-sync', quizCarryLobbySyncPayload(space));
}
}
if (space.gauntletCrownLobbyReady && typeof space.gauntletCrownLobbyReady === 'object') {
@@ -8506,7 +8523,7 @@ io.on('connection', (socket) => {
for (const id of space.peers.keys()) {
if (space.quizCarryLobbyReady[id] === undefined) space.quizCarryLobbyReady[id] = false;
}
socket.emit('quiz-carry-lobby-sync', { readyMap: { ...space.quizCarryLobbyReady } });
socket.emit('quiz-carry-lobby-sync', quizCarryLobbySyncPayload(space));
});
socket.on('quiz-carry-lobby-ready', (data) => {
@@ -8519,7 +8536,7 @@ io.on('connection', (socket) => {
if (space.quizCarryLobbyReady[id] === undefined) space.quizCarryLobbyReady[id] = false;
}
space.quizCarryLobbyReady[socket.id] = !!(data && data.ready);
io.to(sid).emit('quiz-carry-lobby-sync', { readyMap: { ...space.quizCarryLobbyReady } });
io.to(sid).emit('quiz-carry-lobby-sync', quizCarryLobbySyncPayload(space));
});
socket.on('quiz-carry-lobby-start', (_data, cb) => {
@@ -8539,6 +8556,24 @@ io.on('connection', (socket) => {
reply({ ok: true });
});
/* host เลือกคำถาม quiz_carry → relay index ให้คนอื่นในห้องใช้ข้อเดียวกัน (กันสุ่มไม่ตรงกัน) */
socket.on('quiz-carry-question', (data) => {
const sid = socket.data.spaceId;
const space = sid ? spaces.get(sid) : null;
if (!space || space.hostId !== socket.id) return; /* เฉพาะ host */
const idx = Number(data && data.idx);
if (!Number.isInteger(idx) || idx < 0) return;
socket.to(sid).emit('quiz-carry-question', { idx });
});
/* host จบเซสชัน quiz_carry → ให้คนอื่นโชว์สรุปพร้อมกัน */
socket.on('quiz-carry-session-complete', () => {
const sid = socket.data.spaceId;
const space = sid ? spaces.get(sid) : null;
if (!space || space.hostId !== socket.id) return;
socket.to(sid).emit('quiz-carry-session-complete', {});
});
/* ตอบเวลา server ปัจจุบัน → client ใช้คำนวณ clock offset (ชดเชย latency) ให้เวลาในเกมตรงกันทุกเครื่อง */
socket.on('time-sync', (cb) => {
if (typeof cb === 'function') cb({ t: Date.now() });