ludic.npc (18.2): a person a crowd moves
npc_set_crowd hands a person to a crowd. Its walk no longer steps or asks the way: its want is goal_x / goal_z / goal_speed (0 when it is not walking). npc_moved hands back the crowd's place: position, ground, heading from velocity, gait. Arrival, the act and the give-up after twenty seconds without progress are unchanged. Nothing allocates. npc 16/16. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
81b6782c9d
commit
074f10f302
5 changed files with 48 additions and 1 deletions
|
|
@ -52,6 +52,10 @@ export property NpcPerson {
|
||||||
wtx: float = 0.0 # the target that corner was asked for: a new target asks again
|
wtx: float = 0.0 # the target that corner was asked for: a new target asks again
|
||||||
wtz: float = 0.0
|
wtz: float = 0.0
|
||||||
way_t: float = 0.0 # seconds until it asks the way again
|
way_t: float = 0.0 # seconds until it asks the way again
|
||||||
|
crowd: bool = false # walked by the world's crowd (phase 18): it decides, the crowd moves it
|
||||||
|
goal_x: float = 0.0 # where it walks this tick and how fast (0 standing): what a crowd is handed
|
||||||
|
goal_z: float = 0.0
|
||||||
|
goal_speed: float = 0.0
|
||||||
detours: int = 0
|
detours: int = 0
|
||||||
fails: int = 0
|
fails: int = 0
|
||||||
stall: float = 0.0 # seconds walking without moving
|
stall: float = 0.0 # seconds walking without moving
|
||||||
|
|
|
||||||
|
|
@ -81,4 +81,24 @@ program NpcWayTest {
|
||||||
let fs = q_drain(npc_facts(npc_st))
|
let fs = q_drain(npc_facts(npc_st))
|
||||||
for i in 0 .. len(fs) { expect(fs[i].what != NPC_F_STUCK) }
|
for i in 0 .. len(fs) { expect(fs[i].what != NPC_F_STUCK) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "a crowd walker says where and how fast, is moved by hand to its target, and begins there" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState, way_test_st: WayTestState) {
|
||||||
|
fk_setup(npc_st, npc_tests_fake_st)
|
||||||
|
npc_tests_fake_st.fk_want = 1
|
||||||
|
let p = npc_spawn(npc_st, NPCK_HIKER)
|
||||||
|
npc_set_at(p, 0.0, 0.0)
|
||||||
|
npc_set_crowd(p, true)
|
||||||
|
npc_go(npc_st, p, 38.0, -25.0, NPCA_SIT, 30.0)
|
||||||
|
for k in 0 .. 60 { npc_tick(npc_st, 0.05) }
|
||||||
|
expect_eq(p.x, 0.0)
|
||||||
|
expect_eq(p.z, 0.0)
|
||||||
|
expect_eq(p.goal_x, 38.0)
|
||||||
|
expect_eq(p.goal_z, -25.0)
|
||||||
|
expect(p.goal_speed > 0.5)
|
||||||
|
expect_eq(way_test_st.asked, 0)
|
||||||
|
for k in 1 .. 101 { npc_moved(p, 0.38 * float(k), -0.25 * float(k), 1.0, -0.66) }
|
||||||
|
npc_tick(npc_st, 0.05)
|
||||||
|
expect_eq(p.act, NPCA_SIT)
|
||||||
|
expect(p.phase > 100.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ export function npc_tick(npc_st: mut NpcState, dt: float) -> void {
|
||||||
for i in 0 .. len(npc_st.np_walkers) {
|
for i in 0 .. len(npc_st.np_walkers) {
|
||||||
let p = npc_st.np_walkers[i]
|
let p = npc_st.np_walkers[i]
|
||||||
if not p.active or np_leaves(npc_st, p) { continue }
|
if not p.active or np_leaves(npc_st, p) { continue }
|
||||||
|
p.goal_speed = 0.0
|
||||||
if p.act == NPCA_WALK { npc_walk(npc_st, p, dt) } else { npc_act(npc_st, p, dt) }
|
if p.act == NPCA_WALK { npc_walk(npc_st, p, dt) } else { npc_act(npc_st, p, dt) }
|
||||||
npc_notice(npc_st, p)
|
npc_notice(npc_st, p)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export function npc_walk(npc_st: mut NpcState, p: NpcPerson, dt: float) -> void
|
||||||
np_arrive(npc_st, p)
|
np_arrive(npc_st, p)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if not p.via_on {
|
if not p.via_on and not p.crowd {
|
||||||
np_way(p, dt)
|
np_way(p, dt)
|
||||||
gx = p.wx
|
gx = p.wx
|
||||||
gz = p.wz
|
gz = p.wz
|
||||||
|
|
@ -33,6 +33,10 @@ export function npc_walk(npc_st: mut NpcState, p: NpcPerson, dt: float) -> void
|
||||||
var sp = p.speed
|
var sp = p.speed
|
||||||
if sp < 0.1 { sp = 1.2 }
|
if sp < 0.1 { sp = 1.2 }
|
||||||
if p.stalk { sp = Math.min(sp, 0.7) }
|
if p.stalk { sp = Math.min(sp, 0.7) }
|
||||||
|
if p.crowd {
|
||||||
|
np_crowd_want(p, sp)
|
||||||
|
return
|
||||||
|
}
|
||||||
let want = Math.atan2(-dx, -dz)
|
let want = Math.atan2(-dx, -dz)
|
||||||
let r = 3.0 * dt
|
let r = 3.0 * dt
|
||||||
p.yaw = p.yaw + Math.clamp(npc_wrap(want - p.yaw), -r, r)
|
p.yaw = p.yaw + Math.clamp(npc_wrap(want - p.yaw), -r, r)
|
||||||
|
|
|
||||||
|
|
@ -56,3 +56,21 @@ function np_stuck(npc_st: mut NpcState, p: NpcPerson) -> void {
|
||||||
npc_plan(npc_st, p)
|
npc_plan(npc_st, p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# the crowd's side (phase 18): a crowd walker says where it is going and how fast, and is handed
|
||||||
|
# back where the crowd put it; nothing allocates, since this runs for every walker every frame
|
||||||
|
function np_crowd_want(p: NpcPerson, speed: float) -> void {
|
||||||
|
p.goal_x = p.tx
|
||||||
|
p.goal_z = p.tz
|
||||||
|
p.goal_speed = speed
|
||||||
|
}
|
||||||
|
export function npc_set_crowd(p: NpcPerson, on: bool) -> void { p.crowd = on }
|
||||||
|
export function npc_moved(p: NpcPerson, x: float, z: float, vx: float, vz: float) -> void {
|
||||||
|
let dx = x - p.x
|
||||||
|
let dz = z - p.z
|
||||||
|
p.x = x
|
||||||
|
p.z = z
|
||||||
|
p.y = NpcWorld.ground(x, z)
|
||||||
|
if vx * vx + vz * vz > 0.01 { p.yaw = Math.atan2(-vx, -vz) }
|
||||||
|
p.phase = p.phase + Math.sqrt(dx * dx + dz * dz) * 2.6
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue