spoint 0.1.83 → 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.
Files changed (135) hide show
  1. package/apps/maps/aim_kosova_ak47.glb +0 -0
  2. package/apps/maps/aim_kosova_battle.glb +0 -0
  3. package/apps/maps/aim_kosova_famas.glb +0 -0
  4. package/apps/maps/aim_sillos.glb +0 -0
  5. package/apps/maps/awp_india_ks.glb +0 -0
  6. package/apps/maps/awp_kosova.glb +0 -0
  7. package/apps/maps/awp_kosova_battle.glb +0 -0
  8. package/apps/maps/awp_kosovo_trainstation.glb +0 -0
  9. package/apps/maps/de_dolc.glb +0 -0
  10. package/apps/maps/de_dust2_kosovo.glb +0 -0
  11. package/apps/maps/de_gash.glb +0 -0
  12. package/apps/maps/de_kosovo.glb +0 -0
  13. package/apps/maps/de_kps.glb +0 -0
  14. package/apps/maps/de_pub2_r3.glb +0 -0
  15. package/apps/maps/deathrun_kosova.glb +0 -0
  16. package/apps/maps/fy_osama_house.glb +0 -0
  17. package/apps/prop-dynamic/index.js +13 -0
  18. package/apps/props/car1.glb +0 -0
  19. package/apps/props/car2.glb +0 -0
  20. package/apps/props/dynamic/3dPrinter1.glb +0 -0
  21. package/apps/props/dynamic/3dPrinter2.glb +0 -0
  22. package/apps/props/dynamic/AirConditioner1.glb +0 -0
  23. package/apps/props/dynamic/AirConditioner2.glb +0 -0
  24. package/apps/props/dynamic/AirConditioner3.glb +0 -0
  25. package/apps/props/dynamic/AirConditioner4.glb +0 -0
  26. package/apps/props/dynamic/ArmourToolBox1.glb +0 -0
  27. package/apps/props/dynamic/ArmourToolBox2.glb +0 -0
  28. package/apps/props/dynamic/ArmourToolBox3.glb +0 -0
  29. package/apps/props/dynamic/ArmourToolMan.glb +0 -0
  30. package/apps/props/dynamic/Atm1.glb +0 -0
  31. package/apps/props/dynamic/Atm2.glb +0 -0
  32. package/apps/props/dynamic/Atm3.glb +0 -0
  33. package/apps/props/dynamic/Atm4.glb +0 -0
  34. package/apps/props/dynamic/Ball1.glb +0 -0
  35. package/apps/props/dynamic/BeerBottle1.glb +0 -0
  36. package/apps/props/dynamic/BeerBottle2.glb +0 -0
  37. package/apps/props/dynamic/BeerBottle3.glb +0 -0
  38. package/apps/props/dynamic/BeerBottle4.glb +0 -0
  39. package/apps/props/dynamic/BreakRoomChair1.glb +0 -0
  40. package/apps/props/dynamic/BreakRoomChair2.glb +0 -0
  41. package/apps/props/dynamic/BreakRoomChair3.glb +0 -0
  42. package/apps/props/dynamic/BreakRoomChair4.glb +0 -0
  43. package/apps/props/dynamic/BreakRoomCouch1.glb +0 -0
  44. package/apps/props/dynamic/BreakRoomCouch2.glb +0 -0
  45. package/apps/props/dynamic/BreakRoomCouch3.glb +0 -0
  46. package/apps/props/dynamic/BreakRoomCouch4.glb +0 -0
  47. package/apps/props/dynamic/BreakRoomCouch5.glb +0 -0
  48. package/apps/props/dynamic/BreakRoomCouch6.glb +0 -0
  49. package/apps/props/dynamic/BreakRoomCouch7.glb +0 -0
  50. package/apps/props/dynamic/BreakRoomTable1.glb +0 -0
  51. package/apps/props/dynamic/BreakRoomTable2.glb +0 -0
  52. package/apps/props/dynamic/BrokenBeerBottles1.glb +0 -0
  53. package/apps/props/dynamic/BrokenBeerBottles2.glb +0 -0
  54. package/apps/props/dynamic/BrokenOfficeChair1.glb +0 -0
  55. package/apps/props/dynamic/BrokenOfficeChair2.glb +0 -0
  56. package/apps/props/dynamic/BrokenWaterCooler1.glb +0 -0
  57. package/apps/props/dynamic/BrokenWaterCooler2.glb +0 -0
  58. package/apps/props/dynamic/CanMan1.glb +0 -0
  59. package/apps/props/dynamic/CanMan2.glb +0 -0
  60. package/apps/props/dynamic/CanMan3.glb +0 -0
  61. package/apps/props/dynamic/CashRegister1.glb +0 -0
  62. package/apps/props/dynamic/CashRegister2.glb +0 -0
  63. package/apps/props/dynamic/CashRegister3.glb +0 -0
  64. package/apps/props/dynamic/ComfyChairs1.glb +0 -0
  65. package/apps/props/dynamic/CrushedGarbageCan1.glb +0 -0
  66. package/apps/props/dynamic/CrushedGarbageCan2.glb +0 -0
  67. package/apps/props/dynamic/CrushedGarbageCan3.glb +0 -0
  68. package/apps/props/dynamic/CrushedGarbageCan4.glb +0 -0
  69. package/apps/props/dynamic/DinnerTable1.glb +0 -0
  70. package/apps/props/dynamic/DinnerTable2.glb +0 -0
  71. package/apps/props/dynamic/DinnerTable3.glb +0 -0
  72. package/apps/props/dynamic/dj_mixer_baeeec4e_v1.glb +0 -0
  73. package/apps/props/dynamic/dj_mixer_baeeec4e_v2.glb +0 -0
  74. package/apps/props/dynamic/dj_mixer_baeeec4e_v3.glb +0 -0
  75. package/apps/props/dynamic/dj_mixer_baeeec4e_v4.glb +0 -0
  76. package/apps/props/dynamic/fancy_reception_desk_58fde71d_v1.glb +0 -0
  77. package/apps/props/dynamic/fancy_reception_desk_58fde71d_v2.glb +0 -0
  78. package/apps/props/dynamic/fancy_reception_desk_58fde71d_v3.glb +0 -0
  79. package/apps/props/dynamic/fancy_reception_desk_58fde71d_v4.glb +0 -0
  80. package/apps/props/dynamic/heavy_machinery__crane_from_junk_yard_with_magnet_for_lifting_cars_db884752_v1.glb +0 -0
  81. package/apps/props/dynamic/heavy_machinery__crane_from_junk_yard_with_magnet_for_lifting_cars_db884752_v2.glb +0 -0
  82. package/apps/props/dynamic/heavy_machinery__crane_from_junk_yard_with_magnet_for_lifting_cars_db884752_v3.glb +0 -0
  83. package/apps/props/dynamic/hi-fi_sound_system_2a2cc620_v1.glb +0 -0
  84. package/apps/props/dynamic/hi-fi_sound_system_2a2cc620_v2.glb +0 -0
  85. package/apps/props/dynamic/hi-fi_sound_system_2a2cc620_v3.glb +0 -0
  86. package/apps/props/dynamic/hi-fi_sound_system_2a2cc620_v4.glb +0 -0
  87. package/apps/props/dynamic/index.js +21 -0
  88. package/apps/props/dynamic/industrial_pipe_cb740c0c_v1.glb +0 -0
  89. package/apps/props/dynamic/industrial_pipe_cb740c0c_v2.glb +0 -0
  90. package/apps/props/dynamic/industrial_pipe_cb740c0c_v3.glb +0 -0
  91. package/apps/props/dynamic/industrial_pipe_cb740c0c_v4.glb +0 -0
  92. package/apps/props/dynamic/l-shaped_industrial_pipe_3b570c7e_v1.glb +0 -0
  93. package/apps/props/dynamic/l-shaped_industrial_pipe_3b570c7e_v2.glb +0 -0
  94. package/apps/props/dynamic/l-shaped_industrial_pipe_3b570c7e_v3.glb +0 -0
  95. package/apps/props/dynamic/l-shaped_industrial_pipe_3b570c7e_v4.glb +0 -0
  96. package/apps/props/dynamic/l-shaped_industrial_pipe_f7fd8524_v1.glb +0 -0
  97. package/apps/props/dynamic/l-shaped_industrial_pipe_f7fd8524_v2.glb +0 -0
  98. package/apps/props/dynamic/l-shaped_industrial_pipe_f7fd8524_v3.glb +0 -0
  99. package/apps/props/dynamic/l-shaped_industrial_pipe_f7fd8524_v4.glb +0 -0
  100. package/apps/props/dynamic/night_club_speakers_9155e359_v1.glb +0 -0
  101. package/apps/props/dynamic/old_cheap_couch_with_a_bad_floral_pattern_53278f1b_v1.glb +0 -0
  102. package/apps/props/dynamic/old_cheap_couch_with_a_bad_floral_pattern_53278f1b_v2.glb +0 -0
  103. package/apps/props/dynamic/old_cheap_couch_with_a_bad_floral_pattern_53278f1b_v3.glb +0 -0
  104. package/apps/props/dynamic/old_cheap_couch_with_a_bad_floral_pattern_53278f1b_v4.glb +0 -0
  105. package/apps/props/dynamic/server_rack_03b09d1f_v1.glb +0 -0
  106. package/apps/props/dynamic/server_rack_03b09d1f_v2.glb +0 -0
  107. package/apps/props/dynamic/server_rack_03b09d1f_v3.glb +0 -0
  108. package/apps/props/dynamic/server_rack_03b09d1f_v4.glb +0 -0
  109. package/apps/props/dynamic/server_rack_c2999a18_v1.glb +0 -0
  110. package/apps/props/dynamic/server_rack_c2999a18_v2.glb +0 -0
  111. package/apps/props/dynamic/server_rack_c2999a18_v3.glb +0 -0
  112. package/apps/props/dynamic/server_rack_c2999a18_v4.glb +0 -0
  113. package/apps/props/dynamic/shop_counter_f668a712_v1.glb +0 -0
  114. package/apps/props/dynamic/warehouse_crate_6e8a0927_v1.glb +0 -0
  115. package/apps/props/dynamic/warehouse_crate_6e8a0927_v2.glb +0 -0
  116. package/apps/props/dynamic/warehouse_crate_6e8a0927_v4.glb +0 -0
  117. package/apps/props/dynamic/water_tank_c27c18f7_v1.glb +0 -0
  118. package/apps/props/dynamic/water_tank_c27c18f7_v2.glb +0 -0
  119. package/apps/props/dynamic/water_tank_c27c18f7_v3.glb +0 -0
  120. package/apps/props/dynamic/water_tank_c27c18f7_v4.glb +0 -0
  121. package/apps/props/fountain.glb +0 -0
  122. package/apps/props/index.js +24 -0
  123. package/apps/props/rifle.glb +0 -0
  124. package/apps/props/rock1.glb +0 -0
  125. package/apps/props/rock2.glb +0 -0
  126. package/apps/world/index.js +130 -7
  127. package/client/app.js +72 -5
  128. package/package.json +1 -1
  129. package/src/apps/AppRuntime.js +31 -3
  130. package/src/netcode/SnapshotEncoder.js +6 -2
  131. package/src/physics/GLBLoader.js +6 -0
  132. package/src/physics/World.js +4 -0
  133. package/src/sdk/ServerHandlers.js +5 -2
  134. package/src/sdk/TickHandler.js +3 -2
  135. 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
@@ -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
@@ -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
@@ -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
@@ -63,15 +63,138 @@ export default {
63
63
  { id: 'env-kosova', model: './apps/maps/aim_kosova_ak47.glb', position: [200, 0, 0], app: 'environment' },
64
64
  { id: 'env-sillos', model: './apps/maps/aim_sillos.glb', position: [400, 0, 0], app: 'environment' },
65
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' }
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' }
67
189
  ],
68
190
  spawnPoints: [
69
- [-30, 20, -30],
70
- [212, 20, 12],
71
- [412, 20, 12],
72
- [612, 20, 12],
73
- [812, 20, 12]
191
+ [10, 5, 10],
192
+ [200, 5, -10],
193
+ [400, 5, -10],
194
+ [610, 5, 5],
195
+ [800, 5, -10]
74
196
  ],
75
- spawnPoint: [-30, 20, -30],
197
+ spawnPoint: [10, 5, 10],
76
198
  playerModel: './apps/tps-game/cleetus.vrm'
77
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
- for (const p of state.players) {
1051
- if (!playerMeshes.has(p.id)) createPlayerVRM(p.id)
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); if (e.model && !entityMeshes.has(e.id)) loadEntityModel(e.id, e) }
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spoint",
3
- "version": "0.1.83",
3
+ "version": "0.1.84",
4
4
  "description": "Physics and netcode SDK for multiplayer game servers",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -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) { if (relevant.has(id)) entities.push(this._encodeEntity(id, e)) }
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) { return !c ? 0 : c.type === 'sphere' ? (c.radius||1) : c.type === 'capsule' ? Math.max(c.radius||0.5,(c.height||1)/2) : c.type === 'box' ? Math.max(...(c.size||c.halfExtents||[1,1,1])) : 1 }
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 encodeDelta(snapshot, prevEntityMap) {
33
- const players = (snapshot.players || []).map(encodePlayer)
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()
@@ -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) {
@@ -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])
@@ -24,8 +24,11 @@ export function createConnectionHandlers(ctx) {
24
24
  for (const [appName, code] of Object.entries(clientModules)) {
25
25
  connections.send(playerId, MSG.APP_MODULE, { app: appName, code })
26
26
  }
27
- const snap = appRuntime.getSnapshot()
28
- connections.send(playerId, MSG.SNAPSHOT, { seq: ++ctx.snapshotSeq, ...SnapshotEncoder.encode(snap) })
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) })
29
32
  for (const [entityId] of appRuntime.apps) appRuntime.fireMessage(entityId, { type: 'player_join', playerId })
30
33
  emitter.emit('playerJoin', { id: playerId })
31
34
  }
@@ -126,12 +126,13 @@ export function createTickHandler(deps) {
126
126
  ? stageLoader.getActiveStage().spatial.relevanceRadius
127
127
  : (getRelevanceRadius ? getRelevanceRadius() : 0)
128
128
  if (relevanceRadius > 0) {
129
+ const preEncodedPlayers = SnapshotEncoder.encodePlayers(playerSnap.players)
129
130
  for (const player of players) {
130
131
  if (!isKeyframe && player.id % snapGroups !== curGroup) continue
131
132
  const entitySnap = appRuntime.getSnapshotForPlayer(player.state.position, relevanceRadius)
132
- const combined = { tick: playerSnap.tick, timestamp: playerSnap.timestamp, players: playerSnap.players, entities: entitySnap.entities }
133
+ const combined = { tick: playerSnap.tick, timestamp: playerSnap.timestamp, entities: entitySnap.entities }
133
134
  const prevMap = (isKeyframe || !playerEntityMaps.has(player.id)) ? new Map() : playerEntityMaps.get(player.id)
134
- const { encoded, entityMap } = SnapshotEncoder.encodeDelta(combined, prevMap)
135
+ const { encoded, entityMap } = SnapshotEncoder.encodeDelta(combined, prevMap, preEncodedPlayers)
135
136
  playerEntityMaps.set(player.id, entityMap)
136
137
  connections.send(player.id, MSG.SNAPSHOT, { seq: snapshotSeq, ...encoded })
137
138
  }
@@ -46,10 +46,7 @@ export class Stage {
46
46
  }
47
47
 
48
48
  getRelevantEntities(position, radius) {
49
- const nearby = this.spatial.nearby(position, radius || this.spatial.relevanceRadius)
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() {