/* patch_control.mjs — v17: joystick theo hướng camera + nhân vật kiểu cầu thủ MU FIX A (joystick lệch hướng): Trước đây input joystick map thẳng {x:dx, z:-dy} vào tọa độ THẾ GIỚI → atan2(mx,mz) → kéo "lên màn hình" (dy<0) = +z thế giới = đi RA SÂN, trong khi camera nhìn về -z → ngược với cảm nhận màn hình. FIX: xoay hệ quy chiếu theo yaw camera (me.rot): kéo lên = đi xa camera (forward), kéo phải = đi bên phải màn hình (right = forward × up). FIX B (nhân vật chưa giống hangmu.online): Model cũ chỉ là capsule trơn. Nâng cấp thành cầu thủ MU: tóc đen, áo đỏ bo tròn + cổ trắng, tay áo đỏ (cẳng tay màu da), quần đùi đen, tất đỏ, giày trắng — giữ nguyên yOff/gy/held ở render scope (tránh crash v15). Nếu pattern lệch -> exit(1) để build fail. */ import fs from 'node:fs' function patchFile(path, reps) { let s = fs.readFileSync(path, 'utf8') const failures = [] for (const [oldStr, newStr] of reps) { const count = s.split(oldStr).length - 1 if (count === 1) { s = s.replace(oldStr, newStr); continue } failures.push(`pattern not unique/found (count=${count}): ${oldStr.slice(0, 80)}`) } if (failures.length) { console.error(`PATCH CONTROL FAILED (${path}):`) failures.forEach((f) => console.error(' -', f)) process.exit(1) } fs.writeFileSync(path, s) console.log('patched ok:', path) } patchFile('frontend/app.js', [ /* ---- (1) ref bám vị trí mỗi frame, giữ render-scope consts ---- */ [ ` useFrame((_, dt) => { bob.current += dt * (anim || 1) }) const yOff = anim ? Math.sin(bob.current * 1.2) * 0.12 : 0 const gy = floorHeight(Math.hypot(p.pos.x, p.pos.z)) + yOff const held = p.holding ? 0.15 : 0 return ( `, ` useFrame((_, dt) => { bob.current += dt * (anim || 1) // Movement MUTATE me.pos mà KHÔNG gọi set() -> React không re-render. // Cập nhật imperative qua ref để avatar di chuyển mượt theo đúng phím. if (rootRef.current) { rootRef.current.position.set(p.pos.x, floorHeight(Math.hypot(p.pos.x, p.pos.z)) + (p.holding ? 0.15 : 0), p.pos.z) rootRef.current.rotation.y = p.rot } }) const yOff = anim ? Math.sin(bob.current * 1.2) * 0.12 : 0 const gy = floorHeight(Math.hypot(p.pos.x, p.pos.z)) + yOff const held = p.holding ? 0.15 : 0 return ( `, ], [ ` const bob = React.useRef(0) const anim = p.anim === 'run' ? 14 : (p.anim === 'walk' ? 8 : 0) useFrame((_, dt) => { bob.current += dt * (anim || 1) })`, ` const bob = React.useRef(0) const rootRef = React.useRef(null) const anim = p.anim === 'run' ? 14 : (p.anim === 'walk' ? 8 : 0)`, ], /* ---- (2) walkR 95 + guard chống vùng trống ---- */ [ ` const dist = Math.hypot(nx, nz) const walkR = 42 if (dist > walkR) { nx *= walkR / dist; nz *= walkR / dist }`, ` const dist = Math.hypot(nx, nz) const walkR = 95 if (dist > walkR) { nx *= walkR / dist; nz *= walkR / dist } // chống vùng trống: chưa ra sân ngoài (z<55) thì giữ trong bán kính sàn hang (61) if (nz < 55) { const d2 = Math.hypot(nx, nz) if (d2 > 61) { nx *= 61 / d2; nz *= 61 / d2 } }`, ], /* ---- (3) joystick: center reset theo {x,y} ---- */ [ ` const center = () => { joy.current = { x: 0, z: 0 }; if (knobRef.current) knobRef.current.style.transform = 'translate(-50%,-50%)' }`, ` const center = () => { joy.current = { x: 0, y: 0 }; if (knobRef.current) knobRef.current.style.transform = 'translate(-50%,-50%)' }`, ], /* ---- (4) joystick: lưu {x: dx, y: -dy} (dy<0 = kéo lên màn hình) ---- */ [ ` joy.current = { x: dx, z: -dy }`, ` joy.current = { x: dx, y: -dy }`, ], /* ---- (5) joystick: xoay theo hướng camera (kéo lên = đi xa camera) ---- */ [ ` if (touch && (joy.current.x || joy.current.z)) { mx = joy.current.x; mz = joy.current.z }`, ` if (touch && (joy.current.x || joy.current.y)) { const cy = me.rot const fx = -Math.sin(cy), fz = -Math.cos(cy) const rx = Math.cos(cy), rz = -Math.sin(cy) mx = fx * joy.current.y + rx * joy.current.x mz = fz * joy.current.y + rz * joy.current.x }`, ], /* ---- (6) đầu + tóc đen ngắn ---- */ [ ` `, ` `, ], /* ---- (7) thân: áo đỏ + quần đùi đen + cổ trắng ---- */ [ ` `, ` `, ], /* ---- (8a) tay trái ---- */ [ ` `, ` `, ], /* ---- (8b) tay phải ---- */ [ ` `, ` `, ], /* ---- (9a) chân trái: đùi đen + tất đỏ ---- */ [ ` `, ` `, ], /* ---- (9b) chân phải ---- */ [ ` `, ` `, ], /* ---- (10) giày trắng ---- */ [ ` `, ` `, ], ]) console.log('patch_control.mjs OK — camera-relative joystick + MU player model applied')