From cc1da7a11485c40384cab7f04d11ba19852dda33 Mon Sep 17 00:00:00 2001 From: giteaadmin Date: Wed, 6 May 2026 07:12:19 +0000 Subject: [PATCH] minigame 1 add joystick --- www/html/Game/public/js/play.js | 132 +++++++++++++++++++++++++++++++- www/html/Game/public/play.html | 72 ++++++++++++++++- 2 files changed, 199 insertions(+), 5 deletions(-) diff --git a/www/html/Game/public/js/play.js b/www/html/Game/public/js/play.js index 41351e0..4dad97a 100644 --- a/www/html/Game/public/js/play.js +++ b/www/html/Game/public/js/play.js @@ -1962,6 +1962,10 @@ let quizQuestionMissionPhase = null; let quizQuestionMissionCountdownTimer = null; let quizQuestionMissionDeferredPhase = null; + /** mng8a80o live: virtual joystick (+x right, +y down) normalized ~[-1,1] */ + let quizQuestionMissionJoyVecX = 0; + let quizQuestionMissionJoyVecY = 0; + let quizQuestionMissionJoyPointerId = null; /** Stack Tower (mnn93hpi) — flow เดียวกับ crown / quiz mission */ let stackTowerMissionPhase = null; let stackTowerMissionCountdownTimer = null; @@ -15395,6 +15399,60 @@ } } + function quizQuestionMissionJoystickResetVisual() { + quizQuestionMissionJoyVecX = 0; + quizQuestionMissionJoyVecY = 0; + const knob = document.getElementById('quiz-q-mission-joystick-knob'); + if (knob) knob.style.transform = 'translate(-50%, -50%)'; + } + + function quizQuestionMissionJoystickUpdateFromClientXY(clientX, clientY) { + const base = document.getElementById('quiz-q-mission-joystick-base'); + const knob = document.getElementById('quiz-q-mission-joystick-knob'); + if (!base || !knob) return; + const r = base.getBoundingClientRect(); + const cx = r.left + r.width * 0.5; + const cy = r.top + r.height * 0.5; + let dx = clientX - cx; + let dy = clientY - cy; + const half = Math.min(r.width, r.height) * 0.5; + const maxKn = Math.max(18, half * 0.52); + const len = Math.hypot(dx, dy); + if (len > maxKn && len > 1e-6) { + dx = (dx / len) * maxKn; + dy = (dy / len) * maxKn; + } + const nlen = Math.hypot(dx, dy); + const dead = maxKn * 0.12; + if (nlen < dead) { + quizQuestionMissionJoyVecX = 0; + quizQuestionMissionJoyVecY = 0; + knob.style.transform = 'translate(-50%, -50%)'; + return; + } + quizQuestionMissionJoyVecX = dx / maxKn; + quizQuestionMissionJoyVecY = dy / maxKn; + knob.style.transform = 'translate(calc(-50% + ' + dx + 'px), calc(-50% + ' + dy + 'px))'; + } + + function syncQuizQuestionMissionJoystickPlay() { + const root = document.getElementById('quiz-question-mission-joystick'); + if (!root) return; + const on = !!(isQuizQuestionMissionHudActivePlay() && !isChatFocused()); + root.classList.toggle('is-hidden', !on); + root.setAttribute('aria-hidden', on ? 'false' : 'true'); + if (!on) { + const base = document.getElementById('quiz-q-mission-joystick-base'); + if (base && quizQuestionMissionJoyPointerId != null) { + try { + base.releasePointerCapture(quizQuestionMissionJoyPointerId); + } catch (_e) { /* ignore */ } + } + quizQuestionMissionJoyPointerId = null; + quizQuestionMissionJoystickResetVisual(); + } + } + function syncPlayCyberHud() { const root = document.getElementById('play-cyber-hud'); const stackEl = document.getElementById('play-canvas-stack'); @@ -16754,6 +16812,7 @@ } drawQuizTfScorePopupsLayer(ctx, worldToScreen, zDraw, timeMs); syncPlayCyberHud(); + syncQuizQuestionMissionJoystickPlay(); syncQuizCarryGrabButton(); syncGauntletCrownJumpButton(); syncStackTowerDropButtonPlay(); @@ -17068,10 +17127,24 @@ } } if (!usePath) { - if (keys['ArrowUp'] || keys['KeyW']) { accY = -1; me.direction = 'up'; } - if (keys['ArrowDown'] || keys['KeyS']) { accY = 1; me.direction = 'down'; } - if (keys['ArrowLeft'] || keys['KeyA']) { accX = -1; me.direction = 'left'; } - if (keys['ArrowRight'] || keys['KeyD']) { accX = 1; me.direction = 'right'; } + let kx = 0; + let ky = 0; + if (keys['ArrowLeft'] || keys['KeyA']) kx -= 1; + if (keys['ArrowRight'] || keys['KeyD']) kx += 1; + if (keys['ArrowUp'] || keys['KeyW']) ky -= 1; + if (keys['ArrowDown'] || keys['KeyS']) ky += 1; + let jx = 0; + let jy = 0; + if (isQuizQuestionMissionHudActivePlay()) { + jx = quizQuestionMissionJoyVecX; + jy = quizQuestionMissionJoyVecY; + } + accX = kx + jx; + accY = ky + jy; + if (accX !== 0 || accY !== 0) { + if (Math.abs(accY) > Math.abs(accX)) me.direction = accY > 0 ? 'down' : 'up'; + else if (accX !== 0) me.direction = accX > 0 ? 'right' : 'left'; + } } const preWalkX = me.x, preWalkY = me.y; if (accX !== 0 || accY !== 0) { @@ -17177,6 +17250,57 @@ }); })(); + (function wireQuizQuestionMissionJoystick() { + const base = document.getElementById('quiz-q-mission-joystick-base'); + const bg = document.getElementById('quiz-q-mission-joystick-bg'); + const knobImg = document.getElementById('quiz-q-mission-joystick-knob-img'); + if (!base) return; + if (bg) { + bg.src = questionMissionAssetUrl('btn-joystick-1.png'); + bg.onerror = function () { + this.onerror = null; + this.removeAttribute('src'); + }; + } + if (knobImg) { + knobImg.src = questionMissionAssetUrl('btn-joystick-2.png'); + knobImg.onerror = function () { + this.onerror = null; + this.removeAttribute('src'); + }; + } + function endJoy(e) { + if (quizQuestionMissionJoyPointerId == null || e.pointerId !== quizQuestionMissionJoyPointerId) return; + try { + base.releasePointerCapture(e.pointerId); + } catch (_err) { /* ignore */ } + quizQuestionMissionJoyPointerId = null; + quizQuestionMissionJoystickResetVisual(); + } + base.addEventListener('pointerdown', (e) => { + if (!isQuizQuestionMissionHudActivePlay() || isChatFocused()) return; + if (e.button != null && e.button !== 0) return; + e.preventDefault(); + try { + base.setPointerCapture(e.pointerId); + } catch (_err) { /* ignore */ } + quizQuestionMissionJoyPointerId = e.pointerId; + quizQuestionMissionJoystickUpdateFromClientXY(e.clientX, e.clientY); + }); + base.addEventListener('pointermove', (e) => { + if (quizQuestionMissionJoyPointerId == null || e.pointerId !== quizQuestionMissionJoyPointerId) return; + e.preventDefault(); + quizQuestionMissionJoystickUpdateFromClientXY(e.clientX, e.clientY); + }); + base.addEventListener('pointerup', endJoy); + base.addEventListener('pointercancel', endJoy); + base.addEventListener('lostpointercapture', (e) => { + if (quizQuestionMissionJoyPointerId == null || e.pointerId !== quizQuestionMissionJoyPointerId) return; + quizQuestionMissionJoyPointerId = null; + quizQuestionMissionJoystickResetVisual(); + }); + })(); + (function wireGauntletCrownHowtoPrimary() { const btn = document.getElementById('btn-gch-ready'); if (!btn) return; diff --git a/www/html/Game/public/play.html b/www/html/Game/public/play.html index 4ca9ea7..6a4030a 100644 --- a/www/html/Game/public/play.html +++ b/www/html/Game/public/play.html @@ -1673,6 +1673,67 @@ overflow: visible; min-height: unset; } + /* mng8a80o — virtual joystick (Roblox-style) มุมซ้ายล่าง */ + .quiz-q-mission-joystick { + position: fixed; + z-index: 56; + left: max(10px, env(safe-area-inset-left, 0px)); + bottom: max(12px, env(safe-area-inset-bottom, 0px)); + width: min(132px, 28vw); + height: min(132px, 28vw); + touch-action: none; + user-select: none; + -webkit-user-select: none; + pointer-events: auto; + box-sizing: border-box; + } + html.play-preview-editor-embed .quiz-q-mission-joystick { + position: absolute; + bottom: max(12px, env(safe-area-inset-bottom, 0px)); + } + .quiz-q-mission-joystick.is-hidden { + display: none !important; + } + .quiz-q-mission-joystick-base { + position: relative; + width: 100%; + height: 100%; + cursor: grab; + } + .quiz-q-mission-joystick-base:active { + cursor: grabbing; + } + .quiz-q-mission-joystick-bg { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: contain; + pointer-events: none; + image-rendering: pixelated; + filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.45)); + } + .quiz-q-mission-joystick-knob { + position: absolute; + left: 50%; + top: 50%; + width: 42%; + height: 42%; + transform: translate(-50%, -50%); + display: flex; + align-items: center; + justify-content: center; + pointer-events: none; + transition: transform 0.04s linear; + } + .quiz-q-mission-joystick-knob-img { + width: 100%; + height: 100%; + object-fit: contain; + image-rendering: pixelated; + filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.35)); + } + #play-cyber-hud.play-cyber-hud--question-mission .play-cyber-score-list { box-sizing: border-box; width: 100%; @@ -2938,6 +2999,15 @@
RAM ERROR · CORRUPTING · DELETE INITIATED · 01001101 01110011 01100101 01100011
+ + - +
v —