feat(vehicles): 16.7 - a boat is a buoyant hull in the game's physics (VehicleHull), rowed along its bow and turned by the oars; the scripted bob, the wind's drift and the shore refusal are gone
The water floats the hull, the wind pushes every boat as a force, the lake bed stops it at the shore; the swell takes the outward share of a stroke. ludic.physics: phys_hull_add (a buoyant box), phys_row, phys_bow_speed, and a test that floats one a quarter under, rows, turns and grounds it. The vehicles tests row a real Jolt hull on the fake shore; the horse moved to horse.ludic. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ac815bf638
commit
510360b002
12 changed files with 284 additions and 103 deletions
26
packages/ludic.physics/hull.ludic
Normal file
26
packages/ludic.physics/hull.ludic
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# hull.ludic - a boat: a buoyant box floated by PhysWater, rowed along its bow and turned about its
|
||||
# middle. Its bow is -z at yaw 0, the way the valley faces (forward is (-sin yaw, -cos yaw)).
|
||||
|
||||
# hx, hy, hz its half width, depth and length; buoyancy over 1 floats it (4: a quarter under)
|
||||
export function phys_hull_add(physics_st: mut PhysicsState, x: float, y: float, z: float, yaw: float, hx: float, hy: float, hz: float, mass: float, buoyancy: float, drag: float) -> int {
|
||||
let id = phys_body_add(physics_st, phys_box(physics_st, hx, hy, hz), x, y, z, yaw, mass)
|
||||
if id < 0 { return -1 }
|
||||
phys_float(physics_st, id, buoyancy, drag, drag * 1.5)
|
||||
return id
|
||||
}
|
||||
|
||||
# a stroke of `forward` newtons along the bow and a turn of `turn` newton-metres about y, over the
|
||||
# next step
|
||||
export function phys_row(physics_st: mut PhysicsState, id: int, forward: float, turn: float) -> void {
|
||||
let p = phys_pose(physics_st, id)
|
||||
if p == null { return }
|
||||
let yaw = phys_yaw_of(p)
|
||||
phys_force(physics_st, id, -Math.sin(yaw) * forward, 0.0, -Math.cos(yaw) * forward)
|
||||
phys_torque(physics_st, id, 0.0, turn, 0.0)
|
||||
}
|
||||
|
||||
# how fast it goes along its bow (backwards is negative)
|
||||
export function phys_bow_speed(p: PhysPose) -> float {
|
||||
let yaw = phys_yaw_of(p)
|
||||
return -Math.sin(yaw) * p.vx - Math.cos(yaw) * p.vz
|
||||
}
|
||||
|
|
@ -10,4 +10,5 @@ import "bodies.ludic"
|
|||
import "queries.ludic"
|
||||
import "water.ludic"
|
||||
import "walker.ludic"
|
||||
import "hull.ludic"
|
||||
import "system.ludic"
|
||||
|
|
|
|||
|
|
@ -127,4 +127,34 @@ program MotionTest {
|
|||
expect_eq(float_bits(a.z), float_bits(b.z))
|
||||
expect_eq(float_bits(a.qw), float_bits(b.qw))
|
||||
}
|
||||
|
||||
test "a hull floats a quarter under, is rowed along its bow, turned, and stopped by the lake bed" (physics_st: mut PhysicsState) {
|
||||
world(physics_st)
|
||||
let b = phys_hull_add(physics_st, 40.0, 0.5, 0.0, 1.5707963, 0.6, 0.25, 1.9, 180.0, 4.0, 1.0)
|
||||
sim(physics_st, 4.0)
|
||||
let p0 = phys_pose(physics_st, b)
|
||||
expect(p0.y > -0.2)
|
||||
expect(p0.y < 0.2)
|
||||
for i in 0 .. 240 {
|
||||
phys_row(physics_st, b, 420.0, 0.0)
|
||||
phys_step(physics_st)
|
||||
}
|
||||
let p1 = phys_pose(physics_st, b)
|
||||
expect(p1.x < 34.0)
|
||||
expect(phys_bow_speed(p1) > 1.0)
|
||||
for i in 0 .. 120 {
|
||||
phys_row(physics_st, b, 0.0, 520.0)
|
||||
phys_step(physics_st)
|
||||
}
|
||||
expect(Math.abs(phys_yaw_of(phys_pose(physics_st, b)) - phys_yaw_of(p1)) > 0.5)
|
||||
phys_close(physics_st)
|
||||
world(physics_st)
|
||||
let c = phys_hull_add(physics_st, 24.0, 0.5, 0.0, 1.5707963, 0.6, 0.25, 1.9, 180.0, 4.0, 1.0)
|
||||
for i in 0 .. 600 {
|
||||
phys_row(physics_st, c, 420.0, 0.0)
|
||||
phys_step(physics_st)
|
||||
}
|
||||
expect(phys_pose(physics_st, c).x > 19.5)
|
||||
phys_close(physics_st)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,11 +23,19 @@ through the game).
|
|||
`safe_spot` - a wade of up to 1.25 m within its search from a boat, beside the right flank from a
|
||||
horse - and a boat too far out refuses and says so (`VEHICLE_NO_LANDING`) rather than dropping the
|
||||
rider in the lake.
|
||||
- **Steering follows signed speed**: backing up with the stick left swings the nose right.
|
||||
- **A boat is a body in the game's physics** (`VehicleHull`, phase 16; a buoyant Jolt hull in Maroon
|
||||
Lake). The package rows it - the stick forward and back is a stroke along the bow, left and right
|
||||
the oars pulled against each other - and reads back where the hull is and how fast it goes. The
|
||||
water floats it, the wind (`VehicleWorld.wind`) drifts every boat, ridden or not, and the lake bed
|
||||
stops it at the shore; the swell at the sail limit takes the outward share of a stroke.
|
||||
- **A horse's steering follows signed speed**: backing up with the stick left swings the nose right.
|
||||
- A stale rider locks a vehicle for ever, so getting off is `vehicle_dismount` or `vehicle_drop`.
|
||||
|
||||
## The ports
|
||||
|
||||
`VehicleHull { put(nid, x, y, z, yaw), row(nid, forward, turn), drift(nid, fx, fz), read(nid) -> bool,
|
||||
x, y, z, yaw, speed, gone(nid) }` is required: the boat's body, named by the vehicle's nid.
|
||||
|
||||
```ludic
|
||||
export port VehicleWorld {
|
||||
ground: fn(float, float) -> float # required
|
||||
|
|
@ -52,7 +60,7 @@ export port VehicleRider {
|
|||
| | |
|
||||
| --- | --- |
|
||||
| `VEHICLE_HORSE`, `VEHICLE_BOAT`, `VEHICLE_PLACES`, `VEHICLE_HAY_MIN` | the kinds, four places each, and the hay a horse needs to go |
|
||||
| `Vehicle { kind, nid, x, y, z, yaw, speed, fuel, food, seat_h, bob, owned, owner, rider, home_x, home_z }` | a vehicle (change one through the verbs) |
|
||||
| `Vehicle { kind, nid, x, y, z, yaw, speed, fuel, food, seat_h, owned, owner, rider, home_x, home_z }` | a vehicle (change one through the verbs) |
|
||||
| `vehicles_dock_at(x, z, yaw)`, `vehicles_rail_at(x, z, yaw)`, `vehicles_dock_yaw()` | where the places are measured from |
|
||||
| `vehicle_berth_x/z(k)`, `vehicle_post_x/z(k)`, `vehicle_bay_x/z(k)`, `vehicle_place_x/z(kind, k)` | the places |
|
||||
| `vehicle_call_for(pid, kind, k) -> bool`, `vehicle_in_place(kind, k)`, `vehicle_free_place(kind)`, `vehicle_mine_in_place(kind)` | calling one to a place |
|
||||
|
|
@ -60,7 +68,7 @@ export port VehicleRider {
|
|||
| `vehicle_mount(v) -> bool`, `vehicle_dismount() -> bool`, `vehicle_drop()` | on and off |
|
||||
| `vehicle_update(ix, iz, run, dt)` | a frame of riding from the rider's stick (bind it where the body hands its stick over) |
|
||||
| `vehicle_own(v)`, `vehicle_feed(v, hay)`, `vehicle_follow(v, x, y, z, yaw)`, `vehicles_release(pid)`, `vehicle_restore(kind, owned, food)` | hiring, hay, a tied horse's wandering, a player leaving, a saved word |
|
||||
| `vehicles_tick(dt)` | the idle boats' bob (the game calls it where the world is run) |
|
||||
| `vehicles_tick(dt)` | every boat's hull read back and pushed by the wind (the game calls it where the world is run) |
|
||||
| `vehicle_facts() -> Queue<VehicleFact>` | `{ what, nid, kind, owner, x, z, v }`: `VEHICLE_MOUNTED`, `_DISMOUNTED`, `_NO_LANDING`, `_MOVED` (v: metres), `_SWELL` (every 14 s), `_HUNGRY`, `_FED` |
|
||||
| `vehicles_reset()`, `vehicles_system() -> System` | `"vehicles"`: this player's own saved by kind (hired, hay) - where they lie is not saved |
|
||||
|
||||
|
|
@ -71,5 +79,6 @@ ludic test packages/ludic.vehicles
|
|||
```
|
||||
|
||||
A fake shore with a pier head and a trough: berths and bays, calling and fetching, a taken place, a
|
||||
player leaving, a hungry horse, riding a horse into a trunk and to the water's edge, rowing to the
|
||||
shore, the swell and the wind, getting off near a shore and far from one, and the save.
|
||||
player leaving, a hungry horse, riding a horse into a trunk and to the water's edge; a real Jolt hull
|
||||
rowed out, losing its stroke to the swell, turned, stopped by the shore's bed and drifted by the
|
||||
wind; getting off near a shore and far from one, and the save.
|
||||
|
|
|
|||
|
|
@ -15,10 +15,20 @@ function ve_add(vehicles_st: mut VehiclesState, kind: int, x: float, z: float, y
|
|||
}
|
||||
ve_put(v, x, z)
|
||||
push(ve_all(vehicles_st), v)
|
||||
ve_settle(v)
|
||||
VehicleWorld.made(v)
|
||||
return v
|
||||
}
|
||||
|
||||
# a boat's hull put where the vehicle now is, at rest
|
||||
function ve_settle(v: Vehicle) -> void { if v.kind == VEHICLE_BOAT { VehicleHull.put(v.nid, v.x, v.y, v.z, v.yaw) } }
|
||||
|
||||
# gone from the world: its model and, for a boat, its hull
|
||||
function ve_gone(v: Vehicle) -> void {
|
||||
if v.kind == VEHICLE_BOAT { VehicleHull.gone(v.nid) }
|
||||
VehicleWorld.gone(v)
|
||||
}
|
||||
|
||||
# at rest at (x, z): a boat on the water, a horse on the ground and tied there
|
||||
function ve_put(v: Vehicle, x: float, z: float) -> void {
|
||||
v.x = x
|
||||
|
|
@ -48,6 +58,7 @@ export function vehicle_call_for(vehicles_st: mut VehiclesState, pid: int, kind:
|
|||
if v.rider >= 0 { return false }
|
||||
ve_put(v, vehicle_place_x(vehicles_st, kind, k), vehicle_place_z(vehicles_st, kind, k))
|
||||
v.yaw = yaw
|
||||
ve_settle(v)
|
||||
VehicleWorld.placed(v)
|
||||
return true
|
||||
}
|
||||
|
|
@ -58,7 +69,7 @@ export function vehicles_release(vehicles_st: mut VehiclesState, pid: int) -> vo
|
|||
let keep = new []Vehicle
|
||||
let l = ve_all(vehicles_st)
|
||||
for i in 0 .. len(l) {
|
||||
if l[i].owner != pid { push(keep, l[i]) } else { VehicleWorld.gone(l[i]) }
|
||||
if l[i].owner != pid { push(keep, l[i]) } else { ve_gone(l[i]) }
|
||||
}
|
||||
vehicles_st.ve_list = keep
|
||||
}
|
||||
|
|
|
|||
45
packages/ludic.vehicles/horse.ludic
Normal file
45
packages/ludic.vehicles/horse.ludic
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# horse.ludic - a horse under the stick: a walk, a canter on stamina, hay for every metre; it stops at
|
||||
# water, at a rise it cannot take and at a still thing in its way
|
||||
const VE_HORSE_WALK: float = 4.5
|
||||
const VE_HORSE_GALLOP: float = 11.0
|
||||
const VE_HORSE_ACC: float = 4.0
|
||||
const VE_HORSE_TURN: float = 1.6
|
||||
|
||||
function ve_ride_horse(vehicles_st: mut VehiclesState, v: Vehicle, ix: float, iz: float, run: bool, dt: float) -> void {
|
||||
var top = VE_HORSE_WALK
|
||||
if run and v.fuel > 5.0 { top = VE_HORSE_GALLOP }
|
||||
v.speed = v.speed + Math.clamp(iz * top - v.speed, -VE_HORSE_ACC * dt, VE_HORSE_ACC * dt)
|
||||
# Steering follows SIGNED speed: backing up with the stick left swings the nose right
|
||||
if Math.abs(v.speed) > 0.3 {
|
||||
var rate = Math.clamp(Math.abs(v.speed) / 3.0, 0.4, 1.0)
|
||||
if v.speed < 0.0 { rate = -rate }
|
||||
v.yaw = v.yaw - ix * VE_HORSE_TURN * (dt * rate)
|
||||
}
|
||||
let d = v.speed * dt
|
||||
ve_travel(vehicles_st, v, v.x - Math.sin(v.yaw) * d, v.z - Math.cos(v.yaw) * d, d)
|
||||
ve_stamina(v, d, dt)
|
||||
}
|
||||
|
||||
# a horse only on dry ground, up a walkable rise, clear of what is in its way
|
||||
function ve_travel(vehicles_st: mut VehiclesState, v: Vehicle, nx: float, nz: float, d: float) -> void {
|
||||
let nh = VehicleWorld.ground(nx, nz)
|
||||
var ok = true
|
||||
if nh < VehicleWorld.water(nx, nz) + 0.4 { ok = false }
|
||||
if nh - v.y > Math.abs(d) * 0.9 { ok = false }
|
||||
if VehicleWorld.blocked(nx, nz, 0.8) { ok = false }
|
||||
if not ok {
|
||||
v.speed = 0.0
|
||||
return
|
||||
}
|
||||
v.x = nx
|
||||
v.z = nz
|
||||
v.y = nh
|
||||
if Math.abs(d) > 0.0 { ve_say(vehicles_st, VEHICLE_MOVED, v, Math.abs(d)) }
|
||||
}
|
||||
|
||||
# a gallop spends stamina and a walk gives it back; every metre costs hay
|
||||
function ve_stamina(v: Vehicle, d: float, dt: float) -> void {
|
||||
if Math.abs(v.speed) > 6.0 { v.fuel = Math.max(v.fuel - 6.0 * dt, 0.0) } else { v.fuel = Math.min(v.fuel + 3.0 * dt, 100.0) }
|
||||
v.food = Math.max(v.food - Math.abs(d) * 0.02, 0.0)
|
||||
}
|
||||
|
||||
|
|
@ -11,4 +11,5 @@ import "places.ludic"
|
|||
import "call.ludic"
|
||||
import "mount.ludic"
|
||||
import "ride.ludic"
|
||||
import "horse.ludic"
|
||||
import "system.ludic"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ export property Vehicle {
|
|||
fuel: float = 0.0 # a horse's stamina, 0..100
|
||||
food: float = 0.0 # a horse's hay, 0..100
|
||||
seat_h: float = 0.0 # the rider sits this far above it
|
||||
bob: float = 0.0
|
||||
owned: bool = false # hired or rented for the trip
|
||||
owner: int = 0 # the player it belongs to (0: this machine's)
|
||||
rider: int = -1 # the player on it, -1 nobody
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
# ports.ludic - what a vehicle asks the world and its rider. Unbound, the world is dry flat ground
|
||||
# with nothing in the way and no wind, and the rider is put exactly where asked.
|
||||
# ports.ludic - what a vehicle asks the world, its hull and its rider. Unbound, the world is dry
|
||||
# flat ground with nothing in the way and no wind, and the rider is put exactly where asked; the
|
||||
# hull has no default - a boat is a body in the game's physics.
|
||||
export port VehicleWorld {
|
||||
ground: fn(float, float) -> float
|
||||
water: fn(float, float) -> float = fn ve__dry # the surface over a point
|
||||
|
|
@ -13,6 +14,21 @@ export port VehicleWorld {
|
|||
gone: fn(Vehicle) -> void = fn ve__nothing # its owner left: take it away
|
||||
}
|
||||
|
||||
# a boat's hull in the world's physics (Maroon Lake: a buoyant Jolt body), named by the vehicle's
|
||||
# nid. The water floats it and the lake bed stops it; the package rows it and reads where it is.
|
||||
export port VehicleHull {
|
||||
put: fn(int, float, float, float, float) -> void # (nid, x, y, z, yaw): at rest there, made if new
|
||||
row: fn(int, float, float) -> void # (nid, forward N, turn N.m) over the next step
|
||||
drift: fn(int, float, float) -> void # (nid, fx, fz): a push across the water (wind)
|
||||
read: fn(int) -> bool # (nid): its pose into x .. speed; false if none
|
||||
x: fn() -> float
|
||||
y: fn() -> float
|
||||
z: fn() -> float
|
||||
yaw: fn() -> float
|
||||
speed: fn() -> float # along its bow, m/s
|
||||
gone: fn(int) -> void # (nid): out of the world
|
||||
}
|
||||
|
||||
# the body on board: the game's walking body (ludic.character, through the game)
|
||||
export port VehicleRider {
|
||||
board: fn(float, float, float) -> void = fn ve__board # (x, z, yaw)
|
||||
|
|
|
|||
|
|
@ -1,106 +1,69 @@
|
|||
# ride.ludic - the rider's stick steers what they are on: a horse walks, canters on stamina and
|
||||
# stops at water, a slope or a trunk; a boat rows, drifts on the wind and stops at the shore
|
||||
const VE_HORSE_WALK: float = 4.5
|
||||
const VE_HORSE_GALLOP: float = 11.0
|
||||
const VE_HORSE_ACC: float = 4.0
|
||||
const VE_HORSE_TURN: float = 1.6
|
||||
const VE_BOAT_TOP: float = 3.2
|
||||
const VE_BOAT_ACC: float = 1.2
|
||||
const VE_BOAT_TURN: float = 1.1
|
||||
# stops at water, a slope or a trunk; a boat is rowed - a stroke and a turn handed to its hull,
|
||||
# which the water floats, the wind drifts and the lake bed stops
|
||||
const VE_ROW: float = 630.0 # N: a full stroke forward, about 3 m/s on Maroon Lake's hull
|
||||
const VE_ROW_TURN: float = 270.0 # N.m: the oars pulled against each other, about 1.1 rad/s
|
||||
const VE_WIND: float = 70.0 # N on a boat in a full wind
|
||||
|
||||
export function vehicle_update(vehicles_st: mut VehiclesState, ix: float, iz: float, run: bool, dt: float) -> void {
|
||||
let v = vehicles_st.ve_cur
|
||||
if v == null { return }
|
||||
var top = VE_BOAT_TOP
|
||||
var acc = VE_BOAT_ACC
|
||||
var turn = VE_BOAT_TURN
|
||||
if v.kind == VEHICLE_HORSE {
|
||||
acc = VE_HORSE_ACC
|
||||
turn = VE_HORSE_TURN
|
||||
top = VE_HORSE_WALK
|
||||
if run and v.fuel > 5.0 { top = VE_HORSE_GALLOP }
|
||||
}
|
||||
v.speed = v.speed + Math.clamp(iz * top - v.speed, -acc * dt, acc * dt)
|
||||
if v.kind == VEHICLE_BOAT { ve_swell(vehicles_st, v, dt) }
|
||||
# Steering follows SIGNED speed: backing up with the stick left swings the nose right
|
||||
if Math.abs(v.speed) > 0.3 {
|
||||
var rate = Math.clamp(Math.abs(v.speed) / 3.0, 0.4, 1.0)
|
||||
if v.speed < 0.0 { rate = -rate }
|
||||
v.yaw = v.yaw - ix * turn * (dt * rate)
|
||||
}
|
||||
let d = v.speed * dt
|
||||
ve_travel(vehicles_st, v, v.x - Math.sin(v.yaw) * d, v.z - Math.cos(v.yaw) * d, d)
|
||||
if v.kind == VEHICLE_HORSE { ve_stamina(v, d, dt) } else { ve_float(v, d, dt) }
|
||||
if v.kind == VEHICLE_BOAT { ve_row(vehicles_st, v, ix, iz, dt) } else { ve_ride_horse(vehicles_st, v, ix, iz, run, dt) }
|
||||
VehicleWorld.placed(v)
|
||||
VehicleRider.ride_at(v.x, v.y + v.seat_h, v.z, v.yaw, Math.abs(v.speed))
|
||||
}
|
||||
|
||||
# a boat only where there is water under it; a horse only on dry ground, up a walkable rise, clear
|
||||
function ve_travel(vehicles_st: mut VehiclesState, v: Vehicle, nx: float, nz: float, d: float) -> void {
|
||||
let nh = VehicleWorld.ground(nx, nz)
|
||||
var ok = true
|
||||
if v.kind == VEHICLE_BOAT { ok = nh < VehicleWorld.water(nx, nz) - 0.35 } else {
|
||||
if nh < VehicleWorld.water(nx, nz) + 0.4 { ok = false }
|
||||
if nh - v.y > Math.abs(d) * 0.9 { ok = false }
|
||||
if VehicleWorld.blocked(nx, nz, 0.8) { ok = false }
|
||||
}
|
||||
if not ok {
|
||||
v.speed = 0.0
|
||||
return
|
||||
}
|
||||
v.x = nx
|
||||
v.z = nz
|
||||
if v.kind != VEHICLE_BOAT { v.y = nh }
|
||||
if Math.abs(d) > 0.0 { ve_say(vehicles_st, VEHICLE_MOVED, v, Math.abs(d)) }
|
||||
# the stick is the oars: forward and back a stroke, left and right the two pulled against each
|
||||
# other; the swell at the edge takes the outward share of a stroke
|
||||
function ve_row(vehicles_st: mut VehiclesState, v: Vehicle, ix: float, iz: float, dt: float) -> void {
|
||||
ve_hull_read(v)
|
||||
let take = ve_swell(vehicles_st, v, iz, dt)
|
||||
VehicleHull.row(v.nid, iz * VE_ROW * (1.0 - take), -ix * VE_ROW_TURN)
|
||||
let d = Math.abs(v.speed) * dt
|
||||
if d > 0.0 { ve_say(vehicles_st, VEHICLE_MOVED, v, d) }
|
||||
}
|
||||
|
||||
# where the hull is now, and how fast it goes along its bow
|
||||
function ve_hull_read(v: Vehicle) -> void {
|
||||
if not VehicleHull.read(v.nid) { return }
|
||||
v.x = VehicleHull.x()
|
||||
v.y = VehicleHull.y()
|
||||
v.z = VehicleHull.z()
|
||||
v.yaw = VehicleHull.yaw()
|
||||
v.speed = VehicleHull.speed()
|
||||
}
|
||||
|
||||
# a boat gets a longer leash than a swimmer and the same current at the end of it: the outward
|
||||
# part of the stroke only, so a boat at the limit can always be put about
|
||||
function ve_swell(vehicles_st: mut VehiclesState, v: Vehicle, dt: float) -> void {
|
||||
# part of the stroke only, so a boat at the limit can always be put about. The share it takes.
|
||||
function ve_swell(vehicles_st: mut VehiclesState, v: Vehicle, iz: float, dt: float) -> float {
|
||||
var fx = -Math.sin(v.yaw)
|
||||
var fz = -Math.cos(v.yaw)
|
||||
if v.speed < 0.0 {
|
||||
if iz < 0.0 {
|
||||
fx = -fx
|
||||
fz = -fz
|
||||
}
|
||||
let take = Math.clamp(VehicleWorld.swell(v.x, v.z, fx, fz), 0.0, 1.0)
|
||||
v.speed = v.speed * (1.0 - take)
|
||||
if take > 0.55 and vehicles_st.ve_swell_said < 0.0 {
|
||||
vehicles_st.ve_swell_said = 14.0
|
||||
ve_say(vehicles_st, VEHICLE_SWELL, v, take)
|
||||
}
|
||||
vehicles_st.ve_swell_said = vehicles_st.ve_swell_said - dt
|
||||
return take
|
||||
}
|
||||
|
||||
# a gallop spends stamina and a walk gives it back; every metre costs hay
|
||||
function ve_stamina(v: Vehicle, d: float, dt: float) -> void {
|
||||
if Math.abs(v.speed) > 6.0 { v.fuel = Math.max(v.fuel - 6.0 * dt, 0.0) } else { v.fuel = Math.min(v.fuel + 3.0 * dt, 100.0) }
|
||||
v.food = Math.max(v.food - Math.abs(d) * 0.02, 0.0)
|
||||
}
|
||||
|
||||
# the bob on the water, higher in a wind, and the wind pushing the boat across the lake
|
||||
function ve_float(v: Vehicle, d: float, dt: float) -> void {
|
||||
v.bob = v.bob + dt
|
||||
let w = VehicleWorld.wind()
|
||||
v.y = VehicleWorld.water(v.x, v.z) + (0.02 + (0.05 + 0.12 * w) * Math.sin(v.bob * 1.4))
|
||||
let push = w * 0.6 * dt
|
||||
let wx = v.x - Math.sin(VehicleWorld.wind_yaw()) * push
|
||||
let wz = v.z - Math.cos(VehicleWorld.wind_yaw()) * push
|
||||
if VehicleWorld.ground(wx, wz) < VehicleWorld.water(wx, wz) - 0.35 {
|
||||
v.x = wx
|
||||
v.z = wz
|
||||
}
|
||||
}
|
||||
|
||||
# the boats nobody is in ride the swell where they lie
|
||||
# every boat: its hull read back, and the wind pushing it across the lake, ridden or not
|
||||
export function vehicles_tick(vehicles_st: mut VehiclesState, dt: float) -> void {
|
||||
let l = ve_all(vehicles_st)
|
||||
let w = VehicleWorld.wind() * VE_WIND
|
||||
let wy = VehicleWorld.wind_yaw()
|
||||
for i in 0 .. len(l) {
|
||||
let v = l[i]
|
||||
if v.kind == VEHICLE_BOAT and v.rider < 0 {
|
||||
v.bob = v.bob + dt
|
||||
v.y = VehicleWorld.water(v.x, v.z) + (0.02 + 0.04 * Math.sin(v.bob * 1.1))
|
||||
VehicleWorld.placed(v)
|
||||
if v.kind == VEHICLE_BOAT {
|
||||
if w > 0.0 { VehicleHull.drift(v.nid, -Math.sin(wy) * w, -Math.cos(wy) * w) }
|
||||
if v.rider < 0 {
|
||||
ve_hull_read(v)
|
||||
VehicleWorld.placed(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
# system.ludic - vehicles as a system: a reset, and this player's own saved by kind (hired or not,
|
||||
# and the horse's hay); where they lie is not saved - they are called again. The idle boats' bob is
|
||||
# vehicles_tick, which the game calls where the world is run
|
||||
# and the horse's hay); where they lie is not saved - they are called again. The boats' hulls are
|
||||
# read back and pushed by the wind in vehicles_tick, which the game calls where the world is run
|
||||
export function vehicles_reset(vehicles_st: mut VehiclesState) -> void {
|
||||
if vehicles_st.ve_list != null {
|
||||
for i in 0 .. len(vehicles_st.ve_list) { if vehicles_st.ve_list[i].kind == VEHICLE_BOAT { VehicleHull.gone(vehicles_st.ve_list[i].nid) } }
|
||||
}
|
||||
vehicles_st.ve_list = new []Vehicle
|
||||
vehicles_st.ve_cur = null
|
||||
vehicles_st.ve_have_dock = false
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
# vehicles_test.ludic - a fake shore: land at x < 10, a lake past it deepening a metre a metre, a
|
||||
# pier head at the shore facing out and a trough on the meadow, a trunk in a horse's way. Calling to
|
||||
# berths and bays, the owner and the rider, mounting a hungry horse, riding both, the swell, the wind,
|
||||
# getting off near a shore and far from one, a player leaving, and the save.
|
||||
# getting off near a shore and far from one, a player leaving, and the save. The boat's hull is a
|
||||
# real ludic.physics body on the same lake.
|
||||
import "ludic.vehicles"
|
||||
import "ludic.base"
|
||||
import "ludic.physics"
|
||||
program VehiclesTest {
|
||||
numbers float
|
||||
|
||||
|
|
@ -28,6 +30,14 @@ program VehiclesTest {
|
|||
boarded: int = 0
|
||||
sx: float = 0.0
|
||||
sz: float = 0.0
|
||||
hull_nid: int = -1
|
||||
hull: int = -1
|
||||
hx: float = 0.0
|
||||
hy: float = 0.0
|
||||
hz: float = 0.0
|
||||
hyaw: float = 0.0
|
||||
hspeed: float = 0.0
|
||||
hulls: int = 0
|
||||
}
|
||||
function fk_swell(vehicles_test_st: VehiclesTestState, x: float, z: float, fx: float, fz: float) -> float {
|
||||
if fx > 0.0 { return vehicles_test_st.swell }
|
||||
|
|
@ -65,6 +75,52 @@ program VehiclesTest {
|
|||
function fk_sx(vehicles_test_st: VehiclesTestState) -> float { return vehicles_test_st.sx }
|
||||
function fk_sz(vehicles_test_st: VehiclesTestState) -> float { return vehicles_test_st.sz }
|
||||
|
||||
# the hull: one boat's body in a Jolt lake - the shore's bed as a heightfield, the water at 0
|
||||
function fk_lake(physics_st: mut PhysicsState) -> void {
|
||||
if phys_is_open(physics_st) { return }
|
||||
phys_open(physics_st, 64, 1)
|
||||
let n = 96
|
||||
let h = floats(n * n)
|
||||
for j in 0 .. n { for i in 0 .. n { h[j * n + i] = fk_ground(float(i) - 20.0, float(j) - 48.0) } }
|
||||
phys_ground_add(physics_st, phys_heightfield(physics_st, h, n, -20.0, -48.0, 1.0))
|
||||
}
|
||||
function fk_put(physics_st: mut PhysicsState, vehicles_test_st: mut VehiclesTestState, nid: int, x: float, y: float, z: float, yaw: float) -> void {
|
||||
fk_lake(physics_st)
|
||||
if vehicles_test_st.hull >= 0 and vehicles_test_st.hull_nid == nid {
|
||||
phys_place(physics_st, vehicles_test_st.hull, x, y + 0.12, z, yaw)
|
||||
phys_set_velocity(physics_st, vehicles_test_st.hull, 0.0, 0.0, 0.0)
|
||||
return
|
||||
}
|
||||
vehicles_test_st.hull = phys_hull_add(physics_st, x, y + 0.12, z, yaw, 0.6, 0.25, 1.9, 180.0, 4.0, 1.0)
|
||||
vehicles_test_st.hull_nid = nid
|
||||
vehicles_test_st.hulls += 1
|
||||
}
|
||||
function fk_row(physics_st: mut PhysicsState, vehicles_test_st: VehiclesTestState, nid: int, f: float, t: float) -> void { phys_row(physics_st, vehicles_test_st.hull, f, t) }
|
||||
function fk_drift(physics_st: mut PhysicsState, vehicles_test_st: VehiclesTestState, nid: int, fx: float, fz: float) -> void { phys_force(physics_st, vehicles_test_st.hull, fx, 0.0, fz) }
|
||||
function fk_read(physics_st: mut PhysicsState, vehicles_test_st: mut VehiclesTestState, nid: int) -> bool {
|
||||
if vehicles_test_st.hull < 0 or vehicles_test_st.hull_nid != nid { return false }
|
||||
let p = phys_pose(physics_st, vehicles_test_st.hull)
|
||||
if p == null { return false }
|
||||
vehicles_test_st.hx = p.x
|
||||
vehicles_test_st.hy = p.y
|
||||
vehicles_test_st.hz = p.z
|
||||
vehicles_test_st.hyaw = phys_yaw_of(p)
|
||||
vehicles_test_st.hspeed = phys_bow_speed(p)
|
||||
return true
|
||||
}
|
||||
function fk_hull_gone(physics_st: mut PhysicsState, vehicles_test_st: mut VehiclesTestState, nid: int) -> void {
|
||||
if vehicles_test_st.hull_nid != nid { return }
|
||||
phys_remove(physics_st, vehicles_test_st.hull)
|
||||
vehicles_test_st.hull = -1
|
||||
}
|
||||
function fk_hx(vehicles_test_st: VehiclesTestState) -> float { return vehicles_test_st.hx }
|
||||
function fk_hy(vehicles_test_st: VehiclesTestState) -> float { return vehicles_test_st.hy }
|
||||
function fk_hz(vehicles_test_st: VehiclesTestState) -> float { return vehicles_test_st.hz }
|
||||
function fk_hyaw(vehicles_test_st: VehiclesTestState) -> float { return vehicles_test_st.hyaw }
|
||||
function fk_hspeed(vehicles_test_st: VehiclesTestState) -> float { return vehicles_test_st.hspeed }
|
||||
bind VehicleHull { put: fn fk_put, row: fn fk_row, drift: fn fk_drift, read: fn fk_read, x: fn fk_hx, y: fn fk_hy, z: fn fk_hz, yaw: fn fk_hyaw, speed: fn fk_hspeed, gone: fn fk_hull_gone }
|
||||
bind PhysWater { height: fn fk_water }
|
||||
|
||||
bind VehicleWorld { ground: fn fk_ground, water: fn fk_water, blocked: fn fk_blocked, swell: fn fk_swell, wind: fn fk_wind, made: fn fk_made, gone: fn fk_gone, placed: fn fk_placed }
|
||||
bind VehicleRider { board: fn fk_board, ride_at: fn fk_ride, step_off: fn fk_off, safe_spot: fn fk_safe, safe_x: fn fk_sx, safe_z: fn fk_sz }
|
||||
|
||||
|
|
@ -85,8 +141,12 @@ program VehiclesTest {
|
|||
for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } }
|
||||
return n
|
||||
}
|
||||
function ride(vehicles_st: mut VehiclesState, ix: float, iz: float, run: bool, secs: float) -> void {
|
||||
for i in 0 .. int(secs * 60.0) { vehicle_update(vehicles_st, ix, iz, run, 1.0 / 60.0) }
|
||||
function ride(physics_st: mut PhysicsState, vehicles_st: mut VehiclesState, ix: float, iz: float, run: bool, secs: float) -> void {
|
||||
for i in 0 .. int(secs * 60.0) {
|
||||
vehicle_update(vehicles_st, ix, iz, run, 1.0 / 60.0)
|
||||
vehicles_tick(vehicles_st, 1.0 / 60.0)
|
||||
phys_step(physics_st)
|
||||
}
|
||||
}
|
||||
|
||||
test "a berth is off the pier head in the water, a bay on the meadow" (vehicles_st: mut VehiclesState, vehicles_test_st: mut VehiclesTestState) {
|
||||
|
|
@ -147,22 +207,22 @@ program VehiclesTest {
|
|||
expect(vehicle_in_place(vehicles_st, VEHICLE_HORSE, 0) == null)
|
||||
}
|
||||
|
||||
test "a horse walks, gallops on stamina, stops at the water and at a trunk" (vehicles_st: mut VehiclesState, vehicles_test_st: mut VehiclesTestState) {
|
||||
test "a horse walks, gallops on stamina, stops at the water and at a trunk" (physics_st: mut PhysicsState, vehicles_st: mut VehiclesState, vehicles_test_st: mut VehiclesTestState) {
|
||||
fresh(vehicles_st, vehicles_test_st)
|
||||
vehicle_call_for(vehicles_st, 0, VEHICLE_HORSE, 0)
|
||||
let h = vehicle_find(vehicles_st, VEHICLE_HORSE)
|
||||
vehicle_mount(vehicles_st, h)
|
||||
ride(vehicles_st, 0.0, 1.0, false, 3.0)
|
||||
ride(physics_st, vehicles_st, 0.0, 1.0, false, 3.0)
|
||||
expect_near(h.speed, 4.5, 0.05)
|
||||
expect(count(facts(vehicles_st), VEHICLE_MOVED) > 100)
|
||||
ride(vehicles_st, 0.0, 1.0, true, 3.0)
|
||||
ride(physics_st, vehicles_st, 0.0, 1.0, true, 3.0)
|
||||
expect_near(h.speed, 11.0, 0.1)
|
||||
expect(h.fuel < 100.0)
|
||||
expect(h.z < -15.0)
|
||||
expect_near(vehicles_test_st.rz, h.z, 0.001)
|
||||
expect_near(vehicles_test_st.ry, 1.35, 0.001)
|
||||
# the trunk at z = -60 stops it
|
||||
ride(vehicles_st, 0.0, 1.0, true, 6.0)
|
||||
ride(physics_st, vehicles_st, 0.0, 1.0, true, 6.0)
|
||||
expect(h.z > -60.0 + 1.7)
|
||||
expect_near(h.speed, 0.0, 0.001)
|
||||
# and the lake to the east does too
|
||||
|
|
@ -171,29 +231,46 @@ program VehiclesTest {
|
|||
h.z = 0.0
|
||||
h.yaw = EAST
|
||||
vehicle_mount(vehicles_st, h)
|
||||
ride(vehicles_st, 0.0, 1.0, false, 4.0)
|
||||
ride(physics_st, vehicles_st, 0.0, 1.0, false, 4.0)
|
||||
expect(h.x < 9.6)
|
||||
}
|
||||
|
||||
test "a boat rows on water, stops at the shore, loses its stroke to the swell, drifts on the wind" (vehicles_st: mut VehiclesState, vehicles_test_st: mut VehiclesTestState) {
|
||||
test "a boat is rowed out, loses its stroke to the swell, turns, is stopped by the shore's bed, drifts on the wind" (physics_st: mut PhysicsState, vehicles_st: mut VehiclesState, vehicles_test_st: mut VehiclesTestState) {
|
||||
fresh(vehicles_st, vehicles_test_st)
|
||||
vehicle_call_for(vehicles_st, 0, VEHICLE_BOAT, 0)
|
||||
let b = vehicle_find(vehicles_st, VEHICLE_BOAT)
|
||||
expect_eq(vehicles_test_st.hulls, 1)
|
||||
vehicle_mount(vehicles_st, b)
|
||||
ride(vehicles_st, 0.0, 1.0, false, 2.0)
|
||||
let x2 = b.x
|
||||
expect(x2 > 18.0)
|
||||
let x0 = b.x
|
||||
ride(physics_st, vehicles_st, 0.0, 1.0, false, 6.0)
|
||||
let x6 = b.x
|
||||
expect(x6 > x0 + 8.0)
|
||||
expect(b.speed > 2.0)
|
||||
expect(b.y > -0.2)
|
||||
ride(physics_st, vehicles_st, 0.0, 0.0, false, 6.0)
|
||||
vehicles_test_st.swell = 1.0
|
||||
ride(vehicles_st, 0.0, 1.0, false, 1.0)
|
||||
expect_near(b.x, x2, 0.2)
|
||||
let xs = b.x
|
||||
ride(physics_st, vehicles_st, 0.0, 1.0, false, 2.0)
|
||||
expect(b.x - xs < 1.0)
|
||||
expect_eq(count(facts(vehicles_st), VEHICLE_SWELL), 1)
|
||||
vehicles_test_st.swell = 0.0
|
||||
ride(vehicles_st, 0.0, -1.0, false, 12.0)
|
||||
expect(b.x > 10.3)
|
||||
let yaw0 = b.yaw
|
||||
ride(physics_st, vehicles_st, 1.0, 0.0, false, 2.0)
|
||||
expect(Math.abs(char_turn(b.yaw - yaw0)) > 1.0)
|
||||
ride(physics_st, vehicles_st, 0.0, -1.0, false, 25.0)
|
||||
ride(physics_st, vehicles_st, 0.0, 0.0, false, 3.0)
|
||||
expect(b.x > 9.5)
|
||||
vehicles_test_st.wind = 1.0
|
||||
let z0 = b.z
|
||||
ride(vehicles_st, 0.0, 0.0, false, 1.0)
|
||||
expect(b.z < z0)
|
||||
ride(physics_st, vehicles_st, 0.0, 0.0, false, 3.0)
|
||||
expect(b.z < z0 - 0.2)
|
||||
}
|
||||
|
||||
function char_turn(a: float) -> float {
|
||||
var d = a
|
||||
while d > 3.14159265 { d -= 6.2831853 }
|
||||
while d < -3.14159265 { d += 6.2831853 }
|
||||
return d
|
||||
}
|
||||
|
||||
test "getting off a boat needs a shore near enough; a horse is left tied where it stands" (vehicles_st: mut VehiclesState, vehicles_test_st: mut VehiclesTestState) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue