43 lines
1.4 KiB
Python
43 lines
1.4 KiB
Python
from __future__ import annotations
|
|
|
|
import math
|
|
|
|
# Clockwise from east, matching the y-down world convention used throughout (see camera.py).
|
|
_EIGHT_WAY_DIRECTIONS: tuple[tuple[int, int], ...] = (
|
|
(1, 0),
|
|
(1, 1),
|
|
(0, 1),
|
|
(-1, 1),
|
|
(-1, 0),
|
|
(-1, -1),
|
|
(0, -1),
|
|
(1, -1),
|
|
)
|
|
|
|
|
|
def snap_to_8way(dx: float, dy: float) -> tuple[int, int]:
|
|
"""Snaps a free direction to the nearest of the 8 compass directions.
|
|
|
|
The mouse can point anywhere ("no fixed angles" while aiming), but combat/interaction still
|
|
resolves against the tile grid, so this is where continuous aim meets discrete tile logic.
|
|
"""
|
|
if dx == 0.0 and dy == 0.0:
|
|
return 0, 1
|
|
angle = math.atan2(dy, dx)
|
|
index = round(angle / (math.pi / 4)) % 8
|
|
return _EIGHT_WAY_DIRECTIONS[index]
|
|
|
|
|
|
def apply_cone_deviation(dx: int, dy: int, cone_degrees: float, rng) -> tuple[int, int]:
|
|
"""Randomly deviates a direction within +/- cone_degrees/2, snapped back to the grid.
|
|
|
|
This is where "the exact direction of the shot is randomized" within the aim cone happens -
|
|
the caller (e.g. perform_shoot) supplies the cone's size (from skill + weapon mods).
|
|
"""
|
|
if cone_degrees <= 0:
|
|
return dx, dy
|
|
base_angle = math.atan2(dy, dx)
|
|
deviation = math.radians(rng.uniform(-cone_degrees / 2, cone_degrees / 2))
|
|
angle = base_angle + deviation
|
|
return snap_to_8way(math.cos(angle), math.sin(angle))
|