diff --git a/packages/ludic.character/README.md b/packages/ludic.character/README.md index 53859451..9ead3147 100644 --- a/packages/ludic.character/README.md +++ b/packages/ludic.character/README.md @@ -67,14 +67,14 @@ still stick. | `char_update(ix, iz, run, jump, dyaw, dpitch, dt)` | a frame from explicit input; `char_tick(dt)` reads `CharacterInput` (nothing while held) | | `char_teleport(x, z, yaw)`, `char_stand_on`, `char_push_to`, `char_face`, `char_halt`, `char_go_limp`, `char_resume_swim(dive, depth, breath)`, `char_map_changed()` | placing and stopping the body | | `char_config(walk, run)`, `char_allow_run(on)` | 3.2 and 6.8 m/s by default; a spent body cannot run or jump | -| `char_sit(x, z, yaw, mode)`, `char_sit_here() -> CHAR_SAT / _STOOD / _NOT_RIDING / _NOT_AIRBORNE / _NOT_STEEP / _NOT_WET`, `char_stand()` | sitting (`CHAR_SIT_SEAT`, `CHAR_SIT_GROUND`) | +| `char_sit(x, y, z, yaw, mode, h)` (y the seat's top, h its height above the feet), `char_sit_leave(x, z)` (where standing up goes), `char_sit_here() -> CHAR_SAT / _STOOD / _NOT_RIDING / _NOT_AIRBORNE / _NOT_STEEP / _NOT_WET`, `char_stand()` | sitting (`CHAR_SIT_SEAT`, `CHAR_SIT_GROUND`) | | `char_hold(reason)`, `char_release(reason)` (`CHAR_HOLD_SCREEN`, `CHAR_HOLD_STAGE`) | holds by reason: two holders cannot release each other | | `char_board(x, z, yaw)`, `char_ride_at(x, y, z, yaw, speed)`, `char_step_off(x, z)` | carried by something (`CharacterBody.riding`/`ride`) | | `char_safe_spot(x, z, from_y, max_depth) -> bool`, `body_safe_x()`, `body_safe_z()` | somewhere to put a body down | | `char_knock(vx, vz, up)` | knocked off its feet (a shove): the walker carries it and it lands where the world lets it | | `char_stand_at(x, z, from_y)` | the ground with a collider's top when it is a step, for a put-down | | `char_camera(dt)`, `char_camera_third(dt)`, `char_look(yaw, pitch)`, `char_look_pitch`, `char_orbit(dist)`, `char_lock_orbit`, `char_set_fps`, `char_fps_toggle() -> CHAR_EYES_*` | the camera; read it back with `body_eye_x/y/z/yaw/pitch()` and `body_eyes_now()` | -| `body_x()`, `body_y()`, `body_z()`, `body_yaw()`, `body_speed()`, `body_moving()`, `body_run()`, `body_phase()`, `body_time()`, `body_water()`, `body_airborne()`, `body_vy()`, `body_landing()`, `body_steep()`, `body_step()`, `body_sitting()`, `body_sit_mode()`, `body_sit_t()`, `body_swimming()`, `body_diving()`, `body_depth()`, `body_breath()`, `body_breath_max()`, `body_floating()`, `body_swim_phase()`, `body_can_run()`, `body_held()`, `body_fps()`, `body_cam_yaw()`, `body_cam_pitch()`, `body_cam_dist()` | the queries | +| `body_x()`, `body_y()`, `body_z()`, `body_yaw()`, `body_speed()`, `body_moving()`, `body_run()`, `body_phase()`, `body_time()`, `body_water()`, `body_airborne()`, `body_vy()`, `body_landing()`, `body_steep()`, `body_step()`, `body_sitting()`, `body_sit_mode()`, `body_sit_t()`, `body_sit_y()`, `body_sit_h()`, `body_swimming()`, `body_diving()`, `body_depth()`, `body_breath()`, `body_breath_max()`, `body_floating()`, `body_swim_phase()`, `body_can_run()`, `body_held()`, `body_fps()`, `body_cam_yaw()`, `body_cam_pitch()`, `body_cam_dist()` | the queries | | `char_wrap(a)`, `char_toward(cur, target, rate)`, `char_ease(t)` | the helpers a pose needs | | `char_facts() -> Queue` | `{ what, x, y, z, v }`: `CHAR_LANDED`, `_JUMPED`, `_FOOTFALL`, `_TOO_STEEP` (once per refusal), `_STARTED_SWIMMING`, `_LEFT_WATER`, `_OUT_OF_AIR` (v: seconds), `_CURRENT` (every 12 s) | | `char_reset()`, `char_system() -> System` | `"character"`, `PH_INPUT`, nothing saved: where a body stands is the game's to write | diff --git a/packages/ludic.character/ports.ludic b/packages/ludic.character/ports.ludic index d1a78bab..9cce3eba 100644 --- a/packages/ludic.character/ports.ludic +++ b/packages/ludic.character/ports.ludic @@ -76,8 +76,12 @@ export state CharacterState { chr_sit_mode: int = 0 chr_sit_t: float = 0.0 # 0 standing .. 1 sat, eased over a quarter second chr_sit_x: float = 0.0 # the seat's point, walked to over the blend + chr_sit_y: float = 0.0 # the seat's top chr_sit_z: float = 0.0 + chr_sit_h: float = 0.0 # the seat's top above the feet; 0 on the ground chr_sit_yaw: float = 0.0 + chr_leave_x: float = 0.0 # where standing up leaves the seat for + chr_leave_z: float = 0.0 chr_swim: bool = false chr_dive: bool = false chr_depth: float = 0.0 # metres below the surface diff --git a/packages/ludic.character/queries.ludic b/packages/ludic.character/queries.ludic index a96321cf..da57d61d 100644 --- a/packages/ludic.character/queries.ludic +++ b/packages/ludic.character/queries.ludic @@ -17,6 +17,8 @@ export function body_step(character_st: CharacterState) -> float { return charac export function body_sitting(character_st: CharacterState) -> bool { return character_st.chr_sit } export function body_sit_mode(character_st: CharacterState) -> int { return character_st.chr_sit_mode } export function body_sit_t(character_st: CharacterState) -> float { return character_st.chr_sit_t } +export function body_sit_y(character_st: CharacterState) -> float { return character_st.chr_sit_y } +export function body_sit_h(character_st: CharacterState) -> float { return character_st.chr_sit_h } export function body_swimming(character_st: CharacterState) -> bool { return character_st.chr_swim } export function body_diving(character_st: CharacterState) -> bool { return character_st.chr_dive } export function body_depth(character_st: CharacterState) -> float { return character_st.chr_depth } diff --git a/packages/ludic.character/sit.ludic b/packages/ludic.character/sit.ludic index f87b6cde..5563b835 100644 --- a/packages/ludic.character/sit.ludic +++ b/packages/ludic.character/sit.ludic @@ -1,18 +1,31 @@ # sit.ludic - sitting: on a seat's point, or on the ground where the body stands. The pose either # side is a still one, so the sit's own weight (body_sit_t) eases 0 -> 1 over a quarter second and # the body walks the last step to the seat over the same time rather than jumping there. -export function char_sit(character_st: mut CharacterState, x: float, z: float, yaw: float, mode: int) -> void { + +# y is the seat's top and h how far above the feet it is, so the feet rest y - h (0 on the ground); +# standing up leaves from the seat's own point unless char_sit_leave names another +export function char_sit(character_st: mut CharacterState, x: float, y: float, z: float, yaw: float, mode: int, h: float) -> void { character_st.chr_sit = true character_st.chr_sit_mode = mode character_st.chr_speed = 0.0 character_st.chr_move = 0.0 character_st.chr_run = 0.0 character_st.chr_sit_x = x + character_st.chr_sit_y = y character_st.chr_sit_z = z + character_st.chr_sit_h = Math.max(h, 0.0) character_st.chr_sit_yaw = yaw + character_st.chr_leave_x = x + character_st.chr_leave_z = z character_st.chr_water = CharacterGround.water(character_st.chr_x, character_st.chr_z) } +# where the body stands when it gets up out of this seat: in front of a bench, beside a boat's thwart +export function char_sit_leave(character_st: mut CharacterState, x: float, z: float) -> void { + character_st.chr_leave_x = x + character_st.chr_leave_z = z +} + function chr_sit_tick(character_st: mut CharacterState, dt: float) -> void { var want = 0.0 if character_st.chr_sit { want = 1.0 } @@ -22,7 +35,7 @@ function chr_sit_tick(character_st: mut CharacterState, dt: float) -> void { character_st.chr_x = Math.lerp(character_st.chr_x, character_st.chr_sit_x, k) character_st.chr_z = Math.lerp(character_st.chr_z, character_st.chr_sit_z, k) character_st.chr_yaw = character_st.chr_yaw + char_wrap(character_st.chr_sit_yaw - character_st.chr_yaw) * k - character_st.chr_y = CharacterGround.height(character_st.chr_x, character_st.chr_z) + character_st.chr_y = Math.lerp(character_st.chr_y, character_st.chr_sit_y - character_st.chr_sit_h, k) character_st.chr_water = CharacterGround.water(character_st.chr_x, character_st.chr_z) } @@ -38,17 +51,18 @@ export function char_sit_here(character_st: mut CharacterState) -> int { if character_st.chr_air { return CHAR_NOT_AIRBORNE } if CharacterGround.slope(character_st.chr_x, character_st.chr_z) > 0.35 { return CHAR_NOT_STEEP } if character_st.chr_y < character_st.chr_water + 0.9 { return CHAR_NOT_WET } - char_sit(character_st, character_st.chr_x, character_st.chr_z, character_st.chr_yaw, CHAR_SIT_GROUND) + char_sit(character_st, character_st.chr_x, character_st.chr_y, character_st.chr_z, character_st.chr_yaw, CHAR_SIT_GROUND, 0.0) return CHAR_SAT } # standing up out of a seat put down against a boulder or a bank must not leave the body inside it export function char_stand(character_st: mut CharacterState) -> void { character_st.chr_sit = false - if char_safe_spot(character_st, character_st.chr_x, character_st.chr_z, character_st.chr_y, 0.0) { + let from_y = character_st.chr_y + if char_safe_spot(character_st, character_st.chr_leave_x, character_st.chr_leave_z, from_y, 0.0) { character_st.chr_x = character_st.chr_safe_x character_st.chr_z = character_st.chr_safe_z - character_st.chr_y = CharacterGround.height(character_st.chr_x, character_st.chr_z) + character_st.chr_y = char_stand_at(character_st.chr_x, character_st.chr_z, from_y) } } diff --git a/packages/ludic.character/tests/character_test.ludic b/packages/ludic.character/tests/character_test.ludic index f9362895..c7dc48ae 100644 --- a/packages/ludic.character/tests/character_test.ludic +++ b/packages/ludic.character/tests/character_test.ludic @@ -292,7 +292,7 @@ program CharacterTest { test "a seat against the boulder holds the body still: the walker never pushes it off the seat" (character_st: mut CharacterState, character_test_st: mut CharacterTestState) { fresh(character_st, 0.0, 4.5, 0.0) char_update(character_st, 0.0, 0.0, false, false, 0.0, 0.0, 1.0 / 60.0) - char_sit(character_st, 0.0, 5.7, 0.0, CHAR_SIT_SEAT) + char_sit(character_st, 0.0, body_y(character_st) + 0.45, 5.7, 0.0, CHAR_SIT_SEAT, 0.45) for i in 0 .. 90 { char_update(character_st, 0.0, 0.0, false, false, 0.0, 0.0, 1.0 / 60.0) } var most = 0.0 for i in 0 .. 120 { @@ -349,7 +349,7 @@ program CharacterTest { expect(body_eyes_now(character_st)) expect_near(body_eye_y(character_st), 1.62, 0.05) expect_eq(char_fps_toggle(character_st), CHAR_EYES_OFF) - char_sit(character_st, 0.0, 0.0, 0.0, CHAR_SIT_SEAT) + char_sit(character_st, 0.0, 0.45, 0.0, 0.0, CHAR_SIT_SEAT, 0.45) expect_eq(char_fps_toggle(character_st), CHAR_EYES_SITTING) } } diff --git a/packages/ludic.things/README.md b/packages/ludic.things/README.md index 0c4a4adf..56e723a1 100644 --- a/packages/ludic.things/README.md +++ b/packages/ludic.things/README.md @@ -20,7 +20,8 @@ property Thing { active # false: taken, hidden, used up for now reach # how close it is used from (0: its kind's) timer, used, n # state the kind decides the meaning of - sx, sz, syaw # where using it puts the user, and which way that faces + sx, sz, syaw # a heading or a point the kind decides the meaning of + seat, seats, taken # its seats: the first of the game's seat rows, how many, a bit per one sat in shared # the game copies it to others (a party) } ``` @@ -83,6 +84,7 @@ the actor the moment the Thing exists (a caller often scales or tints it on the | `thing_remove(t)`, `things_drop(gone: fn(Thing) -> bool)`, `things_clear()` | gone for good (told to the port); a new world, quietly | | `things_reserve(n)`, `things_spare_count()` | room in the grid for `n` Things, made once at the start; how many removed records wait to be placed again | | `thing_move(t, x, z, yaw)`, `thing_move_to(t, x, y, z, yaw)` | on the ground there, or at a height | +| `thing_set_seats(t, first, n)`, `thing_seat_take(t, i) -> bool`, `thing_seat_leave(t, i)`, `thing_seat_taken(t, i)`, `thing_seat_free(t) -> int` | its seats: a run of the game's seat rows (`seat`, `seats`, saved) and a bit per seat someone is in (`taken`, never saved; up to `THING_SEATS_MAX`) | | `thing_hide(t)`, `thing_show(t)`, `thing_set_active(t, on)` | `active`, told to the port | | `things_all()`, `things_count()`, `things_ready()`, `thing_by_uid(uid)` | every Thing, in the order placed; whether a world was ever set out | | `thing_find(kind, id)`, `thing_nearest(kind, x, z)`, `things_within(x, z, r)`, `things_each(kind)`, `things_each_all(kind)`, `thing_dist2(t, x, z)` | where they are: only active ones answer except the `_all`; a kind below 0 is any; `within` is nearest first | diff --git a/packages/ludic.things/index.ludic b/packages/ludic.things/index.ludic index 0c676826..5b3b9fae 100644 --- a/packages/ludic.things/index.ludic +++ b/packages/ludic.things/index.ludic @@ -15,3 +15,4 @@ import "chunks.ludic" import "dispatch.ludic" import "system.ludic" import "spare.ludic" +import "seats.ludic" diff --git a/packages/ludic.things/kinds.ludic b/packages/ludic.things/kinds.ludic index 87c13b86..026dac5e 100644 --- a/packages/ludic.things/kinds.ludic +++ b/packages/ludic.things/kinds.ludic @@ -15,9 +15,12 @@ export property Thing { timer: float = 0.0 # a clock the kind decides the meaning of used: int = 0 # the kind's own state (split for today, which sign, ...) n: int = 0 # a counter the kind decides the meaning of - sx: float = 0.0 # where using it puts the user, and which way that faces + sx: float = 0.0 # a heading or a point the kind decides the meaning of sz: float = 0.0 syaw: float = 0.0 + seat: int = -1 # the first of its model's seat rows in the game's table; -1 none + seats: int = 0 # how many seat rows follow it + taken: int = 0 # a bit per seat someone is sitting in; never saved shared: bool = false # the game copies it to others (a party) ent: int = -1 # its handle in the table, -1 once removed tab: Table = null # the table holding it, so a verb on the Thing alone keeps the indexes diff --git a/packages/ludic.things/seats.ludic b/packages/ludic.things/seats.ludic new file mode 100644 index 00000000..3d0ae2b8 --- /dev/null +++ b/packages/ludic.things/seats.ludic @@ -0,0 +1,37 @@ +# seats.ludic - a Thing's seats: a run of the game's seat rows, and a bit per seat +# for whoever sits in it. The rows are the game's; the package keeps only which are taken. + +export const THING_SEATS_MAX: int = 30 + +# the Thing's seats are rows first .. first + n - 1 of the game's table; none are taken +export function thing_set_seats(t: Thing, first: int, n: int) -> void { + t.seat = first + t.seats = Math.clamp(n, 0, THING_SEATS_MAX) + if first < 0 { t.seats = 0 } + t.taken = 0 +} + +export function thing_seat_taken(t: Thing, i: int) -> bool { + if i < 0 or i >= t.seats { return true } + return (t.taken >> i) & 1 == 1 +} + +# false when it is not one of the Thing's seats or someone is in it +export function thing_seat_take(t: Thing, i: int) -> bool { + if thing_seat_taken(t, i) { return false } + t.taken = t.taken | (1 << i) + return true +} + +export function thing_seat_leave(t: Thing, i: int) -> void { + if i < 0 or i >= t.seats or (t.taken >> i) & 1 == 0 { return } + t.taken = t.taken - (1 << i) +} + +# the first seat nobody is in, or -1 +export function thing_seat_free(t: Thing) -> int { + for i in 0 .. t.seats { + if not thing_seat_taken(t, i) { return i } + } + return -1 +} diff --git a/packages/ludic.things/spare.ludic b/packages/ludic.things/spare.ludic index 0383d776..765980aa 100644 --- a/packages/ludic.things/spare.ludic +++ b/packages/ludic.things/spare.ludic @@ -47,6 +47,9 @@ function th_clean(t: Thing) -> Thing { t.sx = 0.0 t.sz = 0.0 t.syaw = 0.0 + t.seat = -1 + t.seats = 0 + t.taken = 0 t.shared = false t.ent = -1 t.tab = null diff --git a/packages/ludic.things/system.ludic b/packages/ludic.things/system.ludic index 40ffe568..c8c88d29 100644 --- a/packages/ludic.things/system.ludic +++ b/packages/ludic.things/system.ludic @@ -29,6 +29,8 @@ function th_to_json(t: Thing) -> Val { sv_put_float(o, "sx", t.sx) sv_put_float(o, "sz", t.sz) sv_put_float(o, "syaw", t.syaw) + sv_put_int(o, "seat", t.seat) + sv_put_int(o, "seats", t.seats) return o } @@ -57,6 +59,8 @@ function th_from_json(things_st: mut ThingsState, o: Val) -> void { t.sx = sv_float(o, "sx", 0.0) t.sz = sv_float(o, "sz", 0.0) t.syaw = sv_float(o, "syaw", 0.0) + t.seat = sv_int(o, "seat", -1) + t.seats = sv_int(o, "seats", 0) let x = sv_float(o, "x", 0.0) let z = sv_float(o, "z", 0.0) let y = sv_float(o, "y", ThingsWorld.ground(x, z)) diff --git a/packages/ludic.things/tests/things_test.ludic b/packages/ludic.things/tests/things_test.ludic index f2f14fb1..554acd49 100644 --- a/packages/ludic.things/tests/things_test.ludic +++ b/packages/ludic.things/tests/things_test.ludic @@ -266,4 +266,30 @@ program ThingsTest { expect(r2 == r0) expect_eq(third[0].kind, TH_LURE) } + + test "a Thing's seats: taken one at a time, left, saved without who sits in them" (things_st: mut ThingsState) { + things_clear(things_st) + let l = put(things_st, TH_LURE, 3.0, 4.0) + thing_set_seats(l, 7, 2) + expect_eq(thing_seat_free(l), 0) + expect(thing_seat_take(l, 0)) + expect(not thing_seat_take(l, 0)) + expect(not thing_seat_take(l, 2)) + expect_eq(thing_seat_free(l), 1) + expect(thing_seat_take(l, 1)) + expect_eq(thing_seat_free(l), -1) + thing_seat_leave(l, 0) + thing_seat_leave(l, 0) + expect_eq(thing_seat_free(l), 0) + let root = save_tree() + save_section(root, "things", 1, things_save(things_st)) + let text = save_encode(root) + things_clear(things_st) + let n = load_section(save_decode(text), "things") + things_load(things_st, n.data, n.version) + let t = thing_find(things_st, TH_LURE, -1) + expect_eq(t.seat, 7) + expect_eq(t.seats, 2) + expect_eq(t.taken, 0) + } }