@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,191 @@
1
+ // Sit on this seat: click a sit spot -> the avatar is placed on it facing forward
2
+ // and a looping sitting emote plays; moving cancels the emote engine-side (the
3
+ // same mechanism the Creator Hub's seat smart items and Genesis Plaza use — no
4
+ // avatar attach). Spots are child entities named "Sit Spot…" carrying only a
5
+ // Transform; this script gives them their collider and pointer event at runtime.
6
+ // With no such children (the standalone Sit Spot prefab), the entity itself is
7
+ // the spot.
8
+ import {
9
+ ColliderLayer,
10
+ InputAction,
11
+ Material,
12
+ MeshCollider,
13
+ MeshRenderer,
14
+ Schemas,
15
+ Transform,
16
+ VisibilityComponent,
17
+ engine,
18
+ pointerEventsSystem,
19
+ type Entity,
20
+ type LastWriteWinElementSetComponentDefinition
21
+ } from '@dcl/sdk/ecs'
22
+ import { Quaternion, Vector3 } from '@dcl/sdk/math'
23
+ import { isServer } from '@dcl/sdk/network'
24
+ import { movePlayerTo, triggerEmote, triggerSceneEmote } from '~system/RestrictedActions'
25
+
26
+ const SPOT_NAME_PREFIX = 'sit spot'
27
+ const HOVER_TEXT = 'Sit Here'
28
+ const MAX_DISTANCE = 6
29
+ const EMOTE_DELAY_S = 0.25
30
+ const SITTING_EMOTES = ['sittingChair1', 'sittingChair2']
31
+ // emote .glb files bundled in this prefab's folder (Genesis Plaza-style custom
32
+ // poses, played via triggerSceneEmote); empty = the predefined chair emotes
33
+ const SCENE_EMOTES: string[] = ['emotes/Sit_Edge1_emote.glb', 'emotes/Sit_Edge2_emote.glb']
34
+
35
+ // the Plaza-style "you can sit here" hint: a small glowing dot floating over
36
+ // each free spot, pulsing gently, shown only to nearby players
37
+ const DOT_VISIBLE_DIST = 10
38
+ const DOT_OCCUPIED_DIST = 0.9
39
+ const DOT_HEIGHT = 0.55
40
+ const DOT_SIZE = 0.09
41
+ const DOT_PULSE = 0.25
42
+
43
+ type NameValue = { value: string }
44
+ type NameComponent = LastWriteWinElementSetComponentDefinition<NameValue>
45
+
46
+ // Every placed seat prefab bundles its own copy of this script (prefab folders are
47
+ // self-contained), so the component may already be defined by a sibling copy.
48
+ function nameComponent(): NameComponent {
49
+ const existing = engine.getComponentOrNull('core-schema::Name')
50
+ if (existing !== null) return existing as NameComponent
51
+ return engine.defineComponent('core-schema::Name', { value: Schemas.String })
52
+ }
53
+
54
+ function isSpotName(name: unknown): boolean {
55
+ return typeof name === 'string' && name.toLowerCase().startsWith(SPOT_NAME_PREFIX)
56
+ }
57
+
58
+ function sceneRelativeTransform(entity: Entity): { position: Vector3; rotation: Quaternion } {
59
+ const chain: Entity[] = []
60
+ let cursor = entity
61
+ while (cursor !== engine.RootEntity && chain.length < 32) {
62
+ chain.push(cursor)
63
+ const t = Transform.getOrNull(cursor)
64
+ if (t === null) break
65
+ cursor = t.parent ?? engine.RootEntity
66
+ }
67
+ let position = Vector3.Zero()
68
+ let rotation = Quaternion.Identity()
69
+ let scale = Vector3.One()
70
+ for (const link of chain.reverse()) {
71
+ const t = Transform.getOrNull(link)
72
+ if (t === null) continue
73
+ const local = Vector3.create(
74
+ t.position.x * scale.x,
75
+ t.position.y * scale.y,
76
+ t.position.z * scale.z
77
+ )
78
+ position = Vector3.add(position, Vector3.rotate(local, rotation))
79
+ rotation = Quaternion.multiply(rotation, t.rotation)
80
+ scale = Vector3.create(scale.x * t.scale.x, scale.y * t.scale.y, scale.z * t.scale.z)
81
+ }
82
+ return { position, rotation }
83
+ }
84
+
85
+ export class Seat {
86
+ private emoteIn = 0
87
+ private time = 0
88
+ private dots: Array<{ spot: Entity; dot: Entity; visible: boolean }> = []
89
+
90
+ constructor(
91
+ public src: string,
92
+ public entity: Entity
93
+ ) {}
94
+
95
+ start(): void {
96
+ if (isServer()) { return }
97
+ const Name = nameComponent()
98
+ const spots: Entity[] = []
99
+ for (const [child, transform] of engine.getEntitiesWith(Transform)) {
100
+ if (transform.parent !== this.entity) continue
101
+ const name = Name.getOrNull(child)
102
+ if (name !== null && isSpotName(name.value)) spots.push(child)
103
+ }
104
+ if (spots.length === 0) spots.push(this.entity)
105
+
106
+ for (const spot of spots) {
107
+ MeshCollider.setBox(spot, ColliderLayer.CL_POINTER)
108
+ pointerEventsSystem.onPointerDown(
109
+ {
110
+ entity: spot,
111
+ opts: { button: InputAction.IA_POINTER, hoverText: HOVER_TEXT, maxDistance: MAX_DISTANCE }
112
+ },
113
+ () => this.sit(spot)
114
+ )
115
+ // no collider on the dot: clicks pass through to the spot beneath it
116
+ const dot = engine.addEntity()
117
+ Transform.create(dot, {
118
+ parent: spot,
119
+ position: Vector3.create(0, DOT_HEIGHT, 0),
120
+ scale: Vector3.create(DOT_SIZE, DOT_SIZE, DOT_SIZE)
121
+ })
122
+ MeshRenderer.setSphere(dot)
123
+ Material.setPbrMaterial(dot, {
124
+ albedoColor: { r: 1, g: 1, b: 1, a: 0.85 },
125
+ emissiveColor: { r: 1, g: 1, b: 1 },
126
+ emissiveIntensity: 2,
127
+ roughness: 1
128
+ })
129
+ VisibilityComponent.create(dot, { visible: false })
130
+ this.dots.push({ spot, dot, visible: false })
131
+ }
132
+ }
133
+
134
+ update(dt: number): void {
135
+ // The hint walks a transform chain per spot per frame, and a seat is placed
136
+ // by the dozen: without this the Multiplayer Server pays for every one.
137
+ if (isServer()) { return }
138
+ this.time += dt
139
+ this.updateDots()
140
+ if (this.emoteIn <= 0) return
141
+ this.emoteIn -= dt
142
+ if (this.emoteIn > 0) return
143
+ if (SCENE_EMOTES.length > 0) {
144
+ // src is the script's directory (custom/<slug>/scripts) — the emotes live
145
+ // one level up, in the placed prefab's own folder
146
+ const root = this.src.replace(/\/scripts\/?$/, '')
147
+ const emote = SCENE_EMOTES[Math.floor(Math.random() * SCENE_EMOTES.length)]
148
+ void triggerSceneEmote({ src: `${root}/${emote}`, loop: true })
149
+ return
150
+ }
151
+ const emote = SITTING_EMOTES[Math.floor(Math.random() * SITTING_EMOTES.length)]
152
+ void triggerEmote({ predefinedEmote: emote })
153
+ }
154
+
155
+ private updateDots(): void {
156
+ const player = Transform.getOrNull(engine.PlayerEntity)?.position
157
+ const pulse = 1 + DOT_PULSE * Math.sin(this.time * 3)
158
+ for (const entry of this.dots) {
159
+ let visible = false
160
+ if (player !== undefined) {
161
+ const world = sceneRelativeTransform(entry.spot).position
162
+ const dist = Vector3.distance(player, world)
163
+ // hidden while someone (you) is on the spot — a glow over a seated
164
+ // avatar's head reads as a bug, not a hint
165
+ visible = dist < DOT_VISIBLE_DIST && dist > DOT_OCCUPIED_DIST
166
+ }
167
+ if (entry.visible !== visible) {
168
+ entry.visible = visible
169
+ VisibilityComponent.getMutable(entry.dot).visible = visible
170
+ }
171
+ if (visible) {
172
+ Transform.getMutable(entry.dot).scale = Vector3.create(
173
+ DOT_SIZE * pulse,
174
+ DOT_SIZE * pulse,
175
+ DOT_SIZE * pulse
176
+ )
177
+ }
178
+ }
179
+ }
180
+
181
+ private sit(spot: Entity): void {
182
+ const world = sceneRelativeTransform(spot)
183
+ const forward = Vector3.rotate(Vector3.Forward(), world.rotation)
184
+ void movePlayerTo({
185
+ newRelativePosition: world.position,
186
+ avatarTarget: Vector3.add(world.position, forward)
187
+ })
188
+ // let the teleport settle before the emote starts, or the walk cancels it
189
+ this.emoteIn = EMOTE_DELAY_S
190
+ }
191
+ }
@@ -0,0 +1,100 @@
1
+ {
2
+ "version": 1,
3
+ "components": [
4
+ {
5
+ "name": "core::GltfContainer",
6
+ "data": {
7
+ "0": {
8
+ "json": {
9
+ "src": "{assetPath}/TwoSeater_Couch.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": "Small Couch"
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.62,
43
+ "y": 0,
44
+ "z": 0.14
45
+ },
46
+ "rotation": {
47
+ "x": 0,
48
+ "y": 0.07845909572784494,
49
+ "z": 0,
50
+ "w": 0.996917333733128
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": -1.8,
64
+ "y": 0,
65
+ "z": 0.14
66
+ },
67
+ "rotation": {
68
+ "x": 0,
69
+ "y": 0.07845909572784494,
70
+ "z": 0,
71
+ "w": 0.996917333733128
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": "acca7441-52c9-4b80-83fb-af18bcbf4c7e",
3
+ "name": "Small Couch",
4
+ "description": "A compact couch, seating two. Players can sit at either end.",
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
+ }
@@ -0,0 +1,191 @@
1
+ // Sit on this seat: click a sit spot -> the avatar is placed on it facing forward
2
+ // and a looping sitting emote plays; moving cancels the emote engine-side (the
3
+ // same mechanism the Creator Hub's seat smart items and Genesis Plaza use — no
4
+ // avatar attach). Spots are child entities named "Sit Spot…" carrying only a
5
+ // Transform; this script gives them their collider and pointer event at runtime.
6
+ // With no such children (the standalone Sit Spot prefab), the entity itself is
7
+ // the spot.
8
+ import {
9
+ ColliderLayer,
10
+ InputAction,
11
+ Material,
12
+ MeshCollider,
13
+ MeshRenderer,
14
+ Schemas,
15
+ Transform,
16
+ VisibilityComponent,
17
+ engine,
18
+ pointerEventsSystem,
19
+ type Entity,
20
+ type LastWriteWinElementSetComponentDefinition
21
+ } from '@dcl/sdk/ecs'
22
+ import { Quaternion, Vector3 } from '@dcl/sdk/math'
23
+ import { isServer } from '@dcl/sdk/network'
24
+ import { movePlayerTo, triggerEmote, triggerSceneEmote } from '~system/RestrictedActions'
25
+
26
+ const SPOT_NAME_PREFIX = 'sit spot'
27
+ const HOVER_TEXT = 'Sit Here'
28
+ const MAX_DISTANCE = 6
29
+ const EMOTE_DELAY_S = 0.25
30
+ const SITTING_EMOTES = ['sittingChair1', 'sittingChair2']
31
+ // emote .glb files bundled in this prefab's folder (Genesis Plaza-style custom
32
+ // poses, played via triggerSceneEmote); empty = the predefined chair emotes
33
+ const SCENE_EMOTES: string[] = []
34
+
35
+ // the Plaza-style "you can sit here" hint: a small glowing dot floating over
36
+ // each free spot, pulsing gently, shown only to nearby players
37
+ const DOT_VISIBLE_DIST = 10
38
+ const DOT_OCCUPIED_DIST = 0.9
39
+ const DOT_HEIGHT = 0.55
40
+ const DOT_SIZE = 0.09
41
+ const DOT_PULSE = 0.25
42
+
43
+ type NameValue = { value: string }
44
+ type NameComponent = LastWriteWinElementSetComponentDefinition<NameValue>
45
+
46
+ // Every placed seat prefab bundles its own copy of this script (prefab folders are
47
+ // self-contained), so the component may already be defined by a sibling copy.
48
+ function nameComponent(): NameComponent {
49
+ const existing = engine.getComponentOrNull('core-schema::Name')
50
+ if (existing !== null) return existing as NameComponent
51
+ return engine.defineComponent('core-schema::Name', { value: Schemas.String })
52
+ }
53
+
54
+ function isSpotName(name: unknown): boolean {
55
+ return typeof name === 'string' && name.toLowerCase().startsWith(SPOT_NAME_PREFIX)
56
+ }
57
+
58
+ function sceneRelativeTransform(entity: Entity): { position: Vector3; rotation: Quaternion } {
59
+ const chain: Entity[] = []
60
+ let cursor = entity
61
+ while (cursor !== engine.RootEntity && chain.length < 32) {
62
+ chain.push(cursor)
63
+ const t = Transform.getOrNull(cursor)
64
+ if (t === null) break
65
+ cursor = t.parent ?? engine.RootEntity
66
+ }
67
+ let position = Vector3.Zero()
68
+ let rotation = Quaternion.Identity()
69
+ let scale = Vector3.One()
70
+ for (const link of chain.reverse()) {
71
+ const t = Transform.getOrNull(link)
72
+ if (t === null) continue
73
+ const local = Vector3.create(
74
+ t.position.x * scale.x,
75
+ t.position.y * scale.y,
76
+ t.position.z * scale.z
77
+ )
78
+ position = Vector3.add(position, Vector3.rotate(local, rotation))
79
+ rotation = Quaternion.multiply(rotation, t.rotation)
80
+ scale = Vector3.create(scale.x * t.scale.x, scale.y * t.scale.y, scale.z * t.scale.z)
81
+ }
82
+ return { position, rotation }
83
+ }
84
+
85
+ export class Seat {
86
+ private emoteIn = 0
87
+ private time = 0
88
+ private dots: Array<{ spot: Entity; dot: Entity; visible: boolean }> = []
89
+
90
+ constructor(
91
+ public src: string,
92
+ public entity: Entity
93
+ ) {}
94
+
95
+ start(): void {
96
+ if (isServer()) { return }
97
+ const Name = nameComponent()
98
+ const spots: Entity[] = []
99
+ for (const [child, transform] of engine.getEntitiesWith(Transform)) {
100
+ if (transform.parent !== this.entity) continue
101
+ const name = Name.getOrNull(child)
102
+ if (name !== null && isSpotName(name.value)) spots.push(child)
103
+ }
104
+ if (spots.length === 0) spots.push(this.entity)
105
+
106
+ for (const spot of spots) {
107
+ MeshCollider.setBox(spot, ColliderLayer.CL_POINTER)
108
+ pointerEventsSystem.onPointerDown(
109
+ {
110
+ entity: spot,
111
+ opts: { button: InputAction.IA_POINTER, hoverText: HOVER_TEXT, maxDistance: MAX_DISTANCE }
112
+ },
113
+ () => this.sit(spot)
114
+ )
115
+ // no collider on the dot: clicks pass through to the spot beneath it
116
+ const dot = engine.addEntity()
117
+ Transform.create(dot, {
118
+ parent: spot,
119
+ position: Vector3.create(0, DOT_HEIGHT, 0),
120
+ scale: Vector3.create(DOT_SIZE, DOT_SIZE, DOT_SIZE)
121
+ })
122
+ MeshRenderer.setSphere(dot)
123
+ Material.setPbrMaterial(dot, {
124
+ albedoColor: { r: 1, g: 1, b: 1, a: 0.85 },
125
+ emissiveColor: { r: 1, g: 1, b: 1 },
126
+ emissiveIntensity: 2,
127
+ roughness: 1
128
+ })
129
+ VisibilityComponent.create(dot, { visible: false })
130
+ this.dots.push({ spot, dot, visible: false })
131
+ }
132
+ }
133
+
134
+ update(dt: number): void {
135
+ // The hint walks a transform chain per spot per frame, and a seat is placed
136
+ // by the dozen: without this the Multiplayer Server pays for every one.
137
+ if (isServer()) { return }
138
+ this.time += dt
139
+ this.updateDots()
140
+ if (this.emoteIn <= 0) return
141
+ this.emoteIn -= dt
142
+ if (this.emoteIn > 0) return
143
+ if (SCENE_EMOTES.length > 0) {
144
+ // src is the script's directory (custom/<slug>/scripts) — the emotes live
145
+ // one level up, in the placed prefab's own folder
146
+ const root = this.src.replace(/\/scripts\/?$/, '')
147
+ const emote = SCENE_EMOTES[Math.floor(Math.random() * SCENE_EMOTES.length)]
148
+ void triggerSceneEmote({ src: `${root}/${emote}`, loop: true })
149
+ return
150
+ }
151
+ const emote = SITTING_EMOTES[Math.floor(Math.random() * SITTING_EMOTES.length)]
152
+ void triggerEmote({ predefinedEmote: emote })
153
+ }
154
+
155
+ private updateDots(): void {
156
+ const player = Transform.getOrNull(engine.PlayerEntity)?.position
157
+ const pulse = 1 + DOT_PULSE * Math.sin(this.time * 3)
158
+ for (const entry of this.dots) {
159
+ let visible = false
160
+ if (player !== undefined) {
161
+ const world = sceneRelativeTransform(entry.spot).position
162
+ const dist = Vector3.distance(player, world)
163
+ // hidden while someone (you) is on the spot — a glow over a seated
164
+ // avatar's head reads as a bug, not a hint
165
+ visible = dist < DOT_VISIBLE_DIST && dist > DOT_OCCUPIED_DIST
166
+ }
167
+ if (entry.visible !== visible) {
168
+ entry.visible = visible
169
+ VisibilityComponent.getMutable(entry.dot).visible = visible
170
+ }
171
+ if (visible) {
172
+ Transform.getMutable(entry.dot).scale = Vector3.create(
173
+ DOT_SIZE * pulse,
174
+ DOT_SIZE * pulse,
175
+ DOT_SIZE * pulse
176
+ )
177
+ }
178
+ }
179
+ }
180
+
181
+ private sit(spot: Entity): void {
182
+ const world = sceneRelativeTransform(spot)
183
+ const forward = Vector3.rotate(Vector3.Forward(), world.rotation)
184
+ void movePlayerTo({
185
+ newRelativePosition: world.position,
186
+ avatarTarget: Vector3.add(world.position, forward)
187
+ })
188
+ // let the teleport settle before the emote starts, or the walk cancels it
189
+ this.emoteIn = EMOTE_DELAY_S
190
+ }
191
+ }
@@ -0,0 +1,109 @@
1
+ ---
2
+ prefab: spawner
3
+ ---
4
+
5
+ # Spawner — AI guide
6
+
7
+ A spot that makes copies of a prefab appear while the game runs. Copies are made
8
+ on the player's own game the moment the trigger fires — nothing is stored, and a
9
+ fresh play starts with none.
10
+
11
+ The project copy is normally custom/spawner/ — check what is on disk, a second
12
+ copy is custom/spawner_2/.
13
+
14
+ ## When to use
15
+
16
+ Anything that shows up DURING play: an enemy, a pickup, a crate, a vehicle.
17
+ By default a copy is built at the Spawner's own spot, so place it where the
18
+ thing belongs — the `where` param below covers the other landings.
19
+
20
+ Every prefab in the project can be copied this way — there is no setting to turn
21
+ on. What bounds it is that prefab's Max alive (default 64, listed in the
22
+ [Spawnable prefabs] block): it must be at least as large as the copies you ask
23
+ for, counting every other spawner and wave aimed at the same prefab. The copies
24
+ are built from the prefab's snapshot at run time, whether or not one is also
25
+ placed in the scene.
26
+
27
+ Placement makes names unique, so a second "Button Spawner" is placed as
28
+ "Button Spawner 2" and is a separate spot with its own cap.
29
+
30
+ ## API
31
+
32
+ Params — set them in the placePrefab request, never tell the user to go and
33
+ change them:
34
+ - spawn: the prefab to copy. Give its NAME (the [Spawnable prefabs] block lists
35
+ them) and the editor resolves the id. Empty means nothing appears; the script
36
+ says so in the console.
37
+ - when: 'when clicked' | 'when a player enters' | 'every few seconds' |
38
+ 'when a script asks'. These stored values are the
39
+ wire — write them verbatim. The inspector may show friendlier display labels
40
+ (e.g. 'when a script asks' reads as 'when another script asks'), but the layout
41
+ always stores the values above.
42
+ - everySeconds: for 'every few seconds', how many seconds between copies
43
+ (default 10).
44
+ - hoverLabel: for 'when clicked', the words a player sees before they click
45
+ (default 'Use').
46
+
47
+ WHERE THE SPAWNER SITS IS THE WIRING — there is no target picker and no area
48
+ name. Place it as a CHILD of the thing (the placePrefab position puts it there;
49
+ the editor's right-click gesture parents it for you):
50
+ - 'when clicked': the parent is the button (it needs a collider — the scene
51
+ checks say so if it has none). A spawner with no parent is its own button:
52
+ its disc shows itself while playing so players can see what to click.
53
+ - 'when a player enters': a parent that is a Trigger Area is the area. With no
54
+ area parent, the spawner's own spot is the walk-in area — its scale is the
55
+ area's size in metres, so scale the spawner to size the area.
56
+ - atMostAtOnce: copies from this spot alive at once (default 1).
57
+ - disappearsAfter: seconds a copy sticks around; 0 keeps it until something
58
+ removes it.
59
+ - where: 'at this spawner' (default) | 'at the player' | 'custom spot'. Where a
60
+ copy lands: the spawner's own spot, the feet (and facing) of the player whose
61
+ trigger fired, or a "Spawn Spot" child the creator positions. NEVER create
62
+ that child yourself — setting where to 'custom spot' materializes it, the
63
+ creator places it with the gizmos, and the game hides it. If the user asks
64
+ for a spawn position, set where and tell them to move the marker; do not
65
+ invent coordinates.
66
+
67
+ Two more things are automatic, not settings: several copies spread just enough
68
+ not to stack (one copy lands exactly here), and the Spawner's disc is visible
69
+ while playing exactly when the Spawner itself is the button.
70
+
71
+ From another script, by the Spawner's NAME — that is the whole wiring:
72
+
73
+ import { requestSpawn, retireSpawned } from './runtime/spawnPoints'
74
+ requestSpawn('Crate Spawner')
75
+
76
+ retireSpawned(entity) takes one copy away (a pickup that was collected). Full
77
+ signatures live in that file's header — read those, not a copy of them here.
78
+
79
+ ## Each player sees their own copies
80
+
81
+ Copies live on the client that made them: the player who clicked sees the crate,
82
+ another player standing next to them does not. That is right for pickups,
83
+ personal effects and single-player scenes. For copies every player must agree on
84
+ (a boss, a contested pickup), keep the fact in game.state from a server-side
85
+ Script instead and let every client draw what it reads there.
86
+
87
+ ## Do / Don't
88
+
89
+ - DON'T put a Spawner inside a prefab that gets copied. Every copy would carry
90
+ the same spot under the same name; only the first answers requestSpawn. The
91
+ editor's spawn dropdown already hides prefabs that carry a Spawner for this
92
+ reason — don't route around it by id.
93
+ - DON'T write a spawn position. Copies appear at the Spawner, auto-spread when
94
+ several are out. To drop loot where something died, put a Spawner there when
95
+ you build the scene, or say plainly that the drop is at a fixed spot.
96
+ - At the cap nothing happens: a press while atMostAtOnce copies are already alive
97
+ is ignored rather than moving a live one. Say so if the user asks for a button.
98
+ - requestSpawn reaches ANY Spawner by name, whatever its when — 'when a script
99
+ asks' just means no other trigger is armed. Use it for a copy that only
100
+ another script should set off.
101
+
102
+ ## Example
103
+
104
+ "A crate a lever makes appear": placePrefab spawner named "Crate Spawner" where
105
+ the crate should land, with spawn: the Crate prefab, when: 'when a script asks',
106
+ atMostAtOnce: 1, disappearsAfter: 30. Then, in the lever script's own pull:
107
+
108
+ import { requestSpawn } from './runtime/spawnPoints'
109
+ requestSpawn('Crate Spawner')