DeltaZN
feat: rename god -> world
c58d3eb
Raw
History Blame Contribute Delete
3.9 kB
from __future__ import annotations
import math
from world_simulator.domain import Npc, Vec3
GRID_BLOCKS = 20
BLOCK_SIZE = 4.0
# Common melee reach for every attacker: NPC vs NPC, NPC vs beast, beast vs NPC.
ATTACK_RADIUS = 0.5 * BLOCK_SIZE
TALK_RADIUS = 3.0 * BLOCK_SIZE
VISIBLE_RADIUS = 5.0 * BLOCK_SIZE
MAX_WALK_DISTANCE = 1.0 * BLOCK_SIZE
def is_alive(npc: Npc) -> bool:
return npc.health > 0
def distance_between(first: Npc, second: Npc) -> float:
return math.hypot(first.position.x - second.position.x, first.position.z - second.position.z)
def vec_distance(first: Vec3, second: Vec3) -> float:
"""Planar distance between two world positions (x/z plane)."""
return math.hypot(first.x - second.x, first.z - second.z)
def move_toward(
origin: Vec3,
destination: Vec3,
step: float,
*,
half_width: float | None = None,
half_depth: float | None = None,
) -> Vec3:
"""Move ``origin`` up to ``step`` world units toward ``destination``."""
delta_x = destination.x - origin.x
delta_z = destination.z - origin.z
distance = math.hypot(delta_x, delta_z)
if distance == 0 or distance <= step:
target_x, target_z = destination.x, destination.z
else:
scale = step / distance
target_x = origin.x + (delta_x * scale)
target_z = origin.z + (delta_z * scale)
return _bounded_vec(target_x, target_z, half_width=half_width, half_depth=half_depth)
def move_away(
origin: Vec3,
threat: Vec3,
step: float,
*,
half_width: float | None = None,
half_depth: float | None = None,
) -> Vec3:
"""Move ``origin`` up to ``step`` world units directly away from ``threat``."""
delta_x = origin.x - threat.x
delta_z = origin.z - threat.z
distance = math.hypot(delta_x, delta_z)
if distance == 0:
delta_x = -1.0 if origin.x > 0 else 1.0
delta_z = 0.0
distance = 1.0
scale = step / distance
return _bounded_vec(
origin.x + (delta_x * scale),
origin.z + (delta_z * scale),
half_width=half_width,
half_depth=half_depth,
)
def _bounded_vec(
x: float,
z: float,
*,
half_width: float | None,
half_depth: float | None,
) -> Vec3:
if half_width is not None:
x = _clamp(x, -half_width, half_width)
if half_depth is not None:
z = _clamp(z, -half_depth, half_depth)
return Vec3(x=round(x, 3), y=0.0, z=round(z, 3))
_HOSTILE_KEYWORDS = frozenset(
{
"attack",
"destroy",
"eliminate",
"fight",
"harm",
"hostile",
"hunt",
"hurt",
"kill",
"murder",
"slay",
"violent",
"violence",
}
)
def has_hostile_intent(npc: Npc) -> bool:
if not is_alive(npc):
return False
return _contains_any(npc.god_directive, _HOSTILE_KEYWORDS) or _contains_any(
npc.intention,
_HOSTILE_KEYWORDS,
)
def walk_away_target(
npc: Npc,
threat: Npc,
*,
half_width: float,
half_depth: float,
) -> Vec3:
delta_x = npc.position.x - threat.position.x
delta_z = npc.position.z - threat.position.z
distance = math.hypot(delta_x, delta_z)
if distance == 0:
delta_x = -1.0 if npc.position.x > 0 else 1.0
delta_z = 0.0
distance = 1.0
scale = MAX_WALK_DISTANCE / distance
return Vec3(
x=round(_clamp(npc.position.x + (delta_x * scale), -half_width, half_width), 3),
y=0.0,
z=round(_clamp(npc.position.z + (delta_z * scale), -half_depth, half_depth), 3),
)
def _contains_any(text: str | None, keywords: frozenset[str]) -> bool:
if not text:
return False
lowered = text.lower()
return any(keyword in lowered for keyword in keywords)
def _clamp(value: float, minimum: float, maximum: float) -> float:
return min(max(value, minimum), maximum)