spoint 0.1.600 → 0.1.602
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/apps/_lib/steering.js
CHANGED
|
@@ -37,19 +37,19 @@ import { createComponentPool } from './ComponentPool.js'
|
|
|
37
37
|
// step()/followPath() themselves stay pure functions of their arguments (from/target/dt/peers) --
|
|
38
38
|
// there is no PER-TICK mutable numeric state to pool here (unlike health.js's hp/alive/lastHitAt);
|
|
39
39
|
// followPath's cursor is already caller-owned (`state`), not instance-owned.
|
|
40
|
-
const _pool = createComponentPool({ fields: { speed: 'f64', arriveRadius: 'f64', separation: 'f64', clampToTerrain: 'f64', yOffset: 'f64' } })
|
|
40
|
+
const _pool = createComponentPool({ fields: { speed: 'f64', arriveRadius: 'f64', separation: 'f64', clampToTerrain: 'f64', yOffset: 'f64', useNavCost: 'f64' } })
|
|
41
41
|
// Cached column references + the epoch they were captured at -- see health.js/ComponentPool.js's
|
|
42
42
|
// header comment for why (plain fixed-length typed arrays are ~2.5x faster per-access than the
|
|
43
43
|
// identity-stable resizable-ArrayBuffer alternative this module tried first and reverted; every hot-path
|
|
44
44
|
// entry point calls `_sync()` first -- a single integer compare in the common case, only re-fetching the
|
|
45
45
|
// five column references on the rare epoch mismatch that means a grow() just happened).
|
|
46
46
|
let _speed = _pool.column('speed'), _arriveRadius = _pool.column('arriveRadius'), _separation = _pool.column('separation')
|
|
47
|
-
let _clampToTerrain = _pool.column('clampToTerrain'), _yOffset = _pool.column('yOffset')
|
|
47
|
+
let _clampToTerrain = _pool.column('clampToTerrain'), _yOffset = _pool.column('yOffset'), _useNavCost = _pool.column('useNavCost')
|
|
48
48
|
let _epoch = _pool.epoch
|
|
49
49
|
function _sync() {
|
|
50
50
|
if (_epoch === _pool.epoch) return
|
|
51
51
|
_speed = _pool.column('speed'); _arriveRadius = _pool.column('arriveRadius'); _separation = _pool.column('separation')
|
|
52
|
-
_clampToTerrain = _pool.column('clampToTerrain'); _yOffset = _pool.column('yOffset')
|
|
52
|
+
_clampToTerrain = _pool.column('clampToTerrain'); _yOffset = _pool.column('yOffset'); _useNavCost = _pool.column('useNavCost')
|
|
53
53
|
_epoch = _pool.epoch
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -79,6 +79,10 @@ function _stepImpl(slot, appCtx, from, target, dt, peers) {
|
|
|
79
79
|
const dist = Math.hypot(vx, vz)
|
|
80
80
|
let sp = speed
|
|
81
81
|
if (dist <= arriveRadius) sp = dist > 1e-4 ? speed * (dist / arriveRadius) : 0 // arrive: ease to a stop
|
|
82
|
+
if (_useNavCost[slot] === 1 && typeof appCtx.navCostAt === 'function' && dist > 1e-4) {
|
|
83
|
+
const cost = appCtx.navCostAt(fx, fz)
|
|
84
|
+
if (typeof cost === 'number' && Number.isFinite(cost) && cost > 0) sp = sp / cost
|
|
85
|
+
}
|
|
82
86
|
if (dist > 1e-4) { vx = (vx / dist) * sp; vz = (vz / dist) * sp } else { vx = 0; vz = 0 }
|
|
83
87
|
// separation: sum push-away from nearby peers
|
|
84
88
|
if (separation > 0 && peers && peers.length) {
|
|
@@ -106,6 +110,7 @@ export function defineSteering(spec = {}, appCtx) {
|
|
|
106
110
|
_separation[slot] = spec.separation ?? 0
|
|
107
111
|
_clampToTerrain[slot] = spec.clampToTerrain ? 1 : 0
|
|
108
112
|
_yOffset[slot] = spec.yOffset ?? 0
|
|
113
|
+
_useNavCost[slot] = spec.useNavCost ? 1 : 0
|
|
109
114
|
let _disposed = false
|
|
110
115
|
|
|
111
116
|
const steering = {
|
package/apps/combat-bot/index.js
CHANGED
|
@@ -36,7 +36,7 @@ export default {
|
|
|
36
36
|
const home = [...ctx.entity.position]
|
|
37
37
|
ctx.entity.custom = { ...(ctx.entity.custom || {}), mesh: 'capsule', color: c.color ?? '#dd3344', sx: 0.5, sy: 1.7, sz: 0.5, _isBot: true }
|
|
38
38
|
ctx.state.health = ctx.defineHealth({ max: c.health ?? 100, invulnMs: 300, onDeath: () => _onDeath(ctx) })
|
|
39
|
-
ctx.state.steering = ctx.defineSteering({ speed: c.speed ?? 3.5, arriveRadius: 1, clampToTerrain: true, yOffset: 0.9 })
|
|
39
|
+
ctx.state.steering = ctx.defineSteering({ speed: c.speed ?? 3.5, arriveRadius: 1, clampToTerrain: true, yOffset: 0.9, useNavCost: true })
|
|
40
40
|
ctx.state.weapon = ctx.defineWeapon({ damage: c.damage ?? 20, range: c.range ?? 80, fireRateMs: c.fireRateMs ?? 250, magazine: 0 })
|
|
41
41
|
ctx.state.weaponRange = c.range ?? 80
|
|
42
42
|
ctx.state.home = home
|
package/package.json
CHANGED
package/src/apps/AppContext.js
CHANGED
|
@@ -398,6 +398,17 @@ export class AppContext {
|
|
|
398
398
|
return this._runtime._physics?.terrainHeightAt(x, z) ?? null
|
|
399
399
|
}
|
|
400
400
|
|
|
401
|
+
terrainKindAt(x, z) {
|
|
402
|
+
return this._runtime._physics?._terrainStreamer?.splineCarve?.kindAt(x, z) ?? null
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
navCostAt(x, z) {
|
|
406
|
+
const kind = this.terrainKindAt(x, z)
|
|
407
|
+
if (kind === 'road') return 0.5
|
|
408
|
+
if (kind === 'river') return 3
|
|
409
|
+
return 1
|
|
410
|
+
}
|
|
411
|
+
|
|
401
412
|
// Real per-world sea-level Y, in the SAME local scene-space entity.position[1]/terrainHeightAt already
|
|
402
413
|
// use. Reuses the identical formula the client's underwater fog-tint shader splices in at terrain-ready
|
|
403
414
|
// (client/core/UnderwaterTint.js setSeaLevelY, fed by client/app.js: `(f.offsetY||0) - (f.anchorHeight||0)`)
|
|
@@ -149,11 +149,15 @@ export function createSplineCarveLayer() {
|
|
|
149
149
|
const cz0 = Math.floor((sz - halfWidth) / CELL_M), cz1 = Math.ceil((sz + halfWidth) / CELL_M)
|
|
150
150
|
for (let cz = cz0; cz <= cz1; cz++) {
|
|
151
151
|
for (let cx = cx0; cx <= cx1; cx++) {
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
const
|
|
152
|
+
const cMinX = cx * CELL_M, cMinZ = cz * CELL_M
|
|
153
|
+
const nearestX = Math.max(cMinX, Math.min(sx, cMinX + CELL_M))
|
|
154
|
+
const nearestZ = Math.max(cMinZ, Math.min(sz, cMinZ + CELL_M))
|
|
155
|
+
const d = Math.hypot(nearestX - sx, nearestZ - sz)
|
|
155
156
|
if (d > halfWidth) continue
|
|
156
|
-
const
|
|
157
|
+
const ccx = cMinX + CELL_M * 0.5, ccz = cMinZ + CELL_M * 0.5
|
|
158
|
+
const dCenter = Math.hypot(ccx - sx, ccz - sz)
|
|
159
|
+
const falloffDist = Math.min(dCenter, halfWidth)
|
|
160
|
+
const falloff = 0.5 * (1 + Math.cos((falloffDist / halfWidth) * Math.PI))
|
|
157
161
|
const key = cellKey(cx, cz)
|
|
158
162
|
const base = typeof baseHeightFn === 'function' ? baseHeightFn(ccx, ccz) : null
|
|
159
163
|
const targetDelta = Number.isFinite(base) ? -depth * falloff : 0
|
|
@@ -386,5 +386,6 @@ export async function setupTerrainStreaming({ physics, playerManager, worldDef,
|
|
|
386
386
|
const c = streamer.center || (getCenter ? getCenter() : null) || tcfg.center || [0, 0]
|
|
387
387
|
await streamer._rebuild(c[0], c[1])
|
|
388
388
|
}
|
|
389
|
+
physics._terrainStreamer = streamer
|
|
389
390
|
return streamer
|
|
390
391
|
}
|