join room เพิ่มรูปให้เข้าใจง่าย host client game 1-7

This commit is contained in:
2026-06-23 15:38:48 +00:00
parent 38581636b4
commit 7d8a29b96c
6 changed files with 128 additions and 31 deletions
+15 -12
View File
@@ -51,9 +51,9 @@
"providerUserId": "p_1775109142385_wq7wfy1p32j",
"notes": "auto: player-coins",
"blocked": false,
"coins": 1051,
"coins": 1233,
"createdAt": "2026-04-02T05:52:21+00:00",
"updatedAt": "2026-06-23T09:46:51+00:00",
"updatedAt": "2026-06-23T15:32:42+00:00",
"daily": {
"anchorMs": 1781197200000,
"claimedDays": [
@@ -67,20 +67,21 @@
],
"lockUntilMs": 0
},
"score": 936,
"score": 1118,
"scoreByCase": {
"1": 16,
"10": 340,
"8": 230,
"11": 20,
"13": 88,
"13": 96,
"14": 20,
"9": 40,
"12": 126,
"12": 246,
"15": 20,
"4": 10,
"5": 20,
"7": 6
"7": 6,
"3": 54
},
"lbName": "Q",
"achievements": {
@@ -639,19 +640,21 @@
"providerUserId": "p_1782122326943_7p2fnvivscm",
"notes": "auto: player-coins",
"blocked": false,
"coins": 48,
"coins": 202,
"createdAt": "2026-06-22T09:58:46+00:00",
"updatedAt": "2026-06-23T07:42:20+00:00",
"updatedAt": "2026-06-23T15:32:42+00:00",
"lobbyColorThemeIndex": 3,
"lobbySkinToneIndex": 1,
"score": 38,
"score": 192,
"scoreByCase": {
"10": 20,
"12": 18
"12": 108,
"3": 54,
"13": 10
},
"lbName": "ผู้เล่น",
"lbName": "MONE",
"achievements": {
"d1_minigame_solver": 3
"d1_minigame_solver": 20
},
"daily": {
"anchorMs": 1782147600000,
+2 -2
View File
@@ -89,9 +89,9 @@
"balloonBossPlayerBalloonFallbackUrl": "/Game/img/MegaVirus/Artboard%209.png",
"balloonBossBalloonsPerPlayer": 3,
"forcedMinigameKeys": [
"balloon_boss",
"jump_survive",
"space_shooter",
"balloon_boss"
"space_shooter"
],
"testSpecialCardByMap": {},
"troublesomeForceOffer": false,
File diff suppressed because one or more lines are too long
+105 -14
View File
@@ -5394,6 +5394,17 @@
return n;
}
/** ป้ายชื่อบอท "บอท N (tier)" — เรียงตาม id (เหมือนทุกเครื่อง) ใช้ o.botTier (host เป็นเจ้าของ ผ่าน fill-bot-state) */
function relabelPreviewBotsPlay() {
let k = 0;
[...others.keys()].filter(isPreviewBotId).sort().forEach((bid) => {
const o = others.get(bid);
if (!o) return;
const tag = o.botTier === 'sharp' ? '(ฉลาด)' : o.botTier === 'weak' ? '(พลาดบ่อย)' : '(กลาง)';
o.nickname = 'บอท ' + (++k) + ' ' + tag;
});
}
function rebalancePreviewBots() {
if (!playBotsEnabled() || !mapData) return;
const human = countPlayHumans();
@@ -5506,13 +5517,7 @@
} catch (ePB) { /* ignore */ }
botIds.push(id);
}
let k = 0;
[...others.keys()].filter(isPreviewBotId).sort().forEach((bid) => {
const o = others.get(bid);
if (!o) return;
const tag = o.botTier === 'sharp' ? '(ฉลาด)' : o.botTier === 'weak' ? '(พลาดบ่อย)' : '(กลาง)';
o.nickname = 'บอท ' + (++k) + ' ' + tag;
});
relabelPreviewBotsPlay();
[...others.keys()].filter(isPreviewBotId).sort().forEach((bid, botIdx) => {
const o = others.get(bid);
if (o) o.characterId = pickPreviewBotCharacterId(botIdx);
@@ -9270,7 +9275,7 @@
function isMePlayHost() {
if (myId == null) return false;
if (playHostId == null) return true;
if (playHostId == null) return true; /* fallback (load-bearing): host พึ่งตอน null — อย่าเปลี่ยน (เคยทำ MG3 ready ค้าง 4/5 + MG2 spawn ทับ) */
return String(playHostId) === String(myId);
}
@@ -9285,6 +9290,7 @@
if (!isPreviewBotId(id) || !o) return;
const row = { id: id, x: fbNum(o.x), y: fbNum(o.y), direction: o.direction || 'down', walking: !!o.botIsWalking };
if (o.characterId != null) row.cid = o.characterId; /* ให้ client สร้างบอทที่ขาดได้ → ชุดบอท host-authoritative */
if (o.botTier != null) row.tier = o.botTier; /* tier (ฉลาด/กลาง/พลาดบ่อย) — host เป็นเจ้าของ ให้ label/พฤติกรรมตรงกันทุกเครื่อง (เดิมสุ่ม local → ป้ายไม่ตรง) */
if (o.gauntletScore != null) row.gScore = o.gauntletScore;
if (o.gauntletEliminated != null) row.gElim = !!o.gauntletEliminated;
if (o.gauntletJumpTicks != null) row.gJump = o.gauntletJumpTicks;
@@ -9316,7 +9322,12 @@
}
arr.push(row);
});
if (arr.length) socket.emit('fill-bot-state', { bots: arr });
/* "" host (= myId(host) + others ) non-host force host
(humans ไม sync แบบบอท reconnect างบน client; id-based ไมชนช default) */
const humanRoster = [];
if (myId != null) humanRoster.push(String(myId));
others.forEach((o, id) => { if (!isPreviewBotId(id) && o) humanRoster.push(String(id)); });
if (arr.length) socket.emit('fill-bot-state', { bots: arr, humans: humanRoster });
}
setInterval(broadcastFillBotState, 80); /* ~12.5Hz — self-gate ถ้าไม่ใช่ host หรือไม่มีบอท */
@@ -19324,6 +19335,24 @@
if (!data || isPreviewBotId(data.id)) return;
if (myId != null && String(data.id) === String(myId)) return;
if (socket && socket.id && String(data.id) === String(socket.id)) return;
/* reconnect (""): socket.id (/)
server ลบ peer เกาดวย playerKey เทาน าไม match จะคางเปนตวซ.
ลบ peer เด (คนจร) "ชื่อ+ตัวละครเดียวกัน" แต id าง อนเพมตวใหม (ญญาณวาเปนคนเด) */
const joinNickDedup = (data.nickname != null ? String(data.nickname).trim() : '');
const joinCidDedup = (data.characterId != null ? String(data.characterId) : '');
/* dedup "" (custom) default ('', '', )
ไมนผเลนจร 2 คนทไมงช (default 'ผู้เล่น') จะถกลบผ จำนวนคนหาย/ready าง 4/5 */
const isGenericNick = !joinNickDedup || /^ผู้เล่น/.test(joinNickDedup) || joinNickDedup === 'บอท';
if (!isGenericNick) {
[...others.keys()].forEach((oid) => {
if (isPreviewBotId(oid) || String(oid) === String(data.id)) return;
const ex = others.get(oid);
if (!ex) return;
const exNick = (ex.nickname != null ? String(ex.nickname).trim() : '');
const exCid = (ex.characterId != null ? String(ex.characterId) : '');
if (exNick === joinNickDedup && exCid === joinCidDedup) others.delete(oid);
});
}
const x = Number(data.x);
const y = Number(data.y);
const px = Number.isFinite(x) ? x : 1;
@@ -19491,8 +19520,27 @@
});
/* รับ state ของ fill-bot จาก host (เฉพาะ client ที่ไม่ใช่ host) → render ตาม ไม่จำลองเอง */
socket.on('fill-bot-state', (data) => {
/* fill-bot-state = server relay "host " (server gate + socket.to )
เรา "ไม่ใช่ host" แนนอน. ใช data.hostId แก playHostId (race LobbyBplay ทำใหเขาใจผดวาเป host
ignore fill-bot-state + sim บอทเอง = บอท/จำนวนคนละชดบน 2 จอ) */
if (data && data.hostId != null && myId != null && String(data.hostId) !== String(myId)) {
if (String(playHostId) !== String(data.hostId)) playHostId = data.hostId;
}
if (isMePlayHost()) return;
if (!data || !Array.isArray(data.bots)) return;
/* force "" host (peer host client reconnect/snapshot)
id-based (ไมชนช default). debounce 3 ครงต peer host งไมเหนชวคราวถกลบผ (self-heal) */
if (Array.isArray(data.humans)) {
const hostHumans = new Set(data.humans.map(String));
[...others.keys()].forEach((oid) => {
if (isPreviewBotId(oid)) return;
const o = others.get(oid);
if (!o) return;
if (hostHumans.has(String(oid))) { o._notInHostRoster = 0; return; }
o._notInHostRoster = (o._notInHostRoster || 0) + 1;
if (o._notInHostRoster >= 3) others.delete(oid);
});
}
/* เกมที่ render อ่านตำแหน่งจาก x/y หรือ Cx/Cy ตรงๆ (ไม่ผ่าน lerp ของ main tick) → สแนปตำแหน่งเลย */
const snapPos = isSpaceShooter() || isBalloonBoss() || isJumpSurvive();
/* host client wantBots host ( human )
@@ -19504,6 +19552,7 @@
if (!hostBotIds.has(String(bid))) others.delete(bid);
});
let quizBotScoreChanged = false;
let tierChangedFb = false;
data.bots.forEach((rb) => {
if (!rb || !isPreviewBotId(rb.id)) return;
let o = others.get(rb.id);
@@ -19520,6 +19569,7 @@
spaceShooterScore: 0, balloonBossScore: 0, balloonBossBossDmg: 0,
balloonBossBalloons: balloonBossBalloonsStartPlay(), balloonBossEliminated: false,
spaceShooterEliminated: false, jumpSurviveEliminated: false, quizCarryHeld: null,
botTier: (rb.tier === 'sharp' || rb.tier === 'weak' || rb.tier === 'avg') ? rb.tier : 'avg',
};
others.set(rb.id, o);
if (o.characterId) { try { preloadPlayTintForAvatar(o.characterId, o.playTint); } catch (_e) { /* ignore */ } }
@@ -19538,6 +19588,7 @@
}
}
o.botIsWalking = !!rb.walking;
if ((rb.tier === 'sharp' || rb.tier === 'weak' || rb.tier === 'avg') && o.botTier !== rb.tier) { o.botTier = rb.tier; tierChangedFb = true; }
if (rb.gScore != null) o.gauntletScore = rb.gScore;
if (rb.gElim != null) o.gauntletEliminated = !!rb.gElim;
if (rb.gJump != null) o.gauntletJumpTicks = rb.gJump;
@@ -19566,6 +19617,7 @@
if (playLiveQuizScores[k] !== nv) { playLiveQuizScores[k] = nv; quizBotScoreChanged = true; }
}
});
if (tierChangedFb) relabelPreviewBotsPlay(); /* tier จาก host เปลี่ยน → อัปเดตป้าย "บอท N (tier)" ให้ตรง host */
if (quizBotScoreChanged && isQuiz()) renderPlayQuizScoreboard(playLiveQuizScores);
});
@@ -21086,6 +21138,45 @@
clampPlayEntityFootprintToMap(b, mapData);
}
}
/* "" spawn/
แลวตดกนใน blockPlayer zone (entity บลอกก) เดนไมไดงค.
host นเฉพาะบอท (คนจรงเปนเจาของตำแหนงตวเอง broadcast าน move) */
/* แยกบอท↔คน เฉพาะแมปที่มี blockPlayer (entity บล็อกกันได้ = ติดกันเดินไม่ได้) — แมปอื่นซ้อนได้ไม่ต้องแยก */
if (mapData.blockPlayer) {
const minDHuman = 0.95; /* ระยะ ~1 ช่อง — ให้ footprint แยกชัด คนไม่โดนบล็อก */
const humansSep = [];
if (me && Number.isFinite(me.x) && Number.isFinite(me.y)) humansSep.push(me);
others.forEach((p, pid) => {
if (!isPreviewBotId(pid) && p && Number.isFinite(p.x) && Number.isFinite(p.y)) humansSep.push(p);
});
for (let i = 0; i < ids.length; i++) {
const a = others.get(ids[i]);
if (!a) continue;
for (let k = 0; k < humansSep.length; k++) {
const hu = humansSep[k];
let ddx = a.x - hu.x, ddy = a.y - hu.y;
let d = Math.sqrt(ddx * ddx + ddy * ddy);
if (d >= minDHuman) continue;
if (d < 1e-5) { ddx = (i % 2 === 0) ? 1 : -1; ddy = (i % 3) - 1; d = Math.sqrt(ddx * ddx + ddy * ddy) || 1; }
const ux = ddx / d, uy = ddy / d; /* ดันบอทออกจากคน */
/* minDHuman + (ignorePeers) blockPlayer zone
(าเช peer วย กชองรอบตวคนถกบลอก บอทหนไมออก ดตลอด) */
const need = (minDHuman - d) + 0.06;
let placed = false;
const try1x = a.x + ux * need, try1y = a.y + uy * need;
if (canWalkLikeLobbyForBot(try1x, try1y, a.x, a.y, a, true)) { a.x = try1x; a.y = try1y; placed = true; }
if (!placed) {
/* ทิศตรงชนกำแพง → ลองทิศตั้งฉาก 2 ด้าน */
const perp = [[-uy, ux], [uy, -ux]];
for (let pI = 0; pI < perp.length && !placed; pI++) {
const tx2 = a.x + perp[pI][0] * need, ty2 = a.y + perp[pI][1] * need;
if (canWalkLikeLobbyForBot(tx2, ty2, a.x, a.y, a, true)) { a.x = tx2; a.y = ty2; placed = true; }
}
}
if (placed) clampPlayEntityFootprintToMap(a, mapData);
}
}
}
}
function quizTilesFootprintPlay(px, py) {
@@ -21301,7 +21392,7 @@
return true;
}
function canWalkLikeLobbyForBot(x, y, fromX, fromY, o) {
function canWalkLikeLobbyForBot(x, y, fromX, fromY, o, ignorePeers) {
if (!mapData || !mapData.objects) return false;
if (typeof x !== 'number' || typeof y !== 'number' || !Number.isFinite(x) || !Number.isFinite(y)) return false;
const w = mapData.width || 20, h = mapData.height || 15;
@@ -21319,7 +21410,7 @@
if (wallTilesB.size < colW * colH) return false;
}
const bp = mapData.blockPlayer;
if (bp) {
if (bp && !ignorePeers) {
for (const k of quizTilesFootprintPlay(x, y)) {
const p = k.split(',');
const tx = +p[0], ty = +p[1];
@@ -24510,9 +24601,9 @@
others.forEach((o, id) => {
/* host เดินบอทเองตรงๆ (o.x/o.y) → ข้าม lerp; แต่ client อื่นต้อง lerp ตามตำแหน่งที่ host ส่งมา (o.tx/o.ty) */
if (playBotsEnabled() && isPreviewBotId(id) && isMePlayHost()) return;
/* MG4 (quiz_carry) non-host lerp 0.2 host ~1-2 ("")
เร lerp เฉพาะบอท quiz_carry ใหตามตำแหน host นข (ไมแตะเกมอ/เลนจร) */
const lerpF = (isQuizCarry() && isPreviewBotId(id)) ? 0.5 : LERP;
/* MG4 (quiz_carry) + MG1 (quiz mng8a80o) non-host lerp 0.2 host ~1-2 ("")
เร lerp เฉพาะบอท quiz/quiz_carry ใหตามตำแหน host นข (ไมแตะเกมอ/เลนจร) */
const lerpF = (isPreviewBotId(id) && (isQuizCarry() || isQuiz())) ? 0.5 : LERP;
if (o.tx != null) o.x += (o.tx - o.x) * lerpF;
if (o.ty != null) o.y += (o.ty - o.y) * lerpF;
});
+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.006232008"></script>
<script src="js/play.js?v=0.006232016"></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)
+4 -1
View File
@@ -7905,7 +7905,10 @@ io.on('connection', (socket) => {
const space = sid ? spaces.get(sid) : null;
if (!space || space.hostId !== socket.id) return; /* เฉพาะ host เท่านั้น */
if (!data || !Array.isArray(data.bots) || data.bots.length > 16) return;
socket.to(sid).emit('fill-bot-state', { bots: data.bots });
/* hostId (non-host socket.to ) " host"
แก playHostId ดตอน race LobbyBplay ( 2 จอคดวาเป host sim บอทคนละช) */
const humans = (Array.isArray(data.humans) ? data.humans.filter((h) => h != null).slice(0, 50).map(String) : undefined);
socket.to(sid).emit('fill-bot-state', { bots: data.bots, hostId: space.hostId, humans });
});
/* MG7 balloon_boss relay / ( host)