Files
simvx/examples/ports/clear_code_zelda/nodes/magic.py
T

80 lines
2.8 KiB
Python

"""Heal and flame spells: wrappers that spawn particle effects + damage hits."""
from __future__ import annotations
from simvx.core import Node2D, Vec2
from .particles import ParticleEffect
class FlameProjectile(Node2D):
"""Linear-flying flame; damages the first enemy whose AABB it overlaps.
Self-destructs on hit or after ``lifetime`` seconds.
"""
def __init__(self, position: Vec2, direction: Vec2, frames: list[str], strength: float, **kwargs):
super().__init__(position=position, **kwargs)
self.direction = direction
self.speed = 460.0
self.strength = strength
self.lifetime = 0.9
self._frames = frames
self._consumed = False
def on_ready(self):
self.add_child(ParticleEffect(self._frames, Vec2(0.0, 0.0), fps=14.0, size=42))
# Looping flame: re-spawn the effect when it finishes
self._respawn_timer = 0.18
def on_update(self, dt: float):
if self._consumed:
return
self.lifetime -= dt
if self.lifetime <= 0:
self.destroy()
return
self.position += self.direction * (self.speed * dt)
# Periodically spawn a new flame puff for the looping effect
self._respawn_timer -= dt
if self._respawn_timer <= 0:
self.add_child(ParticleEffect(self._frames, Vec2(0.0, 0.0), fps=14.0, size=42))
self._respawn_timer = 0.18
def consume(self):
self._consumed = True
self.destroy()
class MagicPlayer:
"""Helper bound to the level: keeps spell logic out of the level body."""
def __init__(self, animation_library):
self._anims = animation_library
def heal(self, player, strength: float, cost: int, parent: Node2D) -> None:
if player.energy < cost:
return
if player.hp >= player.max_hp:
return
player.energy = max(0.0, player.energy - cost)
player.hp = min(player.max_hp, player.hp + strength)
parent.add_child(
ParticleEffect(self._anims.get("heal"), Vec2(player.position.x, player.position.y - 60), fps=14.0, size=72)
)
parent.add_child(
ParticleEffect(self._anims.get("aura"), Vec2(player.position.x, player.position.y), fps=14.0, size=80)
)
def flame(self, player, strength: float, cost: int, parent: Node2D) -> FlameProjectile | None:
if player.energy < cost:
return None
player.energy = max(0.0, player.energy - cost)
face = player.facing_vector()
if face.x == 0 and face.y == 0:
face = Vec2(0.0, 1.0)
spawn = Vec2(player.position.x + face.x * 30, player.position.y + face.y * 30)
proj = FlameProjectile(spawn, face, self._anims.get("flame"), strength)
parent.add_child(proj)
return proj