ludic/packages/ludic.npc/port.ludic
Orkuncakilkaya 8b85282502 ludic.zones: a disc per player from their fog, merged into clusters; the simulating packages ask whether a place is simulated
zones_set / zones_clear / zones_merge, zones_contains / zones_nearest and the clusters' bounding circles,
over fixed arrays of ZONES_MAX (16). WildlifeWorld.active freezes an animal outside (no state change, no
dice), NpcWorld.active a walker (and no birth outside), VehicleWorld.active a moored boat, and
ThingsWorld.restocks keeps a morning's restock inside; every default is "everywhere", so an unbound game
is unchanged. Tests: the merge (near players one cluster, far two, a player leaving splits a chain),
contains, nearest, the radius; wildlife, npc, vehicles and things pass as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 15:53:23 +03:00

85 lines
4.4 KiB
Text

# port.ludic - what the people ask the place. Unbound: a flat dry field at noon on day one, one
# player at the origin, nobody wanted, every name free, and nothing drawn
export port NpcWorld {
ground: fn(float, float) -> float = fn nw_zero2
water: fn(float, float) -> float = fn nw_dry # the water's surface there
slope: fn(float, float) -> float = fn nw_zero2
allowed: fn(float, float) -> bool = fn nw_yes2 # a person may stop here (not the player's camp)
active: fn(float, float) -> bool = fn nw_yes2 # simulated here: outside, a walker is frozen and none is born
# (x, z, r, feet, head): true when a still thing takes a body of radius r there; pushed_x / _z say where
push: fn(float, float, float, float, float) -> bool = fn nw_no_push
pushed_x: fn() -> float = fn nw_asked_x
pushed_z: fn() -> float = fn nw_asked_z
clear: fn(float, float, float, float, float, float) -> bool = fn nw_clear # nothing between two points
hour: fn() -> float = fn nw_noon
day: fn() -> int = fn nw_day_one
players: fn() -> int = fn nw_one # player slots
player_on: fn(int) -> bool = fn nw_first
player_x: fn(int) -> float = fn nw_zero1
player_z: fn(int) -> float = fn nw_zero1
want: fn() -> int = fn nw_none # walkers the place wants at this hour
extra: fn() -> int = fn nw_no_kind # a kind to bring out whatever the crowd (-1)
origin_x: fn() -> float = fn nw_zero0 # where anyone is put when nowhere else will do
origin_z: fn() -> float = fn nw_zero0
next_id: fn() -> int = fn nw_next_id
body_of: fn(int) -> int = fn nw_body_of # a look seed's body
taken: fn(string) -> bool = fn nw_free # a name the place already uses
born: fn(NpcPerson) -> void = fn nw_nothing # a new one (or a slot reused): draw it
gone: fn(NpcPerson) -> void = fn nw_nothing # left: stop drawing it
# (p, x, z): the next corner of this person's way toward (x, z) into way_x / _z; false, the
# straight line with its steps round (a navmesh's path by a person's taste, in Maroon Lake)
way: fn(NpcPerson, float, float) -> bool = fn nw_no_way
way_x: fn() -> float = fn nw_asked_x
way_z: fn() -> float = fn nw_asked_z
}
# what the story says through them: the words of a line whose `fill` is set ("" when it has nothing
# to say now), and a greeting (a vendor's, when talked to)
export port NpcTalk {
say: fn(NpcPerson, int) -> string = fn nw_silent
greeting: fn(NpcPerson) -> string = fn nw_hello
}
function nw_zero0() -> float { return 0.0 }
function nw_zero1(p: int) -> float { return 0.0 }
function nw_zero2(x: float, z: float) -> float { return 0.0 }
function nw_dry(x: float, z: float) -> float { return -1000.0 }
function nw_yes2(x: float, z: float) -> bool { return true }
function nw_no_push(x: float, z: float, r: float, y0: float, y1: float) -> bool { return false }
function nw_asked_x(npc_st: NpcState) -> float { return npc_st.np_ask_x }
function nw_asked_z(npc_st: NpcState) -> float { return npc_st.np_ask_z }
function nw_clear(ax: float, ay: float, az: float, bx: float, by: float, bz: float) -> bool { return true }
function nw_noon() -> float { return 12.0 }
function nw_day_one() -> int { return 1 }
function nw_one() -> int { return 1 }
function nw_first(p: int) -> bool { return p == 0 }
function nw_none() -> int { return 0 }
function nw_no_kind() -> int { return -1 }
function nw_body_of(look: int) -> int { return look % 2 }
function nw_free(n: string) -> bool { return false }
function nw_nothing(p: NpcPerson) -> void { }
function nw_silent(p: NpcPerson, line: int) -> string { return "" }
function nw_hello(p: NpcPerson) -> string { return "" }
function nw_next_id(npc_st: mut NpcState) -> int {
npc_st.nw_ids += 1
return npc_st.nw_ids
}
# the nearest player on and their distance (npc_near_i: -1 when nobody is on)
export function npc_near(npc_st: mut NpcState, x: float, z: float) -> float {
var bd = 1000000.0
npc_st.np_near_i = -1
for i in 0 .. NpcWorld.players() {
if not NpcWorld.player_on(i) { continue }
let d = npc_d(x, z, NpcWorld.player_x(i), NpcWorld.player_z(i))
if d < bd {
bd = d
npc_st.np_near_i = i
}
}
return bd
}
export function npc_near_i(npc_st: NpcState) -> int { return npc_st.np_near_i }
function nw_no_way(p: NpcPerson, x: float, z: float) -> bool { return false }