nodes/pickup.pyΒΆ

Part of Q1K3.

  1"""Pickups for Q1K3 port: health, weapons, ammo, key."""
  2
  3from __future__ import annotations
  4
  5import math
  6from typing import TYPE_CHECKING
  7
  8from simvx.core import Material, MeshInstance3D, Node3D, Vec3
  9
 10from . import audio, meshes, textures
 11from .weapon import GrenadeLauncher, Nailgun
 12
 13if TYPE_CHECKING:  # pragma: no cover
 14    from .root import Q1K3Root
 15
 16
 17class Pickup(Node3D):
 18    """Base pickup. Pulses + rotates; despawns on player proximity."""
 19
 20    pickup_radius = 40.0
 21    tex_id = textures.TEX_HEALTH
 22    body_scale = (12, 12, 12)
 23
 24    def __init__(self, game: Q1K3Root, pos: Vec3) -> None:
 25        super().__init__()
 26        self.game = game
 27        self.position = pos
 28        self._spin = 0.0
 29        self._dead = False
 30        # Centre the cube so bottom sits roughly on top of the floor (no clip).
 31        self._inst = MeshInstance3D(
 32            mesh=meshes.cube(),
 33            material=Material(
 34                albedo_map=textures.get(type(self).tex_id),
 35                roughness=0.4,
 36                metallic=0.0,
 37            ),
 38            scale=type(self).body_scale,
 39            position=(0, type(self).body_scale[1] / 2, 0),
 40        )
 41        self.add_child(self._inst)
 42
 43    def on_update(self, dt: float) -> None:
 44        if self._dead:
 45            return
 46        # Bob + spin
 47        self._spin += dt * 1.5
 48        from simvx.core.math.types import Quat
 49
 50        self._inst.rotation = Quat.from_axis_angle(Vec3(0, 1, 0), self._spin)
 51        self._inst.position = Vec3(0, math.sin(self._spin * 2) * 2, 0)
 52
 53        player = self.game.player
 54        if player and not player._dead:
 55            d = self.position - player.p
 56            if d.length() < type(self).pickup_radius:
 57                self._on_pickup()
 58
 59    def _on_pickup(self) -> None:
 60        if self._dead:
 61            return
 62        self._dead = True
 63        self.game.play_sfx(audio.sfx_pickup())
 64        self.game.queue_remove(self)
 65
 66
 67class HealthPickup(Pickup):
 68    tex_id = textures.TEX_HEALTH
 69
 70    def _on_pickup(self):
 71        self.game.player._health = min(100, self.game.player._health + 25)
 72        super()._on_pickup()
 73
 74
 75class NailgunPickup(Pickup):
 76    tex_id = textures.TEX_PICKUP_NAILGUN
 77    body_scale = (14, 8, 22)
 78
 79    def _on_pickup(self):
 80        self.game.player.add_weapon(Nailgun())
 81        super()._on_pickup()
 82
 83
 84class GrenadeLauncherPickup(Pickup):
 85    tex_id = textures.TEX_GRENADES
 86    body_scale = (14, 10, 22)
 87
 88    def _on_pickup(self):
 89        self.game.player.add_weapon(GrenadeLauncher())
 90        super()._on_pickup()
 91
 92
 93class NailsPickup(Pickup):
 94    tex_id = textures.TEX_NAILS
 95    body_scale = (14, 8, 14)
 96
 97    def _on_pickup(self):
 98        added = self.game.player.add_ammo(Nailgun, 50)
 99        if not added:
100            return  # don't consume if player doesn't own the weapon
101        super()._on_pickup()
102
103
104class GrenadesPickup(Pickup):
105    tex_id = textures.TEX_GRENADES
106    body_scale = (14, 8, 14)
107
108    def _on_pickup(self):
109        added = self.game.player.add_ammo(GrenadeLauncher, 10)
110        if not added:
111            return
112        super()._on_pickup()
113
114
115class KeyPickup(Pickup):
116    tex_id = textures.TEX_KEY_DOOR
117    body_scale = (8, 14, 4)
118
119    def _on_pickup(self):
120        self.game.show_message("YOU GOT THE KEY!")
121        self.game.has_key = True
122        super()._on_pickup()