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