diff --git a/www/html/Game/public/js/play.js b/www/html/Game/public/js/play.js
index 7227cea..d885237 100644
--- a/www/html/Game/public/js/play.js
+++ b/www/html/Game/public/js/play.js
@@ -163,6 +163,17 @@
}
/** ทดสอบจากเอดิเตอร์: เติมบอทให้ครบจำนวน (รวมผู้เล่นจริง) — ?fillTotal=6 (ค่าเริ่ม 6) */
const previewFillBots = previewMode && editorEmbedReturn;
+ /** พรีวิวใน iframe เอดิเตอร์: ซูมมุมมองได้ (เดิมไม่มี — zDraw จาก zoom คงที่) */
+ const PLAY_EMBED_USER_ZOOM_MIN = 0.5;
+ const PLAY_EMBED_USER_ZOOM_MAX = 3;
+ const PLAY_EMBED_ZOOM_STEP_KEY = 1.12;
+ let playEmbedUserZoomMul = 1;
+ let lastPlayZDrawForInput = 1.4;
+ /** คูณ world→พิกเซลบนแคนวาสให้ตรงกับ zDraw ใน draw() (รวม preview+embed zoom) — ใช้ซิงก์ DOM ทับฉาก */
+ function playDomSyncZoom() {
+ const z = lastPlayZDrawForInput;
+ return Number.isFinite(z) && z > 0 ? z : zoom;
+ }
const previewTargetHeadcount = Math.min(24, Math.max(1, parseInt(params.get('fillTotal'), 10) || 6));
const PREVIEW_BOT_PREFIX = '__pv_bot_';
let previewBotSeq = 0;
@@ -2756,10 +2767,11 @@
}
const camX = me.x * tileSize;
const camY = me.y * tileSize;
- const left = (bounds.minX * tileSize - camX) * zoom + canvas.width / 2;
- const top = (bounds.minY * tileSize - camY) * zoom + canvas.height / 2;
- const wPx = (bounds.maxX - bounds.minX + 1) * tileSize * zoom;
- const hPx = (bounds.maxY - bounds.minY + 1) * tileSize * zoom;
+ const zDom = playDomSyncZoom();
+ const left = (bounds.minX * tileSize - camX) * zDom + canvas.width / 2;
+ const top = (bounds.minY * tileSize - camY) * zDom + canvas.height / 2;
+ const wPx = (bounds.maxX - bounds.minX + 1) * tileSize * zDom;
+ const hPx = (bounds.maxY - bounds.minY + 1) * tileSize * zDom;
textEl.textContent = text;
panel.style.left = Math.round(left) + 'px';
panel.style.top = Math.round(top) + 'px';
@@ -2782,7 +2794,7 @@
const thFont = isQuizCarry()
? getEffectiveCarryMapPanelThemeForApply()
: getEffectiveQuizMapPanelThemeForApply();
- const startFontPx = quizMapQuestionFontPxLikeCarryPlaques(thFont, zoom);
+ const startFontPx = quizMapQuestionFontPxLikeCarryPlaques(thFont, zDom);
const mnFont = Math.max(8, Math.round(Number(thFont.questionFontMinPx) || 10));
/* ไม่โหลด hud-question-plaque.png อัตโนมัติ — บนเซิร์ฟมักไม่มีไฟล์ → 404 ใน Network; ใช้คลาส + CSS แทน (อัปโหลด PNG แล้วค่อยผูกทีหลังได้) */
if (isQuiz() && isQuizQuestionMissionUiMapPlay() && quizQuestionMissionPhase === 'live') {
@@ -2884,10 +2896,11 @@
}
const camX = me.x * tileSize;
const camY = me.y * tileSize;
- const left = (bounds.minX * tileSize - camX) * zoom + canvas.width / 2;
- const top = (bounds.minY * tileSize - camY) * zoom + canvas.height / 2;
- const wPx = (bounds.maxX - bounds.minX + 1) * tileSize * zoom;
- const hPx = (bounds.maxY - bounds.minY + 1) * tileSize * zoom;
+ const zDom = playDomSyncZoom();
+ const left = (bounds.minX * tileSize - camX) * zDom + canvas.width / 2;
+ const top = (bounds.minY * tileSize - camY) * zDom + canvas.height / 2;
+ const wPx = (bounds.maxX - bounds.minX + 1) * tileSize * zDom;
+ const hPx = (bounds.maxY - bounds.minY + 1) * tileSize * zDom;
anchor.style.left = Math.round(left) + 'px';
anchor.style.top = Math.round(top) + 'px';
anchor.style.width = Math.round(Math.max(48, wPx)) + 'px';
@@ -2955,10 +2968,11 @@
}
const camX = me.x * tileSize;
const camY = me.y * tileSize;
- const l = (bounds.minX * tileSize - camX) * zoom + canvasEl.width / 2;
- const t = (bounds.minY * tileSize - camY) * zoom + canvasEl.height / 2;
- const wPx = (bounds.maxX - bounds.minX + 1) * tileSize * zoom;
- const hPx = (bounds.maxY - bounds.minY + 1) * tileSize * zoom;
+ const zDom = playDomSyncZoom();
+ const l = (bounds.minX * tileSize - camX) * zDom + canvasEl.width / 2;
+ const t = (bounds.minY * tileSize - camY) * zDom + canvasEl.height / 2;
+ const wPx = (bounds.maxX - bounds.minX + 1) * tileSize * zDom;
+ const hPx = (bounds.maxY - bounds.minY + 1) * tileSize * zDom;
const r = canvasEl.getBoundingClientRect();
const sl = r.left + l;
const st = r.top + t;
@@ -12302,6 +12316,7 @@
const prevWasSpaceShooter = mapData && mapData.gameType === 'space_shooter';
const prevWasBalloonBoss = mapData && mapData.gameType === 'balloon_boss';
mapData = gameMapData;
+ playEmbedUserZoomMul = 1;
if (mapData.gameType !== 'quiz_carry') {
quizCarryWalkSpeedMultActive = QUIZ_CARRY_WALK_SPEED_MULT;
}
@@ -13154,6 +13169,7 @@
if (!ev || !ev.mapId) return;
const applySnap = (md) => {
mapData = md;
+ playEmbedUserZoomMul = 1;
if (ev.mapId != null && String(ev.mapId).trim() !== '') {
playSessionMapId = String(ev.mapId).trim();
}
@@ -13521,11 +13537,19 @@
const vh = window.innerHeight || document.documentElement.clientHeight || 600;
const header = document.querySelector('.game-header');
const headerH = header && header.offsetHeight ? header.offsetHeight : 48;
- const parent = canvas && canvas.parentElement;
+ const stage = document.getElementById('play-canvas-stage');
+ const parent = stage || (canvas && canvas.parentElement);
let cw = canvas.clientWidth || 0;
let ch = canvas.clientHeight || 0;
if (parent && parent.clientWidth > 80) cw = Math.max(cw, parent.clientWidth);
if (parent && parent.clientHeight > 80) ch = Math.max(ch, parent.clientHeight);
+ /* จอใหญ่ / iframe embed: client บางทียัง 0 ช้ากว่า layout — ใช้ offset เป็นทางเลือก */
+ if (parent) {
+ const ow = parent.offsetWidth || 0;
+ const oh = parent.offsetHeight || 0;
+ if (ow > 80) cw = Math.max(cw, ow);
+ if (oh > 80) ch = Math.max(ch, oh);
+ }
if (ch < 120) ch = Math.max(240, vh - headerH - 6);
if (cw < 120) cw = Math.max(320, vw);
canvas.width = Math.max(320, cw);
@@ -13537,6 +13561,29 @@
if (!mapData || !isStack() || isChatFocused()) return;
tryHumanStackDrop();
});
+ (function setupPlayEditorEmbedViewZoom() {
+ const stack = document.getElementById('play-canvas-stack');
+ if (!stack) return;
+ const wheelZoomHandler = (e) => {
+ if (!previewMode || !editorEmbedReturn || !mapData) return;
+ const t = e.target;
+ if (t && typeof t.closest === 'function') {
+ if (t.closest('button, input, textarea, select, a[href]')) return;
+ }
+ /* ล้อธรรม / trackpad pinch มักไม่มี ctrl — ให้ซูมได้ใน embed; Ctrl/⌘+ล้อยังใช้ได้ */
+ e.preventDefault();
+ e.stopPropagation();
+ const raw = e.deltaMode === 1 ? e.deltaY * 14 : e.deltaMode === 2 ? e.deltaY * 320 : e.deltaY;
+ const k = Math.exp(-raw * 0.002);
+ playEmbedUserZoomMul = Math.max(
+ PLAY_EMBED_USER_ZOOM_MIN,
+ Math.min(PLAY_EMBED_USER_ZOOM_MAX, playEmbedUserZoomMul * k),
+ );
+ draw();
+ };
+ stack.addEventListener('wheel', wheelZoomHandler, { passive: false, capture: true });
+ })();
+
canvas.addEventListener('dblclick', (e) => {
if (!mapData || isFrogger() || isGauntlet() || isStack() || isJumpSurvive() || isSpaceShooter() || isBalloonBoss() || isChatFocused() || isQuizCarryEmbedCountdownBlockingMovement()) return;
const r = canvas.getBoundingClientRect();
@@ -13544,8 +13591,9 @@
const sy = e.clientY - r.top;
const camX = me.x * tileSize;
const camY = me.y * tileSize;
- const gx = (sx - canvas.width / 2) / zoom + camX;
- const gy = (sy - canvas.height / 2) / zoom + camY;
+ const zHit = lastPlayZDrawForInput > 0 ? lastPlayZDrawForInput : zoom;
+ const gx = (sx - canvas.width / 2) / zHit + camX;
+ const gy = (sy - canvas.height / 2) / zHit + camY;
const tx = Math.floor(gx / tileSize);
const ty = Math.floor(gy / tileSize);
const mw = mapData.width || 20, mh = mapData.height || 15;
@@ -14818,20 +14866,6 @@
statusEl.textContent = 'SPECTATOR · BALLOONS GONE';
} else if (isGauntletCrownHeistMapPlay()) {
statusEl.textContent = '';
- const tag = document.createElement('div');
- tag.className = 'play-cyber-self-tagline';
- tag.textContent = 'QUIZ LINK ·';
- const sub = document.createElement('div');
- sub.className = 'play-cyber-self-subtag';
- sub.textContent = 'ประลองความรู้';
- const nm = document.createElement('div');
- nm.className = 'play-cyber-self-nick-line';
- const raw = String(me.nickname || nick || '').trim();
- nm.textContent = raw || 'LINK';
- nm.title = raw || '';
- statusEl.appendChild(tag);
- statusEl.appendChild(sub);
- statusEl.appendChild(nm);
} else {
statusEl.textContent = 'OPERATOR ONLINE';
}
@@ -15119,11 +15153,15 @@
camY = mhPx * 0.5;
zDraw = Math.min(canvas.width / mwPx, canvas.height / mhPx) * 0.96;
}
+ if (previewMode && editorEmbedReturn && mapData) {
+ zDraw *= playEmbedUserZoomMul;
+ }
const gauntletGroupCam = getGauntletCrownHeistGroupCameraCenterPxPlay(tileSize, canvas.width, canvas.height, zDraw);
if (gauntletGroupCam) {
camX = gauntletGroupCam.px;
camY = gauntletGroupCam.py;
}
+ lastPlayZDrawForInput = zDraw;
const halfW = canvas.width / (2 * zDraw);
const halfH = canvas.height / (2 * zDraw);
@@ -15885,6 +15923,24 @@
document.addEventListener('keydown', (e) => {
if (isMovementKey(e.code) && isChatFocused()) return;
+ if (previewMode && editorEmbedReturn && mapData && !isChatFocused()) {
+ if (e.code === 'BracketLeft' || e.code === 'Minus' || e.code === 'NumpadSubtract') {
+ e.preventDefault();
+ playEmbedUserZoomMul = Math.max(PLAY_EMBED_USER_ZOOM_MIN, playEmbedUserZoomMul / PLAY_EMBED_ZOOM_STEP_KEY);
+ draw();
+ return;
+ }
+ if (
+ e.code === 'BracketRight'
+ || e.code === 'NumpadAdd'
+ || (e.code === 'Equal' && e.shiftKey)
+ ) {
+ e.preventDefault();
+ playEmbedUserZoomMul = Math.min(PLAY_EMBED_USER_ZOOM_MAX, playEmbedUserZoomMul * PLAY_EMBED_ZOOM_STEP_KEY);
+ draw();
+ return;
+ }
+ }
if (isSpaceShooter() && mapData && !isChatFocused() && isSpaceShooterMissionPregameBlockingPlay()) {
if (['Space', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'KeyW', 'KeyA', 'KeyS', 'KeyD'].includes(e.code)) {
e.preventDefault();
@@ -16097,7 +16153,8 @@
others.forEach((o) => {
if (o) clampPlayEntityFootprintToMap(o, mapData);
});
- const zGa = zoom;
+ let zGa = zoom;
+ if (previewMode && editorEmbedReturn && mapData) zGa *= playEmbedUserZoomMul;
const gCam = getGauntletCrownHeistGroupCameraCenterPxPlay(tileSize, canvas.width, canvas.height, zGa);
if (gCam) maybeEliminateGauntletPreviewBotsTailOffCameraPlay(gCam.px, gCam.py, zGa);
draw();
@@ -16416,4 +16473,25 @@
resizeCanvas();
draw();
});
+ /** แคนวาส flex ขยายทีหลัง (จอใหญ่ / editor iframe) — ResizeObserver จับได้ดีกว่า resize อย่างเดียว */
+ (function setupPlayCanvasStackResizeObserver() {
+ const stack = document.getElementById('play-canvas-stack');
+ if (!stack || typeof ResizeObserver === 'undefined') return;
+ let roRaf = null;
+ const run = () => {
+ roRaf = null;
+ if (!mapData || !canvas) return;
+ resizeCanvas();
+ draw();
+ };
+ const ro = new ResizeObserver(() => {
+ if (roRaf) cancelAnimationFrame(roRaf);
+ roRaf = requestAnimationFrame(run);
+ });
+ ro.observe(stack);
+ const wrap = stack.closest('.game-wrap');
+ if (wrap) ro.observe(wrap);
+ const stageEl = document.getElementById('play-canvas-stage');
+ if (stageEl) ro.observe(stageEl);
+ })();
})();
diff --git a/www/html/Game/public/js/version.js b/www/html/Game/public/js/version.js
index 35c9bb1..866681f 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.0260';
+window.APP_VERSION = '0.0286';
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 f83677d..987bef7 100644
--- a/www/html/Game/public/play.html
+++ b/www/html/Game/public/play.html
@@ -10,8 +10,36 @@