nodes/magic.pyΒΆ
Part of Clear Code Zelda.
1"""Heal and flame spells: wrappers that spawn particle effects + damage hits."""
2
3from __future__ import annotations
4
5from simvx.core import Node2D, Vec2
6
7from .particles import ParticleEffect
8
9
10class FlameProjectile(Node2D):
11 """Linear-flying flame; damages the first enemy whose AABB it overlaps.
12
13 Self-destructs on hit or after ``lifetime`` seconds.
14 """
15
16 def __init__(self, position: Vec2, direction: Vec2, frames: list[str], strength: float, **kwargs):
17 super().__init__(position=position, **kwargs)
18 self.direction = direction
19 self.speed = 460.0
20 self.strength = strength
21 self.lifetime = 0.9
22 self._frames = frames
23 self._consumed = False
24
25 def on_ready(self):
26 self.add_child(ParticleEffect(self._frames, Vec2(0.0, 0.0), fps=14.0, size=42))
27 # Looping flame: re-spawn the effect when it finishes
28 self._respawn_timer = 0.18
29
30 def on_update(self, dt: float):
31 if self._consumed:
32 return
33 self.lifetime -= dt
34 if self.lifetime <= 0:
35 self.destroy()
36 return
37 self.position += self.direction * (self.speed * dt)
38 # Periodically spawn a new flame puff for the looping effect
39 self._respawn_timer -= dt
40 if self._respawn_timer <= 0:
41 self.add_child(ParticleEffect(self._frames, Vec2(0.0, 0.0), fps=14.0, size=42))
42 self._respawn_timer = 0.18
43
44 def consume(self):
45 self._consumed = True
46 self.destroy()
47
48
49class MagicPlayer:
50 """Helper bound to the level: keeps spell logic out of the level body."""
51
52 def __init__(self, animation_library):
53 self._anims = animation_library
54
55 def heal(self, player, strength: float, cost: int, parent: Node2D) -> None:
56 if player.energy < cost:
57 return
58 if player.hp >= player.max_hp:
59 return
60 player.energy = max(0.0, player.energy - cost)
61 player.hp = min(player.max_hp, player.hp + strength)
62 parent.add_child(
63 ParticleEffect(self._anims.get("heal"), Vec2(player.position.x, player.position.y - 60), fps=14.0, size=72)
64 )
65 parent.add_child(
66 ParticleEffect(self._anims.get("aura"), Vec2(player.position.x, player.position.y), fps=14.0, size=80)
67 )
68
69 def flame(self, player, strength: float, cost: int, parent: Node2D) -> FlameProjectile | None:
70 if player.energy < cost:
71 return None
72 player.energy = max(0.0, player.energy - cost)
73 face = player.facing_vector()
74 if face.x == 0 and face.y == 0:
75 face = Vec2(0.0, 1.0)
76 spawn = Vec2(player.position.x + face.x * 30, player.position.y + face.y * 30)
77 proj = FlameProjectile(spawn, face, self._anims.get("flame"), strength)
78 parent.add_child(proj)
79 return proj