From 52a45f0ae0dd74d9c020b470fc5e58c55c9815c2 Mon Sep 17 00:00:00 2001 From: giteaadmin Date: Sun, 14 Jun 2026 04:46:57 +0000 Subject: [PATCH] update all games --- www/html/Admin/admin.js | 53 ++++++++++ www/html/Admin/index.html | 43 ++++++++ www/html/Admin/private/store.json | 12 ++- www/html/Game/data/game-timing.json | 7 +- www/html/Game/public/game-timing-admin.html | 55 ++++++++++ www/html/Game/public/js/play.js | 106 +++++++++++++------- www/html/Game/public/play.html | 2 +- www/html/Game/server.js | 36 ++++++- 8 files changed, 273 insertions(+), 41 deletions(-) diff --git a/www/html/Admin/admin.js b/www/html/Admin/admin.js index c7e1f8f..c165dfc 100644 --- a/www/html/Admin/admin.js +++ b/www/html/Admin/admin.js @@ -3969,6 +3969,56 @@ }); } + function applyForcedMinigameKeysToForm(keys) { + var arr = Array.isArray(keys) ? keys : []; + for (var i = 1; i <= 3; i++) { + var sel = el('forced-mg-' + i); + if (sel) sel.value = arr[i - 1] || ''; + } + } + + function readForcedMinigameKeysFromForm() { + var out = []; + for (var i = 1; i <= 3; i++) { + var sel = el('forced-mg-' + i); + out.push(sel ? String(sel.value || '') : ''); + } + return out; + } + + function loadForcedMinigamePanel() { + gameTimingFetch('GET') + .then(function (data) { + applyForcedMinigameKeysToForm(data && data.forcedMinigameKeys); + setMsg('forced-minigame-msg', '', ''); + }) + .catch(function (e) { + setMsg('forced-minigame-msg', e.message || 'โหลดไม่ได้', 'error'); + }); + } + + function saveForcedMinigamePanel() { + var keys = readForcedMinigameKeysFromForm(); + var anyForced = keys.some(function (k) { return !!k; }); + var btn = el('btn-forced-minigame-save'); + if (btn) btn.disabled = true; + gameTimingFetch('GET') + .then(function (data) { + data.forcedMinigameKeys = keys; + return gameTimingFetch('PUT', data); + }) + .then(function (res) { + if (res && typeof res === 'object') applyForcedMinigameKeysToForm(res.forcedMinigameKeys); + setMsg('forced-minigame-msg', anyForced ? 'บันทึกแล้ว — เริ่มคดีใหม่เพื่อเทสต์การ์ดที่ตั้งไว้' : 'กลับเป็นปกติ (สุ่มทั้ง 3 ใบ) แล้ว', 'ok'); + }) + .catch(function (e) { + setMsg('forced-minigame-msg', e.message || 'บันทึกไม่ได้', 'error'); + }) + .then(function () { + if (btn) btn.disabled = false; + }); + } + function saveJumpSurviveTimingPanel() { var inp = el('jump-survive-height-mult'); var jumpSurvMult = inp ? parseFloat(String(inp.value)) : 1.5; @@ -4487,6 +4537,7 @@ if (name === 'game-timing') loadGameTimingPanel(); if (name === 'stack-game') loadStackGamePanel(); if (name === 'highscore') loadHighscore(); + if (name === 'test-mode') loadForcedMinigamePanel(); } function boot() { @@ -4710,6 +4761,8 @@ syncTestModeUi(); }); } + var forcedMgSaveBtn = el('btn-forced-minigame-save'); + if (forcedMgSaveBtn) forcedMgSaveBtn.addEventListener('click', saveForcedMinigamePanel); syncTestModeUi(); /* ฟัง storage event — sync เมื่อเปิดหลายแท็บ */ window.addEventListener('storage', function (e) { diff --git a/www/html/Admin/index.html b/www/html/Admin/index.html index a20962a..b1dbc34 100644 --- a/www/html/Admin/index.html +++ b/www/html/Admin/index.html @@ -1112,6 +1112,49 @@

+ +

บังคับมินิเกม (เทสต์ตอนเล่นจริง)

+

เลือกเกมต่อการ์ดผู้ต้องสงสัย 3 ใบ → บันทึก → เริ่มคดีใหม่ (สร้างห้อง → LobbyA → เลือกคดี → LobbyB) · ช่องที่เลือก "— สุ่ม —" จะสุ่มจาก 7 เกมเหมือนเดิม · เลือก "— สุ่ม —" ครบทั้ง 3 ช่อง = กลับเป็นปกติ

+
+ + + + +
+

diff --git a/www/html/Admin/private/store.json b/www/html/Admin/private/store.json index 87594b6..6d707e1 100644 --- a/www/html/Admin/private/store.json +++ b/www/html/Admin/private/store.json @@ -77,14 +77,20 @@ { "id": "d1d5c39d42a2dce9a4949d5a", "email": "", - "displayName": "Guest", + "displayName": "ผู้เล่น", "loginType": "guest", "providerUserId": "p_1777015659667_xqfjh247bgp", "notes": "auto: player-coins", "blocked": false, - "coins": 0, + "coins": 90, "createdAt": "2026-04-24T07:28:02+00:00", - "updatedAt": "2026-04-24T07:28:02+00:00" + "updatedAt": "2026-06-14T04:39:10+00:00", + "score": 90, + "scoreByCase": { + "8": 60, + "5": 30 + }, + "lbName": "ผู้เล่น" }, { "id": "d666c2fbf6005b69ee192882", diff --git a/www/html/Game/data/game-timing.json b/www/html/Game/data/game-timing.json index 62342ee..fa15f0a 100644 --- a/www/html/Game/data/game-timing.json +++ b/www/html/Game/data/game-timing.json @@ -87,5 +87,10 @@ "/Game/img/MegaVirus/balloon-6.png" ], "balloonBossPlayerBalloonFallbackUrl": "/Game/img/MegaVirus/Artboard%209.png", - "balloonBossBalloonsPerPlayer": 3 + "balloonBossBalloonsPerPlayer": 3, + "forcedMinigameKeys": [ + "quiz", + "quiz_carry", + "balloon_boss" + ] } \ No newline at end of file diff --git a/www/html/Game/public/game-timing-admin.html b/www/html/Game/public/game-timing-admin.html index e1c0abd..4b118bd 100644 --- a/www/html/Game/public/game-timing-admin.html +++ b/www/html/Game/public/game-timing-admin.html @@ -47,6 +47,7 @@ +
@@ -100,6 +101,50 @@
+ +
@@ -115,6 +160,7 @@ 'panel-shooter': document.getElementById('panel-shooter'), 'panel-gauntlet': document.getElementById('panel-gauntlet'), 'panel-stack': document.getElementById('panel-stack'), + 'panel-test': document.getElementById('panel-test'), }; tabs.forEach((btn) => { btn.addEventListener('click', () => { @@ -145,6 +191,11 @@ document.getElementById('stackSwingHz').value = timing.stackSwingHz != null ? timing.stackSwingHz : 0.1; const sb = document.getElementById('stackBlockWidthTiles'); sb.value = timing.stackBlockWidthTiles != null && timing.stackBlockWidthTiles !== '' ? timing.stackBlockWidthTiles : ''; + const fks = Array.isArray(timing.forcedMinigameKeys) ? timing.forcedMinigameKeys : []; + for (let i = 1; i <= 3; i++) { + const s = document.getElementById('forcedMg' + i); + if (s) s.value = fks[i - 1] || ''; + } } function readForm() { @@ -168,6 +219,10 @@ const v = document.getElementById('stackBlockWidthTiles').value.trim(); return v === '' ? null : parseFloat(v, 10); })(), + forcedMinigameKeys: [1, 2, 3].map(function (i) { + const s = document.getElementById('forcedMg' + i); + return s ? (s.value || '') : ''; + }), }; } diff --git a/www/html/Game/public/js/play.js b/www/html/Game/public/js/play.js index 246918e..908eb24 100644 --- a/www/html/Game/public/js/play.js +++ b/www/html/Game/public/js/play.js @@ -8220,6 +8220,22 @@ return lines; } + /** ตัดบรรทัดระดับตัวอักษรให้แต่ละบรรทัดพอดีกว้าง (รองรับคำไทยยาวไม่มีช่องว่าง) — คืนทุกบรรทัด */ + function quizCarryWrapLinesFit(ctx, text, maxWidth) { + const raw = String(text || '').trim(); + if (!raw) return []; + const lines = []; + let cur = ''; + for (const chr of raw) { + if (chr === '\n') { lines.push(cur); cur = ''; continue; } + const test = cur + chr; + if (cur === '' || ctx.measureText(test).width <= maxWidth) cur = test; + else { lines.push(cur); cur = chr; } + } + if (cur) lines.push(cur); + return lines; + } + function quizCarryOptionHeldByAnyone(optionIdx) { if (optionIdx == null || optionIdx < 0) return false; if (me.quizCarryHeld === optionIdx) return true; @@ -15931,28 +15947,17 @@ if (o.balloonBossBotNextFire <= 0) { o.balloonBossBotNextFire = 0.9 + Math.random() * 1.1; const bspd = 500 + Math.random() * 90; - let bvx; - let bvy; - let bdelay; - if (isMegaVirusMissionShellMapPlay()) { - /** Mega Virus: บอทยิงตามมุม "ลูกศรวงกลม" ของตัวเอง (ทันที) เหมือนผู้เล่น — ไม่ auto-aim ใส่บอส */ - const aim = (typeof o.balloonBossAimRad === 'number' && Number.isFinite(o.balloonBossAimRad)) - ? o.balloonBossAimRad - : Math.atan2(bossCy - o.balloonBossCy, bossCx - o.balloonBossCx); - bvx = Math.cos(aim) * bspd; - bvy = Math.sin(aim) * bspd; - bdelay = 0; - } else { - const dx = bossCx - o.balloonBossCx; - const dy = bossCy - o.balloonBossCy; - const L = Math.hypot(dx, dy) || 1; - bvx = (dx / L) * bspd; - bvy = (dy / L) * bspd; - bdelay = balloonBossFireDelayMsPlay(); - } + /** บอทยิงตามมุม "ลูกศรวงกลม" ของตัวเอง (ทันที) เหมือนผู้เล่น — ลูกศรหมุนตลอดทั้งทดสอบ/เล่นจริง ไม่ auto-aim ใส่บอส */ + const aim = (typeof o.balloonBossAimRad === 'number' && Number.isFinite(o.balloonBossAimRad)) + ? o.balloonBossAimRad + : Math.atan2(bossCy - o.balloonBossCy, bossCx - o.balloonBossCx); + const bvx = Math.cos(aim) * bspd; + const bvy = Math.sin(aim) * bspd; + /** ยิงจากปลายลูกศรเหมือนผู้เล่น */ balloonBossPendingShots.push({ - releaseAt: performance.now() + bdelay, - sx: o.balloonBossCx, sy: o.balloonBossCy, + releaseAt: performance.now(), + sx: o.balloonBossCx + Math.cos(aim) * 26, + sy: o.balloonBossCy - 10.5 + Math.sin(aim) * 26, vx: bvx, vy: bvy, ownerId: bid, @@ -16214,17 +16219,19 @@ if (wantFire && balloonBossPlayerFireCd <= 0 && me.balloonBossCy != null) { balloonBossPlayerFireCd = 0.35; /** ยิงตามมุม "ลูกศรวงกลม" เสมอ (me.balloonBossAimRad อัปเดตทุกเฟรม) — fallback เล็งบอสเฉพาะตอนมุมยังไม่พร้อม */ - /** Mega Virus: ยิงทันที (delay 0) ไม่งั้นลูกศรหมุนต่อระหว่างหน่วง → กระสุนโผล่ไม่ตรงลูกศร */ - const delayMs = isMegaVirusMissionShellMapPlay() ? 0 : balloonBossFireDelayMsPlay(); + /** ลูกศรหมุนตลอดสำหรับ balloon_boss → ยิงทันที (delay 0) ทุกกรณี ทั้งทดสอบ/เล่นจริง ไม่งั้นกระสุนโผล่ไม่ตรงลูกศร */ + const delayMs = 0; const bspd = 480; const aim = (typeof me.balloonBossAimRad === 'number' && Number.isFinite(me.balloonBossAimRad)) ? me.balloonBossAimRad : Math.atan2(bossC.cy - me.balloonBossCy, bossC.cx - me.balloonBossCx); const vx = Math.cos(aim) * bspd; const vy = Math.sin(aim) * bspd; + /** ยิงออกจาก "ปลายลูกศร" ไม่ใช่กลางตัว — วงอยู่เหนือตัว ~10.5px (world) รัศมีปลายหาง ~26px */ balloonBossPendingShots.push({ releaseAt: now + delayMs, - sx: me.balloonBossCx, sy: me.balloonBossCy, + sx: me.balloonBossCx + Math.cos(aim) * 26, + sy: me.balloonBossCy - 10.5 + Math.sin(aim) * 26, vx, vy, ownerId: myId, @@ -18499,6 +18506,30 @@ } }); } + /** เกมจริง: bot ไม่อยู่ในผล server → เช็คโซนยืนเอง ผิด = เป็นผี (เหมือน path preview) */ + if (isQuiz() && Array.isArray(r.results) && playBotsEnabled()) { + const correctTrueBot = !!r.correctTrue; + others.forEach(function (o, id) { + if (!isPreviewBotId(id) || !o) return; + const br = quizResolveChoiceFromStanding(o.x, o.y, correctTrueBot); + o.botPath = []; + o.botAnswerWander = false; + if (br.right) { + playLiveQuizScores[id] = (playLiveQuizScores[id] || 0) + QUIZ_TF_POINTS_PER_CORRECT; + } else { + playQuizEverWrong[String(id)] = true; + o.quizCannotTrue = true; + o.quizCannotFalse = true; + const ordB = quizPreviewJoinOrderForRespawn(id); + const sp = pickSpawnForJoinPlay(mapData, ordB); + const pos = findNearestOutsideQuizAnswerZonesPlay(mapData, sp.x + 0.5, sp.y + 0.5); + o.x = pos.x + (Math.random() - 0.5) * 0.35; + o.y = pos.y + (Math.random() - 0.5) * 0.35; + o.tx = o.x; + o.ty = o.y; + } + }); + } if (isQuiz() && Array.isArray(r.results) && r.results.some(function (row) { return row && row.right; })) { spawnQuizTrueFalseScorePopupPlay(!!r.correctTrue); } @@ -22069,24 +22100,31 @@ const baseMaxW = Math.min(280, Math.max(100, halfW * 2.4 + tileSize * zDraw * 0.5)); const maxPlaqueW = Math.min(340, Math.round(baseMaxW * heldPs)); - const fontPx = Math.max(12, Math.min(22, tileSize * zDraw * 0.22 * heldPs)); - ctx.font = `600 ${fontPx}px system-ui, "Segoe UI", "Kanit", sans-serif`; const padX = 12; const padY = 9; const maxInner = Math.min(maxPlaqueW - padX * 2, Math.max(72, tileSize * zDraw * 4.2)); + const MAX_LINES = 3; + const MIN_FONT = 8; + let fontPx = Math.max(12, Math.min(22, tileSize * zDraw * 0.22 * heldPs)); let lines; if (!raw) { lines = []; + ctx.font = `600 ${fontPx}px system-ui, "Segoe UI", "Kanit", sans-serif`; } else { - lines = canvasWordWrapLines(ctx, raw, maxInner); - if (!lines.length) lines = [raw]; - if (lines.length > 3) lines = lines.slice(0, 3); - for (let i = 0; i < lines.length; i++) { - let s = lines[i]; - if (ctx.measureText(s).width <= maxInner) continue; - while (s.length > 2 && ctx.measureText(s + '…').width > maxInner) s = s.slice(0, -1); - lines[i] = s + '…'; + /* ย่อฟอนต์จนข้อความพอดีกรอบ (คำไทยยาวไม่มีช่องว่าง → char-wrap + ย่อ ไม่ตัด/ล้นกรอบ) */ + for (;;) { + ctx.font = `600 ${fontPx}px system-ui, "Segoe UI", "Kanit", sans-serif`; + lines = quizCarryWrapLinesFit(ctx, raw, maxInner); + if (lines.length <= MAX_LINES || fontPx <= MIN_FONT) break; + fontPx -= 1; } + if (lines.length > MAX_LINES) { + lines = lines.slice(0, MAX_LINES); + let s = lines[MAX_LINES - 1]; + while (s.length > 1 && ctx.measureText(s + '…').width > maxInner) s = s.slice(0, -1); + lines[MAX_LINES - 1] = s + '…'; + } + if (!lines.length) lines = [raw]; } let maxLineW = 0; for (let i = 0; i < lines.length; i++) { diff --git a/www/html/Game/public/play.html b/www/html/Game/public/play.html index 48f9c22..02592e0 100644 --- a/www/html/Game/public/play.html +++ b/www/html/Game/public/play.html @@ -5180,7 +5180,7 @@ - +
v —