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)