File size: 3,374 Bytes
1390db3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
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);
    }
  }
}