Spaces:
Running
Running
Download patch_control.mjs from bep40/hangmu-docker: direct link, hf CLI and curl.
- Browser
- Download file 10.9 kB
-
https://huggingface.co/spaces/bep40/hangmu-docker/resolve/main/patch_control.mjs
- Command line
-
hf download hf://spaces/bep40/hangmu-docker/patch_control.mjs
-
curl -L -o patch_control.mjs https://huggingface.co/spaces/bep40/hangmu-docker/resolve/main/patch_control.mjs
10.9 kB
| /* 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 ( | |
| <group position={[p.pos.x, gy + held, p.pos.z]} rotation={[0, p.rot, 0]}>`, | |
| ` 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 ( | |
| <group ref={rootRef} position={[p.pos.x, gy + held, p.pos.z]} rotation={[0, p.rot, 0]}>`, | |
| ], | |
| [ | |
| ` 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 ---- */ | |
| [ | |
| ` <mesh castShadow position={[0, 2.55 + yOff, 0]}> | |
| <sphereGeometry args={[0.45, 16, 16]} /> | |
| <meshStandardMaterial color={0xfdbcb4} roughness={0.9} /> | |
| </mesh>`, | |
| ` <group position={[0, 2.55 + yOff, 0]}> | |
| <mesh castShadow> | |
| <sphereGeometry args={[0.45, 16, 16]} /> | |
| <meshStandardMaterial color={0xfdbcb4} roughness={0.9} /> | |
| </mesh> | |
| <mesh castShadow position={[0, 0.13, -0.02]} scale={[1.05, 0.8, 1.05]}> | |
| <sphereGeometry args={[0.45, 16, 12, 0, Math.PI * 2, 0, Math.PI * 0.74]} /> | |
| <meshStandardMaterial color={0x1c1610} roughness={0.92} /> | |
| </mesh> | |
| </group>`, | |
| ], | |
| /* ---- (7) thân: áo đỏ + quần đùi đen + cổ trắng ---- */ | |
| [ | |
| ` <mesh castShadow position={[0, 1.05 + yOff, 0]}> | |
| <capsuleGeometry args={[0.5, 1.5, 4, 8]} /> | |
| <meshStandardMaterial color={color} roughness={0.7} metalness={0.1} /> | |
| </mesh>`, | |
| ` <mesh castShadow position={[0, 1.62 + yOff, 0]}> | |
| <capsuleGeometry args={[0.53, 1.26, 4, 10]} /> | |
| <meshStandardMaterial color={color} roughness={0.72} metalness={0.08} /> | |
| </mesh> | |
| <mesh castShadow position={[0, 0.72 + yOff, 0]} scale={[1.28, 0.62, 1.05]}> | |
| <sphereGeometry args={[0.42, 10, 8]} /> | |
| <meshStandardMaterial color={0x14141f} roughness={0.85} /> | |
| </mesh> | |
| <mesh position={[0, 2.2 + yOff, 0]}> | |
| <torusGeometry args={[0.3, 0.06, 6, 18]} /> | |
| <meshStandardMaterial color="#e8e8e8" roughness={0.55} /> | |
| </mesh>`, | |
| ], | |
| /* ---- (8a) tay trái ---- */ | |
| [ | |
| ` <group position={[-0.78, 2.05 + yOff, 0]}> | |
| <mesh castShadow rotation={[Math.sin(bob.current * anim / 4) * 0.9, 0, 0.35]}> | |
| <capsuleGeometry args={[0.16, 0.85, 4, 8]} /> | |
| <meshStandardMaterial color={0xfdbcb4} roughness={0.9} /> | |
| </mesh> | |
| </group>`, | |
| ` <group position={[-0.78, 2.05 + yOff, 0]}> | |
| <mesh castShadow rotation={[Math.sin(bob.current * anim / 4) * 0.9, 0, 0.35]}> | |
| <capsuleGeometry args={[0.19, 0.5, 4, 8]} /> | |
| <meshStandardMaterial color={color} roughness={0.72} /> | |
| </mesh> | |
| <mesh castShadow position={[0, -0.48, 0]} rotation={[Math.sin(bob.current * anim / 4) * 0.9, 0, 0.35]}> | |
| <capsuleGeometry args={[0.145, 0.46, 4, 8]} /> | |
| <meshStandardMaterial color={0xfdbcb4} roughness={0.9} /> | |
| </mesh> | |
| </group>`, | |
| ], | |
| /* ---- (8b) tay phải ---- */ | |
| [ | |
| ` <group position={[0.78, 2.05 + yOff, 0]}> | |
| <mesh castShadow rotation={[-Math.sin(bob.current * anim / 4) * 0.9, 0, -0.35]}> | |
| <capsuleGeometry args={[0.16, 0.85, 4, 8]} /> | |
| <meshStandardMaterial color={0xfdbcb4} roughness={0.9} /> | |
| </mesh> | |
| </group>`, | |
| ` <group position={[0.78, 2.05 + yOff, 0]}> | |
| <mesh castShadow rotation={[-Math.sin(bob.current * anim / 4) * 0.9, 0, -0.35]}> | |
| <capsuleGeometry args={[0.19, 0.5, 4, 8]} /> | |
| <meshStandardMaterial color={color} roughness={0.72} /> | |
| </mesh> | |
| <mesh castShadow position={[0, -0.48, 0]} rotation={[-Math.sin(bob.current * anim / 4) * 0.9, 0, -0.35]}> | |
| <capsuleGeometry args={[0.145, 0.46, 4, 8]} /> | |
| <meshStandardMaterial color={0xfdbcb4} roughness={0.9} /> | |
| </mesh> | |
| </group>`, | |
| ], | |
| /* ---- (9a) chân trái: đùi đen + tất đỏ ---- */ | |
| [ | |
| ` <group position={[-0.22, 1.0 + yOff, 0]}> | |
| <mesh castShadow rotation={[Math.sin(bob.current * anim / 4) * 0.9, 0, 0]}> | |
| <capsuleGeometry args={[0.18, 0.95, 4, 8]} /> | |
| <meshStandardMaterial color={0x14141f} roughness={0.8} metalness={0.1} /> | |
| </mesh> | |
| </group>`, | |
| ` <group position={[-0.22, 1.0 + yOff, 0]}> | |
| <mesh castShadow rotation={[Math.sin(bob.current * anim / 4) * 0.9, 0, 0]}> | |
| <capsuleGeometry args={[0.18, 0.55, 4, 8]} /> | |
| <meshStandardMaterial color={0x14141f} roughness={0.8} metalness={0.1} /> | |
| </mesh> | |
| <mesh castShadow position={[0, -0.42, 0.03]} rotation={[Math.sin(bob.current * anim / 4) * 0.9, 0, 0]}> | |
| <capsuleGeometry args={[0.17, 0.3, 4, 8]} /> | |
| <meshStandardMaterial color={0xc8102e} roughness={0.8} /> | |
| </mesh> | |
| </group>`, | |
| ], | |
| /* ---- (9b) chân phải ---- */ | |
| [ | |
| ` <group position={[0.22, 1.0 + yOff, 0]}> | |
| <mesh castShadow rotation={[-Math.sin(bob.current * anim / 4) * 0.9, 0, 0]}> | |
| <capsuleGeometry args={[0.18, 0.95, 4, 8]} /> | |
| <meshStandardMaterial color={0x14141f} roughness={0.8} metalness={0.1} /> | |
| </mesh> | |
| </group>`, | |
| ` <group position={[0.22, 1.0 + yOff, 0]}> | |
| <mesh castShadow rotation={[-Math.sin(bob.current * anim / 4) * 0.9, 0, 0]}> | |
| <capsuleGeometry args={[0.18, 0.55, 4, 8]} /> | |
| <meshStandardMaterial color={0x14141f} roughness={0.8} metalness={0.1} /> | |
| </mesh> | |
| <mesh castShadow position={[0, -0.42, 0.03]} rotation={[-Math.sin(bob.current * anim / 4) * 0.9, 0, 0]}> | |
| <capsuleGeometry args={[0.17, 0.3, 4, 8]} /> | |
| <meshStandardMaterial color={0xc8102e} roughness={0.8} /> | |
| </mesh> | |
| </group>`, | |
| ], | |
| /* ---- (10) giày trắng ---- */ | |
| [ | |
| ` <mesh position={[-0.09, 0.22 + yOff, 0.16]} rotation-x={0.25}> | |
| <boxGeometry args={[0.18, 0.09, 0.3]} /> | |
| <meshStandardMaterial color={0x1a1a1a} roughness={0.5} metalness={0.15} /> | |
| </mesh> | |
| <mesh position={[0.09, 0.22 + yOff, 0.16]} rotation-x={0.25}> | |
| <boxGeometry args={[0.18, 0.09, 0.3]} /> | |
| <meshStandardMaterial color={0x1a1a1a} roughness={0.5} metalness={0.15} /> | |
| </mesh>`, | |
| ` <mesh position={[-0.09, 0.22 + yOff, 0.16]} rotation-x={0.25}> | |
| <boxGeometry args={[0.18, 0.09, 0.3]} /> | |
| <meshStandardMaterial color={0xf2f2f2} roughness={0.4} metalness={0.1} /> | |
| </mesh> | |
| <mesh position={[0.09, 0.22 + yOff, 0.16]} rotation-x={0.25}> | |
| <boxGeometry args={[0.18, 0.09, 0.3]} /> | |
| <meshStandardMaterial color={0xf2f2f2} roughness={0.4} metalness={0.1} /> | |
| </mesh>`, | |
| ], | |
| ]) | |
| console.log('patch_control.mjs OK — camera-relative joystick + MU player model applied') |