scripts/weapons.py¶
Part of Dungeon Explorer.
1"""Weapon behaviours: melee arc and ranged projectile attacks."""
2
3from nodes.combat import Hitbox, calculate_damage
4from nodes.projectile import Projectile
5
6from simvx.core import Vec2
7
8# ── Weapon visual profiles ──────────────────────────────────────────────
9# Each profile: (arc_colour, arc_spread, hitbox_radius, cooldown)
10MELEE_PROFILES: dict[str, tuple[tuple, float, int, float]] = {
11 "sword": ((1.0, 1.0, 1.0, 1.0), 1.05, 16, 0.30),
12 "axe": ((1.0, 0.5, 0.15, 1.0), 1.4, 20, 0.50),
13 "dagger": ((0.4, 1.0, 0.5, 1.0), 0.7, 12, 0.18),
14}
15MELEE_DEFAULT = ((1.0, 0.9, 0.3, 1.0), 0.8, 14, 0.35) # Unarmed / unknown
16
17# Each profile: (proj_colour, proj_style, speed, max_range, cooldown)
18RANGED_PROFILES: dict[str, tuple[tuple, str, float, float, float]] = {
19 "bow": ((1.0, 0.85, 0.2, 1.0), "arrow", 300.0, 320.0, 0.28),
20 "longbow": ((1.0, 0.6, 0.1, 1.0), "arrow", 350.0, 400.0, 0.40),
21 "staff": ((0.5, 0.3, 1.0, 1.0), "bolt", 200.0, 250.0, 0.40),
22 "wand": ((0.3, 0.8, 1.0, 1.0), "bolt", 220.0, 220.0, 0.25),
23}
24
25
26def get_melee_profile(weapon_key: str) -> tuple[tuple, float, int, float]:
27 """Return (arc_colour, arc_spread, hitbox_radius, cooldown) for a melee weapon."""
28 return MELEE_PROFILES.get(weapon_key, MELEE_DEFAULT)
29
30
31def get_ranged_profile(weapon_key: str) -> tuple[tuple, str, float, float, float]:
32 """Return (proj_colour, proj_style, speed, max_range, cooldown) for a ranged weapon."""
33 return RANGED_PROFILES[weapon_key]
34
35
36def is_ranged(weapon_key: str) -> bool:
37 return weapon_key in RANGED_PROFILES
38
39
40def melee_attack(
41 attacker,
42 direction: Vec2,
43 base_damage: int,
44 attacker_str: int = 0,
45 crit_chance: float = 0.0,
46 hitbox_radius: int = 16,
47) -> Hitbox:
48 """Spawn a melee hitbox in front of the attacker."""
49 damage, is_crit = calculate_damage(base_damage, attacker_str, crit_chance)
50 hitbox = Hitbox(damage=damage, direction=direction, name="MeleeHit")
51 hitbox.position = Vec2(
52 attacker.position.x + direction.x * 20,
53 attacker.position.y + direction.y * 20,
54 )
55 hitbox.lifetime = 0.15
56 hitbox.radius = hitbox_radius
57 if attacker.parent:
58 attacker.parent.add_child(hitbox)
59 return hitbox
60
61
62def ranged_attack(
63 attacker,
64 direction: Vec2,
65 base_damage: int,
66 speed: float = 250.0,
67 max_range: float = 300.0,
68 attacker_str: int = 0,
69 crit_chance: float = 0.0,
70 colour: tuple = (1.0, 0.8, 0.2, 1.0),
71 style: str = "arrow",
72) -> Projectile:
73 """Spawn a projectile in the given direction."""
74 damage, is_crit = calculate_damage(base_damage, attacker_str, crit_chance)
75 proj = Projectile(
76 damage=damage,
77 direction=direction,
78 speed=speed,
79 max_range=max_range,
80 colour=colour,
81 style=style,
82 name="Projectile",
83 )
84 proj.position = Vec2(
85 attacker.position.x + direction.x * 12,
86 attacker.position.y + direction.y * 12,
87 )
88 if attacker.parent:
89 attacker.parent.add_child(proj)
90 return proj