import * as THREE from 'three'; import { G } from './globals.js'; import { getTerrainHeight } from './world.js'; // Pop the enemy's helmet off and add simple physics so it drops to ground export function popHelmet(enemy, impulseDir = new THREE.Vector3(0, 1, 0), hitPoint = null) { if (!enemy || !enemy.helmet || !enemy.helmetAttached) return; const h = enemy.helmet; // Get world transform before detaching const worldPos = new THREE.Vector3(); const worldQuat = new THREE.Quaternion(); const worldScale = new THREE.Vector3(); h.updateMatrixWorld(); h.getWorldPosition(worldPos); h.getWorldQuaternion(worldQuat); h.getWorldScale(worldScale); // Detach from enemy and add to scene root if (h.parent) h.parent.remove(h); h.position.copy(worldPos); h.quaternion.copy(worldQuat); h.scale.copy(worldScale); G.scene.add(h); // It should no longer count as enemy geometry for ray hits if (h.userData) { h.userData.enemy = null; h.userData.hitZone = null; h.userData.isHelmet = true; } // Initial velocity: away from shot direction with a fun hop up const dir = impulseDir.clone().normalize(); const upBoost = 3 + G.random() * 1.5; const sideJitter = new THREE.Vector3((G.random() - 0.5) * 1.5, 0, (G.random() - 0.5) * 1.5); const vel = dir.multiplyScalar(2.2).add(new THREE.Vector3(0, upBoost, 0)).add(sideJitter); // Angular velocity for comedic spin const angVel = new THREE.Vector3( (G.random() - 0.5) * 6, (G.random() - 0.5) * 8, (G.random() - 0.5) * 6 ); G.helmets.push({ mesh: h, pos: h.position, vel, angVel, life: 12, // fade after a while grounded: false }); enemy.helmetAttached = false; } export function updateHelmets(delta) { const gravity = 14; // stronger than arrows for punchy drop const bounce = 0.35; for (let i = G.helmets.length - 1; i >= 0; i--) { const h = G.helmets[i]; // Integrate h.vel.y -= gravity * delta; h.pos.addScaledVector(h.vel, delta); // Simple rotation integration if (h.angVel) { h.mesh.rotateX(h.angVel.x * delta); h.mesh.rotateY(h.angVel.y * delta); h.mesh.rotateZ(h.angVel.z * delta); } // Ground collision and bounce against terrain const groundY = getTerrainHeight(h.pos.x, h.pos.z); if (h.pos.y <= groundY) { if (!h.grounded) { // First impact gets a stronger bounce h.grounded = true; } h.pos.y = groundY; if (Math.abs(h.vel.y) > 0.4) { h.vel.y = -h.vel.y * bounce; } else { h.vel.y = 0; } // Friction on ground h.vel.x *= 0.7; h.vel.z *= 0.7; // Damp spin as it settles if (h.angVel) h.angVel.multiplyScalar(0.8); if (Math.hypot(h.vel.x, h.vel.z) < 0.2 && Math.abs(h.vel.y) < 0.2) { h.vel.set(0, 0, 0); if (h.angVel) h.angVel.set(0, 0, 0); } } // Lifetime fade/cleanup h.life -= delta; if (h.life <= 2) { const m = h.mesh.material; if (m && m.opacity !== undefined) { m.transparent = true; m.opacity = Math.max(0, h.life / 2); } } if (h.life <= 0) { // Dispose per-helmet geometries h.mesh.traverse((obj) => { if (obj.isMesh && obj.geometry?.dispose) obj.geometry.dispose(); }); G.scene.remove(h.mesh); G.helmets.splice(i, 1); } } }