nodes/loot_pickup.pyΒΆ

Part of Dungeon Explorer.

 1"""World item pickup: dropped by enemies, collected by player proximity."""
 2
 3import math
 4
 5from scripts.item import Item, Rarity
 6
 7from simvx.core import Node2D, Vec2
 8
 9from .loot_drop import LootDrop
10
11
12class LootPickup(Node2D):
13    """A world item that bobs and can be collected by walking near it."""
14
15    # Procedural world drop: rebuilt with the dungeon from its seed, never
16    # snapshotted (its path would be missing when a save is re-applied).
17    __save_persist__ = False
18
19    def __init__(self, item: Item, **kwargs):
20        super().__init__(name="LootPickup", **kwargs)
21        self.item = item
22        self._time = 0.0
23        self._base_y = 0.0
24        self._collected = False
25
26    def on_ready(self):
27        self._base_y = self.position.y
28
29    def on_update(self, dt: float):
30        self._time += dt
31        self.position = Vec2(self.position.x, self._base_y + math.sin(self._time * 6.28) * 4)
32
33    def try_collect(self, player_pos: Vec2, inventory) -> bool:
34        """Try to collect if player is within 20px. Returns True if collected."""
35        if self._collected:
36            return False
37        dx = player_pos.x - self.position.x
38        dy = player_pos.y - self.position.y
39        if dx * dx + dy * dy > 20 * 20:
40            return False
41        if inventory.is_full:
42            return False
43        inventory.add(self.item)
44        self._collected = True
45        # Spawn notification
46        if self.parent:
47            notif = LootDrop(f"+{self.item.display_name}", colour=self.item.colour)
48            notif.position = Vec2(self.position.x, self.position.y - 10)
49            self.parent.add_child(notif)
50        self.destroy()
51        return True
52
53    def on_draw(self, renderer):
54        colour = self.item.colour
55        r = self.item.rarity
56
57        if r <= Rarity.COMMON:
58            # Plain square
59            renderer.rect((-4, -4), (8, 8), fill=colour)
60        elif r == Rarity.UNCOMMON:
61            # Bordered square
62            renderer.rect((-5, -5), (10, 10), fill=(1.0, 1.0, 1.0, 0.3))
63            renderer.rect((-4, -4), (8, 8), fill=colour)
64        elif r == Rarity.RARE:
65            # Diamond shape
66            renderer.polygon([(0, -6), (-6, 0), (6, 0)], fill=colour)
67            renderer.polygon([(0, 6), (-6, 0), (6, 0)], fill=colour)
68        elif r == Rarity.EPIC:
69            # Star-ish shape (two overlapping triangles)
70            renderer.polygon([(0, -7), (-6, 4), (6, 4)], fill=colour)
71            renderer.polygon([(0, 7), (-6, -4), (6, -4)], fill=colour)
72        else:
73            # Legendary: pulsing star with glow
74            pulse = 0.7 + math.sin(self._time * 8.0) * 0.3
75            glow = (colour[0], colour[1], colour[2], 0.25 * pulse)
76            renderer.circle((0, 0), 8, fill=glow)
77            renderer.polygon([(0, -7), (-6, 4), (6, 4)], fill=colour)
78            renderer.polygon([(0, 7), (-6, -4), (6, -4)], fill=colour)