diff --git a/www/html/Game/public/js/play.js b/www/html/Game/public/js/play.js
index 5d095c1..70ecb40 100644
--- a/www/html/Game/public/js/play.js
+++ b/www/html/Game/public/js/play.js
@@ -14,6 +14,8 @@
const forceDefaultCharacter = params.get('defaultChar') === '1';
const editorEmbedReturn = params.get('editorEmbed') === '1';
const playMapIdFromQuery = (params.get('map') || '').trim();
+ /** mapId จาก join / game-start — ใช้จับฉาก crown เมื่อ URL ไม่มี ?map= */
+ let playSessionMapId = playMapIdFromQuery;
/** สรุปภารกิจแบบ mock (popup-result ฯลฯ) — ใช้เฉพาะฉากนี้จาก editor (id ใน URL เช่น editor.html?id=mnorwqx1) */
const quizCarryMissionSummaryMapId = 'mnorwqx1';
const quizCarryUseMissionSummaryOverlay = playMapIdFromQuery === quizCarryMissionSummaryMapId;
@@ -99,7 +101,7 @@
/** โหลดจาก mapData.gridImageLibrary — ดัชนีตรงกับ gridImageCells[y][x] */
let mapGridImageImgs = [];
let mapGridImageHeldImgs = [];
- let me = { x: 1, y: 1, direction: 'down', nickname: nick, isWalking: false, playTint: null, gauntletScore: 0, tx: null, ty: null, quizCarryHeld: null };
+ let me = { x: 1, y: 1, direction: 'down', nickname: nick, isWalking: false, playTint: null, gauntletScore: 0, gauntletEliminated: false, tx: null, ty: null, quizCarryHeld: null };
const others = new Map();
/** Stack preview HUD: เทอร์มินัลล็อก (cyber UI) */
const STACK_PREVIEW_HUD_LOG_MAX = 8;
@@ -2026,7 +2028,10 @@
if (o.botAnswerWander == null) o.botAnswerWander = false;
if (o.gauntletJumpTicks == null) o.gauntletJumpTicks = 0;
if (o.gauntletJumpVis == null) o.gauntletJumpVis = o.gauntletJumpTicks;
- if (o.gauntletScore == null) o.gauntletScore = 0;
+ if (o.gauntletScore == null) {
+ o.gauntletScore = (mapData && mapData.gameType === 'gauntlet' && isGauntletCrownHeistMapPlay()) ? 100 : 0;
+ }
+ if (o.gauntletEliminated == null) o.gauntletEliminated = false;
if (o.botWanderDx == null || o.botWanderDy == null || (o.botWanderDx === 0 && o.botWanderDy === 0)) {
const wd = [[0, -1], [0, 1], [-1, 0], [1, 0]][Math.floor(Math.random() * 4)];
o.botWanderDx = wd[0];
@@ -2058,6 +2063,7 @@
const tierRoll = Math.random();
const botTier = tierRoll < 0.28 ? 'sharp' : tierRoll < 0.62 ? 'avg' : 'weak';
const wd = [[0, -1], [0, 1], [-1, 0], [1, 0]][Math.floor(Math.random() * 4)];
+ const crownStart = mapData.gameType === 'gauntlet' && isGauntletCrownHeistMapPlay();
others.set(id, {
x, y, tx: x, ty: y,
direction: ['down', 'up', 'left', 'right'][Math.floor(Math.random() * 4)],
@@ -2067,7 +2073,8 @@
botTier,
gauntletJumpTicks: 0,
gauntletJumpVis: 0,
- gauntletScore: 0,
+ gauntletScore: crownStart ? 100 : 0,
+ gauntletEliminated: false,
quizCannotTrue: false,
quizCannotFalse: false,
quizCarryHeld: null,
@@ -2441,7 +2448,15 @@
function isFrogger() { return mapData && mapData.gameType === 'frogger'; }
function isGauntlet() { return mapData && mapData.gameType === 'gauntlet'; }
function isGauntletFaceRightMapMno9kb07() {
- return !!(isGauntlet() && playMapIdFromQuery === GAUNTLET_FACE_RIGHT_MAP_ID);
+ return !!(isGauntlet() && playSessionMapId === GAUNTLET_FACE_RIGHT_MAP_ID);
+ }
+ /** Crown heist rules + UI (เดียวกับหันขวา — ฉาก mno9kb07) */
+ function isGauntletCrownHeistMapPlay() {
+ return isGauntletFaceRightMapMno9kb07();
+ }
+ let gauntletCrownHowtoVisible = false;
+ function isGauntletCrownHowtoBlocking() {
+ return isGauntletCrownHeistMapPlay() && gauntletCrownHowtoVisible;
}
function isStack() { return mapData && mapData.gameType === 'stack'; }
function isJumpSurvive() { return mapData && mapData.gameType === 'jump_survive'; }
@@ -5068,6 +5083,7 @@
* — ตอบสนองสิ่งกีดขวางที่ชนเซลล์เดียวกัน + กระโดดล่วงหน้าเมื่อ threat อยู่คอลัมน์ถัดไป
*/
function maybePreviewBotGauntletJump(o, obstacles, w, h) {
+ if (isGauntletCrownHeistMapPlay() && o.gauntletEliminated) return;
let px = Math.floor(Number(o.x)) || 0;
let py = Math.floor(Number(o.y)) || 0;
px = Math.max(0, Math.min(w - 1, px));
@@ -5101,12 +5117,14 @@
/** หนึ่ง tick เดียวกับ runGauntletTick ฝั่งเซิร์ฟเวอร์ (เฉพาะ collision + เลื่อน x) */
function applyGauntletPhysicsToPreviewBot(o, obstacles, w, h) {
+ if (isGauntletCrownHeistMapPlay() && o.gauntletEliminated) return;
let px = Math.floor(Number(o.x)) || 0;
let py = Math.floor(Number(o.y)) || 0;
px = Math.max(0, Math.min(w - 1, px));
py = Math.max(0, Math.min(h - 1, py));
const air = (o.gauntletJumpTicks || 0) > 0;
const { ch: gauntletCh2 } = getCharacterFootprintWH(mapData);
+ const crown = isGauntletCrownHeistMapPlay();
let advanceX = false;
let hitBack = false;
for (let i = 0; i < obstacles.length; i++) {
@@ -5129,9 +5147,21 @@
if (advanceX) {
px = Math.min(w - 2, px + 1);
o.gauntletJumpTicks = 0;
- o.gauntletScore = (o.gauntletScore || 0) + 1;
+ if (!crown) {
+ o.gauntletScore = (o.gauntletScore || 0) + 1;
+ }
} else if (hitBack) {
- px = Math.max(0, px - 1);
+ if (crown) {
+ if (px <= 0) {
+ o.gauntletEliminated = true;
+ o.gauntletScore = 0;
+ } else {
+ o.gauntletScore = Math.max(0, (o.gauntletScore || 0) - 10);
+ px = Math.max(0, px - 1);
+ }
+ } else {
+ px = Math.max(0, px - 1);
+ }
}
if ((o.gauntletJumpTicks || 0) > 0) o.gauntletJumpTicks--;
o.tx = px;
@@ -5165,6 +5195,163 @@
socket.emit('gauntlet-preview-rows', { ys: [...ysSet] });
}
+ /** ซ่อน overlay โหมดอื่นที่ค้างจาก embed / session ก่อน — กัน UI ซ้อนไม่ตรง mock */
+ function hideConflictingOverlaysForGauntletCrown() {
+ hideQuizCarryPregameOverlay();
+ quizCarryPregameActive = false;
+ const hideIds = [
+ 'quiz-game-overlay',
+ 'quiz-carry-mission-overlay',
+ 'gauntlet-ended-overlay',
+ 'quiz-battle-mcq-overlay',
+ 'quiz-carry-embed-countdown',
+ 'quiz-carry-embed-q-strip',
+ 'play-quiz-scoreboard',
+ 'play-quiz-feedback',
+ ];
+ for (let i = 0; i < hideIds.length; i++) {
+ const el = document.getElementById(hideIds[i]);
+ if (el) el.classList.add('is-hidden');
+ }
+ const gcm = document.getElementById('gauntlet-crown-mission-overlay');
+ if (gcm) gcm.classList.add('is-hidden');
+ }
+
+ function gcmRankBadgeUrl(rank) {
+ const r = Math.floor(Number(rank)) || 99;
+ if (r === 1) return BASE + '/img/gauntlet-assets/result-1st.png';
+ if (r === 2) return BASE + '/img/gauntlet-assets/result-2nd.png';
+ if (r === 3) return BASE + '/img/gauntlet-assets/result-3rd.png';
+ return BASE + '/img/gauntlet-assets/result-txt-2.png';
+ }
+
+ function showGauntletCrownHowtoOverlay() {
+ if (!isGauntletCrownHeistMapPlay()) return;
+ hideConflictingOverlaysForGauntletCrown();
+ const ov = document.getElementById('gauntlet-crown-howto-overlay');
+ if (!ov) return;
+ gauntletCrownHowtoVisible = true;
+ ov.classList.remove('is-hidden');
+ const btn = document.getElementById('btn-gch-ready');
+ if (btn) {
+ btn.onclick = function () {
+ ov.classList.add('is-hidden');
+ gauntletCrownHowtoVisible = false;
+ };
+ }
+ }
+
+ function showGauntletCrownMissionOverlay(mission) {
+ const ov = document.getElementById('gauntlet-crown-mission-overlay');
+ const rowEl = document.getElementById('gcm-rank-row');
+ const totalEl = document.getElementById('gcm-total');
+ const gradeEl = document.getElementById('gcm-grade');
+ const bonusEl = document.getElementById('gcm-bonus');
+ const btn = document.getElementById('btn-gcm-done');
+ if (!ov || !rowEl || !mission || !Array.isArray(mission.ranked)) return;
+ rowEl.innerHTML = '';
+ mission.ranked.forEach((r) => {
+ const cell = document.createElement('div');
+ cell.className = 'gcm-cell';
+ const tag = document.createElement('div');
+ tag.className = 'gcm-rank-tag';
+ const rk = Math.floor(Number(r.rank)) || 0;
+ if (rk >= 1 && rk <= 3) {
+ const tagImg = document.createElement('img');
+ tagImg.src = gcmRankBadgeUrl(rk);
+ tagImg.alt = '[' + String(r.rankLabel || rk) + ']';
+ tagImg.style.maxWidth = '100%';
+ tagImg.style.height = 'auto';
+ tag.appendChild(tagImg);
+ } else {
+ tag.textContent = '[' + String(r.rankLabel || rk) + ']';
+ }
+ const frame = document.createElement('div');
+ frame.className = 'gcm-frame';
+ const img = document.createElement('img');
+ img.className = 'gcm-av';
+ img.alt = '';
+ const cid = r.characterId ? String(r.characterId) : '';
+ if (cid) {
+ const im = getCharacterImg(cid, 'down');
+ if (im && im.src) img.src = im.src;
+ } else {
+ img.src = defaultAvatarImg.src;
+ }
+ img.onerror = function () {
+ this.onerror = null;
+ this.src = defaultAvatarImg.src;
+ };
+ frame.appendChild(img);
+ if (r.eliminated) {
+ const st = document.createElement('img');
+ st.className = 'gcm-stamp';
+ st.src = BASE + '/img/gauntlet-assets/result-Lose_stamp.png';
+ st.alt = 'เสียชีวิต';
+ frame.appendChild(st);
+ }
+ const nick = document.createElement('div');
+ nick.className = 'gcm-nick';
+ nick.textContent = r.nickname || '—';
+ const sc = document.createElement('div');
+ sc.className = 'gcm-sc';
+ sc.textContent = String(r.baseScore || 0) + '(+' + String(r.rankBonus || 0) + ')';
+ cell.appendChild(tag);
+ cell.appendChild(frame);
+ cell.appendChild(nick);
+ cell.appendChild(sc);
+ rowEl.appendChild(cell);
+ });
+ if (totalEl) {
+ const parts = (mission.totalParts || []).map((n) => String(Math.max(0, Number(n) || 0)));
+ const sum = Math.max(0, Number(mission.totalSum) || 0);
+ const avg = Math.max(0, Math.floor(Number(mission.averageScore) || 0));
+ totalEl.innerHTML = 'คะแนนรวม (' + parts.join('+') + ') = ' + sum + ' · เฉลี่ย ' + avg + ' → เกรด';
+ }
+ if (gradeEl) {
+ const g = String(mission.grade || 'C').toUpperCase().charAt(0);
+ gradeEl.textContent = g;
+ gradeEl.className = 'gcm-grade gcm-grade--' + g.toLowerCase();
+ }
+ if (bonusEl) {
+ bonusEl.innerHTML = '';
+ const row = document.createElement('div');
+ row.style.display = 'flex';
+ row.style.alignItems = 'center';
+ row.style.gap = '8px';
+ const chk = document.createElement('img');
+ chk.src = BASE + '/img/gauntlet-assets/result-check.png';
+ chk.alt = '';
+ chk.width = 26;
+ chk.height = 26;
+ const sp = document.createElement('span');
+ sp.style.color = '#9ece6a';
+ sp.style.fontWeight = '700';
+ sp.textContent = 'ภารกิจสำเร็จ';
+ row.appendChild(chk);
+ row.appendChild(sp);
+ bonusEl.appendChild(row);
+ const rc = mission.rewardCard;
+ if (rc && rc.th) {
+ const pl = document.createElement('div');
+ pl.className = 'gcm-plaque';
+ pl.innerHTML = '' + String(rc.th) + '' + String(rc.en || '') + '';
+ bonusEl.appendChild(pl);
+ }
+ }
+ ov.classList.remove('is-hidden');
+ function goLobby() {
+ ov.classList.add('is-hidden');
+ window.location.href = 'room-lobby.html?space=' + encodeURIComponent(spaceId) + '&nick=' + encodeURIComponent(nick);
+ }
+ if (btn) {
+ btn.onclick = function () {
+ if (previewMode && editorEmbedReturn) ov.classList.add('is-hidden');
+ else goLobby();
+ };
+ }
+ }
+
function isLobby() { return mapData && mapData.gameType === 'lobby'; }
function isQuiz() { return mapData && mapData.gameType === 'quiz'; }
function getLane(y) {
@@ -7351,6 +7538,9 @@
const prevWasSpaceShooter = mapData && mapData.gameType === 'space_shooter';
const prevWasBalloonBoss = mapData && mapData.gameType === 'balloon_boss';
mapData = gameMapData;
+ if (res && res.mapId != null && String(res.mapId).trim() !== '') {
+ playSessionMapId = String(res.mapId).trim();
+ }
if (res && res.hostId != null) playHostId = res.hostId;
if (mapData.gameType !== 'space_shooter') {
spaceShooterGameEnded = false;
@@ -7426,6 +7616,7 @@
{
const sc0 = Number(myPeer && myPeer.gauntletScore);
me.gauntletScore = Number.isFinite(sc0) ? Math.max(0, sc0) : 0;
+ me.gauntletEliminated = !!(myPeer && myPeer.gauntletEliminated);
}
{
const sh0 = Number(myPeer && myPeer.spaceShooterScore);
@@ -7465,6 +7656,7 @@
gauntletJumpTicks: typeof p.gauntletJumpTicks === 'number' ? p.gauntletJumpTicks : 0,
gauntletJumpVis: typeof p.gauntletJumpTicks === 'number' ? p.gauntletJumpTicks : 0,
gauntletScore: (() => { const s = Number(p.gauntletScore); return Number.isFinite(s) ? Math.max(0, s) : 0; })(),
+ gauntletEliminated: !!p.gauntletEliminated,
spaceShooterScore: (() => { const s = Number(p.spaceShooterScore); return Number.isFinite(s) ? Math.max(0, s) : 0; })(),
balloonBossScore: (() => { const s = Number(p.balloonBossScore); return Number.isFinite(s) ? Math.max(0, s) : 0; })(),
balloonBossBalloons: (() => {
@@ -7558,6 +7750,16 @@
else if (isQuizCarry()) setupPlayQuizCarryUi();
else if (isQuizBattle()) setupPlayQuizBattleUi();
else teardownPlayQuizUi();
+ if (mapData.gameType === 'gauntlet' && isGauntletCrownHeistMapPlay()) {
+ hideConflictingOverlaysForGauntletCrown();
+ setTimeout(function () { showGauntletCrownHowtoOverlay(); }, 50);
+ } else {
+ const hov = document.getElementById('gauntlet-crown-howto-overlay');
+ if (hov) {
+ hov.classList.add('is-hidden');
+ gauntletCrownHowtoVisible = false;
+ }
+ }
resizeCanvas(); draw(); tick();
}
@@ -7862,8 +8064,10 @@
if (p.direction) me.direction = p.direction;
const jt = Number(p.gauntletJumpTicks);
meGauntletJumpTicks = Number.isFinite(jt) ? jt : 0;
+ meGauntletJumpVis = meGauntletJumpTicks;
const sc = Number(p.gauntletScore);
me.gauntletScore = Number.isFinite(sc) ? Math.max(0, sc) : 0;
+ me.gauntletEliminated = !!p.gauntletEliminated;
} else {
const o = others.get(p.id) ?? others.get(pid);
if (o) {
@@ -7877,6 +8081,7 @@
if (o.gauntletJumpVis == null) o.gauntletJumpVis = o.gauntletJumpTicks;
const sc = Number(p.gauntletScore);
o.gauntletScore = Number.isFinite(sc) ? Math.max(0, sc) : 0;
+ o.gauntletEliminated = !!p.gauntletEliminated;
}
}
});
@@ -7887,13 +8092,20 @@
});
socket.on('gauntlet-ended', (data) => {
+ gauntletEndsAtMs = null;
+ const hto = document.getElementById('gauntlet-crown-howto-overlay');
+ if (hto) hto.classList.add('is-hidden');
+ gauntletCrownHowtoVisible = false;
+ if (data && data.crownMission) {
+ showGauntletCrownMissionOverlay(data.crownMission);
+ return;
+ }
const ov = document.getElementById('gauntlet-ended-overlay');
const msgEl = document.getElementById('gauntlet-ended-message');
const titleEl = document.getElementById('gauntlet-ended-title');
const listEl = document.getElementById('gauntlet-ended-rankings');
const btn = document.getElementById('btn-gauntlet-ended-lobby');
if (!ov || !msgEl || !listEl) return;
- gauntletEndsAtMs = null;
if (titleEl) {
titleEl.textContent = data && data.reason === 'time' ? 'หมดเวลา · Time up' : 'เกมจบ · Game over';
}
@@ -7923,6 +8135,9 @@
if (!ev || !ev.mapId) return;
const applySnap = (md) => {
mapData = md;
+ if (ev.mapId != null && String(ev.mapId).trim() !== '') {
+ playSessionMapId = String(ev.mapId).trim();
+ }
if (mapData.gameType !== 'space_shooter') {
spaceShooterGameEnded = false;
}
@@ -7955,6 +8170,7 @@
meGauntletJumpVis = meGauntletJumpTicks;
const sc = Number(p.gauntletScore);
me.gauntletScore = Number.isFinite(sc) ? Math.max(0, sc) : 0;
+ me.gauntletEliminated = !!p.gauntletEliminated;
} else {
const o = others.get(p.id) ?? others.get(String(p.id));
if (o) {
@@ -7968,6 +8184,7 @@
o.gauntletJumpVis = o.gauntletJumpTicks;
const sc = Number(p.gauntletScore);
o.gauntletScore = Number.isFinite(sc) ? Math.max(0, sc) : 0;
+ o.gauntletEliminated = !!p.gauntletEliminated;
}
}
});
@@ -7975,10 +8192,12 @@
meGauntletJumpTicks = 0;
meGauntletJumpVis = 0;
me.gauntletScore = 0;
+ me.gauntletEliminated = false;
others.forEach((o) => {
o.gauntletJumpTicks = 0;
o.gauntletJumpVis = 0;
o.gauntletScore = 0;
+ o.gauntletEliminated = false;
});
}
if (mapData.gameType === 'gauntlet') {
@@ -7995,8 +8214,18 @@
if (stackMini && isStack()) stackMini.phaseSpeed = playStackSwingHz;
})
.catch(() => {});
+ if (String(ev.mapId || '') === GAUNTLET_FACE_RIGHT_MAP_ID) {
+ setTimeout(function () { showGauntletCrownHowtoOverlay(); }, 60);
+ } else {
+ const hto2 = document.getElementById('gauntlet-crown-howto-overlay');
+ if (hto2) hto2.classList.add('is-hidden');
+ gauntletCrownHowtoVisible = false;
+ }
} else {
gauntletEndsAtMs = null;
+ const hto3 = document.getElementById('gauntlet-crown-howto-overlay');
+ if (hto3) hto3.classList.add('is-hidden');
+ gauntletCrownHowtoVisible = false;
}
if (mapData.gameType !== 'gauntlet') {
me.tx = null;
@@ -8004,6 +8233,8 @@
}
const gauntletOv = document.getElementById('gauntlet-ended-overlay');
if (gauntletOv) gauntletOv.classList.add('is-hidden');
+ const gcmOv = document.getElementById('gauntlet-crown-mission-overlay');
+ if (gcmOv) gcmOv.classList.add('is-hidden');
if (mapData.gameType === 'stack') {
if (!mapData.stackReleaseArea) mapData.stackReleaseArea = [];
if (!mapData.stackLandArea) mapData.stackLandArea = [];
@@ -9192,6 +9423,8 @@
: (me.balloonBossEliminated
? 'คุณตกรอบแล้ว — ดูเพื่อนเล่น · You are out — spectate'
: 'ยานมีแรงเฉื่อย — A D / arrows / W S เร่งทิศทาง · ปล่อยปุ่มแล้วยังไหล (แรงหน่วง) · Space = ยิง (ดีเลย์) · ลูกโป้งหมด = ตกรอบ');
+ } else if (isGauntletCrownHeistMapPlay()) {
+ hintEl.textContent = 'Space / W / ↑ jump · Start 100 pts · Hit -10 · Fall off left edge = out · Rank bonus at end';
} else {
hintEl.textContent = 'Space / W / ↑ jump · Lanes + lasers · Clear = right + score';
}
@@ -9221,7 +9454,8 @@
: (isSpaceShooter() ? (me.spaceShooterScore || 0)
: (isBalloonBoss() ? (me.balloonBossScore || 0) : 0))),
y: safeYv(me.y),
- eliminated: !!(isJumpSurvive() && jumpSurviveEliminated) || !!(isBalloonBoss() && me.balloonBossEliminated),
+ eliminated: !!(isJumpSurvive() && jumpSurviveEliminated) || !!(isBalloonBoss() && me.balloonBossEliminated)
+ || !!(isGauntletCrownHeistMapPlay() && me.gauntletEliminated),
isMe: true,
});
others.forEach((o, id) => {
@@ -9235,7 +9469,8 @@
: (isBalloonBoss() ? (o.balloonBossScore || 0) : 0))),
y: safeYv(o.y),
eliminated: !!(isJumpSurvive() && isPreviewBotId(id) && o.jumpSurviveEliminated)
- || !!(isBalloonBoss() && o.balloonBossEliminated),
+ || !!(isBalloonBoss() && o.balloonBossEliminated)
+ || !!(isGauntletCrownHeistMapPlay() && o.gauntletEliminated),
isMe: false,
});
});
@@ -9296,7 +9531,7 @@
st.textContent = isQuizCarry()
? ''
: (row.eliminated
- ? (isBalloonBoss() ? 'OUT' : 'OFFLINE')
+ ? (isBalloonBoss() ? 'OUT' : (isGauntletCrownHeistMapPlay() ? 'เสียชีวิต' : 'OFFLINE'))
: (isBalloonBoss()
? ('♥' + Math.max(0, row.isMe ? (me.balloonBossBalloons | 0) : ((() => {
const ox = others.get(row.id);
@@ -9987,10 +10222,17 @@
ctx.fillStyle = '#f7768e';
ctx.font = 'bold 14px sans-serif';
ctx.textAlign = 'left';
- ctx.fillText('พรมแดงสุดท้าย | คะแนน: ' + (me.gauntletScore || 0) + ' (ข้าม 1 ชิ้น +1) · สูงสุด 6 คน', 10, 24);
- ctx.font = '12px sans-serif';
- ctx.fillStyle = '#c0caf5';
- ctx.fillText('Space / W / ↑ = กระโดด · เลน + เลเซอร์ · ข้ามสำเร็จ → ขวา 1 + คะแนน · ชน → ซ้าย 1', 10, 42);
+ if (isGauntletCrownHeistMapPlay()) {
+ ctx.fillText('พรมแดงสุดท้าย | คะแนน ' + (me.gauntletScore || 0) + ' · ชน -10 · ขอบซ้ายตาย · สูงสุด 6 คน', 10, 24);
+ ctx.font = '12px sans-serif';
+ ctx.fillStyle = '#c0caf5';
+ ctx.fillText('Space / W / ↑ = กระโดด · เลน + เลเซอร์ · จบเกมมีโบนัสอันดับ + เกรดทีม', 10, 42);
+ } else {
+ ctx.fillText('พรมแดงสุดท้าย | คะแนน: ' + (me.gauntletScore || 0) + ' (ข้าม 1 ชิ้น +1) · สูงสุด 6 คน', 10, 24);
+ ctx.font = '12px sans-serif';
+ ctx.fillStyle = '#c0caf5';
+ ctx.fillText('Space / W / ↑ = กระโดด · เลน + เลเซอร์ · ข้ามสำเร็จ → ขวา 1 + คะแนน · ชน → ซ้าย 1', 10, 42);
+ }
ctx.fillStyle = '#e0af68';
if (gauntletEndsAtMs != null && Number.isFinite(gauntletEndsAtMs)) {
const rem = Math.max(0, Math.ceil((gauntletEndsAtMs - Date.now()) / 1000));
@@ -10082,6 +10324,8 @@
if (isGauntlet() && mapData && !isChatFocused()) {
if (e.code === 'Space' || e.code === 'ArrowUp' || e.code === 'KeyW') {
e.preventDefault();
+ if (isGauntletCrownHowtoBlocking()) return;
+ if (isGauntletCrownHeistMapPlay() && me.gauntletEliminated) return;
const now = Date.now();
if (now - lastGauntletJumpKey < 200) return;
lastGauntletJumpKey = now;
diff --git a/www/html/Game/public/js/version.js b/www/html/Game/public/js/version.js
index a0e87c7..6d696b4 100644
--- a/www/html/Game/public/js/version.js
+++ b/www/html/Game/public/js/version.js
@@ -1,6 +1,6 @@
// ทุกครั้งที่มีการเพิ่มหรือเปลี่ยน ให้เพิ่ม v +0.0001
// หลังแก้ค่าแล้วต้อง copy ไป path ที่ Nginx ชี้ (หรือรัน copy-frogger-files-only.sh) ถึงจะเห็นบนเว็บ
-window.APP_VERSION = '0.0169';
+window.APP_VERSION = '0.0170';
document.addEventListener('DOMContentLoaded', function () {
var t = document.querySelector('.version-tag');
if (t) t.textContent = 'v ' + window.APP_VERSION;
diff --git a/www/html/Game/public/play.html b/www/html/Game/public/play.html
index 1b0dc71..c0b8491 100644
--- a/www/html/Game/public/play.html
+++ b/www/html/Game/public/play.html
@@ -12,6 +12,17 @@