nodes/grass.pyΒΆ

Part of HeartBeast Action RPG.

 1"""Grass patches: destructible world decoration.
 2
 3Mirrors the upstream HeartBeast Grass scene: a tuft with a hurtbox that the
 4player's sword can cut. Cutting it emits ``destroyed`` (the World turns that
 5into a burst of leaves) and removes the node.
 6"""
 7
 8from __future__ import annotations
 9
10from settings import COLOUR_GRASS_BLADE, COLOUR_SHADOW, GRASS_HURTBOX_RADIUS
11
12from simvx.core import Node2D, Signal, Vec2
13
14
15class Grass(Node2D):
16    """Destructible grass tuft."""
17
18    destroyed = Signal()  # (position: Vec2)
19
20    def __init__(self, position: Vec2 | None = None, **kwargs):
21        super().__init__(position=position if position is not None else Vec2(0.0, 0.0), **kwargs)
22        self._alive = True
23
24    @property
25    def is_alive(self) -> bool:
26        """False once the tuft has been cut."""
27        return self._alive
28
29    def on_draw(self, renderer):
30        if not self._alive:
31            return
32        renderer.circle((0, 1), 4.0, fill=COLOUR_SHADOW)
33        blades = [
34            [(-5, 1), (-3, -8), (-1, 1)],
35            [(-1, 1), (0.5, -11), (2, 1)],
36            [(2, 1), (4.5, -7), (6, 1)],
37        ]
38        for blade in blades:
39            renderer.polygon(blade, fill=COLOUR_GRASS_BLADE)
40
41    def take_damage(self) -> bool:
42        """Cut this tuft down. Returns True if it was still standing."""
43        if not self._alive:
44            return False
45        self._alive = False
46        # ``on_draw`` reads a plain attribute, so the retained 2D cache is told
47        # by hand that this node's output changed before the node goes away.
48        self.queue_redraw()
49        self.destroyed.emit(self.position)
50        self.destroy()
51        return True
52
53    def get_hurtbox(self) -> tuple[Vec2, float]:
54        """``(centre, radius)`` of the tuft's hurtbox."""
55        return (self.position, GRASS_HURTBOX_RADIUS)