nodes/projectile.pyΒΆ

Part of SNKRX.

 1"""Player projectiles: pierce shots from archers, AOE bursts from mages.
 2
 3Projectiles are short-lived ``Node2D`` instances that walk a velocity vector
 4and emit trail particles. They flag themselves ``alive=False`` when out of
 5range or after collision; the arena destroys them in its post-update sweep.
 6"""
 7
 8from __future__ import annotations
 9
10from simvx.core import Node2D, Vec2
11
12from .colours import BLUE, FG, GREEN
13
14
15class Projectile(Node2D):
16    def __init__(
17        self,
18        *,
19        kind: str,
20        position: Vec2,
21        velocity: Vec2,
22        damage: float,
23        max_distance: float,
24        radius: float = 4.0,
25        pierce: int = 1,
26        aoe_radius: float = 0.0,
27        colour: tuple[float, float, float, float] = FG,
28        **kwargs,
29    ):
30        super().__init__(name="Projectile", position=Vec2(position), **kwargs)
31        self.kind = kind
32        self.velocity = Vec2(velocity)
33        self.damage = damage
34        self.max_distance = max_distance
35        self.radius = radius
36        self.pierce_left = pierce
37        self.aoe_radius = aoe_radius
38        self.colour = colour
39        self._travelled = 0.0
40        self._hit_set: set[int] = set()
41        self.alive = True
42
43    def update(self, dt: float):
44        if not self.alive or dt <= 0:
45            return
46        step = self.velocity * dt
47        self.position = self.position + step
48        self._travelled += step.length()
49        if self._travelled >= self.max_distance:
50            self.alive = False
51
52    def on_draw(self, renderer):
53        if not self.alive:
54            return
55        # Draw the projectile head
56        renderer.circle((0, 0), self.radius + 1.0, fill=(1.0, 1.0, 1.0, 0.45), segments=12)
57        renderer.circle((0, 0), self.radius, fill=self.colour, segments=12)
58        if self.aoe_radius > 0:
59            renderer.circle(
60                (0, 0), self.aoe_radius, stroke=(self.colour[0], self.colour[1], self.colour[2], 0.18), segments=18
61            )
62
63
64def archer_arrow(position: Vec2, direction: Vec2, damage: float) -> Projectile:
65    return Projectile(
66        kind="arrow",
67        position=position,
68        velocity=direction.normalized() * 520.0,
69        damage=damage,
70        max_distance=520.0,
71        radius=3.5,
72        pierce=3,
73        colour=GREEN,
74    )
75
76
77def mage_bolt(position: Vec2, direction: Vec2, damage: float) -> Projectile:
78    return Projectile(
79        kind="bolt",
80        position=position,
81        velocity=direction.normalized() * 360.0,
82        damage=damage,
83        max_distance=360.0,
84        radius=5.0,
85        pierce=1,
86        aoe_radius=42.0,
87        colour=BLUE,
88    )