feat(packages): ludic.session - a party's roster (slot 0 a port, the others as reported), the roles and who owns the world, the shared night, votes and a world-changing act asking the party; joins, leaves, roles, votes and the night as facts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-25 07:45:26 +03:00
parent 12172d7b75
commit 79ed7a335b
12 changed files with 707 additions and 0 deletions

View file

@ -0,0 +1,60 @@
# ludic.session
Who is in a party and who runs the world, whatever carries its messages: the roster, the roles,
the shared night, votes, and "a world-changing act asks the party". It sends nothing - what the
other machines have to hear comes out as facts, and the game puts them on its wire. Uses
[`ludic.base`](../ludic.base/README.md) and nothing else.
```ludic
import "ludic.session"
```
## The roster
Slot 0 is this machine's player, read live through the `SessionLocal` port; slots 1 to
`PLY_MAX - 1` are remote players, holding what their own machines reported (where they stand,
their heading and speed, how much their rest speeds the clock, whether they have turned in, their
name). World code asks the roster, never "the player": `for p in 0 .. PLY_MAX { if ply_on(p) ...
ply_x(p) ... }`, or `ply_near(x, z)` and `ply_near_i()`.
```ludic
port SessionLocal {
x, y, z: fn() -> float # required
yaw, speed: fn() -> float (0), rest: fn() -> float (1), name: fn() -> string ("")
}
```
## Roles
`SES_SOLO` owns the world and has no company; `SES_HOST` owns it and has company; `SES_GUEST`
owns only its own player. `ses_owns_world()`, `ses_live()`, `ses_night_passes()` (alone, lying
down ends the night). The transport says what this machine became (`ses_set_role`).
## API
| | |
| --- | --- |
| `ply_init()`, `ply_join(pid) -> slot`, `ply_leave(p)`, `ply_leave_all()` | the roster (-1: full, or pid 0) |
| `ply_report(p, x, y, z, yaw, speed)`, `ply_report_rest(p, rest, sleeping)`, `ply_report_name(p, name)` | what a remote machine said |
| `ply_on`, `ply_is_local`, `ply_slot_of(pid)`, `ply_count`, `ply_pid`, `ply_name`, `ply_x/y/z/yaw/speed/rest(p)`, `ply_sleeping(p)` | who and where |
| `ply_near(x, z)`, `ply_near_d2`, `ply_near_i()`, `ply_dist(p, x, z)` | the nearest player |
| `ses_role()`, `ses_set_role(r)`, `ses_owns_world()`, `ses_live()`, `ses_hosting()`, `ses_guest()`, `ses_night_passes()` | roles |
| `ses_set_party(max, open)`, `ses_set_open(b)`, `ses_max()`, `ses_open()`, `ses_room_for_one()` | the party a host allows |
| `ses_sleep(on) -> bool`, `ses_sleeping()`, `ses_sleep_ready(p, on)`, `ses_night_check()`, `ses_night_passed()`, `ses_sleep_waiting()` | the shared night: false alone; the host counts |
| `ses_may(kind, param, text) -> SES_MAY_GO / _ASKED / _BUSY / _HOSTS` | a world-changing act |
| `ses_vote_open`, `ses_vote_cast`, `ses_vote_heard(id, yes)`, `ses_vote_told(id, kind, text)`, `ses_vote_ended(id, passed)`, `ses_vote_tick(dt)` | a majority of the players, the host included, within thirty seconds |
| `ses_vote_live/id/kind/text/left/yes/no/mine/passed()` | the vote as it stands |
| `ses_reset()` | everyone else gone, alone, nothing asked, nobody asleep |
| `ses_facts() -> Queue<SessionFact>` | `SES_JOINED` / `SES_LEFT` (slot, pid), `SES_ROLE` (role), `SES_VOTE_OPENED`, `SES_VOTE_CAST` (a guest's answer to send), `SES_VOTE_RESOLVED` (yes: passed), `SES_NIGHT` (the host: everyone is in), `SES_SLEEP` (this player turned in or got up) |
It is not a `System`: a party exists before any world does (a join from the title), so the game
calls `ses_vote_tick` and drains `ses_facts()` in its own frame.
## Tests
```bash
ludic test packages/ludic.session
```
A fake local player: the roster filling and emptying, the nearest player, roles, the party's size,
the night, votes passing, failing and timing out, and a guest's side of each.

View file

@ -0,0 +1,13 @@
# ludic.session - a party's roster and rules, whatever carries its messages: slot 0 is this
# machine's player (a port), the others are what their own machines reported
module ludic_session uses ludic_base
numbers float
import "ludic.base"
import "state.ludic"
import "roster.ludic"
import "where.ludic"
import "near.ludic"
import "roles.ludic"
import "night.ludic"
import "votes.ludic"
import "vote_state.ludic"

View file

@ -0,0 +1,30 @@
# near.ludic - the world asks who is nearest, never "the player": an animal notices whoever is
# closest, a camper keeps clear of everyone
export function ply_near_d2(x: float, z: float) -> float {
session__near_i = 0
var best = 0.0
var first = true
for p in 0 .. PLY_MAX {
if not ply_on(p) { continue }
let dx = x - ply_x(p)
let dz = z - ply_z(p)
let d2 = dx * dx + dz * dz
if first or d2 < best {
best = d2
session__near_i = p
first = false
}
}
return best
}
# the distance to the nearest player, in metres; ply_near_i() says which
export function ply_near(x: float, z: float) -> float { return Math.sqrt(ply_near_d2(x, z)) }
export function ply_near_i() -> int { return session__near_i }
export function ply_dist(p: int, x: float, z: float) -> float {
let dx = x - ply_x(p)
let dz = z - ply_z(p)
return Math.sqrt(dx * dx + dz * dz)
}

View file

@ -0,0 +1,43 @@
# night.ludic - with company the night passes when everyone has turned in. Lying down marks this
# player ready and getting up takes it back; the host counts, and when the last one is in it hears
# SES_NIGHT and passes the night for everyone.
var session__me_sleeping: bool = false
export function ses_sleeping() -> bool { return session__me_sleeping }
# this player turns in (or gets up): false alone, where a bed simply sleeps
export function ses_sleep(on: bool) -> bool {
if not ses_live() { return false }
if on == session__me_sleeping { return on }
session__me_sleeping = on
let f = session__fact(SES_SLEEP, 0, 0)
f.yes = on
if on { ses_night_check() }
return true
}
# the host: a guest said it turned in, or got up
export function ses_sleep_ready(p: int, on: bool) -> void {
if p <= 0 or not ply_on(p) { return }
session__slot[p].ready = on
ses_night_check()
}
# the host: is everyone in? Then the night is due, and everyone's readiness starts again
export function ses_night_check() -> void {
if session__role != SES_HOST or not session__me_sleeping { return }
for p in 1 .. PLY_MAX { if ply_on(p) and not session__slot[p].ready { return } }
for p in 1 .. PLY_MAX { session__slot[p].ready = false }
session__fact(SES_NIGHT, -1, 0)
}
# the night went by on this machine: awake again
export function ses_night_passed() -> void { session__me_sleeping = false }
# how many are still up, this player included
export function ses_sleep_waiting() -> int {
var n = 0
for p in 1 .. PLY_MAX { if ply_on(p) and not session__slot[p].sleeping { n += 1 } }
if not session__me_sleeping { n += 1 }
return n
}

View file

@ -0,0 +1,5 @@
# ludic.session - who is in a party and who runs the world: the roster, the roles, the shared
# night, votes and "a world-changing act asks the party". Transport-free. See README.md.
package "ludic.session"
version "0.1.0"
kind source

View file

@ -0,0 +1,46 @@
# roles.ludic - who owns the world. Alone, this machine does; a host does and has company; a
# guest owns only its own player. The transport says what this machine became.
export function ses_role() -> int { return session__role }
export function ses_set_role(r: int) -> void {
if r == session__role { return }
session__role = r
session__fact(SES_ROLE, -1, 0)
}
# this machine runs the world: the clock, the weather, the animals, the people
export function ses_owns_world() -> bool { return session__role != SES_GUEST }
# somebody else is in this world, so nothing this player opens may stop it
export function ses_live() -> bool { return session__role != SES_SOLO }
export function ses_hosting() -> bool { return session__role == SES_HOST }
export function ses_guest() -> bool { return session__role == SES_GUEST }
# whether this player lying down ends the night; alone, it always does
export function ses_night_passes() -> bool { return ses_owns_world() and not ses_live() }
# the party a host allows (2..PLY_MAX) and whether it takes new players
export function ses_set_party(max: int, open: bool) -> void {
session__max = max
if session__max < 2 { session__max = 2 }
if session__max > PLY_MAX { session__max = PLY_MAX }
session__open = open
}
export function ses_set_open(open: bool) -> void { session__open = open }
export function ses_max() -> int { return session__max }
export function ses_open() -> bool { return session__open }
# may one more join: open, and not full
export function ses_room_for_one() -> bool { return session__open and ply_count() < session__max }
# the session is over: everyone else gone, alone again, nothing asked and nobody asleep
export function ses_reset() -> void {
ply_leave_all()
ses_set_role(SES_SOLO)
session__me_sleeping = false
session__vote_t = 0.0
session__vote_mine = -1
}

View file

@ -0,0 +1,76 @@
# roster.ludic - who is here. Slot 0 is this machine's player and reads the SessionLocal port
# live; slots 1.. are remote players, filled by what their machines report.
export function ply_init() -> void { session__init() }
export function ply_on(p: int) -> bool {
if p == 0 { return true }
if session__slot == null or p < 0 or p >= PLY_MAX { return false }
return session__slot[p].on
}
export function ply_is_local(p: int) -> bool { return p == 0 }
# the roster slot holding network player `pid`, or -1
export function ply_slot_of(pid: int) -> int {
if session__slot == null or pid <= 0 { return -1 }
for p in 1 .. PLY_MAX { if session__slot[p].on and session__slot[p].pid == pid { return p } }
return -1
}
export function ply_count() -> int {
var n = 1
for p in 1 .. PLY_MAX { if ply_on(p) { n += 1 } }
return n
}
# a remote player comes in, standing where this one stands until they say: the first free
# slot, or -1 when the party is full
export function ply_join(pid: int) -> int {
session__init()
if pid <= 0 { return -1 } # 0 is this machine's player
for p in 1 .. PLY_MAX {
if session__slot[p].on { continue }
let r = new PartyMember
r.on = true
r.pid = pid
r.x = SessionLocal.x()
r.y = SessionLocal.y()
r.z = SessionLocal.z()
session__slot[p] = r
session__fact(SES_JOINED, p, pid)
return p
}
return -1
}
export function ply_leave(p: int) -> void {
if p <= 0 or not ply_on(p) { return }
let r = session__slot[p]
r.on = false
r.ready = false
session__fact(SES_LEFT, p, r.pid)
}
export function ply_leave_all() -> void { for p in 1 .. PLY_MAX { ply_leave(p) } }
# what a remote player's machine reported this tick
export function ply_report(p: int, x: float, y: float, z: float, yaw: float, speed: float) -> void {
if p <= 0 or not ply_on(p) { return }
let r = session__slot[p]
r.x = x
r.y = y
r.z = z
r.yaw = yaw
r.speed = speed
}
export function ply_report_rest(p: int, rest: float, sleeping: bool) -> void {
if p <= 0 or not ply_on(p) { return }
session__slot[p].rest = rest
session__slot[p].sleeping = sleeping
}
export function ply_report_name(p: int, name: string) -> void {
if p <= 0 or not ply_on(p) { return }
session__slot[p].name = name
}

View file

@ -0,0 +1,89 @@
# state.ludic - a roster slot, the local player's port, the roles, and the facts the game drains
export const PLY_MAX: int = 12 # a party: this player and eleven more
export const SES_SOLO: int = 0
export const SES_HOST: int = 1
export const SES_GUEST: int = 2
# what happened: a player in or out (slot, pid), the role changed (role), a vote opened, this
# guest's own vote to send, a vote decided (id, kind, param, passed, text), everyone in for the
# night (the host), this player turned in or got up (yes)
export const SES_JOINED: int = 0
export const SES_LEFT: int = 1
export const SES_ROLE: int = 2
export const SES_VOTE_OPENED: int = 3
export const SES_VOTE_CAST: int = 4
export const SES_VOTE_RESOLVED: int = 5
export const SES_NIGHT: int = 6
export const SES_SLEEP: int = 7
export property SessionFact {
what: int = 0,
slot: int = -1,
pid: int = 0,
role: int = 0,
id: int = 0,
kind: int = 0,
param: int = 0,
yes: bool = false,
text: string = ""
}
# a remote player, as its own machine reported it
property PartyMember {
on: bool = false,
pid: int = 0, # the session's id for them (0 is never a remote player)
name: string = "",
x: float = 0.0,
y: float = 0.0,
z: float = 0.0,
yaw: float = 0.0,
speed: float = 0.0,
rest: float = 1.0, # how much faster their rest runs the clock (1 on their feet)
sleeping: bool = false, # turned in, as their body says
ready: bool = false # turned in, as they told the host (the host's count for the night)
}
# this machine's player, read live
export port SessionLocal {
x: fn() -> float
y: fn() -> float
z: fn() -> float
yaw: fn() -> float = fn session__zero
speed: fn() -> float = fn session__zero
rest: fn() -> float = fn session__one
name: fn() -> string = fn session__nameless
}
function session__zero() -> float { return 0.0 }
function session__one() -> float { return 1.0 }
function session__nameless() -> string { return "" }
var session__slot: []PartyMember = null
var session__role: int = 0
var session__max: int = 4
var session__open: bool = true
var session__near_i: int = 0
var session__facts: Queue<SessionFact> = null
export function ses_facts() -> Queue<SessionFact> {
if session__facts == null { session__facts = queue_new("session.facts") }
return session__facts
}
function session__fact(what: int, slot: int, pid: int) -> SessionFact {
let f = new SessionFact
f.what = what
f.slot = slot
f.pid = pid
f.role = session__role
q_push(ses_facts(), f)
return f
}
function session__init() -> void {
if session__slot != null { return }
session__slot = new []PartyMember
for i in 0 .. PLY_MAX { push(session__slot, new PartyMember) }
session__slot[0].on = true
}

View file

@ -0,0 +1,177 @@
# session_test.ludic - the roster, the roles, the nearest player, the shared night and votes,
# with a fake local player
import "ludic.session"
import "ludic.base"
program SessionTest {
numbers float
var me_x: float = 0.0
var me_z: float = 0.0
function fx() -> float { return me_x }
function fy() -> float { return 0.0 }
function fz() -> float { return me_z }
function fname() -> string { return "me" }
bind SessionLocal { x: fn fx, y: fn fy, z: fn fz, name: fn fname }
function whats() -> []int {
let fs = q_drain(ses_facts())
let out = new []int
for i in 0 .. len(fs) { push(out, fs[i].what) }
return out
}
function last_of(what: int) -> SessionFact {
let fs = q_drain(ses_facts())
var f: SessionFact = null
for i in 0 .. len(fs) { if fs[i].what == what { f = fs[i] } }
return f
}
# a host with n guests, pids 2.., facts drained
function party(n: int) -> void {
ply_init()
ses_set_role(SES_HOST)
for i in 0 .. n { ply_join(2 + i) }
q_clear(ses_facts())
}
test "slot 0 is the local player, read live; others join at its feet and leave" {
ply_init()
me_x = 5.0
expect(ply_on(0))
expect_near(ply_x(0), 5.0, 0.001)
expect(ply_name(0) == "me")
let p = ply_join(7)
expect_eq(p, 1)
expect_near(ply_x(p), 5.0, 0.001)
expect_eq(ply_slot_of(7), 1)
expect_eq(ply_count(), 2)
expect_eq(ply_join(0), -1)
ply_leave(p)
expect_eq(ply_slot_of(7), -1)
expect_eq(ply_count(), 1)
let w = whats()
expect_eq(len(w), 2)
expect_eq(w[0], SES_JOINED)
expect_eq(w[1], SES_LEFT)
}
test "the roster fills at twelve" {
ply_init()
for i in 0 .. PLY_MAX - 1 { expect(ply_join(10 + i) > 0) }
expect_eq(ply_join(99), -1)
expect_eq(ply_count(), PLY_MAX)
}
test "the nearest player, whoever it is" {
ply_init()
me_x = 100.0
let p = ply_join(2)
ply_report(p, 3.0, 0.0, 4.0, 0.0, 1.0)
expect_near(ply_near(0.0, 0.0), 5.0, 0.001)
expect_eq(ply_near_i(), p)
expect_near(ply_dist(0, 100.0, 0.0), 0.0, 0.001)
}
test "roles: alone owns the world, a guest does not, and a change is a fact" {
ply_init()
expect(ses_owns_world())
expect(not ses_live())
expect(ses_night_passes())
ses_set_role(SES_GUEST)
expect(not ses_owns_world())
expect(ses_live())
expect(not ses_night_passes())
let f = last_of(SES_ROLE)
expect_eq(f.role, SES_GUEST)
ses_set_role(SES_HOST)
expect(ses_owns_world())
expect(not ses_night_passes())
}
test "the party's size and door" {
ses_set_party(20, true)
expect_eq(ses_max(), PLY_MAX)
ses_set_party(3, true)
party(1)
expect(ses_room_for_one())
ply_join(9)
expect(not ses_room_for_one())
ses_set_party(12, false)
expect(not ses_room_for_one())
}
test "the night passes when the host and every guest have turned in" {
party(2)
expect(ses_sleep(true))
expect_eq(ses_sleep_waiting(), 2)
ses_sleep_ready(1, true)
expect(last_of(SES_NIGHT) == null)
ses_sleep_ready(2, true)
expect(last_of(SES_NIGHT) != null)
ses_night_passed()
expect(not ses_sleeping())
}
test "a guest's turning in is a fact for the host to hear; alone a bed just sleeps" {
ply_init()
expect(not ses_sleep(true))
ses_set_role(SES_GUEST)
q_clear(ses_facts())
expect(ses_sleep(true))
let f = last_of(SES_SLEEP)
expect(f.yes)
ses_sleep(false)
expect(not ses_sleeping())
}
test "a vote passes on a majority, the host's yes counted" {
party(2)
expect_eq(ses_may(4, 9, "set out"), SES_MAY_ASKED)
expect(ses_vote_live())
expect_eq(ses_may(4, 9, "set out"), SES_MAY_BUSY)
ses_vote_heard(ses_vote_id(), true)
let f = last_of(SES_VOTE_RESOLVED)
expect(f.yes)
expect_eq(f.param, 9)
expect(f.text == "set out")
expect(not ses_vote_live())
}
test "a vote fails on half saying no, or when the time runs out" {
party(1)
ses_vote_open(1, 0, "a")
ses_vote_heard(ses_vote_id(), false)
expect(not last_of(SES_VOTE_RESOLVED).yes)
party(3)
ses_vote_open(1, 0, "b")
ses_vote_tick(31.0)
let f = last_of(SES_VOTE_RESOLVED)
expect(f != null)
expect(not f.yes)
}
test "alone it may go; a guest may not, and its vote goes to the host" {
ply_init()
expect_eq(ses_may(1, 0, "x"), SES_MAY_GO)
ses_set_role(SES_GUEST)
expect_eq(ses_may(1, 0, "x"), SES_MAY_HOSTS)
ses_vote_told(5, 1, "x")
expect(ses_vote_live())
q_clear(ses_facts())
ses_vote_cast(true)
let f = last_of(SES_VOTE_CAST)
expect_eq(f.id, 5)
expect(f.yes)
ses_vote_ended(5, true)
expect_eq(ses_vote_passed(), 1)
}
test "a reset leaves everyone and stands alone" {
party(2)
ses_sleep(true)
ses_reset()
expect_eq(ply_count(), 1)
expect_eq(ses_role(), SES_SOLO)
expect(not ses_sleeping())
}
}

View file

@ -0,0 +1,10 @@
# vote_state.ludic - the vote as it stands
export function ses_vote_live() -> bool { return session__vote_t > 0.0 }
export function ses_vote_id() -> int { return session__vote_id }
export function ses_vote_kind() -> int { return session__vote_kind }
export function ses_vote_text() -> string { return session__vote_text }
export function ses_vote_left() -> float { return session__vote_t }
export function ses_vote_yes() -> int { return session__vote_yes }
export function ses_vote_no() -> int { return session__vote_no }
export function ses_vote_mine() -> int { return session__vote_mine }
export function ses_vote_passed() -> int { return session__vote_passed }

View file

@ -0,0 +1,111 @@
# votes.ludic - what changes the world for everyone is the host's to put to the party and the
# party's to agree: a majority of the players, the host included, within thirty seconds. A guest
# only hears of a vote (ses_vote_told), casts its own (SES_VOTE_CAST) and hears how it ended.
export const SES_MAY_GO: int = 0 # alone, or nobody else here: go ahead
export const SES_MAY_ASKED: int = 1 # the party has been asked; the act waits for SES_VOTE_RESOLVED
export const SES_MAY_BUSY: int = 2 # a vote is already open
export const SES_MAY_HOSTS: int = 3 # a guest: it is for the host to ask
const SESSION_VOTE_SECS: float = 30.0
var session__vote_id: int = 0
var session__vote_kind: int = 0
var session__vote_param: int = 0
var session__vote_text: string = ""
var session__vote_t: float = 0.0
var session__vote_yes: int = 0
var session__vote_no: int = 0
var session__vote_mine: int = -1 # -1 not voted, 0 no, 1 yes
var session__vote_passed: int = -1 # the last vote: -1 none, 0 failed, 1 passed
# a world-changing act: may it go ahead now, or does the party have to be asked first?
export function ses_may(kind: int, param: int, text: string) -> int {
if not ses_live() { return SES_MAY_GO }
if session__role == SES_GUEST { return SES_MAY_HOSTS }
if ply_count() <= 1 { return SES_MAY_GO }
if not ses_vote_open(kind, param, text) { return SES_MAY_BUSY }
return SES_MAY_ASKED
}
# the host puts it to the party, voting yes itself
export function ses_vote_open(kind: int, param: int, text: string) -> bool {
if session__role != SES_HOST or ses_vote_live() { return false }
session__vote_id += 1
session__vote_kind = kind
session__vote_param = param
session__vote_text = text
session__vote_t = SESSION_VOTE_SECS
session__vote_yes = 1
session__vote_no = 0
session__vote_mine = 1
session__vote_passed = -1
let f = session__vote_fact(SES_VOTE_OPENED)
f.yes = true
session__vote_check()
return true
}
# this player's answer; a guest's goes to the host as SES_VOTE_CAST
export function ses_vote_cast(yes: bool) -> void {
if not ses_vote_live() or session__vote_mine >= 0 { return }
if yes { session__vote_mine = 1 } else { session__vote_mine = 0 }
if session__role == SES_GUEST {
let f = session__vote_fact(SES_VOTE_CAST)
f.yes = yes
return
}
ses_vote_heard(session__vote_id, yes)
}
# the host: a player's answer to vote `id`
export function ses_vote_heard(id: int, yes: bool) -> void {
if session__role != SES_HOST or id != session__vote_id or not ses_vote_live() { return }
if yes { session__vote_yes += 1 } else { session__vote_no += 1 }
session__vote_check()
}
# a guest: the host opened vote `id`, or ended it
export function ses_vote_told(id: int, kind: int, text: string) -> void {
session__vote_id = id
session__vote_kind = kind
session__vote_text = text
session__vote_t = SESSION_VOTE_SECS
session__vote_mine = -1
}
export function ses_vote_ended(id: int, passed: bool) -> void {
if id != session__vote_id { return }
session__vote_t = 0.0
if passed { session__vote_passed = 1 } else { session__vote_passed = 0 }
}
# the vote's thirty seconds; on the host, running out decides it
export function ses_vote_tick(dt: float) -> void {
if not ses_vote_live() { return }
session__vote_t = session__vote_t - dt
if session__role == SES_HOST and not ses_vote_live() { session__vote_finish() }
}
function session__vote_check() -> void {
let n = ply_count()
let yes = session__vote_yes
let no = session__vote_no
if yes * 2 > n or no * 2 >= n or yes + no >= n { session__vote_finish() }
}
function session__vote_finish() -> void {
let passed = session__vote_yes * 2 > ply_count()
session__vote_t = 0.0
if passed { session__vote_passed = 1 } else { session__vote_passed = 0 }
let f = session__vote_fact(SES_VOTE_RESOLVED)
f.yes = passed
}
function session__vote_fact(what: int) -> SessionFact {
let f = session__fact(what, -1, 0)
f.id = session__vote_id
f.kind = session__vote_kind
f.param = session__vote_param
f.text = session__vote_text
return f
}

View file

@ -0,0 +1,47 @@
# where.ludic - who and where each player is; slot 0 answers through SessionLocal, live
export function ply_pid(p: int) -> int {
if p <= 0 or not ply_on(p) { return 0 }
return session__slot[p].pid
}
export function ply_name(p: int) -> string {
if p == 0 { return SessionLocal.name() }
if not ply_on(p) { return "" }
return session__slot[p].name
}
export function ply_x(p: int) -> float {
if p == 0 { return SessionLocal.x() }
return session__slot[p].x
}
export function ply_y(p: int) -> float {
if p == 0 { return SessionLocal.y() }
return session__slot[p].y
}
export function ply_z(p: int) -> float {
if p == 0 { return SessionLocal.z() }
return session__slot[p].z
}
export function ply_yaw(p: int) -> float {
if p == 0 { return SessionLocal.yaw() }
return session__slot[p].yaw
}
export function ply_speed(p: int) -> float {
if p == 0 { return SessionLocal.speed() }
return session__slot[p].speed
}
# how much a player's rest speeds the clock: their seat's or the ground's, 1 on their feet
export function ply_rest(p: int) -> float {
if p == 0 { return SessionLocal.rest() }
return session__slot[p].rest
}
export function ply_sleeping(p: int) -> bool {
if p == 0 { return session__me_sleeping }
return ply_on(p) and session__slot[p].sleeping
}