Merge branch 'lang/foundations' into lang/ecs
This commit is contained in:
commit
b9d0cca281
32 changed files with 445 additions and 58 deletions
|
|
@ -46,9 +46,18 @@ export port WildlifeWorld { # every member has a default: a flat dry mead
|
|||
next_id: fn() -> int # an animal's id across machines
|
||||
born: fn(WildAnimal) -> void # draw it
|
||||
placed: fn(WildRange, int) -> void # a range was set out: put its water and forage down
|
||||
way: fn(WildAnimal, float, float) -> bool # the next corner of its way toward (x, z), into way_x / _z
|
||||
goal: fn(WildAnimal, x, z, r, seed) -> bool # a spot about r from (x, z) it can walk to, into way_x / _z
|
||||
}
|
||||
```
|
||||
|
||||
**An animal walks by the world's way where the world has one.** Wandering and going to water or
|
||||
forage face the next corner `way` gives (asked again every half second, or on reaching the corner),
|
||||
and a wander's target is a spot `goal` says it can reach, seeded by the same two draws the old
|
||||
target took - so binding them changes where an animal walks and never the order of the dice. With
|
||||
no answer (the defaults) it walks straight and turns 75 degrees off water and a climb, as before.
|
||||
Maroon Lake binds both to its navmesh, walked by the species' habitat's taste.
|
||||
|
||||
## API
|
||||
|
||||
| | |
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ export property WildAnimal {
|
|||
home_z: float = 0.0
|
||||
tx: float = 0.0
|
||||
tz: float = 0.0
|
||||
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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export function wildlife_tick_ground(wildlife_st: mut WildlifeState, a: WildAnim
|
|||
}
|
||||
return
|
||||
}
|
||||
wl_face(a, a.tx, a.tz, 2.0, dt)
|
||||
wl_head(a, a.tx, a.tz, 2.0, dt)
|
||||
a.speed = s.walk
|
||||
wl_step(wildlife_st, a, dt)
|
||||
if wl_at_target(a, 1.5) or a.timer < 0.0 {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import "ranges.ludic"
|
|||
import "migrate.ludic"
|
||||
import "senses.ludic"
|
||||
import "steer.ludic"
|
||||
import "way.ludic"
|
||||
import "lures.ludic"
|
||||
import "needs.ludic"
|
||||
import "ground.ludic"
|
||||
|
|
|
|||
|
|
@ -46,6 +46,13 @@ export port WildlifeWorld {
|
|||
next_id: fn() -> int = fn wl_next_id
|
||||
born: fn(WildAnimal) -> void = fn wl_nothing # a new animal: draw it
|
||||
placed: fn(WildRange, int) -> void = fn wl_range_nothing # a range was set out
|
||||
# (a, x, z): the next corner of this animal's way toward (x, z) into way_x / _z; false, the
|
||||
# straight line (a navmesh's path by its habitat's taste, in Maroon Lake)
|
||||
way: fn(WildAnimal, float, float) -> bool = fn wl_no_way
|
||||
# (a, x, z, r, seed): a spot about r from (x, z) this animal can walk to, into way_x / _z
|
||||
goal: fn(WildAnimal, float, float, float, int) -> bool = fn wl_no_goal
|
||||
way_x: fn() -> float = fn wl_asked_x
|
||||
way_z: fn() -> float = fn wl_asked_z
|
||||
}
|
||||
function wl_zero0() -> float { return 0.0 }
|
||||
function wl_zero1(p: int) -> float { return 0.0 }
|
||||
|
|
@ -73,6 +80,8 @@ function wl_no_spot(a: WildAnimal, water: bool, s: WildSpot) -> bool { return fa
|
|||
function wl_no_lure(i: int, l: WildLure) -> bool { return false }
|
||||
function wl_nothing(a: WildAnimal) -> void { }
|
||||
function wl_range_nothing(r: WildRange, i: int) -> void { }
|
||||
function wl_no_way(a: WildAnimal, x: float, z: float) -> bool { return false }
|
||||
function wl_no_goal(a: WildAnimal, x: float, z: float, r: float, seed: int) -> bool { return false }
|
||||
function wl_next_id(wildlife_st: mut WildlifeState) -> int {
|
||||
wildlife_st.wl_ids += 1
|
||||
return wildlife_st.wl_ids
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# steer.ludic - turning toward a point at a rate, a step along the facing that turns away from water
|
||||
# and a climb, and a target somewhere about home; a walker leaves a print every 2.8 m
|
||||
# and a climb; a walker leaves a print every 2.8 m (the way there and a target about home: way.ludic)
|
||||
function wl_wrap(a: float) -> float {
|
||||
var r = a
|
||||
while r > 3.1415927 { r = r - WL_TAU }
|
||||
|
|
@ -58,13 +58,6 @@ function wl_push(wildlife_st: mut WildlifeState, a: WildAnimal, s: WildSpecies,
|
|||
return hit
|
||||
}
|
||||
|
||||
function wl_pick_target(wildlife_st: WildlifeState, a: WildAnimal, radius: float) -> void {
|
||||
let ang = wl_rnd(wildlife_st) * WL_TAU
|
||||
let d = wl_rnd(wildlife_st) * radius
|
||||
a.tx = a.home_x + Math.cos(ang) * d
|
||||
a.tz = a.home_z + Math.sin(ang) * d
|
||||
}
|
||||
|
||||
function wl_at_target(a: WildAnimal, r: float) -> bool {
|
||||
let dx = a.tx - a.x
|
||||
let dz = a.tz - a.z
|
||||
|
|
|
|||
106
packages/ludic.wildlife/tests/way_test.ludic
Normal file
106
packages/ludic.wildlife/tests/way_test.ludic
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# way_test.ludic - an animal walks by the world's way where the world has one: round a wall the
|
||||
# straight line runs into (by the corners WildlifeWorld.way gives), and to a wander target the world
|
||||
# says it can reach (goal); with no answer it walks straight, as before
|
||||
import "ludic.wildlife"
|
||||
import "ludic.base"
|
||||
import "fake"
|
||||
program WayTest {
|
||||
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
|
||||
way: fn wt_way
|
||||
way_x: fn wt_x
|
||||
way_z: fn wt_z
|
||||
goal: fn wt_goal
|
||||
}
|
||||
|
||||
state WayTestState {
|
||||
wx: float = 0.0
|
||||
wz: float = 0.0
|
||||
asked: int = 0
|
||||
}
|
||||
# a wall across z = -40 for |x| < 30: the way goes round its east end, then on to the target
|
||||
function wt_way(way_test_st: mut WayTestState, a: WildAnimal, x: float, z: float) -> bool {
|
||||
way_test_st.asked += 1
|
||||
way_test_st.wx = x
|
||||
way_test_st.wz = z
|
||||
if a.z > -38.0 and a.x < 31.0 {
|
||||
way_test_st.wx = 32.0
|
||||
way_test_st.wz = -40.0
|
||||
}
|
||||
return true
|
||||
}
|
||||
# a wander target a known step from home, whatever the seed
|
||||
function wt_goal(way_test_st: mut WayTestState, a: WildAnimal, x: float, z: float, r: float, seed: int) -> bool {
|
||||
way_test_st.wx = x + 7.0
|
||||
way_test_st.wz = z + 3.0
|
||||
return true
|
||||
}
|
||||
function wt_x(way_test_st: WayTestState) -> float { return way_test_st.wx }
|
||||
function wt_z(way_test_st: WayTestState) -> float { return way_test_st.wz }
|
||||
|
||||
test "it walks round the wall by the corners it is given, and asks again as it goes" (wildlife_st: mut WildlifeState, wildlife_tests_fake_st: mut WildlifeTestsFakeState, way_test_st: WayTestState) {
|
||||
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
|
||||
a.state = WILD_WANDER
|
||||
a.tx = 0.0
|
||||
a.tz = -100.0
|
||||
a.timer = 300.0
|
||||
var east = -100.0
|
||||
for k in 0 .. 3000 {
|
||||
wildlife_tick(wildlife_st, 0.05, 0.0)
|
||||
if a.z > -42.0 and a.z < -38.0 { east = Math.max(east, a.x) }
|
||||
}
|
||||
expect(east > 29.0)
|
||||
expect(a.z < -60.0)
|
||||
expect(way_test_st.asked > 10)
|
||||
}
|
||||
|
||||
test "a wander target is where the world says it can walk" (wildlife_st: mut WildlifeState, wildlife_tests_fake_st: mut WildlifeTestsFakeState, way_test_st: WayTestState) {
|
||||
fk_setup(wildlife_st, wildlife_tests_fake_st)
|
||||
let a = wildlife_make(wildlife_st, 0, 5.0, 5.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
|
||||
a.hunger = 0.0
|
||||
a.thirst = 0.0
|
||||
a.state = WILD_IDLE
|
||||
a.timer = -1.0
|
||||
var k = 0
|
||||
while a.state != WILD_WANDER and k < 2000 {
|
||||
wildlife_tick(wildlife_st, 0.05, 0.0)
|
||||
k += 1
|
||||
}
|
||||
expect_eq(a.state, WILD_WANDER)
|
||||
expect_near(a.tx, a.home_x + 7.0, 0.001)
|
||||
expect_near(a.tz, a.home_z + 3.0, 0.001)
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ function wl_seek(wildlife_st: mut WildlifeState, a: WildAnimal, dt: float) -> bo
|
|||
a.timer = 2.0
|
||||
return true
|
||||
}
|
||||
wl_face(a, a.spot_x, a.spot_z, 2.0, dt)
|
||||
wl_head(a, a.spot_x, a.spot_z, 2.0, dt)
|
||||
a.speed = wl_sp(wildlife_st, a.sp).walk
|
||||
a.tx = a.spot_x
|
||||
a.tz = a.spot_z
|
||||
|
|
@ -44,6 +44,7 @@ function wl_drink(wildlife_st: WildlifeState, a: WildAnimal, dt: float, gh: floa
|
|||
let water = a.thirst > a.hunger
|
||||
if not WildlifeWorld.spot(a, water, wildlife_st.wl_spot_ask) { return false }
|
||||
a.spot = wildlife_st.wl_spot_ask.uid
|
||||
a.way_t = 0.0
|
||||
a.spot_x = wildlife_st.wl_spot_ask.x
|
||||
a.spot_z = wildlife_st.wl_spot_ask.z
|
||||
a.spot_water = wildlife_st.wl_spot_ask.water
|
||||
|
|
|
|||
36
packages/ludic.wildlife/way.ludic
Normal file
36
packages/ludic.wildlife/way.ludic
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# way.ludic - where an animal walks: by the world's path to where it is going (WildlifeWorld.way, a
|
||||
# navmesh by its habitat's taste in Maroon Lake), to a target about home it can reach (goal), and
|
||||
# straight, as before, where the world has no answer
|
||||
|
||||
# face the way toward (tx, tz): the next corner of the world's path for it, asked again every
|
||||
# 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 {
|
||||
a.way_t = a.way_t - dt
|
||||
let cx = a.wx - a.x
|
||||
let cz = a.wz - a.z
|
||||
if a.way_t <= 0.0 or cx * cx + cz * cz < 1.0 {
|
||||
a.way_t = WL_WAY_EVERY
|
||||
a.wx = tx
|
||||
a.wz = tz
|
||||
if WildlifeWorld.way(a, tx, tz) {
|
||||
a.wx = WildlifeWorld.way_x()
|
||||
a.wz = WildlifeWorld.way_z()
|
||||
}
|
||||
}
|
||||
wl_face(a, a.wx, a.wz, rate, dt)
|
||||
}
|
||||
|
||||
# somewhere about home: a spot the world says it can walk to when it knows, seeded by the same two
|
||||
# draws the old target took, so the dice are the same whether it knows or not
|
||||
function wl_pick_target(wildlife_st: WildlifeState, a: WildAnimal, radius: float) -> void {
|
||||
let ang = wl_rnd(wildlife_st) * WL_TAU
|
||||
let d = wl_rnd(wildlife_st) * radius
|
||||
a.tx = a.home_x + Math.cos(ang) * d
|
||||
a.tz = a.home_z + Math.sin(ang) * d
|
||||
a.way_t = 0.0
|
||||
if WildlifeWorld.goal(a, a.home_x, a.home_z, radius, int(ang * 100000.0) + int(d * 1000.0)) {
|
||||
a.tx = WildlifeWorld.way_x()
|
||||
a.tz = WildlifeWorld.way_z()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue