diff --git a/packages/ludic.wildlife/animal.ludic b/packages/ludic.wildlife/animal.ludic index 56e92221..15ae0444 100644 --- a/packages/ludic.wildlife/animal.ludic +++ b/packages/ludic.wildlife/animal.ludic @@ -44,6 +44,10 @@ export property WildAnimal { wx: float = 0.0 # the corner of its way it is walking to now (WildlifeWorld.way) wz: float = 0.0 way_t: float = 0.0 # seconds until it asks the way again; 0 asks at once + 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: what a crowd is handed + goal_z: float = 0.0 + goal_speed: float = 0.0 thirst: float = 0.0 # 0..100: over 60 it goes looking for water hunger: float = 0.0 scat_t: float = 0.0 # game minutes until it might leave sign diff --git a/packages/ludic.wildlife/ground.ludic b/packages/ludic.wildlife/ground.ludic index 26313853..05b8bac9 100644 --- a/packages/ludic.wildlife/ground.ludic +++ b/packages/ludic.wildlife/ground.ludic @@ -1,6 +1,7 @@ # ground.ludic - a walker's tick: its needs, the alert, then the first of charging, reacting, going # to its spot, walking to something set out, eating, idling and wandering that has it this tick export function wildlife_tick_ground(wildlife_st: mut WildlifeState, a: WildAnimal, dt: float, gh: float) -> void { + a.goal_speed = 0.0 wl_needs(wildlife_st, a, gh) let dh = wl_near(wildlife_st, a.x, a.z) let np = wildlife_st.wl_near_i diff --git a/packages/ludic.wildlife/index.ludic b/packages/ludic.wildlife/index.ludic index 37f48c0a..6ce99a14 100644 --- a/packages/ludic.wildlife/index.ludic +++ b/packages/ludic.wildlife/index.ludic @@ -28,3 +28,4 @@ import "birds.ludic" import "run.ludic" import "spawn.ludic" import "system.ludic" +import "walked.ludic" diff --git a/packages/ludic.wildlife/steer.ludic b/packages/ludic.wildlife/steer.ludic index 0d6e5588..9341c0e4 100644 --- a/packages/ludic.wildlife/steer.ludic +++ b/packages/ludic.wildlife/steer.ludic @@ -11,14 +11,11 @@ function wl_face(a: WildAnimal, tx: float, tz: float, rate: float, dt: float) -> let want = Math.atan2(-(tx - a.x), -(tz - a.z)) let r = rate * dt a.yaw = a.yaw + Math.clamp(wl_wrap(want - a.yaw), -r, r) + a.goal_x = tx + a.goal_z = tz } # true when it moved; a walker is slid round a still thing (a trunk, a tent), a bird is not -# how many steps were refused and turned from since the start: how often the way was not enough -export function wildlife_turned(wildlife_st: WildlifeState) -> int { return wildlife_st.wl_turned } -# of those, how many for water ahead and how many for a climb (the rest: pushed back by a still thing) -export function wildlife_turned_wet(wildlife_st: WildlifeState) -> int { return wildlife_st.wl_turned_wet } -export function wildlife_turned_steep(wildlife_st: WildlifeState) -> int { return wildlife_st.wl_turned_steep } # a climb is judged over half a metre ahead, not one frame's step: a few centimetres of the drawn # ground rise steeply over a pebble a walker steps across, and a frame's step shrinks with the rate @@ -31,6 +28,7 @@ function wl_climb(a: WildAnimal, nx: float, nz: float) -> float { } function wl_step(wildlife_st: mut WildlifeState, a: WildAnimal, dt: float) -> bool { + if a.crowd { return wl_crowd_step(a) } let d = a.speed * dt if d < 0.0001 { return false } var nx = a.x - Math.sin(a.yaw) * d @@ -54,11 +52,7 @@ function wl_step(wildlife_st: mut WildlifeState, a: WildAnimal, dt: float) -> bo a.x = nx a.z = nz a.y = nh - a.track_d = a.track_d + d - if s.tracks and a.track_d > 2.8 { - a.track_d = 0.0 - wl_fact(wildlife_st, WILD_PRINT, a) - } + wl_walked(wildlife_st, a, s, d) return true } diff --git a/packages/ludic.wildlife/tests/crowd_test.ludic b/packages/ludic.wildlife/tests/crowd_test.ludic new file mode 100644 index 00000000..ba9396a4 --- /dev/null +++ b/packages/ludic.wildlife/tests/crowd_test.ludic @@ -0,0 +1,83 @@ +# crowd_test.ludic - a walker a crowd moves (phase 18): it says where it is going and how fast and +# stays where it is, standing still is a speed of 0, and where the crowd puts it is its place, +# its heading and its prints, found there by the table's index +import "ludic.wildlife" +import "ludic.base" +import "fake" +program WildCrowdTest { + numbers float + bind WildlifeWorld { + ground: fn fk_ground + water: fn fk_water + forest: fn fk_forest + sight: fn fk_sight + push: fn fk_push + pushed_x: fn fk_pushed_x + pushed_z: fn fk_pushed_z + players: fn fk_players + player_on: fn fk_player_on + player_x: fn fk_x + player_y: fn fk_y + player_z: fn fk_z + hidden: fn fk_is_hidden + noise: fn fk_noise_of + scent: fn fk_scent_of + stillness: fn fk_still_of + wind: fn fk_wind_of + wind_dir: fn fk_wind_to + winterness: fn fk_winter_of + about: fn fk_about_of + per_range: fn fk_one + lures: fn fk_count + lure: fn fk_lure + present: fn fk_present + born: fn fk_on_born + spot: fn fk_spot + } + + + function walker(wildlife_st: mut WildlifeState, wildlife_tests_fake_st: mut WildlifeTestsFakeState) -> WildAnimal { + fk_setup(wildlife_st, wildlife_tests_fake_st) + let a = wildlife_make(wildlife_st, 0, 0.0, 0.0, false, 0.0, 0, 1.0, 1) + wildlife_tests_fake_st.fk_px[0] = 400.0 + wildlife_tests_fake_st.fk_pz[0] = 0.0 + wildlife_tests_fake_st.fk_hidden = true + wildlife_set_crowd(a, true) + return a + } + + test "a crowd walker says where and how fast, and does not move itself" (wildlife_st: mut WildlifeState, wildlife_tests_fake_st: mut WildlifeTestsFakeState) { + let a = walker(wildlife_st, wildlife_tests_fake_st) + a.state = WILD_WANDER + a.tx = 0.0 + a.tz = -100.0 + a.timer = 300.0 + for k in 0 .. 120 { wildlife_tick(wildlife_st, 1.0 / 60.0, 0.0) } + expect_eq(a.x, 0.0) + expect_eq(a.z, 0.0) + expect_eq(a.goal_x, 0.0) + expect_eq(a.goal_z, -100.0) + expect(a.goal_speed > 0.5) + a.state = WILD_IDLE + a.timer = 300.0 + wildlife_tick(wildlife_st, 1.0 / 60.0, 0.0) + expect_eq(a.goal_speed, 0.0) + } + + test "where the crowd puts it is its place, its heading, its prints, and the index finds it there" (wildlife_st: mut WildlifeState, wildlife_tests_fake_st: mut WildlifeTestsFakeState) { + let a = walker(wildlife_st, wildlife_tests_fake_st) + q_clear(wildlife_facts(wildlife_st)) + for k in 1 .. 41 { wildlife_moved(wildlife_st, a, 0.0, -0.1 * float(k), 0.0, -1.2) } + expect_near(a.z, -4.0, 0.0001) + expect_near(a.y, 10.0, 0.0001) + expect_near(Math.sin(a.yaw), 0.0, 0.0001) + expect(Math.cos(a.yaw) > 0.99) + var prints = 0 + let fs = q_drain(wildlife_facts(wildlife_st)) + for i in 0 .. len(fs) { if fs[i].what == WILD_PRINT { prints += 1 } } + expect_eq(prints, 1) + let out = new []WildAnimal + expect_eq(wildlife_near(wildlife_st, 0.0, -4.0, 1.0, out), 1) + expect_eq(wildlife_near(wildlife_st, 0.0, 0.0, 1.0, out), 0) + } +} diff --git a/packages/ludic.wildlife/walked.ludic b/packages/ludic.wildlife/walked.ludic new file mode 100644 index 00000000..58c8e2b6 --- /dev/null +++ b/packages/ludic.wildlife/walked.ludic @@ -0,0 +1,42 @@ +# walked.ludic - what a step leaves behind (a print every 2.8 m), how often a step was refused, and +# the crowd's side (phase 18): a crowd walker only says where and how fast, and is handed back where +# the crowd put it. Nothing here allocates: it runs for every walker every frame. + +# how many steps were refused and turned from since the start: how often the way was not enough +export function wildlife_turned(wildlife_st: WildlifeState) -> int { return wildlife_st.wl_turned } +# of those, how many for water ahead and how many for a climb (the rest: pushed back by a still thing) +export function wildlife_turned_wet(wildlife_st: WildlifeState) -> int { return wildlife_st.wl_turned_wet } +export function wildlife_turned_steep(wildlife_st: WildlifeState) -> int { return wildlife_st.wl_turned_steep } + +# d metres covered: a print when it has gone far enough since the last +function wl_walked(wildlife_st: mut WildlifeState, a: WildAnimal, s: WildSpecies, d: float) -> void { + a.track_d = a.track_d + d + if s.tracks and a.track_d > 2.8 { + a.track_d = 0.0 + wl_fact(wildlife_st, WILD_PRINT, a) + } +} + +# a crowd walker's step: its want is where it faces or heads (goal_x / _z) at this tick's speed +function wl_crowd_step(a: WildAnimal) -> bool { + a.goal_speed = a.speed + return true +} + +# walked by a crowd, or by its own step again (off, it keeps where the crowd left it) +export function wildlife_set_crowd(a: WildAnimal, on: bool) -> void { a.crowd = on } + +# where the crowd put a walker, and how fast it went: its place, its heading from its velocity, its +# ground from the world's, and its prints as it goes +export function wildlife_moved(wildlife_st: mut WildlifeState, a: WildAnimal, x: float, z: float, vx: float, vz: float) -> void { + let dx = x - a.x + let dz = z - a.z + let d = Math.sqrt(dx * dx + dz * dz) + a.x = x + a.z = z + a.y = WildlifeWorld.ground(x, z) + if vx * vx + vz * vz > 0.01 { a.yaw = Math.atan2(-vx, -vz) } + let r = wl_row(a) + if r >= 0 { tb_set_xz(a.tab, WA_X, WA_Z, r, x, z) } + wl_walked(wildlife_st, a, wl_sp(wildlife_st, a.sp), d) +} diff --git a/packages/ludic.wildlife/way.ludic b/packages/ludic.wildlife/way.ludic index 6cb5f9e9..da4dfe94 100644 --- a/packages/ludic.wildlife/way.ludic +++ b/packages/ludic.wildlife/way.ludic @@ -6,6 +6,11 @@ # WL_WAY_EVERY seconds or on reaching the corner; without a path, the straight line as before const WL_WAY_EVERY: float = 0.5 function wl_head(a: WildAnimal, tx: float, tz: float, rate: float, dt: float) -> void { + if a.crowd { + a.goal_x = tx + a.goal_z = tz + return + } a.way_t = a.way_t - dt let cx = a.wx - a.x let cz = a.wz - a.z