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        x, y = self.position.x, self.position.y
55        colour = self.item.colour
56        r = self.item.rarity
57
58        if r <= Rarity.COMMON:
59            # Plain square
60            renderer.draw_rect((x - 4, y - 4), (8, 8), colour=colour, filled=True)
61        elif r == Rarity.UNCOMMON:
62            # Bordered square
63            renderer.draw_rect((x - 5, y - 5), (10, 10), colour=(1.0, 1.0, 1.0, 0.3), filled=True)
64            renderer.draw_rect((x - 4, y - 4), (8, 8), colour=colour, filled=True)
65        elif r == Rarity.RARE:
66            # Diamond shape
67            renderer.fill_triangle(x, y - 6, x - 6, y, x + 6, y, colour=colour)
68            renderer.fill_triangle(x, y + 6, x - 6, y, x + 6, y, colour=colour)
69        elif r == Rarity.EPIC:
70            # Star-ish shape (two overlapping triangles)
71            renderer.fill_triangle(x, y - 7, x - 6, y + 4, x + 6, y + 4, colour=colour)
72            renderer.fill_triangle(x, y + 7, x - 6, y - 4, x + 6, y - 4, colour=colour)
73        else:
74            # Legendary: pulsing star with glow
75            pulse = 0.7 + math.sin(self._time * 8.0) * 0.3
76            glow = (colour[0], colour[1], colour[2], 0.25 * pulse)
77            renderer.draw_circle((x, y), 8, colour=glow, filled=True)
78            renderer.fill_triangle(x, y - 7, x - 6, y + 4, x + 6, y + 4, colour=colour)
79            renderer.fill_triangle(x, y + 7, x - 6, y - 4, x + 6, y - 4, colour=colour)