@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[] = []
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,67 @@
1
+ ---
2
+ prefab: server-clock
3
+ claims-messages: runtime.timeSync, runtime.timeSyncResponse
4
+ ---
5
+
6
+ # Server Clock — AI guide
7
+
8
+ One clock every player agrees on: the Multiplayer Server's time, NTP-sampled by
9
+ each client, so "the round ends at 12:04:30" means the same instant everywhere.
10
+
11
+ The project copy is normally custom/server_clock/ — check what is on disk, a
12
+ second copy is custom/server_clock_2/.
13
+
14
+ ## When to use
15
+
16
+ Any shared deadline or shared elapsed time: a round timer, a countdown, a daily
17
+ event, a scoreboard's "started at". Also just to show the time. Needs an
18
+ scene with a Multiplayer Server (data.json requiresSdk: auth-server).
19
+
20
+ ## API
21
+
22
+ import { getServerTime, initTimeSync, isTimeSyncReady } from './runtime/timeSync'
23
+
24
+ - getServerTime(): server-clock now, in epoch milliseconds. On the server it is
25
+ Date.now(); on a client it is Date.now() plus the measured offset.
26
+ - isTimeSyncReady(): false until the first samples land (a client takes a moment
27
+ after joining, and re-syncs every minute). Until then getServerTime() is just
28
+ local time — show a placeholder rather than a wrong time.
29
+ - initTimeSync(): starts the right half for the side it runs on. Idempotent, and
30
+ the placed prefab already calls it, so you only need it in a script that uses
31
+ the clock in a scene where the prefab might not be placed.
32
+
33
+ Params of the prefab's script — set them in the placePrefab request:
34
+ - label: text above the time (default 'SERVER TIME'; empty shows the time alone).
35
+ - utc: true (default) shows UTC, false shows each viewer's own timezone. A shared
36
+ deadline every player reads should stay UTC — local time differs per player.
37
+ - display: '3D text' (default) is floating text at the entity, '2D UI' is an
38
+ overlay on the player's client instead.
39
+ - position: where the 2D overlay sits — 'top' (default), 'top left', 'top right'
40
+ or 'bottom'. Ignored for '3D text'.
41
+
42
+ ## Shared deadlines
43
+
44
+ Express a deadline as an absolute server timestamp, share THAT, and derive the
45
+ countdown locally every frame:
46
+
47
+ const endsAt = getServerTime() + 60_000
48
+ const secondsLeft = Math.max(0, Math.ceil((endsAt - getServerTime()) / 1000))
49
+
50
+ ## Do / Don't
51
+
52
+ - DON'T build a countdown out of Date.now() or by accumulating dt: two players'
53
+ clocks differ by seconds and a reloading client loses its accumulator.
54
+ - DON'T re-implement time sync or add another timestamp message. Import this
55
+ module; the message names it registers are the scene's, and a second registrar
56
+ collides with it.
57
+ - DON'T format a time before isTimeSyncReady() — render '--:--:--' or hide it.
58
+ - The server decides WHEN, the client only renders it. Anything that must be the
59
+ same for everyone (round end, spawn moment) is decided on the server side of
60
+ the bundle, with isServer() from '@dcl/sdk/network'.
61
+
62
+ ## Example
63
+
64
+ "Show a 60-second round timer everyone sees the same": place Server Clock (or
65
+ call initTimeSync() in a script of your own), store endsAt as a server timestamp
66
+ when the round starts, and render Math.ceil((endsAt - getServerTime()) / 1000)
67
+ in update() while isTimeSyncReady().
@@ -0,0 +1,45 @@
1
+ {
2
+ "version": 1,
3
+ "components": [
4
+ {
5
+ "name": "core-schema::Name",
6
+ "data": {
7
+ "0": {
8
+ "json": {
9
+ "value": "Server Clock"
10
+ }
11
+ }
12
+ }
13
+ },
14
+ {
15
+ "name": "core::TextShape",
16
+ "data": {
17
+ "0": {
18
+ "json": {
19
+ "text": "SERVER TIME\n--:--:--",
20
+ "fontSize": 4,
21
+ "textColor": { "r": 1, "g": 1, "b": 1, "a": 1 },
22
+ "outlineColor": { "r": 0, "g": 0, "b": 0 },
23
+ "outlineWidth": 0.1
24
+ }
25
+ }
26
+ }
27
+ },
28
+ {
29
+ "name": "asset-packs::Script",
30
+ "data": {
31
+ "0": {
32
+ "json": {
33
+ "value": [
34
+ {
35
+ "path": "{assetPath}/scripts/server-clock.ts",
36
+ "priority": 0,
37
+ "layout": "{\"params\":{},\"actions\":[]}"
38
+ }
39
+ ]
40
+ }
41
+ }
42
+ }
43
+ }
44
+ ]
45
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "id": "9e37c253-b33e-4505-8231-530f62715d21",
3
+ "name": "Server Clock",
4
+ "description": "A clock showing NTP-synced server time, identical for every player. Runs on the Multiplayer Server.",
5
+ "category": "custom",
6
+ "group": "Multiplayer Server",
7
+ "tags": [
8
+ "multiplayer",
9
+ "server",
10
+ "clock",
11
+ "time"
12
+ ],
13
+ "requiresSdk": "auth-server",
14
+ "version": "1.0.0",
15
+ "changelog": [
16
+ {
17
+ "version": "1.0.0",
18
+ "notes": "Initial release: the Multiplayer Server's NTP-synced time, identical for every player, shown as 3D text at the item or as a 2D overlay on the player's client."
19
+ }
20
+ ],
21
+ "origin": {
22
+ "source": "builtin"
23
+ }
24
+ }
@@ -0,0 +1,55 @@
1
+ import ReactEcs, { Label, ReactEcsRenderer, UiEntity } from '@dcl/sdk/react-ecs'
2
+ import { type Entity } from '@dcl/sdk/ecs'
3
+
4
+ let label = ''
5
+ let time = ''
6
+ let mounted = false
7
+
8
+ export function setClockHudText(nextLabel: string, nextTime: string): void {
9
+ label = nextLabel
10
+ time = nextTime
11
+ }
12
+
13
+ export type ClockHudPosition = 'top' | 'top left' | 'top right' | 'bottom'
14
+
15
+ function justifyFor(where: ClockHudPosition): 'flex-start' | 'flex-end' | 'center' {
16
+ switch (where) {
17
+ case 'top left':
18
+ return 'flex-start'
19
+ case 'top right':
20
+ return 'flex-end'
21
+ default:
22
+ return 'center'
23
+ }
24
+ }
25
+
26
+ export function mountClockHud(entity: Entity, where: ClockHudPosition = 'top'): void {
27
+ if (mounted) return
28
+ mounted = true
29
+ const vertical = where === 'bottom' ? { bottom: 24, left: 0 } : { top: 24, left: 0 }
30
+ const justify = justifyFor(where)
31
+ ReactEcsRenderer.addUiRenderer(entity, () => (
32
+ <UiEntity
33
+ uiTransform={{
34
+ positionType: 'absolute',
35
+ position: vertical,
36
+ width: '100%',
37
+ padding: { left: 24, right: 24 },
38
+ justifyContent: justify,
39
+ alignItems: 'flex-start'
40
+ }}
41
+ >
42
+ <UiEntity
43
+ uiTransform={{
44
+ padding: { top: 6, bottom: 8, left: 18, right: 18 },
45
+ flexDirection: 'column',
46
+ alignItems: 'center'
47
+ }}
48
+ uiBackground={{ color: { r: 0.06, g: 0.09, b: 0.08, a: 0.6 } }}
49
+ >
50
+ {label !== '' ? <Label value={label} fontSize={12} color={{ r: 1, g: 1, b: 1, a: 0.75 }} /> : null}
51
+ <Label value={time === '' ? '--:--:--' : time} fontSize={26} color={{ r: 1, g: 1, b: 1, a: 1 }} />
52
+ </UiEntity>
53
+ </UiEntity>
54
+ ))
55
+ }
@@ -0,0 +1,71 @@
1
+ import { Entity, TextShape } from '@dcl/sdk/ecs'
2
+ import { isServer } from '@dcl/sdk/network'
3
+ import { initTimeSync, getServerTime, isTimeSyncReady } from '~runtime/timeSync'
4
+ import { mountClockHud, setClockHudText } from './clock-hud'
5
+
6
+ // Displays the Multiplayer Server's clock, NTP-synced, identical for every
7
+ // player — as floating 3D text, or as a 2D overlay on the player's client. Both
8
+ // halves start here: initTimeSync() registers the responder on the server and
9
+ // the asking half on the client, so placing the item is all the setup there is.
10
+
11
+ function pad(n: number): string {
12
+ return n < 10 ? `0${n}` : `${n}`
13
+ }
14
+
15
+ export class ServerClock {
16
+ private accum = 0
17
+
18
+ constructor(
19
+ public src: string,
20
+ public entity: Entity,
21
+ /** Label above the time */ public label: string = 'SERVER TIME',
22
+ /** Show in UTC instead of each viewer's timezone */ public utc: boolean = true,
23
+ /** How the clock is shown */ public display: '3D text' | '2D UI' = '3D text',
24
+ /** Where the 2D UI sits in the player's view */ public position: 'top' | 'top left' | 'top right' | 'bottom' = 'top'
25
+ ) {}
26
+
27
+ private get onScreen(): boolean {
28
+ return this.display === '2D UI'
29
+ }
30
+
31
+ start(): void {
32
+ initTimeSync()
33
+ if (isServer()) return
34
+ if (this.onScreen) {
35
+ // the 3D text becomes the overlay — remove the placed shape
36
+ TextShape.deleteFrom(this.entity)
37
+ mountClockHud(this.entity, this.position)
38
+ }
39
+ this.render('--:--:--')
40
+ }
41
+
42
+ update(dt: number): void {
43
+ if (isServer()) return
44
+ this.accum += dt
45
+ if (this.accum < 0.25) return
46
+ this.accum = 0
47
+ if (!isTimeSyncReady()) return
48
+ this.render(this.formatTime(new Date(getServerTime())))
49
+ }
50
+
51
+ private formatTime(d: Date): string {
52
+ const parts = this.utc
53
+ ? [d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds()]
54
+ : [d.getHours(), d.getMinutes(), d.getSeconds()]
55
+ return parts.map(pad).join(':')
56
+ }
57
+
58
+ private render(time: string): void {
59
+ if (this.onScreen) {
60
+ setClockHudText(this.label, time)
61
+ return
62
+ }
63
+ TextShape.createOrReplace(this.entity, {
64
+ text: this.label === '' ? time : `${this.label}\n${time}`,
65
+ fontSize: 4,
66
+ textColor: { r: 1, g: 1, b: 1, a: 1 },
67
+ outlineColor: { r: 0, g: 0, b: 0 },
68
+ outlineWidth: 0.1
69
+ })
70
+ }
71
+ }
@@ -0,0 +1,74 @@
1
+ {
2
+ "version": 1,
3
+ "components": [
4
+ {
5
+ "name": "core::GltfContainer",
6
+ "data": {
7
+ "0": {
8
+ "json": {
9
+ "src": "{assetPath}/Chair_03.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": "Simple Chair"
22
+ }
23
+ },
24
+ "513": {
25
+ "json": {
26
+ "value": "Sit Spot"
27
+ }
28
+ }
29
+ }
30
+ },
31
+ {
32
+ "name": "core::Transform",
33
+ "data": {
34
+ "513": {
35
+ "json": {
36
+ "position": {
37
+ "x": -0.32,
38
+ "y": 0.15,
39
+ "z": 0
40
+ },
41
+ "rotation": {
42
+ "x": 0,
43
+ "y": 0.7071067811865476,
44
+ "z": 0,
45
+ "w": -0.7071067811865475
46
+ },
47
+ "scale": {
48
+ "x": 1,
49
+ "y": 1,
50
+ "z": 1
51
+ },
52
+ "parent": 0
53
+ }
54
+ }
55
+ }
56
+ },
57
+ {
58
+ "name": "asset-packs::Script",
59
+ "data": {
60
+ "0": {
61
+ "json": {
62
+ "value": [
63
+ {
64
+ "path": "{assetPath}/scripts/seat.ts",
65
+ "priority": 0,
66
+ "layout": "{\"params\":{},\"actions\":[]}"
67
+ }
68
+ ]
69
+ }
70
+ }
71
+ }
72
+ }
73
+ ]
74
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "id": "fe41823d-a0ef-4152-bf66-b9f44cdcffa2",
3
+ "name": "Simple Chair",
4
+ "description": "A simple lightweight chair. 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.1",
19
+ "notes": "Ships the model's texture, which was missing from the folder — without it the model rendered blank white in-world."
20
+ },
21
+ {
22
+ "version": "1.0.0",
23
+ "notes": "Initial release"
24
+ }
25
+ ],
26
+ "origin": {
27
+ "source": "builtin"
28
+ },
29
+ "requiredPermissions": [
30
+ "ALLOW_TO_MOVE_PLAYER_INSIDE_SCENE",
31
+ "ALLOW_TO_TRIGGER_AVATAR_EMOTE"
32
+ ],
33
+ "group": "Seats"
34
+ }
Binary file