spoint 0.1.82 → 0.1.84
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/maps/aim_kosova_ak47.glb +0 -0
- package/apps/maps/aim_kosova_battle.glb +0 -0
- package/apps/maps/aim_kosova_famas.glb +0 -0
- package/apps/maps/aim_sillos.glb +0 -0
- package/apps/maps/awp_india_ks.glb +0 -0
- package/apps/maps/awp_kosova.glb +0 -0
- package/apps/maps/awp_kosova_battle.glb +0 -0
- package/apps/maps/awp_kosovo_trainstation.glb +0 -0
- package/apps/maps/de_dolc.glb +0 -0
- package/apps/maps/de_dust2_kosovo.glb +0 -0
- package/apps/maps/de_gash.glb +0 -0
- package/apps/maps/de_kosovo.glb +0 -0
- package/apps/maps/de_kps.glb +0 -0
- package/apps/maps/de_pub2_r3.glb +0 -0
- package/apps/maps/deathrun_kosova.glb +0 -0
- package/apps/maps/fy_osama_house.glb +0 -0
- package/apps/prop-dynamic/index.js +13 -0
- package/apps/props/car1.glb +0 -0
- package/apps/props/car2.glb +0 -0
- package/apps/props/dynamic/3dPrinter1.glb +0 -0
- package/apps/props/dynamic/3dPrinter2.glb +0 -0
- package/apps/props/dynamic/AirConditioner1.glb +0 -0
- package/apps/props/dynamic/AirConditioner2.glb +0 -0
- package/apps/props/dynamic/AirConditioner3.glb +0 -0
- package/apps/props/dynamic/AirConditioner4.glb +0 -0
- package/apps/props/dynamic/ArmourToolBox1.glb +0 -0
- package/apps/props/dynamic/ArmourToolBox2.glb +0 -0
- package/apps/props/dynamic/ArmourToolBox3.glb +0 -0
- package/apps/props/dynamic/ArmourToolMan.glb +0 -0
- package/apps/props/dynamic/Atm1.glb +0 -0
- package/apps/props/dynamic/Atm2.glb +0 -0
- package/apps/props/dynamic/Atm3.glb +0 -0
- package/apps/props/dynamic/Atm4.glb +0 -0
- package/apps/props/dynamic/Ball1.glb +0 -0
- package/apps/props/dynamic/BeerBottle1.glb +0 -0
- package/apps/props/dynamic/BeerBottle2.glb +0 -0
- package/apps/props/dynamic/BeerBottle3.glb +0 -0
- package/apps/props/dynamic/BeerBottle4.glb +0 -0
- package/apps/props/dynamic/BreakRoomChair1.glb +0 -0
- package/apps/props/dynamic/BreakRoomChair2.glb +0 -0
- package/apps/props/dynamic/BreakRoomChair3.glb +0 -0
- package/apps/props/dynamic/BreakRoomChair4.glb +0 -0
- package/apps/props/dynamic/BreakRoomCouch1.glb +0 -0
- package/apps/props/dynamic/BreakRoomCouch2.glb +0 -0
- package/apps/props/dynamic/BreakRoomCouch3.glb +0 -0
- package/apps/props/dynamic/BreakRoomCouch4.glb +0 -0
- package/apps/props/dynamic/BreakRoomCouch5.glb +0 -0
- package/apps/props/dynamic/BreakRoomCouch6.glb +0 -0
- package/apps/props/dynamic/BreakRoomCouch7.glb +0 -0
- package/apps/props/dynamic/BreakRoomTable1.glb +0 -0
- package/apps/props/dynamic/BreakRoomTable2.glb +0 -0
- package/apps/props/dynamic/BrokenBeerBottles1.glb +0 -0
- package/apps/props/dynamic/BrokenBeerBottles2.glb +0 -0
- package/apps/props/dynamic/BrokenOfficeChair1.glb +0 -0
- package/apps/props/dynamic/BrokenOfficeChair2.glb +0 -0
- package/apps/props/dynamic/BrokenWaterCooler1.glb +0 -0
- package/apps/props/dynamic/BrokenWaterCooler2.glb +0 -0
- package/apps/props/dynamic/CanMan1.glb +0 -0
- package/apps/props/dynamic/CanMan2.glb +0 -0
- package/apps/props/dynamic/CanMan3.glb +0 -0
- package/apps/props/dynamic/CashRegister1.glb +0 -0
- package/apps/props/dynamic/CashRegister2.glb +0 -0
- package/apps/props/dynamic/CashRegister3.glb +0 -0
- package/apps/props/dynamic/ComfyChairs1.glb +0 -0
- package/apps/props/dynamic/CrushedGarbageCan1.glb +0 -0
- package/apps/props/dynamic/CrushedGarbageCan2.glb +0 -0
- package/apps/props/dynamic/CrushedGarbageCan3.glb +0 -0
- package/apps/props/dynamic/CrushedGarbageCan4.glb +0 -0
- package/apps/props/dynamic/DinnerTable1.glb +0 -0
- package/apps/props/dynamic/DinnerTable2.glb +0 -0
- package/apps/props/dynamic/DinnerTable3.glb +0 -0
- package/apps/props/dynamic/dj_mixer_baeeec4e_v1.glb +0 -0
- package/apps/props/dynamic/dj_mixer_baeeec4e_v2.glb +0 -0
- package/apps/props/dynamic/dj_mixer_baeeec4e_v3.glb +0 -0
- package/apps/props/dynamic/dj_mixer_baeeec4e_v4.glb +0 -0
- package/apps/props/dynamic/fancy_reception_desk_58fde71d_v1.glb +0 -0
- package/apps/props/dynamic/fancy_reception_desk_58fde71d_v2.glb +0 -0
- package/apps/props/dynamic/fancy_reception_desk_58fde71d_v3.glb +0 -0
- package/apps/props/dynamic/fancy_reception_desk_58fde71d_v4.glb +0 -0
- package/apps/props/dynamic/heavy_machinery__crane_from_junk_yard_with_magnet_for_lifting_cars_db884752_v1.glb +0 -0
- package/apps/props/dynamic/heavy_machinery__crane_from_junk_yard_with_magnet_for_lifting_cars_db884752_v2.glb +0 -0
- package/apps/props/dynamic/heavy_machinery__crane_from_junk_yard_with_magnet_for_lifting_cars_db884752_v3.glb +0 -0
- package/apps/props/dynamic/hi-fi_sound_system_2a2cc620_v1.glb +0 -0
- package/apps/props/dynamic/hi-fi_sound_system_2a2cc620_v2.glb +0 -0
- package/apps/props/dynamic/hi-fi_sound_system_2a2cc620_v3.glb +0 -0
- package/apps/props/dynamic/hi-fi_sound_system_2a2cc620_v4.glb +0 -0
- package/apps/props/dynamic/index.js +21 -0
- package/apps/props/dynamic/industrial_pipe_cb740c0c_v1.glb +0 -0
- package/apps/props/dynamic/industrial_pipe_cb740c0c_v2.glb +0 -0
- package/apps/props/dynamic/industrial_pipe_cb740c0c_v3.glb +0 -0
- package/apps/props/dynamic/industrial_pipe_cb740c0c_v4.glb +0 -0
- package/apps/props/dynamic/l-shaped_industrial_pipe_3b570c7e_v1.glb +0 -0
- package/apps/props/dynamic/l-shaped_industrial_pipe_3b570c7e_v2.glb +0 -0
- package/apps/props/dynamic/l-shaped_industrial_pipe_3b570c7e_v3.glb +0 -0
- package/apps/props/dynamic/l-shaped_industrial_pipe_3b570c7e_v4.glb +0 -0
- package/apps/props/dynamic/l-shaped_industrial_pipe_f7fd8524_v1.glb +0 -0
- package/apps/props/dynamic/l-shaped_industrial_pipe_f7fd8524_v2.glb +0 -0
- package/apps/props/dynamic/l-shaped_industrial_pipe_f7fd8524_v3.glb +0 -0
- package/apps/props/dynamic/l-shaped_industrial_pipe_f7fd8524_v4.glb +0 -0
- package/apps/props/dynamic/night_club_speakers_9155e359_v1.glb +0 -0
- package/apps/props/dynamic/old_cheap_couch_with_a_bad_floral_pattern_53278f1b_v1.glb +0 -0
- package/apps/props/dynamic/old_cheap_couch_with_a_bad_floral_pattern_53278f1b_v2.glb +0 -0
- package/apps/props/dynamic/old_cheap_couch_with_a_bad_floral_pattern_53278f1b_v3.glb +0 -0
- package/apps/props/dynamic/old_cheap_couch_with_a_bad_floral_pattern_53278f1b_v4.glb +0 -0
- package/apps/props/dynamic/server_rack_03b09d1f_v1.glb +0 -0
- package/apps/props/dynamic/server_rack_03b09d1f_v2.glb +0 -0
- package/apps/props/dynamic/server_rack_03b09d1f_v3.glb +0 -0
- package/apps/props/dynamic/server_rack_03b09d1f_v4.glb +0 -0
- package/apps/props/dynamic/server_rack_c2999a18_v1.glb +0 -0
- package/apps/props/dynamic/server_rack_c2999a18_v2.glb +0 -0
- package/apps/props/dynamic/server_rack_c2999a18_v3.glb +0 -0
- package/apps/props/dynamic/server_rack_c2999a18_v4.glb +0 -0
- package/apps/props/dynamic/shop_counter_f668a712_v1.glb +0 -0
- package/apps/props/dynamic/warehouse_crate_6e8a0927_v1.glb +0 -0
- package/apps/props/dynamic/warehouse_crate_6e8a0927_v2.glb +0 -0
- package/apps/props/dynamic/warehouse_crate_6e8a0927_v4.glb +0 -0
- package/apps/props/dynamic/water_tank_c27c18f7_v1.glb +0 -0
- package/apps/props/dynamic/water_tank_c27c18f7_v2.glb +0 -0
- package/apps/props/dynamic/water_tank_c27c18f7_v3.glb +0 -0
- package/apps/props/dynamic/water_tank_c27c18f7_v4.glb +0 -0
- package/apps/props/fountain.glb +0 -0
- package/apps/props/index.js +24 -0
- package/apps/props/rifle.glb +0 -0
- package/apps/props/rock1.glb +0 -0
- package/apps/props/rock2.glb +0 -0
- package/apps/tps-game/schwust.glb +0 -0
- package/apps/world/index.js +138 -3
- package/client/app.js +72 -5
- package/package.json +1 -1
- package/src/apps/AppRuntime.js +31 -3
- package/src/netcode/SnapshotEncoder.js +6 -2
- package/src/physics/GLBLoader.js +6 -0
- package/src/physics/World.js +4 -0
- package/src/sdk/BotHarness.js +5 -5
- package/src/sdk/ServerAPI.js +3 -1
- package/src/sdk/ServerHandlers.js +7 -3
- package/src/sdk/TickHandler.js +13 -9
- package/src/sdk/server.js +2 -1
- package/src/stage/Stage.js +1 -4
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
server: {
|
|
3
|
+
async setup(ctx) {
|
|
4
|
+
ctx.physics.setDynamic(true)
|
|
5
|
+
ctx.physics.addBoxCollider(0.5, 0.5, 0.5)
|
|
6
|
+
}
|
|
7
|
+
},
|
|
8
|
+
client: {
|
|
9
|
+
render(ctx) {
|
|
10
|
+
return { position: ctx.entity.position, rotation: ctx.entity.rotation, scale: ctx.entity.scale, model: ctx.entity.model }
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
server: {
|
|
3
|
+
async setup(ctx) {
|
|
4
|
+
ctx.physics.setDynamic(true)
|
|
5
|
+
if (ctx.entity.model) {
|
|
6
|
+
try {
|
|
7
|
+
await ctx.physics.addConvexFromModel(0)
|
|
8
|
+
} catch (e) {
|
|
9
|
+
ctx.physics.addBoxCollider(0.5, 0.5, 0.5)
|
|
10
|
+
}
|
|
11
|
+
} else {
|
|
12
|
+
ctx.physics.addBoxCollider(0.5, 0.5, 0.5)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
client: {
|
|
17
|
+
render(ctx) {
|
|
18
|
+
return { position: ctx.entity.position, rotation: ctx.entity.rotation, scale: ctx.entity.scale, model: ctx.entity.model }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
server: {
|
|
3
|
+
async setup(ctx) {
|
|
4
|
+
ctx.physics.setStatic(true)
|
|
5
|
+
if (ctx.entity.model) {
|
|
6
|
+
try {
|
|
7
|
+
await ctx.physics.addConvexFromModel(0)
|
|
8
|
+
} catch (e) {
|
|
9
|
+
ctx.physics.addBoxCollider(1, 1, 1)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
client: {
|
|
15
|
+
render(ctx) {
|
|
16
|
+
return {
|
|
17
|
+
position: ctx.entity.position,
|
|
18
|
+
rotation: ctx.entity.rotation,
|
|
19
|
+
scale: ctx.entity.scale,
|
|
20
|
+
model: ctx.entity.model
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/apps/world/index.js
CHANGED
|
@@ -2,6 +2,7 @@ export default {
|
|
|
2
2
|
port: 3001,
|
|
3
3
|
tickRate: 128,
|
|
4
4
|
gravity: [0, -9.81, 0],
|
|
5
|
+
relevanceRadius: 150,
|
|
5
6
|
movement: {
|
|
6
7
|
maxSpeed: 4.0,
|
|
7
8
|
groundAccel: 10.0,
|
|
@@ -58,8 +59,142 @@ export default {
|
|
|
58
59
|
fadeTime: 0.15
|
|
59
60
|
},
|
|
60
61
|
entities: [
|
|
61
|
-
{ id: '
|
|
62
|
+
{ id: 'env-schwust', model: './apps/tps-game/schwust.glb', position: [0, 0, 0], app: 'environment' },
|
|
63
|
+
{ id: 'env-kosova', model: './apps/maps/aim_kosova_ak47.glb', position: [200, 0, 0], app: 'environment' },
|
|
64
|
+
{ id: 'env-sillos', model: './apps/maps/aim_sillos.glb', position: [400, 0, 0], app: 'environment' },
|
|
65
|
+
{ id: 'env-dust2', model: './apps/maps/de_dust2_kosovo.glb', position: [600, 0, 0], app: 'environment' },
|
|
66
|
+
{ id: 'env-gash', model: './apps/maps/de_gash.glb', position: [800, 0, 0], app: 'environment' },
|
|
67
|
+
|
|
68
|
+
{ id: 'prop-rock1-a', model: './apps/props/rock1.glb', position: [-10, 5, -10], app: 'props' },
|
|
69
|
+
{ id: 'prop-rock1-b', model: './apps/props/rock1.glb', position: [10, 5, 10], app: 'props' },
|
|
70
|
+
{ id: 'prop-rock2-a', model: './apps/props/rock2.glb', position: [-15, 5, 5], app: 'props' },
|
|
71
|
+
{ id: 'prop-car1-a', model: './apps/props/car1.glb', position: [-5, 5, -20], app: 'props' },
|
|
72
|
+
{ id: 'prop-car2-a', model: './apps/props/car2.glb', position: [15, 5, -15], app: 'props' },
|
|
73
|
+
{ id: 'prop-fountain', model: './apps/props/fountain.glb',position: [0, 5, 15], app: 'props' },
|
|
74
|
+
|
|
75
|
+
{ id: 'prop-rock1-c', model: './apps/props/rock1.glb', position: [190, 5, -10], app: 'props' },
|
|
76
|
+
{ id: 'prop-rock2-b', model: './apps/props/rock2.glb', position: [210, 5, 10], app: 'props' },
|
|
77
|
+
{ id: 'prop-car1-b', model: './apps/props/car1.glb', position: [205, 5, -15], app: 'props' },
|
|
78
|
+
|
|
79
|
+
{ id: 'prop-rock1-d', model: './apps/props/rock1.glb', position: [390, 5, -10], app: 'props' },
|
|
80
|
+
{ id: 'prop-car2-b', model: './apps/props/car2.glb', position: [415, 5, 10], app: 'props' },
|
|
81
|
+
|
|
82
|
+
{ id: 'prop-rock2-c', model: './apps/props/rock2.glb', position: [590, 5, -10], app: 'props' },
|
|
83
|
+
{ id: 'prop-car1-c', model: './apps/props/car1.glb', position: [610, 5, 5], app: 'props' },
|
|
84
|
+
|
|
85
|
+
{ id: 'prop-rock1-e', model: './apps/props/rock1.glb', position: [790, 5, -10], app: 'props' },
|
|
86
|
+
{ id: 'prop-car2-c', model: './apps/props/car2.glb', position: [810, 5, 5], app: 'props' },
|
|
87
|
+
|
|
88
|
+
{ id: 'dyn-ground', app: 'box-static', position: [66, -1, 236], config: { hx: 80, hy: 1, hz: 80, color: 0x888888 } },
|
|
89
|
+
{ id: 'dyn-0', model: './apps/props/dynamic/3dPrinter1.glb', position: [30, 30, 200], app: 'prop-dynamic' },
|
|
90
|
+
{ id: 'dyn-1', model: './apps/props/dynamic/3dPrinter2.glb', position: [38, 30, 200], app: 'prop-dynamic' },
|
|
91
|
+
{ id: 'dyn-2', model: './apps/props/dynamic/AirConditioner1.glb', position: [46, 30, 200], app: 'prop-dynamic' },
|
|
92
|
+
{ id: 'dyn-3', model: './apps/props/dynamic/AirConditioner2.glb', position: [54, 30, 200], app: 'prop-dynamic' },
|
|
93
|
+
{ id: 'dyn-4', model: './apps/props/dynamic/AirConditioner3.glb', position: [62, 30, 200], app: 'prop-dynamic' },
|
|
94
|
+
{ id: 'dyn-5', model: './apps/props/dynamic/AirConditioner4.glb', position: [70, 30, 200], app: 'prop-dynamic' },
|
|
95
|
+
{ id: 'dyn-6', model: './apps/props/dynamic/ArmourToolBox1.glb', position: [78, 30, 200], app: 'prop-dynamic' },
|
|
96
|
+
{ id: 'dyn-7', model: './apps/props/dynamic/ArmourToolBox2.glb', position: [86, 30, 200], app: 'prop-dynamic' },
|
|
97
|
+
{ id: 'dyn-8', model: './apps/props/dynamic/ArmourToolBox3.glb', position: [94, 30, 200], app: 'prop-dynamic' },
|
|
98
|
+
{ id: 'dyn-9', model: './apps/props/dynamic/ArmourToolMan.glb', position: [102, 30, 200], app: 'prop-dynamic' },
|
|
99
|
+
{ id: 'dyn-10', model: './apps/props/dynamic/Atm1.glb', position: [30, 33, 208], app: 'prop-dynamic' },
|
|
100
|
+
{ id: 'dyn-11', model: './apps/props/dynamic/Atm2.glb', position: [38, 33, 208], app: 'prop-dynamic' },
|
|
101
|
+
{ id: 'dyn-12', model: './apps/props/dynamic/Atm3.glb', position: [46, 33, 208], app: 'prop-dynamic' },
|
|
102
|
+
{ id: 'dyn-13', model: './apps/props/dynamic/Atm4.glb', position: [54, 33, 208], app: 'prop-dynamic' },
|
|
103
|
+
{ id: 'dyn-14', model: './apps/props/dynamic/Ball1.glb', position: [62, 33, 208], app: 'prop-dynamic' },
|
|
104
|
+
{ id: 'dyn-15', model: './apps/props/dynamic/BeerBottle1.glb', position: [70, 33, 208], app: 'prop-dynamic' },
|
|
105
|
+
{ id: 'dyn-16', model: './apps/props/dynamic/BeerBottle2.glb', position: [78, 33, 208], app: 'prop-dynamic' },
|
|
106
|
+
{ id: 'dyn-17', model: './apps/props/dynamic/BeerBottle3.glb', position: [86, 33, 208], app: 'prop-dynamic' },
|
|
107
|
+
{ id: 'dyn-18', model: './apps/props/dynamic/BeerBottle4.glb', position: [94, 33, 208], app: 'prop-dynamic' },
|
|
108
|
+
{ id: 'dyn-19', model: './apps/props/dynamic/BreakRoomChair1.glb', position: [102, 33, 208], app: 'prop-dynamic' },
|
|
109
|
+
{ id: 'dyn-20', model: './apps/props/dynamic/BreakRoomChair2.glb', position: [30, 36, 216], app: 'prop-dynamic' },
|
|
110
|
+
{ id: 'dyn-21', model: './apps/props/dynamic/BreakRoomChair3.glb', position: [38, 36, 216], app: 'prop-dynamic' },
|
|
111
|
+
{ id: 'dyn-22', model: './apps/props/dynamic/BreakRoomChair4.glb', position: [46, 36, 216], app: 'prop-dynamic' },
|
|
112
|
+
{ id: 'dyn-23', model: './apps/props/dynamic/BreakRoomCouch1.glb', position: [54, 36, 216], app: 'prop-dynamic' },
|
|
113
|
+
{ id: 'dyn-24', model: './apps/props/dynamic/BreakRoomCouch2.glb', position: [62, 36, 216], app: 'prop-dynamic' },
|
|
114
|
+
{ id: 'dyn-25', model: './apps/props/dynamic/BreakRoomCouch3.glb', position: [70, 36, 216], app: 'prop-dynamic' },
|
|
115
|
+
{ id: 'dyn-26', model: './apps/props/dynamic/BreakRoomCouch4.glb', position: [78, 36, 216], app: 'prop-dynamic' },
|
|
116
|
+
{ id: 'dyn-27', model: './apps/props/dynamic/BreakRoomCouch5.glb', position: [86, 36, 216], app: 'prop-dynamic' },
|
|
117
|
+
{ id: 'dyn-28', model: './apps/props/dynamic/BreakRoomCouch6.glb', position: [94, 36, 216], app: 'prop-dynamic' },
|
|
118
|
+
{ id: 'dyn-29', model: './apps/props/dynamic/BreakRoomCouch7.glb', position: [102, 36, 216], app: 'prop-dynamic' },
|
|
119
|
+
{ id: 'dyn-30', model: './apps/props/dynamic/BreakRoomTable1.glb', position: [30, 39, 224], app: 'prop-dynamic' },
|
|
120
|
+
{ id: 'dyn-31', model: './apps/props/dynamic/BreakRoomTable2.glb', position: [38, 39, 224], app: 'prop-dynamic' },
|
|
121
|
+
{ id: 'dyn-32', model: './apps/props/dynamic/BrokenBeerBottles1.glb', position: [46, 39, 224], app: 'prop-dynamic' },
|
|
122
|
+
{ id: 'dyn-33', model: './apps/props/dynamic/BrokenBeerBottles2.glb', position: [54, 39, 224], app: 'prop-dynamic' },
|
|
123
|
+
{ id: 'dyn-34', model: './apps/props/dynamic/BrokenOfficeChair1.glb', position: [62, 39, 224], app: 'prop-dynamic' },
|
|
124
|
+
{ id: 'dyn-35', model: './apps/props/dynamic/BrokenOfficeChair2.glb', position: [70, 39, 224], app: 'prop-dynamic' },
|
|
125
|
+
{ id: 'dyn-36', model: './apps/props/dynamic/BrokenWaterCooler1.glb', position: [78, 39, 224], app: 'prop-dynamic' },
|
|
126
|
+
{ id: 'dyn-37', model: './apps/props/dynamic/BrokenWaterCooler2.glb', position: [86, 39, 224], app: 'prop-dynamic' },
|
|
127
|
+
{ id: 'dyn-38', model: './apps/props/dynamic/CanMan1.glb', position: [94, 39, 224], app: 'prop-dynamic' },
|
|
128
|
+
{ id: 'dyn-39', model: './apps/props/dynamic/CanMan2.glb', position: [102, 39, 224], app: 'prop-dynamic' },
|
|
129
|
+
{ id: 'dyn-40', model: './apps/props/dynamic/CanMan3.glb', position: [30, 42, 232], app: 'prop-dynamic' },
|
|
130
|
+
{ id: 'dyn-41', model: './apps/props/dynamic/CashRegister1.glb', position: [38, 42, 232], app: 'prop-dynamic' },
|
|
131
|
+
{ id: 'dyn-42', model: './apps/props/dynamic/CashRegister2.glb', position: [46, 42, 232], app: 'prop-dynamic' },
|
|
132
|
+
{ id: 'dyn-43', model: './apps/props/dynamic/CashRegister3.glb', position: [54, 42, 232], app: 'prop-dynamic' },
|
|
133
|
+
{ id: 'dyn-44', model: './apps/props/dynamic/ComfyChairs1.glb', position: [62, 42, 232], app: 'prop-dynamic' },
|
|
134
|
+
{ id: 'dyn-45', model: './apps/props/dynamic/CrushedGarbageCan1.glb', position: [70, 42, 232], app: 'prop-dynamic' },
|
|
135
|
+
{ id: 'dyn-46', model: './apps/props/dynamic/CrushedGarbageCan2.glb', position: [78, 42, 232], app: 'prop-dynamic' },
|
|
136
|
+
{ id: 'dyn-47', model: './apps/props/dynamic/CrushedGarbageCan3.glb', position: [86, 42, 232], app: 'prop-dynamic' },
|
|
137
|
+
{ id: 'dyn-48', model: './apps/props/dynamic/CrushedGarbageCan4.glb', position: [94, 42, 232], app: 'prop-dynamic' },
|
|
138
|
+
{ id: 'dyn-49', model: './apps/props/dynamic/DinnerTable1.glb', position: [102, 42, 232], app: 'prop-dynamic' },
|
|
139
|
+
{ id: 'dyn-50', model: './apps/props/dynamic/DinnerTable2.glb', position: [30, 45, 240], app: 'prop-dynamic' },
|
|
140
|
+
{ id: 'dyn-51', model: './apps/props/dynamic/DinnerTable3.glb', position: [38, 45, 240], app: 'prop-dynamic' },
|
|
141
|
+
{ id: 'dyn-52', model: './apps/props/dynamic/dj_mixer_baeeec4e_v1.glb', position: [46, 45, 240], app: 'prop-dynamic' },
|
|
142
|
+
{ id: 'dyn-53', model: './apps/props/dynamic/dj_mixer_baeeec4e_v2.glb', position: [54, 45, 240], app: 'prop-dynamic' },
|
|
143
|
+
{ id: 'dyn-54', model: './apps/props/dynamic/dj_mixer_baeeec4e_v3.glb', position: [62, 45, 240], app: 'prop-dynamic' },
|
|
144
|
+
{ id: 'dyn-55', model: './apps/props/dynamic/dj_mixer_baeeec4e_v4.glb', position: [70, 45, 240], app: 'prop-dynamic' },
|
|
145
|
+
{ id: 'dyn-56', model: './apps/props/dynamic/fancy_reception_desk_58fde71d_v1.glb', position: [78, 45, 240], app: 'prop-dynamic' },
|
|
146
|
+
{ id: 'dyn-57', model: './apps/props/dynamic/fancy_reception_desk_58fde71d_v2.glb', position: [86, 45, 240], app: 'prop-dynamic' },
|
|
147
|
+
{ id: 'dyn-58', model: './apps/props/dynamic/fancy_reception_desk_58fde71d_v3.glb', position: [94, 45, 240], app: 'prop-dynamic' },
|
|
148
|
+
{ id: 'dyn-59', model: './apps/props/dynamic/fancy_reception_desk_58fde71d_v4.glb', position: [102, 45, 240], app: 'prop-dynamic' },
|
|
149
|
+
{ id: 'dyn-60', model: './apps/props/dynamic/heavy_machinery__crane_from_junk_yard_with_magnet_for_lifting_cars_db884752_v1.glb', position: [30, 48, 248], app: 'prop-dynamic' },
|
|
150
|
+
{ id: 'dyn-61', model: './apps/props/dynamic/heavy_machinery__crane_from_junk_yard_with_magnet_for_lifting_cars_db884752_v2.glb', position: [38, 48, 248], app: 'prop-dynamic' },
|
|
151
|
+
{ id: 'dyn-62', model: './apps/props/dynamic/heavy_machinery__crane_from_junk_yard_with_magnet_for_lifting_cars_db884752_v3.glb', position: [46, 48, 248], app: 'prop-dynamic' },
|
|
152
|
+
{ id: 'dyn-63', model: './apps/props/dynamic/hi-fi_sound_system_2a2cc620_v1.glb', position: [54, 48, 248], app: 'prop-dynamic' },
|
|
153
|
+
{ id: 'dyn-64', model: './apps/props/dynamic/hi-fi_sound_system_2a2cc620_v2.glb', position: [62, 48, 248], app: 'prop-dynamic' },
|
|
154
|
+
{ id: 'dyn-65', model: './apps/props/dynamic/hi-fi_sound_system_2a2cc620_v3.glb', position: [70, 48, 248], app: 'prop-dynamic' },
|
|
155
|
+
{ id: 'dyn-66', model: './apps/props/dynamic/hi-fi_sound_system_2a2cc620_v4.glb', position: [78, 48, 248], app: 'prop-dynamic' },
|
|
156
|
+
{ id: 'dyn-67', model: './apps/props/dynamic/industrial_pipe_cb740c0c_v1.glb', position: [86, 48, 248], app: 'prop-dynamic' },
|
|
157
|
+
{ id: 'dyn-68', model: './apps/props/dynamic/industrial_pipe_cb740c0c_v2.glb', position: [94, 48, 248], app: 'prop-dynamic' },
|
|
158
|
+
{ id: 'dyn-69', model: './apps/props/dynamic/industrial_pipe_cb740c0c_v3.glb', position: [102, 48, 248], app: 'prop-dynamic' },
|
|
159
|
+
{ id: 'dyn-70', model: './apps/props/dynamic/industrial_pipe_cb740c0c_v4.glb', position: [30, 51, 256], app: 'prop-dynamic' },
|
|
160
|
+
{ id: 'dyn-71', model: './apps/props/dynamic/l-shaped_industrial_pipe_3b570c7e_v1.glb', position: [38, 51, 256], app: 'prop-dynamic' },
|
|
161
|
+
{ id: 'dyn-72', model: './apps/props/dynamic/l-shaped_industrial_pipe_3b570c7e_v2.glb', position: [46, 51, 256], app: 'prop-dynamic' },
|
|
162
|
+
{ id: 'dyn-73', model: './apps/props/dynamic/l-shaped_industrial_pipe_3b570c7e_v3.glb', position: [54, 51, 256], app: 'prop-dynamic' },
|
|
163
|
+
{ id: 'dyn-74', model: './apps/props/dynamic/l-shaped_industrial_pipe_3b570c7e_v4.glb', position: [62, 51, 256], app: 'prop-dynamic' },
|
|
164
|
+
{ id: 'dyn-75', model: './apps/props/dynamic/l-shaped_industrial_pipe_f7fd8524_v1.glb', position: [70, 51, 256], app: 'prop-dynamic' },
|
|
165
|
+
{ id: 'dyn-76', model: './apps/props/dynamic/l-shaped_industrial_pipe_f7fd8524_v2.glb', position: [78, 51, 256], app: 'prop-dynamic' },
|
|
166
|
+
{ id: 'dyn-77', model: './apps/props/dynamic/l-shaped_industrial_pipe_f7fd8524_v3.glb', position: [86, 51, 256], app: 'prop-dynamic' },
|
|
167
|
+
{ id: 'dyn-78', model: './apps/props/dynamic/l-shaped_industrial_pipe_f7fd8524_v4.glb', position: [94, 51, 256], app: 'prop-dynamic' },
|
|
168
|
+
{ id: 'dyn-79', model: './apps/props/dynamic/night_club_speakers_9155e359_v1.glb', position: [102, 51, 256], app: 'prop-dynamic' },
|
|
169
|
+
{ id: 'dyn-80', model: './apps/props/dynamic/old_cheap_couch_with_a_bad_floral_pattern_53278f1b_v1.glb', position: [30, 54, 264], app: 'prop-dynamic' },
|
|
170
|
+
{ id: 'dyn-81', model: './apps/props/dynamic/old_cheap_couch_with_a_bad_floral_pattern_53278f1b_v2.glb', position: [38, 54, 264], app: 'prop-dynamic' },
|
|
171
|
+
{ id: 'dyn-82', model: './apps/props/dynamic/old_cheap_couch_with_a_bad_floral_pattern_53278f1b_v3.glb', position: [46, 54, 264], app: 'prop-dynamic' },
|
|
172
|
+
{ id: 'dyn-83', model: './apps/props/dynamic/old_cheap_couch_with_a_bad_floral_pattern_53278f1b_v4.glb', position: [54, 54, 264], app: 'prop-dynamic' },
|
|
173
|
+
{ id: 'dyn-84', model: './apps/props/dynamic/server_rack_03b09d1f_v1.glb', position: [62, 54, 264], app: 'prop-dynamic' },
|
|
174
|
+
{ id: 'dyn-85', model: './apps/props/dynamic/server_rack_03b09d1f_v2.glb', position: [70, 54, 264], app: 'prop-dynamic' },
|
|
175
|
+
{ id: 'dyn-86', model: './apps/props/dynamic/server_rack_03b09d1f_v3.glb', position: [78, 54, 264], app: 'prop-dynamic' },
|
|
176
|
+
{ id: 'dyn-87', model: './apps/props/dynamic/server_rack_03b09d1f_v4.glb', position: [86, 54, 264], app: 'prop-dynamic' },
|
|
177
|
+
{ id: 'dyn-88', model: './apps/props/dynamic/server_rack_c2999a18_v1.glb', position: [94, 54, 264], app: 'prop-dynamic' },
|
|
178
|
+
{ id: 'dyn-89', model: './apps/props/dynamic/server_rack_c2999a18_v2.glb', position: [102, 54, 264], app: 'prop-dynamic' },
|
|
179
|
+
{ id: 'dyn-90', model: './apps/props/dynamic/server_rack_c2999a18_v3.glb', position: [30, 57, 272], app: 'prop-dynamic' },
|
|
180
|
+
{ id: 'dyn-91', model: './apps/props/dynamic/server_rack_c2999a18_v4.glb', position: [38, 57, 272], app: 'prop-dynamic' },
|
|
181
|
+
{ id: 'dyn-92', model: './apps/props/dynamic/shop_counter_f668a712_v1.glb', position: [46, 57, 272], app: 'prop-dynamic' },
|
|
182
|
+
{ id: 'dyn-93', model: './apps/props/dynamic/warehouse_crate_6e8a0927_v1.glb', position: [54, 57, 272], app: 'prop-dynamic' },
|
|
183
|
+
{ id: 'dyn-94', model: './apps/props/dynamic/warehouse_crate_6e8a0927_v2.glb', position: [62, 57, 272], app: 'prop-dynamic' },
|
|
184
|
+
{ id: 'dyn-95', model: './apps/props/dynamic/warehouse_crate_6e8a0927_v4.glb', position: [70, 57, 272], app: 'prop-dynamic' },
|
|
185
|
+
{ id: 'dyn-96', model: './apps/props/dynamic/water_tank_c27c18f7_v1.glb', position: [78, 57, 272], app: 'prop-dynamic' },
|
|
186
|
+
{ id: 'dyn-97', model: './apps/props/dynamic/water_tank_c27c18f7_v2.glb', position: [86, 57, 272], app: 'prop-dynamic' },
|
|
187
|
+
{ id: 'dyn-98', model: './apps/props/dynamic/water_tank_c27c18f7_v3.glb', position: [94, 57, 272], app: 'prop-dynamic' },
|
|
188
|
+
{ id: 'dyn-99', model: './apps/props/dynamic/water_tank_c27c18f7_v4.glb', position: [102, 57, 272], app: 'prop-dynamic' }
|
|
62
189
|
],
|
|
63
|
-
|
|
64
|
-
|
|
190
|
+
spawnPoints: [
|
|
191
|
+
[10, 5, 10],
|
|
192
|
+
[200, 5, -10],
|
|
193
|
+
[400, 5, -10],
|
|
194
|
+
[610, 5, 5],
|
|
195
|
+
[800, 5, -10]
|
|
196
|
+
],
|
|
197
|
+
spawnPoint: [10, 5, 10],
|
|
198
|
+
playerModel: './apps/tps-game/cleetus.vrm'
|
|
65
199
|
}
|
|
200
|
+
|
package/client/app.js
CHANGED
|
@@ -21,6 +21,37 @@ import { createLoadingScreen } from './createLoadingScreen.js'
|
|
|
21
21
|
import { MobileControls, detectDevice } from './MobileControls.js'
|
|
22
22
|
import { XRControls, createXRButton } from './XRControls.js'
|
|
23
23
|
|
|
24
|
+
function patchGLB(arrayBuffer) {
|
|
25
|
+
try {
|
|
26
|
+
const v = new DataView(arrayBuffer)
|
|
27
|
+
if (v.getUint32(0, true) !== 0x46546C67) return arrayBuffer
|
|
28
|
+
const jsonLen = v.getUint32(12, true)
|
|
29
|
+
const jsonBytes = new Uint8Array(arrayBuffer, 20, jsonLen)
|
|
30
|
+
const json = JSON.parse(new TextDecoder().decode(jsonBytes))
|
|
31
|
+
if (!json.textures) return arrayBuffer
|
|
32
|
+
json.textures = json.textures.map(t => {
|
|
33
|
+
if (t.source === undefined && (!t.extensions || !Object.keys(t.extensions).some(k => t.extensions[k]?.source !== undefined))) {
|
|
34
|
+
return { ...t, source: 0 }
|
|
35
|
+
}
|
|
36
|
+
return t
|
|
37
|
+
})
|
|
38
|
+
const patched = new TextEncoder().encode(JSON.stringify(json))
|
|
39
|
+
const pad = (4 - (patched.length % 4)) % 4
|
|
40
|
+
const out = new ArrayBuffer(12 + 8 + patched.length + pad + (arrayBuffer.byteLength - 20 - jsonLen))
|
|
41
|
+
const ov = new DataView(out)
|
|
42
|
+
const ou = new Uint8Array(out)
|
|
43
|
+
ov.setUint32(0, 0x46546C67, true)
|
|
44
|
+
ov.setUint32(4, v.getUint32(4, true), true)
|
|
45
|
+
ov.setUint32(8, out.byteLength, true)
|
|
46
|
+
ov.setUint32(12, patched.length + pad, true)
|
|
47
|
+
ov.setUint32(16, 0x4E4F534A, true)
|
|
48
|
+
ou.set(patched, 20)
|
|
49
|
+
for (let i = 0; i < pad; i++) ou[20 + patched.length + i] = 0x20
|
|
50
|
+
ou.set(new Uint8Array(arrayBuffer, 20 + jsonLen), 20 + patched.length + pad)
|
|
51
|
+
return out
|
|
52
|
+
} catch (_) { return arrayBuffer }
|
|
53
|
+
}
|
|
54
|
+
|
|
24
55
|
const loadingMgr = new LoadingManager()
|
|
25
56
|
const loadingScreen = createLoadingScreen(loadingMgr)
|
|
26
57
|
loadingMgr.setStage('CONNECTING')
|
|
@@ -705,6 +736,21 @@ let vrmBuffer = null
|
|
|
705
736
|
let animAssets = null
|
|
706
737
|
let assetsReady = null
|
|
707
738
|
let assetsLoaded = false
|
|
739
|
+
const MAX_VRM_CONCURRENT = 6
|
|
740
|
+
let _vrmActive = 0
|
|
741
|
+
const _vrmQueue = []
|
|
742
|
+
function _vrmSlot() {
|
|
743
|
+
if (_vrmActive >= MAX_VRM_CONCURRENT || _vrmQueue.length === 0) return
|
|
744
|
+
_vrmActive++
|
|
745
|
+
const resolve = _vrmQueue.shift()
|
|
746
|
+
resolve()
|
|
747
|
+
}
|
|
748
|
+
function acquireVrmSlot() {
|
|
749
|
+
return new Promise(r => { _vrmQueue.push(r); _vrmSlot() })
|
|
750
|
+
}
|
|
751
|
+
function releaseVrmSlot() {
|
|
752
|
+
_vrmActive--; _vrmSlot()
|
|
753
|
+
}
|
|
708
754
|
|
|
709
755
|
function detectVrmVersion(buffer) {
|
|
710
756
|
try {
|
|
@@ -739,6 +785,8 @@ async function createPlayerVRM(id) {
|
|
|
739
785
|
playerMeshes.set(id, group)
|
|
740
786
|
if (assetsReady) await assetsReady
|
|
741
787
|
if (!vrmBuffer) return group
|
|
788
|
+
await acquireVrmSlot()
|
|
789
|
+
if (!playerMeshes.has(id)) { releaseVrmSlot(); return group }
|
|
742
790
|
try {
|
|
743
791
|
const gltf = await gltfLoader.parseAsync(vrmBuffer.buffer.slice(0), '')
|
|
744
792
|
const vrm = gltf.userData.vrm
|
|
@@ -786,7 +834,7 @@ async function createPlayerVRM(id) {
|
|
|
786
834
|
console.log('[shader] vrm warmup compile done (fallback)')
|
|
787
835
|
})
|
|
788
836
|
}
|
|
789
|
-
} catch (e) { console.error('[vrm]', id, e.message) }
|
|
837
|
+
} catch (e) { console.error('[vrm]', id, e.message) } finally { releaseVrmSlot() }
|
|
790
838
|
return group
|
|
791
839
|
}
|
|
792
840
|
|
|
@@ -979,8 +1027,11 @@ function loadEntityModel(entityId, entityState) {
|
|
|
979
1027
|
const mp = entityState.position; model.position.set(mp[0], mp[1], mp[2])
|
|
980
1028
|
const mr = entityState.rotation; if (mr) model.quaternion.set(mr[0], mr[1], mr[2], mr[3])
|
|
981
1029
|
const colliders = []
|
|
1030
|
+
const SKIP_MATS = new Set(['aaatrigger', '{invisible', 'playerclip', 'clip', 'nodraw', 'trigger', 'sky', 'toolsclip', 'toolsplayerclip', 'toolsnodraw', 'toolsskybox', 'toolstrigger'])
|
|
982
1031
|
model.traverse(c => {
|
|
983
1032
|
if (c.isMesh) {
|
|
1033
|
+
const matName = (c.material?.name || '').toLowerCase()
|
|
1034
|
+
if (SKIP_MATS.has(matName) || SKIP_MATS.has(c.material?.name)) { c.visible = false; return }
|
|
984
1035
|
c.castShadow = true
|
|
985
1036
|
c.receiveShadow = true
|
|
986
1037
|
if (!c.isSkinnedMesh) { c.matrixAutoUpdate = false; c.geometry.computeBoundsTree(); colliders.push(c) }
|
|
@@ -1000,7 +1051,7 @@ function loadEntityModel(entityId, entityState) {
|
|
|
1000
1051
|
}
|
|
1001
1052
|
const onGltfErr = (err) => { console.error('[gltf]', url, err); pendingLoads.delete(entityId); if (firstSnapshotEntityPending.has(entityId)) { firstSnapshotEntityPending.delete(entityId); if (firstSnapshotEntityPending.size === 0) checkAllLoaded() } }
|
|
1002
1053
|
fetchCached(url).then(buf => {
|
|
1003
|
-
gltfLoader.parse(buf.buffer.slice(0), '', onGltfLoad, onGltfErr)
|
|
1054
|
+
gltfLoader.parse(patchGLB(buf.buffer.slice(0)), '', onGltfLoad, onGltfErr)
|
|
1004
1055
|
}).catch(onGltfErr)
|
|
1005
1056
|
}
|
|
1006
1057
|
|
|
@@ -1047,8 +1098,24 @@ const client = new PhysicsNetworkClient({
|
|
|
1047
1098
|
predictionEnabled: true,
|
|
1048
1099
|
smoothInterpolation: true,
|
|
1049
1100
|
onStateUpdate: (state) => {
|
|
1050
|
-
|
|
1051
|
-
|
|
1101
|
+
const myPos = state.players.find(p => p.id === client.playerId)?.position
|
|
1102
|
+
const sorted = myPos ? [...state.players].sort((a, b) => {
|
|
1103
|
+
if (a.id === client.playerId) return -1
|
|
1104
|
+
if (b.id === client.playerId) return 1
|
|
1105
|
+
const da = (a.position[0]-myPos[0])**2+(a.position[1]-myPos[1])**2+(a.position[2]-myPos[2])**2
|
|
1106
|
+
const db = (b.position[0]-myPos[0])**2+(b.position[1]-myPos[1])**2+(b.position[2]-myPos[2])**2
|
|
1107
|
+
return da - db
|
|
1108
|
+
}) : state.players
|
|
1109
|
+
const MAX_VISIBLE_PLAYERS = 32
|
|
1110
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
1111
|
+
const p = sorted[i]
|
|
1112
|
+
if (!playerMeshes.has(p.id)) {
|
|
1113
|
+
if (i < MAX_VISIBLE_PLAYERS) createPlayerVRM(p.id)
|
|
1114
|
+
else { const g = new THREE.Group(); scene.add(g); playerMeshes.set(p.id, g) }
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
for (const [id] of playerMeshes) {
|
|
1118
|
+
if (!state.players.find(p => p.id === id)) removePlayerMesh(id)
|
|
1052
1119
|
}
|
|
1053
1120
|
for (const e of state.entities) {
|
|
1054
1121
|
const mesh = entityMeshes.get(e.id)
|
|
@@ -1079,7 +1146,7 @@ const client = new PhysicsNetworkClient({
|
|
|
1079
1146
|
worldConfig = wd
|
|
1080
1147
|
if (wd.playerModel) initAssets(wd.playerModel.startsWith('./') ? '/' + wd.playerModel.slice(2) : wd.playerModel)
|
|
1081
1148
|
else { assetsReady = Promise.resolve(); assetsLoaded = true; checkAllLoaded() }
|
|
1082
|
-
if (wd.entities) for (const e of wd.entities) { if (e.app) entityAppMap.set(e.id, e.app)
|
|
1149
|
+
if (wd.entities) for (const e of wd.entities) { if (e.app) entityAppMap.set(e.id, e.app) }
|
|
1083
1150
|
if (wd.scene) applySceneConfig(wd.scene)
|
|
1084
1151
|
if (wd.camera) cam.applyConfig(wd.camera)
|
|
1085
1152
|
if (wd.input) {
|
package/package.json
CHANGED
package/src/apps/AppRuntime.js
CHANGED
|
@@ -126,7 +126,21 @@ export class AppRuntime {
|
|
|
126
126
|
const ctx = this.contexts.get(entityId); if (!ctx) continue
|
|
127
127
|
this._safeCall(appDef.server || appDef, 'update', [ctx, dt], `update(${entityId})`)
|
|
128
128
|
}
|
|
129
|
-
this._tickTimers(dt); this._spatialSync(); this._tickCollisions(); this._tickInteractables()
|
|
129
|
+
this._tickTimers(dt); this._syncDynamicBodies(); this._spatialSync(); this._tickCollisions(); this._tickInteractables()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
_syncDynamicBodies() {
|
|
133
|
+
if (!this._physics) return
|
|
134
|
+
for (const e of this.entities.values()) {
|
|
135
|
+
if (e.bodyType !== 'dynamic' || e._physicsBodyId === undefined) continue
|
|
136
|
+
const active = this._physics.isBodyActive(e._physicsBodyId)
|
|
137
|
+
if (!active && e._dynSleeping) continue
|
|
138
|
+
e._dynSleeping = !active
|
|
139
|
+
const p = this._physics.getBodyPosition(e._physicsBodyId)
|
|
140
|
+
const r = this._physics.getBodyRotation(e._physicsBodyId)
|
|
141
|
+
e.position[0] = p[0]; e.position[1] = p[1]; e.position[2] = p[2]
|
|
142
|
+
e.rotation[0] = r[0]; e.rotation[1] = r[1]; e.rotation[2] = r[2]; e.rotation[3] = r[3]
|
|
143
|
+
}
|
|
130
144
|
}
|
|
131
145
|
|
|
132
146
|
_encodeEntity(id, e) {
|
|
@@ -143,7 +157,9 @@ export class AppRuntime {
|
|
|
143
157
|
getSnapshotForPlayer(playerPosition, radius) {
|
|
144
158
|
const relevant = new Set(this.relevantEntities(playerPosition, radius))
|
|
145
159
|
const entities = []
|
|
146
|
-
for (const [id, e] of this.entities) {
|
|
160
|
+
for (const [id, e] of this.entities) {
|
|
161
|
+
if (relevant.has(id) || e._appName === 'environment') entities.push(this._encodeEntity(id, e))
|
|
162
|
+
}
|
|
147
163
|
return { tick: this.currentTick, timestamp: Date.now(), entities }
|
|
148
164
|
}
|
|
149
165
|
|
|
@@ -207,7 +223,19 @@ export class AppRuntime {
|
|
|
207
223
|
}
|
|
208
224
|
}
|
|
209
225
|
|
|
210
|
-
_colR(c) {
|
|
226
|
+
_colR(c) {
|
|
227
|
+
if (!c) return 0
|
|
228
|
+
if (c.type === 'sphere') return c.radius || 1
|
|
229
|
+
if (c.type === 'capsule') return Math.max(c.radius || 0.5, (c.height || 1) / 2)
|
|
230
|
+
if (c.type === 'box') {
|
|
231
|
+
const s = c.size; const h = c.halfExtents
|
|
232
|
+
if (Array.isArray(s)) return Math.max(...s)
|
|
233
|
+
if (typeof s === 'number') return s
|
|
234
|
+
if (Array.isArray(h)) return Math.max(...h)
|
|
235
|
+
return 1
|
|
236
|
+
}
|
|
237
|
+
return 1
|
|
238
|
+
}
|
|
211
239
|
setPlayerManager(pm) { this._playerManager = pm }
|
|
212
240
|
setStageLoader(sl) { this._stageLoader = sl }
|
|
213
241
|
getPlayers() { return this._playerManager ? this._playerManager.getConnectedPlayers() : [] }
|
|
@@ -29,8 +29,12 @@ function encodeEntity(e) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export class SnapshotEncoder {
|
|
32
|
-
static
|
|
33
|
-
|
|
32
|
+
static encodePlayers(players) {
|
|
33
|
+
return (players || []).map(encodePlayer)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static encodeDelta(snapshot, prevEntityMap, preEncodedPlayers) {
|
|
37
|
+
const players = preEncodedPlayers || (snapshot.players || []).map(encodePlayer)
|
|
34
38
|
const currentIds = new Set()
|
|
35
39
|
const entities = []
|
|
36
40
|
const nextMap = new Map()
|
package/src/physics/GLBLoader.js
CHANGED
|
@@ -389,6 +389,9 @@ export async function extractAllMeshesFromGLBAsync(filepath) {
|
|
|
389
389
|
|
|
390
390
|
// Build a node->transform map for node hierarchy
|
|
391
391
|
const nodeTransforms = buildNodeTransforms(json)
|
|
392
|
+
const materials = json.materials || []
|
|
393
|
+
// Source Engine / CS:GO invisible/trigger materials — exclude from physics
|
|
394
|
+
const SKIP_MATS = new Set(['aaatrigger', '{invisible', 'playerclip', 'clip', 'nodraw', 'trigger', 'sky', 'toolsclip', 'toolsplayerclip', 'toolsnodraw', 'toolsskybox', 'toolstrigger'])
|
|
392
395
|
|
|
393
396
|
for (let meshIdx = 0; meshIdx < (json.meshes || []).length; meshIdx++) {
|
|
394
397
|
const mesh = json.meshes[meshIdx]
|
|
@@ -398,6 +401,9 @@ export async function extractAllMeshesFromGLBAsync(filepath) {
|
|
|
398
401
|
|
|
399
402
|
for (let primIdx = 0; primIdx < mesh.primitives.length; primIdx++) {
|
|
400
403
|
const prim = mesh.primitives[primIdx]
|
|
404
|
+
// Skip invisible/trigger materials that should not have physics collision
|
|
405
|
+
const matName = prim.material !== undefined ? (materials[prim.material]?.name || '') : ''
|
|
406
|
+
if (SKIP_MATS.has(matName)) continue
|
|
401
407
|
let result
|
|
402
408
|
try {
|
|
403
409
|
if (prim.extensions?.KHR_draco_mesh_compression) {
|
package/src/physics/World.js
CHANGED
|
@@ -267,6 +267,10 @@ export class PhysicsWorld {
|
|
|
267
267
|
this.Jolt.destroy(v)
|
|
268
268
|
return r
|
|
269
269
|
}
|
|
270
|
+
isBodyActive(bodyId) {
|
|
271
|
+
const b = this._getBody(bodyId); if (!b) return false
|
|
272
|
+
const id = b.GetID(); const r = this.bodyInterface.IsActive(id); this.Jolt.destroy(id); return r
|
|
273
|
+
}
|
|
270
274
|
setBodyPosition(bodyId, position) {
|
|
271
275
|
const b = this._getBody(bodyId); if (!b) return
|
|
272
276
|
const p = this._tmpRVec3 || new this.Jolt.RVec3(0, 0, 0); p.Set(position[0], position[1], position[2])
|
package/src/sdk/BotHarness.js
CHANGED
|
@@ -2,12 +2,12 @@ import { WebSocket } from 'ws'
|
|
|
2
2
|
import { pack, unpack } from '../protocol/msgpack.js'
|
|
3
3
|
|
|
4
4
|
const CONFIG = {
|
|
5
|
-
botCount: 100,
|
|
6
|
-
durationMs: 60000,
|
|
7
|
-
inputHz: 60,
|
|
5
|
+
botCount: parseInt(process.env.BOT_COUNT || '100'),
|
|
6
|
+
durationMs: parseInt(process.env.BOT_DURATION || '60000'),
|
|
7
|
+
inputHz: parseInt(process.env.BOT_HZ || '60'),
|
|
8
8
|
serverUrl: process.env.BOT_URL || 'ws://localhost:3001/ws',
|
|
9
|
-
batchSize:
|
|
10
|
-
batchDelayMs:
|
|
9
|
+
batchSize: parseInt(process.env.BOT_BATCH || '20'),
|
|
10
|
+
batchDelayMs: parseInt(process.env.BOT_DELAY || '100')
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const MSG_INPUT = 0x11
|
package/src/sdk/ServerAPI.js
CHANGED
|
@@ -33,7 +33,9 @@ export function createServerAPI(ctx) {
|
|
|
33
33
|
|
|
34
34
|
async loadWorld(worldDef) {
|
|
35
35
|
ctx.currentWorldDef = worldDef
|
|
36
|
-
if (worldDef.
|
|
36
|
+
if (worldDef.spawnPoints?.length) ctx.worldSpawnPoints = worldDef.spawnPoints
|
|
37
|
+
else if (worldDef.spawnPoint) ctx.worldSpawnPoints = [worldDef.spawnPoint]
|
|
38
|
+
ctx.worldSpawnPoint = ctx.worldSpawnPoints?.[0] || worldDef.spawnPoint || [0, 5, 0]
|
|
37
39
|
await appLoader.loadAll()
|
|
38
40
|
const stage = stageLoader.loadFromDefinition('main', worldDef)
|
|
39
41
|
return { entities: new Map(), apps: new Map(), count: stage.entityCount }
|
|
@@ -5,7 +5,8 @@ export function createConnectionHandlers(ctx) {
|
|
|
5
5
|
const { tickSystem, playerManager, networkState, lagCompensator, physicsIntegration, connections, sessions, appLoader, appRuntime, emitter, inspector } = ctx
|
|
6
6
|
|
|
7
7
|
function onClientConnect(transport) {
|
|
8
|
-
const
|
|
8
|
+
const spawnPoints = ctx.worldSpawnPoints || [ctx.worldSpawnPoint]
|
|
9
|
+
const sp = [...spawnPoints[Math.floor(Math.random() * spawnPoints.length)]]
|
|
9
10
|
const playerConfig = ctx.currentWorldDef?.player || {}
|
|
10
11
|
const playerId = playerManager.addPlayer(transport, { position: sp, health: playerConfig.health })
|
|
11
12
|
networkState.addPlayer(playerId, { position: sp })
|
|
@@ -23,8 +24,11 @@ export function createConnectionHandlers(ctx) {
|
|
|
23
24
|
for (const [appName, code] of Object.entries(clientModules)) {
|
|
24
25
|
connections.send(playerId, MSG.APP_MODULE, { app: appName, code })
|
|
25
26
|
}
|
|
26
|
-
const
|
|
27
|
-
|
|
27
|
+
const relevanceRadius = ctx.currentWorldDef?.relevanceRadius || 0
|
|
28
|
+
const snapEntities = relevanceRadius > 0 ? appRuntime.getSnapshotForPlayer(sp, relevanceRadius) : appRuntime.getSnapshot()
|
|
29
|
+
const playerSnap = networkState.getSnapshot()
|
|
30
|
+
const combined = { tick: playerSnap.tick, timestamp: playerSnap.timestamp, players: playerSnap.players, entities: snapEntities.entities }
|
|
31
|
+
connections.send(playerId, MSG.SNAPSHOT, { seq: ++ctx.snapshotSeq, ...SnapshotEncoder.encode(combined) })
|
|
28
32
|
for (const [entityId] of appRuntime.apps) appRuntime.fireMessage(entityId, { type: 'player_join', playerId })
|
|
29
33
|
emitter.emit('playerJoin', { id: playerId })
|
|
30
34
|
}
|
package/src/sdk/TickHandler.js
CHANGED
|
@@ -5,13 +5,13 @@ import { isUnreliable } from '../protocol/MessageTypes.js'
|
|
|
5
5
|
import { applyMovement as _applyMovement, DEFAULT_MOVEMENT as _DEFAULT_MOVEMENT } from '../shared/movement.js'
|
|
6
6
|
|
|
7
7
|
const KEYFRAME_INTERVAL = 128
|
|
8
|
-
const
|
|
8
|
+
const MAX_SENDS_PER_TICK = 25
|
|
9
9
|
|
|
10
10
|
export function createTickHandler(deps) {
|
|
11
11
|
const {
|
|
12
12
|
networkState, playerManager, physicsIntegration,
|
|
13
13
|
lagCompensator, physics, appRuntime, connections,
|
|
14
|
-
movement: m = {}, stageLoader, eventLog, _movement
|
|
14
|
+
movement: m = {}, stageLoader, eventLog, _movement, getRelevanceRadius
|
|
15
15
|
} = deps
|
|
16
16
|
const applyMovement = _movement?.applyMovement || _applyMovement
|
|
17
17
|
const DEFAULT_MOVEMENT = _movement?.DEFAULT_MOVEMENT || _DEFAULT_MOVEMENT
|
|
@@ -119,16 +119,20 @@ export function createTickHandler(deps) {
|
|
|
119
119
|
const playerSnap = networkState.getSnapshot()
|
|
120
120
|
snapshotSeq++
|
|
121
121
|
const isKeyframe = snapshotSeq % KEYFRAME_INTERVAL === 0
|
|
122
|
-
const
|
|
122
|
+
const snapGroups = Math.max(1, Math.ceil(players.length / MAX_SENDS_PER_TICK))
|
|
123
|
+
const curGroup = tick % snapGroups
|
|
123
124
|
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
const relevanceRadius = (stageLoader && stageLoader.getActiveStage())
|
|
126
|
+
? stageLoader.getActiveStage().spatial.relevanceRadius
|
|
127
|
+
: (getRelevanceRadius ? getRelevanceRadius() : 0)
|
|
128
|
+
if (relevanceRadius > 0) {
|
|
129
|
+
const preEncodedPlayers = SnapshotEncoder.encodePlayers(playerSnap.players)
|
|
126
130
|
for (const player of players) {
|
|
127
|
-
if (!isKeyframe && player.id %
|
|
131
|
+
if (!isKeyframe && player.id % snapGroups !== curGroup) continue
|
|
128
132
|
const entitySnap = appRuntime.getSnapshotForPlayer(player.state.position, relevanceRadius)
|
|
129
|
-
const combined = { tick: playerSnap.tick, timestamp: playerSnap.timestamp,
|
|
133
|
+
const combined = { tick: playerSnap.tick, timestamp: playerSnap.timestamp, entities: entitySnap.entities }
|
|
130
134
|
const prevMap = (isKeyframe || !playerEntityMaps.has(player.id)) ? new Map() : playerEntityMaps.get(player.id)
|
|
131
|
-
const { encoded, entityMap } = SnapshotEncoder.encodeDelta(combined, prevMap)
|
|
135
|
+
const { encoded, entityMap } = SnapshotEncoder.encodeDelta(combined, prevMap, preEncodedPlayers)
|
|
132
136
|
playerEntityMaps.set(player.id, entityMap)
|
|
133
137
|
connections.send(player.id, MSG.SNAPSHOT, { seq: snapshotSeq, ...encoded })
|
|
134
138
|
}
|
|
@@ -140,7 +144,7 @@ export function createTickHandler(deps) {
|
|
|
140
144
|
broadcastEntityMap = entityMap
|
|
141
145
|
const data = pack({ type: MSG.SNAPSHOT, payload: { seq: snapshotSeq, ...encoded } })
|
|
142
146
|
for (const player of players) {
|
|
143
|
-
if (!isKeyframe && player.id %
|
|
147
|
+
if (!isKeyframe && player.id % snapGroups !== curGroup) continue
|
|
144
148
|
connections.sendPacked(player.id, data, snapUnreliable)
|
|
145
149
|
}
|
|
146
150
|
}
|
package/src/sdk/server.js
CHANGED
|
@@ -171,7 +171,8 @@ export async function createServer(config = {}) {
|
|
|
171
171
|
connections,
|
|
172
172
|
movement,
|
|
173
173
|
stageLoader,
|
|
174
|
-
eventLog
|
|
174
|
+
eventLog,
|
|
175
|
+
getRelevanceRadius: () => ctx.currentWorldDef?.relevanceRadius || 0
|
|
175
176
|
}))
|
|
176
177
|
|
|
177
178
|
const { onClientConnect } = createConnectionHandlers(ctx)
|
package/src/stage/Stage.js
CHANGED
|
@@ -46,10 +46,7 @@ export class Stage {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
getRelevantEntities(position, radius) {
|
|
49
|
-
|
|
50
|
-
const set = new Set(nearby)
|
|
51
|
-
for (const sid of this._staticIds) set.add(sid)
|
|
52
|
-
return Array.from(set)
|
|
49
|
+
return this.spatial.nearby(position, radius || this.spatial.relevanceRadius)
|
|
53
50
|
}
|
|
54
51
|
|
|
55
52
|
getStaticIds() {
|