@dcl-regenesislabs/web-editor 0.0.0-stage → 0.1.0

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 (277) hide show
  1. package/editor.css +1 -0
  2. package/editor.js +33891 -0
  3. package/package.json +8 -4
  4. package/prefabs/admin-tools/composite.json +77 -0
  5. package/prefabs/admin-tools/data.json +28 -0
  6. package/prefabs/admin-tools/icons/admin-panel-control-button.png +0 -0
  7. package/prefabs/admin-tools/icons/admin-panel-moderation-control-button.png +0 -0
  8. package/prefabs/admin-tools/icons/admin-panel-rewards-control-button.png +0 -0
  9. package/prefabs/admin-tools/icons/admin-panel-smart-item-control-button.png +0 -0
  10. package/prefabs/admin-tools/icons/admin-panel-text-announcement-control-button.png +0 -0
  11. package/prefabs/admin-tools/icons/admin-panel-video-control-button.png +0 -0
  12. package/prefabs/admin-tools/icons/admin-tool-background.png +0 -0
  13. package/prefabs/admin-tools/icons/announcements/text-announcement-chat-message.png +0 -0
  14. package/prefabs/admin-tools/icons/announcements/text-announcement-check.png +0 -0
  15. package/prefabs/admin-tools/icons/announcements/text-announcement-close-button.png +0 -0
  16. package/prefabs/admin-tools/icons/moderation/admin-panel-verified-user.png +0 -0
  17. package/prefabs/admin-tools/icons/moderation/ban.png +0 -0
  18. package/prefabs/admin-tools/icons/moderation/chevron-back.png +0 -0
  19. package/prefabs/admin-tools/icons/moderation/chevron-forward.png +0 -0
  20. package/prefabs/admin-tools/icons/moderation/close.png +0 -0
  21. package/prefabs/admin-tools/icons/moderation/error.png +0 -0
  22. package/prefabs/admin-tools/icons/moderation/person-outline.png +0 -0
  23. package/prefabs/admin-tools/icons/moderation-control-icon.png +0 -0
  24. package/prefabs/admin-tools/icons/rewards/close.png +0 -0
  25. package/prefabs/admin-tools/icons/rewards/send.png +0 -0
  26. package/prefabs/admin-tools/icons/rewards-control.png +0 -0
  27. package/prefabs/admin-tools/icons/smart-item-control.png +0 -0
  28. package/prefabs/admin-tools/icons/text-announcement-control.png +0 -0
  29. package/prefabs/admin-tools/icons/video/chevron-down.png +0 -0
  30. package/prefabs/admin-tools/icons/video/chevron-up.png +0 -0
  31. package/prefabs/admin-tools/icons/video/copy-to-clipboard.png +0 -0
  32. package/prefabs/admin-tools/icons/video/error.png +0 -0
  33. package/prefabs/admin-tools/icons/video/eye-off.png +0 -0
  34. package/prefabs/admin-tools/icons/video/eye.png +0 -0
  35. package/prefabs/admin-tools/icons/video/help.png +0 -0
  36. package/prefabs/admin-tools/icons/video/info.png +0 -0
  37. package/prefabs/admin-tools/icons/video/person-outline.png +0 -0
  38. package/prefabs/admin-tools/icons/video/star.png +0 -0
  39. package/prefabs/admin-tools/icons/video/video-control-dcl-cast.png +0 -0
  40. package/prefabs/admin-tools/icons/video/video-control-live.png +0 -0
  41. package/prefabs/admin-tools/icons/video/video-control-loop.png +0 -0
  42. package/prefabs/admin-tools/icons/video/video-control-mute.png +0 -0
  43. package/prefabs/admin-tools/icons/video/video-control-pause-button.png +0 -0
  44. package/prefabs/admin-tools/icons/video/video-control-play-button.png +0 -0
  45. package/prefabs/admin-tools/icons/video/video-control-video-icon.png +0 -0
  46. package/prefabs/admin-tools/icons/video/video-control-volume-minus-button.png +0 -0
  47. package/prefabs/admin-tools/icons/video/video-control-volume-plus-button.png +0 -0
  48. package/prefabs/admin-tools/icons/video-control.png +0 -0
  49. package/prefabs/admin-tools/scripts/actions.ts +206 -0
  50. package/prefabs/admin-tools/scripts/admin.tsx +253 -0
  51. package/prefabs/admin-tools/scripts/api.ts +125 -0
  52. package/prefabs/admin-tools/scripts/components.ts +190 -0
  53. package/prefabs/admin-tools/scripts/icons.ts +53 -0
  54. package/prefabs/admin-tools/scripts/message-bus.ts +297 -0
  55. package/prefabs/admin-tools/scripts/state.ts +69 -0
  56. package/prefabs/admin-tools/scripts/tabs/Moderation.tsx +155 -0
  57. package/prefabs/admin-tools/scripts/tabs/Rewards.tsx +136 -0
  58. package/prefabs/admin-tools/scripts/tabs/SmartItemActions.tsx +167 -0
  59. package/prefabs/admin-tools/scripts/tabs/TextAnnouncements.tsx +195 -0
  60. package/prefabs/admin-tools/scripts/tabs/VideoControl.tsx +156 -0
  61. package/prefabs/admin-tools/scripts/tabs/announcements/Overlay.tsx +94 -0
  62. package/prefabs/admin-tools/scripts/tabs/announcements/icons.ts +15 -0
  63. package/prefabs/admin-tools/scripts/tabs/announcements/state.ts +71 -0
  64. package/prefabs/admin-tools/scripts/tabs/announcements/ui.tsx +37 -0
  65. package/prefabs/admin-tools/scripts/tabs/moderation/AddUserInput.tsx +184 -0
  66. package/prefabs/admin-tools/scripts/tabs/moderation/RemoveAdminConfirmation.tsx +93 -0
  67. package/prefabs/admin-tools/scripts/tabs/moderation/UsersList.tsx +228 -0
  68. package/prefabs/admin-tools/scripts/tabs/moderation/api.ts +35 -0
  69. package/prefabs/admin-tools/scripts/tabs/moderation/icons.ts +23 -0
  70. package/prefabs/admin-tools/scripts/tabs/moderation/kick.ts +95 -0
  71. package/prefabs/admin-tools/scripts/tabs/moderation/state.ts +69 -0
  72. package/prefabs/admin-tools/scripts/tabs/moderation/ui.tsx +172 -0
  73. package/prefabs/admin-tools/scripts/tabs/moderation/utils.ts +88 -0
  74. package/prefabs/admin-tools/scripts/tabs/rewards/Button.tsx +52 -0
  75. package/prefabs/admin-tools/scripts/tabs/rewards/Captcha.tsx +88 -0
  76. package/prefabs/admin-tools/scripts/tabs/rewards/api.ts +138 -0
  77. package/prefabs/admin-tools/scripts/tabs/rewards/claim.ts +99 -0
  78. package/prefabs/admin-tools/scripts/tabs/rewards/controller.ts +103 -0
  79. package/prefabs/admin-tools/scripts/tabs/rewards/icons.ts +13 -0
  80. package/prefabs/admin-tools/scripts/tabs/rewards/state.ts +32 -0
  81. package/prefabs/admin-tools/scripts/tabs/smart-items/selection.ts +90 -0
  82. package/prefabs/admin-tools/scripts/tabs/video/DclCast.tsx +226 -0
  83. package/prefabs/admin-tools/scripts/tabs/video/LiveStream.tsx +284 -0
  84. package/prefabs/admin-tools/scripts/tabs/video/SpeakerShowcase.tsx +112 -0
  85. package/prefabs/admin-tools/scripts/tabs/video/VideoUrl.tsx +107 -0
  86. package/prefabs/admin-tools/scripts/tabs/video/Volume.tsx +69 -0
  87. package/prefabs/admin-tools/scripts/tabs/video/api.ts +172 -0
  88. package/prefabs/admin-tools/scripts/tabs/video/controls.ts +108 -0
  89. package/prefabs/admin-tools/scripts/tabs/video/icons.ts +59 -0
  90. package/prefabs/admin-tools/scripts/tabs/video/speakers.ts +43 -0
  91. package/prefabs/admin-tools/scripts/tabs/video/state.ts +82 -0
  92. package/prefabs/admin-tools/scripts/tabs/video/ui.tsx +243 -0
  93. package/prefabs/admin-tools/scripts/types.ts +46 -0
  94. package/prefabs/admin-tools/scripts/ui.tsx +122 -0
  95. package/prefabs/admin-tools/thumbnail.png +0 -0
  96. package/prefabs/announcer/ai.md +76 -0
  97. package/prefabs/announcer/composite.json +31 -0
  98. package/prefabs/announcer/data.json +35 -0
  99. package/prefabs/announcer/scripts/announcer.tsx +79 -0
  100. package/prefabs/announcer/scripts/pure/toast.ts +30 -0
  101. package/prefabs/announcer/thumbnail.png +0 -0
  102. package/prefabs/black-chair/HighBackChair.glb +0 -0
  103. package/prefabs/black-chair/composite.json +74 -0
  104. package/prefabs/black-chair/data.json +30 -0
  105. package/prefabs/black-chair/scripts/seat.ts +191 -0
  106. package/prefabs/black-chair/thumbnail.png +0 -0
  107. package/prefabs/catalog.json +1 -0
  108. package/prefabs/classic-bench/Bench_01.glb +0 -0
  109. package/prefabs/classic-bench/composite.json +100 -0
  110. package/prefabs/classic-bench/data.json +34 -0
  111. package/prefabs/classic-bench/file1.png +0 -0
  112. package/prefabs/classic-bench/scripts/seat.ts +191 -0
  113. package/prefabs/classic-bench/thumbnail.png +0 -0
  114. package/prefabs/classic-bench-armrests/Bench 1.glb +0 -0
  115. package/prefabs/classic-bench-armrests/composite.json +100 -0
  116. package/prefabs/classic-bench-armrests/data.json +30 -0
  117. package/prefabs/classic-bench-armrests/scripts/seat.ts +191 -0
  118. package/prefabs/classic-bench-armrests/thumbnail.png +0 -0
  119. package/prefabs/classroom-chair/Chair_02.glb +0 -0
  120. package/prefabs/classroom-chair/composite.json +74 -0
  121. package/prefabs/classroom-chair/data.json +34 -0
  122. package/prefabs/classroom-chair/file1.png +0 -0
  123. package/prefabs/classroom-chair/scripts/seat.ts +191 -0
  124. package/prefabs/classroom-chair/thumbnail.png +0 -0
  125. package/prefabs/court-chair/Chair_Wood_01.glb +0 -0
  126. package/prefabs/court-chair/FanstasyPack_TX.png +0 -0
  127. package/prefabs/court-chair/composite.json +74 -0
  128. package/prefabs/court-chair/data.json +34 -0
  129. package/prefabs/court-chair/scripts/seat.ts +191 -0
  130. package/prefabs/court-chair/thumbnail.png +0 -0
  131. package/prefabs/curved-couch/CurvedCouch.glb +0 -0
  132. package/prefabs/curved-couch/composite.json +100 -0
  133. package/prefabs/curved-couch/data.json +30 -0
  134. package/prefabs/curved-couch/scripts/seat.ts +191 -0
  135. package/prefabs/curved-couch/thumbnail.png +0 -0
  136. package/prefabs/game-flow/ai.md +94 -0
  137. package/prefabs/game-flow/composite.json +45 -0
  138. package/prefabs/game-flow/data.json +27 -0
  139. package/prefabs/game-flow/scripts/game-flow.ts +168 -0
  140. package/prefabs/game-flow/scripts/pure/flow.ts +185 -0
  141. package/prefabs/game-flow/thumbnail.png +0 -0
  142. package/prefabs/health-respawn/ai.md +76 -0
  143. package/prefabs/health-respawn/composite.json +31 -0
  144. package/prefabs/health-respawn/data.json +38 -0
  145. package/prefabs/health-respawn/scripts/health-respawn.ts +98 -0
  146. package/prefabs/health-respawn/scripts/health.ts +22 -0
  147. package/prefabs/health-respawn/scripts/pure/health.ts +60 -0
  148. package/prefabs/health-respawn/thumbnail.png +0 -0
  149. package/prefabs/high-stool/HighStool.glb +0 -0
  150. package/prefabs/high-stool/composite.json +74 -0
  151. package/prefabs/high-stool/data.json +30 -0
  152. package/prefabs/high-stool/scripts/seat.ts +191 -0
  153. package/prefabs/high-stool/thumbnail.png +0 -0
  154. package/prefabs/large-couch/ThreeSeater_Couch.glb +0 -0
  155. package/prefabs/large-couch/composite.json +100 -0
  156. package/prefabs/large-couch/data.json +30 -0
  157. package/prefabs/large-couch/scripts/seat.ts +191 -0
  158. package/prefabs/large-couch/thumbnail.png +0 -0
  159. package/prefabs/leaderboard/ai.md +75 -0
  160. package/prefabs/leaderboard/board-panel.glb +0 -0
  161. package/prefabs/leaderboard/composite.json +82 -0
  162. package/prefabs/leaderboard/data.json +35 -0
  163. package/prefabs/leaderboard/scripts/leaderboard.ts +66 -0
  164. package/prefabs/leaderboard/scripts/pure/board.ts +71 -0
  165. package/prefabs/leaderboard/thumbnail.png +0 -0
  166. package/prefabs/loveseat/Couch_01.glb +0 -0
  167. package/prefabs/loveseat/composite.json +100 -0
  168. package/prefabs/loveseat/data.json +34 -0
  169. package/prefabs/loveseat/file1.png +0 -0
  170. package/prefabs/loveseat/scripts/seat.ts +191 -0
  171. package/prefabs/loveseat/thumbnail.png +0 -0
  172. package/prefabs/modern-sofa/ModernSofa.glb +0 -0
  173. package/prefabs/modern-sofa/composite.json +100 -0
  174. package/prefabs/modern-sofa/data.json +30 -0
  175. package/prefabs/modern-sofa/scripts/seat.ts +191 -0
  176. package/prefabs/modern-sofa/thumbnail.png +0 -0
  177. package/prefabs/outdoor-chair/Chair_07.glb +0 -0
  178. package/prefabs/outdoor-chair/composite.json +74 -0
  179. package/prefabs/outdoor-chair/data.json +34 -0
  180. package/prefabs/outdoor-chair/file1.png +0 -0
  181. package/prefabs/outdoor-chair/scripts/seat.ts +191 -0
  182. package/prefabs/outdoor-chair/thumbnail.png +0 -0
  183. package/prefabs/rustic-bench/Bench_01.glb +0 -0
  184. package/prefabs/rustic-bench/PiratesPack_TX.png.png +0 -0
  185. package/prefabs/rustic-bench/composite.json +100 -0
  186. package/prefabs/rustic-bench/data.json +34 -0
  187. package/prefabs/rustic-bench/scripts/seat.ts +191 -0
  188. package/prefabs/rustic-bench/thumbnail.png +0 -0
  189. package/prefabs/rustic-round-stool/Chairwood_01.glb +0 -0
  190. package/prefabs/rustic-round-stool/PiratesPack_TX.png.png +0 -0
  191. package/prefabs/rustic-round-stool/composite.json +74 -0
  192. package/prefabs/rustic-round-stool/data.json +34 -0
  193. package/prefabs/rustic-round-stool/scripts/seat.ts +191 -0
  194. package/prefabs/rustic-round-stool/thumbnail.png +0 -0
  195. package/prefabs/rustic-square-stool/Chairwood_02.glb +0 -0
  196. package/prefabs/rustic-square-stool/PiratesPack_TX.png.png +0 -0
  197. package/prefabs/rustic-square-stool/composite.json +74 -0
  198. package/prefabs/rustic-square-stool/data.json +34 -0
  199. package/prefabs/rustic-square-stool/scripts/seat.ts +191 -0
  200. package/prefabs/rustic-square-stool/thumbnail.png +0 -0
  201. package/prefabs/server-clock/ai.md +67 -0
  202. package/prefabs/server-clock/composite.json +45 -0
  203. package/prefabs/server-clock/data.json +24 -0
  204. package/prefabs/server-clock/scripts/clock-hud.tsx +55 -0
  205. package/prefabs/server-clock/scripts/server-clock.ts +71 -0
  206. package/prefabs/server-clock/thumbnail.png +0 -0
  207. package/prefabs/simple-chair/Chair_03.glb +0 -0
  208. package/prefabs/simple-chair/composite.json +74 -0
  209. package/prefabs/simple-chair/data.json +34 -0
  210. package/prefabs/simple-chair/file1.png +0 -0
  211. package/prefabs/simple-chair/scripts/seat.ts +191 -0
  212. package/prefabs/simple-chair/thumbnail.png +0 -0
  213. package/prefabs/sit-spot/composite.json +45 -0
  214. package/prefabs/sit-spot/data.json +30 -0
  215. package/prefabs/sit-spot/scripts/seat.ts +191 -0
  216. package/prefabs/sit-spot/thumbnail.png +0 -0
  217. package/prefabs/sit-spot-edge/composite.json +42 -0
  218. package/prefabs/sit-spot-edge/data.json +32 -0
  219. package/prefabs/sit-spot-edge/emotes/Sit_Edge1_emote.glb +0 -0
  220. package/prefabs/sit-spot-edge/emotes/Sit_Edge2_emote.glb +0 -0
  221. package/prefabs/sit-spot-edge/scripts/seat.ts +191 -0
  222. package/prefabs/sit-spot-edge/thumbnail.png +0 -0
  223. package/prefabs/small-couch/TwoSeater_Couch.glb +0 -0
  224. package/prefabs/small-couch/composite.json +100 -0
  225. package/prefabs/small-couch/data.json +30 -0
  226. package/prefabs/small-couch/scripts/seat.ts +191 -0
  227. package/prefabs/small-couch/thumbnail.png +0 -0
  228. package/prefabs/spawner/ai.md +109 -0
  229. package/prefabs/spawner/composite.json +73 -0
  230. package/prefabs/spawner/data.json +22 -0
  231. package/prefabs/spawner/scripts/spawner.ts +380 -0
  232. package/prefabs/spawner/thumbnail.png +0 -0
  233. package/prefabs/steampunk-bench/Steampunk Bench.glb +0 -0
  234. package/prefabs/steampunk-bench/composite.json +100 -0
  235. package/prefabs/steampunk-bench/data.json +30 -0
  236. package/prefabs/steampunk-bench/scripts/seat.ts +191 -0
  237. package/prefabs/steampunk-bench/thumbnail.png +0 -0
  238. package/prefabs/steampunk-chair/Steampunk Chair.glb +0 -0
  239. package/prefabs/steampunk-chair/composite.json +74 -0
  240. package/prefabs/steampunk-chair/data.json +30 -0
  241. package/prefabs/steampunk-chair/scripts/seat.ts +191 -0
  242. package/prefabs/steampunk-chair/thumbnail.png +0 -0
  243. package/prefabs/tall-stool/Stool_D.glb +0 -0
  244. package/prefabs/tall-stool/composite.json +74 -0
  245. package/prefabs/tall-stool/data.json +30 -0
  246. package/prefabs/tall-stool/scripts/seat.ts +191 -0
  247. package/prefabs/tall-stool/thumbnail.png +0 -0
  248. package/prefabs/trigger-zone/ai.md +115 -0
  249. package/prefabs/trigger-zone/composite.json +52 -0
  250. package/prefabs/trigger-zone/data.json +31 -0
  251. package/prefabs/trigger-zone/scripts/trigger-zone.ts +89 -0
  252. package/prefabs/trigger-zone/thumbnail.png +0 -0
  253. package/prefabs/video-screen/composite.json +98 -0
  254. package/prefabs/video-screen/data.json +21 -0
  255. package/prefabs/video-screen/thumbnail.png +0 -0
  256. package/prefabs/video-screen/video_player.glb +0 -0
  257. package/prefabs/wooden-chair/Chair 1.glb +0 -0
  258. package/prefabs/wooden-chair/composite.json +74 -0
  259. package/prefabs/wooden-chair/data.json +30 -0
  260. package/prefabs/wooden-chair/scripts/seat.ts +191 -0
  261. package/prefabs/wooden-chair/thumbnail.png +0 -0
  262. package/prefabs/wooden-chair-armrests/Chair 3.glb +0 -0
  263. package/prefabs/wooden-chair-armrests/composite.json +74 -0
  264. package/prefabs/wooden-chair-armrests/data.json +30 -0
  265. package/prefabs/wooden-chair-armrests/scripts/seat.ts +191 -0
  266. package/prefabs/wooden-chair-armrests/thumbnail.png +0 -0
  267. package/scene/about +17 -0
  268. package/scene/bafkreias2w4xuqaic6sdcvblrisayi6xg7drfowzpt5ubjkmrk3mx6czc4 +1 -0
  269. package/scene/bafkreiby2gmthtauf7cckttphmhpecsueyypi2hzlke4lcodg7fm4cm2sq +23 -0
  270. package/scene/bafkreidhwqwajqk4nsuffef5fkdrgmp5ql6vnn2knpqsuqalyosv5o6lk4 +1 -0
  271. package/scene/bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku +0 -0
  272. package/scene/bafybeihw3xlbff4sf5sc3kvmlmlwf2visnty5lfymawlunzt5vrkp5fd7u +90 -0
  273. package/web-build/esbuild.wasm +0 -0
  274. package/web-build/index.js +3 -0
  275. package/web-build/sdk-snapshot.json +1 -0
  276. package/web-build/worker.js +701 -0
  277. package/README.md +0 -3
@@ -0,0 +1,73 @@
1
+ {
2
+ "version": 1,
3
+ "components": [
4
+ {
5
+ "name": "core-schema::Name",
6
+ "data": {
7
+ "0": {
8
+ "json": {
9
+ "value": "Spawner"
10
+ }
11
+ }
12
+ }
13
+ },
14
+ {
15
+ "name": "core::MeshRenderer",
16
+ "data": {
17
+ "0": {
18
+ "json": {
19
+ "mesh": {
20
+ "$case": "cylinder",
21
+ "cylinder": {
22
+ "radiusTop": 0.5,
23
+ "radiusBottom": 0.5
24
+ }
25
+ }
26
+ }
27
+ }
28
+ }
29
+ },
30
+ {
31
+ "name": "core::Material",
32
+ "data": {
33
+ "0": {
34
+ "json": {
35
+ "material": {
36
+ "$case": "unlit",
37
+ "unlit": {
38
+ "diffuseColor": { "r": 0.25, "g": 0.8, "b": 0.69, "a": 0.6 },
39
+ "castShadows": false
40
+ }
41
+ }
42
+ }
43
+ }
44
+ }
45
+ },
46
+ {
47
+ "name": "core::VisibilityComponent",
48
+ "data": {
49
+ "0": {
50
+ "json": {
51
+ "visible": true
52
+ }
53
+ }
54
+ }
55
+ },
56
+ {
57
+ "name": "asset-packs::Script",
58
+ "data": {
59
+ "0": {
60
+ "json": {
61
+ "value": [
62
+ {
63
+ "path": "{assetPath}/scripts/spawner.ts",
64
+ "priority": 0,
65
+ "layout": "{\"params\":{},\"actions\":[]}"
66
+ }
67
+ ]
68
+ }
69
+ }
70
+ }
71
+ }
72
+ ]
73
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "id": "fc759fc0-57a1-461c-aa06-22c0d3d4c3e0",
3
+ "name": "Spawner",
4
+ "description": "Makes copies of a prefab appear while playing — on a click, when a player walks in, on a timer, or when another script asks. Copies appear at the Spawner's own spot, only on this player's client.",
5
+ "category": "custom",
6
+ "tags": [
7
+ "spawner",
8
+ "spawn",
9
+ "pickup",
10
+ "enemy"
11
+ ],
12
+ "version": "1.0.0",
13
+ "changelog": [
14
+ {
15
+ "version": "1.0.0",
16
+ "notes": "Initial release: copies of a prefab appear while playing — on a click, when a player walks in, on a timer, or when another Script asks — made on that player's own client, with a per-spot cap, a lifetime and scatter."
17
+ }
18
+ ],
19
+ "origin": {
20
+ "source": "builtin"
21
+ }
22
+ }
@@ -0,0 +1,380 @@
1
+ // A spawn point. Pick the prefab it copies and what sets it off — a click, a
2
+ // player walking in, a timer, or another script asking — and a copy appears at
3
+ // the Spawner's own spot. Copies are made right here on this player's game:
4
+ // no server round-trip, nothing stored, nothing to wait for.
5
+ //
6
+ // The spot's id is the entity's NAME, the handle the creator, the inspector and
7
+ // another script already share, so a lever somewhere else sets this one off with
8
+ // requestSpawn('Crate Spawner') and nothing has to be wired. Placement makes
9
+ // names unique, so a second "Crate Spawner" is "Crate Spawner 2" and is its own
10
+ // spot.
11
+ //
12
+ // The pool is opened HERE rather than inside a runtime module: the guarantee
13
+ // scan skips every path under runtime/, so a pool opened there would leave the
14
+ // prefab this spot copies every ten seconds reading "Not used yet" forever.
15
+ import {
16
+ InputAction,
17
+ MeshCollider,
18
+ PointerEventType,
19
+ PointerEvents,
20
+ Transform,
21
+ TriggerArea,
22
+ VisibilityComponent,
23
+ engine,
24
+ inputSystem,
25
+ triggerAreaEventsSystem,
26
+ type Entity
27
+ } from '@dcl/sdk/ecs'
28
+ import { isServer } from '@dcl/sdk/network'
29
+ import { localPlayerPosition } from '~runtime/playerPositions'
30
+ import { pool as openPool, snapshotRootComponent, type Pool } from '~runtime/spawner'
31
+ import { registerSpawnPoint } from '~runtime/spawnPoints'
32
+ import { onZone, zoneOf } from '~runtime/zoneBus'
33
+ import { effectiveScatter, scatterOffset } from '~runtime/pure/spawnScatter'
34
+ import { composeWorld, type LocalTransform, type WorldTransform } from '~runtime/pure/worldTransform'
35
+
36
+ /** A prefab id picked in the inspector's prefab dropdown. */
37
+ type PrefabRef = string
38
+
39
+ // Collision layers by value, not by name: the SDK renamed CL_RESERVED2 to
40
+ // CL_MAIN_PLAYER, and this script compiles against whatever pin the creator's
41
+ // project happens to carry.
42
+ const CL_POINTER = 1
43
+ // The local avatar's collider carries this layer and remote avatars' do not, so
44
+ // the walk-in area fires for the player standing at THIS game, exactly once.
45
+ const CL_MAIN_PLAYER = 8
46
+ const CLICK_RANGE_M = 8
47
+ const TRANSFORM = 'core::Transform'
48
+ const PARENT_DEPTH_MAX = 32
49
+ // The editor materializes this child when `where` is 'custom spot' — a marker
50
+ // showing the prefab's model, positioned with the gizmos, hidden while playing.
51
+ // The prefix stops at a word boundary, so "Spawn Spot 2" and "spawn spot-a" are
52
+ // markers and the creator's own "Spawn Spotlight" is not — the same rule the
53
+ // editor's actions/spawn-spot.ts matches on, and the two must agree.
54
+ const SPAWN_SPOT_MATCH = /^spawn spot(?![a-z])/
55
+
56
+ export class Spawner {
57
+ private pool: Pool | null = null
58
+ private name = ''
59
+ /** copy → seconds left; Infinity when disappearsAfter is 0 */
60
+ private live = new Map<Entity, number>()
61
+ private spawnedCount = 0
62
+ private countdownS = 0
63
+ private armed = false
64
+ private subscribing = false
65
+ private customSpot: Entity | null = null
66
+
67
+ constructor(
68
+ public src: string,
69
+ public entity: Entity,
70
+ /** What appears here. Pick any prefab in this project. */
71
+ public spawn: PrefabRef = '',
72
+ /** What makes a copy appear */
73
+ public when:
74
+ | 'when clicked'
75
+ | 'when a player enters'
76
+ | 'every few seconds'
77
+ | 'when a script asks' = 'when clicked',
78
+ /** For "every few seconds": how many seconds between copies. */
79
+ public everySeconds: number = 10,
80
+ /** For "when clicked": the words a player sees before they click. */
81
+ public hoverLabel: string = 'Use',
82
+ /** How many copies can be out at once. At the limit, nothing more appears until one goes. */
83
+ public atMostAtOnce: number = 1,
84
+ /** Seconds a copy sticks around. 0 keeps it until something removes it. */
85
+ public disappearsAfter: number = 0,
86
+ /** Where a copy appears. 'custom spot' uses the "Spawn Spot" marker you position with the gizmos. */
87
+ public where: 'at this spawner' | 'at the player' | 'custom spot' = 'at this spawner'
88
+ ) {}
89
+
90
+ // What sets this spot off is derived from WHERE IT SITS, never picked from a
91
+ // list: parented to something, that something is the button or the zone; on
92
+ // its own, the spawner itself is — its disc becomes the button, its spot
93
+ // becomes the walk-in area (scale is metres, the gizmo is the resize tool).
94
+ // Spread and marker visibility are automatic for the same reason: several
95
+ // copies spread just enough not to stack, and the disc shows itself in Play
96
+ // exactly when this spawner IS the button.
97
+ private parentEntity(): Entity | null {
98
+ return parentOf(Transform.getOrNull(this.entity))
99
+ }
100
+
101
+ private get isOwnButton(): boolean {
102
+ return this.when === 'when clicked' && this.parentEntity() === null
103
+ }
104
+
105
+ start(): void {
106
+ // Every trigger is a player's gesture, so the copies are the player's too. In
107
+ // a scene that runs a Multiplayer Server this same script boots there — where
108
+ // the timer would drip copies nobody can see. That half stands down whole.
109
+ if (isServer()) return
110
+ VisibilityComponent.createOrReplace(this.entity, { visible: this.isOwnButton })
111
+ this.name = zoneOf(this.entity)
112
+ this.pool = this.spawnPool()
113
+ if (this.pool === null) return
114
+ // A cap above the prefab's Max alive can never fill; say the cure once, here.
115
+ if (this.atMostAtOnce > this.pool.max) {
116
+ console.log(
117
+ `[Spawner] '${this.name}' asks for ${this.atMostAtOnce} copies but the prefab's Max alive is ${this.pool.max} — raise the prefab's copy limit in the Prefabs tab`
118
+ )
119
+ this.atMostAtOnce = this.pool.max
120
+ }
121
+ this.countdownS = Math.max(1, this.everySeconds)
122
+ // Every mode registers, not just 'when a script asks': requestSpawn by NAME is
123
+ // the whole cross-script wiring, and a round script poking a click spawner for
124
+ // an extra crate must keep working the way it always has.
125
+ registerSpawnPoint(this.name, () => this.spawnOne())
126
+ this.hideSpawnSpots()
127
+ if (this.when === 'when a player enters') this.armWalkIn()
128
+ }
129
+
130
+ // The marker is an authoring surface: it shows the model in the editor so the
131
+ // creator can aim it, and disappears the moment the scene runs. Hiding runs in
132
+ // EVERY mode, not just 'custom spot' — a marker left behind by a changed
133
+ // dropdown must never ship as a ghost model players can see. Its GltfContainer
134
+ // ships with zeroed collision masks, so hiding the mesh is all that is left.
135
+ private hideSpawnSpots(): void {
136
+ for (const [child] of engine.getEntitiesWith(Transform)) {
137
+ if (parentOf(Transform.getOrNull(child)) !== this.entity) continue
138
+ if (!SPAWN_SPOT_MATCH.test(zoneOf(child).trim().toLowerCase())) continue
139
+ VisibilityComponent.createOrReplace(child, { visible: false })
140
+ if (this.customSpot === null) this.customSpot = child
141
+ }
142
+ if (this.where === 'custom spot' && this.customSpot === null) {
143
+ console.log(
144
+ `[Spawner] '${this.name}' is set to a custom spot but its "Spawn Spot" marker is gone — copies appear at the spawner instead`
145
+ )
146
+ }
147
+ }
148
+
149
+ update(dt: number): void {
150
+ if (this.pool === null) return
151
+ if (!this.armed) {
152
+ this.armed = true
153
+ this.subscribing = false
154
+ // The pointer hint is merged on the first TICK, not in start(): script start
155
+ // order is not authored, so another script's createOrReplace of PointerEvents
156
+ // during start() would silently erase it.
157
+ if (this.when === 'when clicked') this.armClick()
158
+ }
159
+ this.expire(dt)
160
+ if (this.when === 'every few seconds') {
161
+ this.countdownS -= dt
162
+ if (this.countdownS <= 0) {
163
+ this.countdownS = Math.max(1, this.everySeconds)
164
+ this.spawnOne()
165
+ }
166
+ }
167
+ if (this.when !== 'when clicked') return
168
+ if (inputSystem.isTriggered(InputAction.IA_POINTER, PointerEventType.PET_DOWN, this.clickTarget())) {
169
+ this.spawnOne()
170
+ }
171
+ }
172
+
173
+ private spawnPool(): Pool | null {
174
+ if (this.spawn === '') {
175
+ console.log('[Spawner] no prefab picked — set "spawn" to the prefab this spot should copy')
176
+ return null
177
+ }
178
+ // A stale prefab ref (Spawnable turned off, prefab deleted) makes the pool
179
+ // throw, and a throw out of start() aborts every script the runner has not
180
+ // started yet plus the scene's own main(). One dropdown must not kill a scene.
181
+ try {
182
+ return openPool(this.spawn, 'seeded')
183
+ } catch (error) {
184
+ console.log('[Spawner] the "spawn" param is not a Spawnable prefab —', error)
185
+ return null
186
+ }
187
+ }
188
+
189
+ // --- the triggers ----------------------------------------------------------
190
+
191
+ private armWalkIn(): void {
192
+ const parent = this.parentEntity()
193
+ const parentZone = parent !== null && TriggerArea.getOrNull(parent) !== null ? zoneOf(parent) : ''
194
+ if (parentZone !== '') {
195
+ this.subscribing = true
196
+ onZone(parentZone, 'enter', (event) => {
197
+ // Late subscribers get current occupancy replayed as enters, and those arrive
198
+ // synchronously inside this call: someone already standing here did not just
199
+ // walk in, and a copy must not appear because a script started.
200
+ if (this.subscribing || !event.local) return
201
+ this.spawnOne()
202
+ })
203
+ return
204
+ }
205
+ // Not sitting in a zone: this spot IS the walk-in area. The engine's enter
206
+ // event only fires on the transition, so someone already standing here when
207
+ // the scene starts does not set it off — same rule as the zone path.
208
+ TriggerArea.setBox(this.entity, CL_MAIN_PLAYER)
209
+ triggerAreaEventsSystem.onTriggerEnter(this.entity, () => this.spawnOne())
210
+ }
211
+
212
+ private clickTarget(): Entity {
213
+ return this.parentEntity() ?? this.entity
214
+ }
215
+
216
+ private armClick(): void {
217
+ const target = this.clickTarget()
218
+ // Only the marker gets a collider of its own. Another entity the creator
219
+ // pointed at is theirs — a cylinder stamped over it would be the wrong shape.
220
+ if (target === this.entity) {
221
+ MeshCollider.createOrReplace(target, {
222
+ mesh: { $case: 'cylinder', cylinder: { radiusTop: 0.5, radiusBottom: 0.5 } },
223
+ collisionMask: CL_POINTER
224
+ })
225
+ }
226
+ const existing = PointerEvents.getOrNull(target)?.pointerEvents ?? []
227
+ PointerEvents.createOrReplace(target, {
228
+ pointerEvents: [
229
+ ...existing,
230
+ {
231
+ eventType: PointerEventType.PET_DOWN,
232
+ eventInfo: {
233
+ button: InputAction.IA_POINTER,
234
+ hoverText: this.hoverLabel,
235
+ maxDistance: CLICK_RANGE_M
236
+ }
237
+ }
238
+ ]
239
+ })
240
+ }
241
+
242
+ // --- the copies ------------------------------------------------------------
243
+
244
+ private spawnOne(): void {
245
+ if (this.pool === null) return
246
+ // Drop before the cap check, not only in expire(): retireSpawned frees the
247
+ // pool synchronously, and "retire then request" in one callback must spawn.
248
+ this.dropReleased()
249
+ if (this.live.size >= Math.max(1, this.atMostAtOnce)) return
250
+ const world = this.spawnBase()
251
+ const offset = scatterOffset(this.spawnedCount, effectiveScatter(0, this.atMostAtOnce))
252
+ // The init write replaces the clone's whole Transform, and the spot's world
253
+ // scale must not leak into it — a spawner shrunk to a marker would shrink its
254
+ // zombies. Position and rotation are the spot's; scale stays the prefab's own.
255
+ const authoredScale = localOf(snapshotRootComponent(this.pool.prefab, TRANSFORM)).scale
256
+ let spawned: Entity | null = null
257
+ try {
258
+ // A clone's own start() is creator code. Letting it throw out of here would
259
+ // abort the rest of the frame, so one bad script loses one copy, not the scene.
260
+ spawned = this.pool.acquire(undefined, {
261
+ [TRANSFORM]: {
262
+ position: {
263
+ x: world.position.x + offset.x,
264
+ y: world.position.y,
265
+ z: world.position.z + offset.z
266
+ },
267
+ rotation: world.rotation,
268
+ scale: authoredScale
269
+ }
270
+ })
271
+ } catch (error) {
272
+ console.error(`[Spawner] '${this.name}' could not make a copy of ${this.pool.prefab}:`, error)
273
+ return
274
+ }
275
+ if (spawned === null) {
276
+ console.log(`[Spawner] '${this.name}': the prefab's Max alive is reached — no copy until one goes`)
277
+ return
278
+ }
279
+ this.spawnedCount++
280
+ this.live.set(spawned, this.disappearsAfter > 0 ? this.disappearsAfter : Infinity)
281
+ }
282
+
283
+ /** Let go of copies something else already released (retire, round reset). */
284
+ private dropReleased(): void {
285
+ const pool = this.pool
286
+ if (pool === null) return
287
+ for (const copy of [...this.live.keys()]) {
288
+ if (pool.instanceIdOf(copy) === null) this.live.delete(copy)
289
+ }
290
+ }
291
+
292
+ private expire(dt: number): void {
293
+ if (this.pool === null || this.live.size === 0) return
294
+ this.dropReleased()
295
+ for (const [copy, left] of [...this.live]) {
296
+ if (left === Infinity) continue
297
+ const remaining = left - dt
298
+ if (remaining > 0) {
299
+ this.live.set(copy, remaining)
300
+ continue
301
+ }
302
+ this.live.delete(copy)
303
+ this.pool.release(copy)
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Where a copy lands, by the `where` setting. Every branch that cannot answer
309
+ * (avatar not loaded yet, marker deleted) falls back to the spawner's own spot
310
+ * — a trigger that fired must always produce a copy somewhere sensible.
311
+ */
312
+ private spawnBase(): WorldTransform {
313
+ if (this.where === 'at the player') {
314
+ // Both come off the avatar's own Transform — localPlayerPosition converts
315
+ // it to the scene's frame — so they are there together or not at all.
316
+ const avatar = Transform.getOrNull(engine.PlayerEntity)
317
+ const position = localPlayerPosition()
318
+ if (avatar !== null && position !== null) {
319
+ return { position, rotation: avatar.rotation, scale: { x: 1, y: 1, z: 1 } }
320
+ }
321
+ }
322
+ // The marker may have been removed mid-play by a cleanup script — a spawn
323
+ // must land at the spawner then, never at whatever id 0 composes to.
324
+ if (this.where === 'custom spot' && this.customSpot !== null && Transform.getOrNull(this.customSpot) !== null) {
325
+ return worldOf(this.customSpot)
326
+ }
327
+ return worldOf(this.entity)
328
+ }
329
+ }
330
+
331
+ /** An entity's composed world transform, walking parents to the scene root. */
332
+ function worldOf(entity: Entity): WorldTransform {
333
+ const chain: LocalTransform[] = []
334
+ const seen = new Set<number>()
335
+ let current: Entity | null = entity
336
+ for (let depth = 0; current !== null && depth < PARENT_DEPTH_MAX; depth++) {
337
+ const id = Number(current)
338
+ if (id === 0 || seen.has(id)) break
339
+ seen.add(id)
340
+ const local = Transform.getOrNull(current)
341
+ chain.push(localOf(local))
342
+ current = parentOf(local)
343
+ }
344
+ chain.reverse()
345
+ return composeWorld(chain)
346
+ }
347
+
348
+ /** The parent to walk to next, or null at the scene root. */
349
+ function parentOf(local: { parent?: Entity } | null): Entity | null {
350
+ const parent = local?.parent
351
+ return parent === undefined || Number(parent) === 0 ? null : parent
352
+ }
353
+
354
+ /** Read a Transform — a live component or a snapshot's JSON — with identity for anything it does not say. */
355
+ function localOf(value: unknown): LocalTransform {
356
+ const source: Record<string, unknown> = isRecord(value) ? value : {}
357
+ const position = isRecord(source.position) ? source.position : {}
358
+ const rotation = isRecord(source.rotation) ? source.rotation : {}
359
+ const scale = isRecord(source.scale) ? source.scale : {}
360
+ return {
361
+ position: { x: numberOf(position.x, 0), y: numberOf(position.y, 0), z: numberOf(position.z, 0) },
362
+ rotation: {
363
+ x: numberOf(rotation.x, 0),
364
+ y: numberOf(rotation.y, 0),
365
+ z: numberOf(rotation.z, 0),
366
+ w: numberOf(rotation.w, 1)
367
+ },
368
+ scale: { x: numberOf(scale.x, 1), y: numberOf(scale.y, 1), z: numberOf(scale.z, 1) },
369
+ // worldOf walks the chain itself; composeWorld never reads this.
370
+ parent: 0
371
+ }
372
+ }
373
+
374
+ function isRecord(value: unknown): value is Record<string, unknown> {
375
+ return typeof value === 'object' && value !== null
376
+ }
377
+
378
+ function numberOf(value: unknown, fallback: number): number {
379
+ return typeof value === 'number' && Number.isFinite(value) ? value : fallback
380
+ }
Binary file
@@ -0,0 +1,100 @@
1
+ {
2
+ "version": 1,
3
+ "components": [
4
+ {
5
+ "name": "core::GltfContainer",
6
+ "data": {
7
+ "0": {
8
+ "json": {
9
+ "src": "{assetPath}/Steampunk Bench.glb",
10
+ "visibleMeshesCollisionMask": 0,
11
+ "invisibleMeshesCollisionMask": 3
12
+ }
13
+ }
14
+ }
15
+ },
16
+ {
17
+ "name": "core-schema::Name",
18
+ "data": {
19
+ "0": {
20
+ "json": {
21
+ "value": "Steampunk Bench"
22
+ }
23
+ },
24
+ "513": {
25
+ "json": {
26
+ "value": "Sit Spot 1"
27
+ }
28
+ },
29
+ "514": {
30
+ "json": {
31
+ "value": "Sit Spot 2"
32
+ }
33
+ }
34
+ }
35
+ },
36
+ {
37
+ "name": "core::Transform",
38
+ "data": {
39
+ "513": {
40
+ "json": {
41
+ "position": {
42
+ "x": 0.47,
43
+ "y": 0.12,
44
+ "z": 0.28
45
+ },
46
+ "rotation": {
47
+ "x": 0,
48
+ "y": 0.07845909893512726,
49
+ "z": 0,
50
+ "w": 0.9969173073768616
51
+ },
52
+ "scale": {
53
+ "x": 1,
54
+ "y": 1,
55
+ "z": 1
56
+ },
57
+ "parent": 0
58
+ }
59
+ },
60
+ "514": {
61
+ "json": {
62
+ "position": {
63
+ "x": -0.4,
64
+ "y": 0.1182,
65
+ "z": 0.28
66
+ },
67
+ "rotation": {
68
+ "x": 0,
69
+ "y": 0.07845906913280487,
70
+ "z": 0,
71
+ "w": 0.9969173669815063
72
+ },
73
+ "scale": {
74
+ "x": 1,
75
+ "y": 1,
76
+ "z": 1
77
+ },
78
+ "parent": 0
79
+ }
80
+ }
81
+ }
82
+ },
83
+ {
84
+ "name": "asset-packs::Script",
85
+ "data": {
86
+ "0": {
87
+ "json": {
88
+ "value": [
89
+ {
90
+ "path": "{assetPath}/scripts/seat.ts",
91
+ "priority": 0,
92
+ "layout": "{\"params\":{},\"actions\":[]}"
93
+ }
94
+ ]
95
+ }
96
+ }
97
+ }
98
+ }
99
+ ]
100
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "id": "8a80b964-1f4d-4ea3-8f6b-078809891a17",
3
+ "name": "Steampunk Bench",
4
+ "description": "A riveted steampunk bench, seating two. Players can sit on it.",
5
+ "category": "custom",
6
+ "tags": [
7
+ "seat",
8
+ "furniture",
9
+ "sit"
10
+ ],
11
+ "version": "1.1.0",
12
+ "changelog": [
13
+ {
14
+ "version": "1.1.0",
15
+ "notes": "The seat no longer runs on the Multiplayer Server, where it was walking every sit spot's transform chain each frame for nobody to see."
16
+ },
17
+ {
18
+ "version": "1.0.0",
19
+ "notes": "Initial release"
20
+ }
21
+ ],
22
+ "origin": {
23
+ "source": "builtin"
24
+ },
25
+ "requiredPermissions": [
26
+ "ALLOW_TO_MOVE_PLAYER_INSIDE_SCENE",
27
+ "ALLOW_TO_TRIGGER_AVATAR_EMOTE"
28
+ ],
29
+ "group": "Seats"
30
+ }