@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
+ }
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}/ThreeSeater_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": "Large 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.6891,
43
+ "y": 0.0556,
44
+ "z": 0.0829
45
+ },
46
+ "rotation": {
47
+ "x": 0,
48
+ "y": 0.07845906168222427,
49
+ "z": 0,
50
+ "w": 0.9969173669815063
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": -2.24,
64
+ "y": 0.06,
65
+ "z": 0.14
66
+ },
67
+ "rotation": {
68
+ "x": 0,
69
+ "y": 0.07845909893512726,
70
+ "z": 0,
71
+ "w": 0.9969173073768616
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": "ac233c16-699e-4f01-8e7c-910bb5a168af",
3
+ "name": "Large Couch",
4
+ "description": "A long couch, seating three. Players can sit along 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
+ }
@@ -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,75 @@
1
+ ---
2
+ prefab: leaderboard
3
+ ---
4
+
5
+ # Leaderboard — AI guide
6
+
7
+ A panel that shows one board: it reads a `game.state` key and paints the places.
8
+ It never decides or stores a score.
9
+
10
+ The project copy is normally custom/leaderboard/ — check what is on disk, a
11
+ second copy is custom/leaderboard_2/.
12
+
13
+ ## When to use
14
+
15
+ Whenever a game already keeps a ranked list and wants it on a wall. Place one per
16
+ board: round scores and all-time bests are two keys, so two panels.
17
+
18
+ Place NOTHING if no server code writes rows — the panel would just show its empty
19
+ line. Write the rows first, then place the board on that key.
20
+
21
+ ## API
22
+
23
+ There is nothing to import. The contract is the state key:
24
+
25
+ game.setState({ leaderboard: [{ player, score }, …] })
26
+
27
+ - Rows are objects. The player field may be `player`, `p`, `address`, `wallet` or
28
+ `name`; the score field may be `score`, `points`, `pts`, `value`, `seconds`,
29
+ `time` or `best`. Anything else in a row is ignored, and a row missing either
30
+ half is skipped.
31
+ - WHICH SIDE: the `game.setState` that writes the rows goes on the SERVER, inside
32
+ the `if (isServer())` branch. The panel itself reads and paints on every
33
+ player's client — that half is this prefab's, and you write no client code.
34
+ - The panel sorts the rows itself, so the writer's order does not matter.
35
+ - Late joiners get the board from the snapshot. Write rows with `game.setState`,
36
+ never with a broadcast — a message is a moment and a board is a fact.
37
+
38
+ Params of the prefab's script — set them in the placePrefab request:
39
+
40
+ - `title`: the heading on the panel (default "Leaderboard").
41
+ - `boardKey`: the `game.state` key to read (default `'leaderboard'`). Game Flow
42
+ reads the same key for its winners line — point both at one key.
43
+ - `sort`: `'desc'` (highest wins — points) or `'asc'` (lowest wins — best times,
44
+ which also renders the score as m:ss).
45
+ - `rows`: how many places the panel lists (default 8, max 25).
46
+
47
+ ## The all-time board idiom
48
+
49
+ `game.playerData` cannot be listed, so an all-time or season board is kept by
50
+ folding results into `game.saved` at the end of a round and copying the top N
51
+ into `game.state` — all of it in the server branch:
52
+
53
+ const board = fold(game.saved.get('bestTimes') ?? [], results)
54
+ game.saved.set('bestTimes', board)
55
+ game.setState({ bestTimes: board })
56
+
57
+ Then place a second Leaderboard with `boardKey: 'bestTimes'`.
58
+
59
+ ## Do / Don't
60
+
61
+ - DON'T keep a score on a client and send it up. Count it in a server handler; a
62
+ number a client reports is a claim.
63
+ - DON'T write more than the visible places into the key. Fold to the top ten in
64
+ server code — every extra row rides the wire on every change.
65
+ - DO expect wallet addresses. There is no name lookup for a player who is not
66
+ connected, so offline players show as a shortened address.
67
+
68
+ ## Example
69
+
70
+ "Show the fastest climbers": in a handler inside the server branch keep the list,
71
+ and place a board on that key.
72
+
73
+ game.setState({ leaderboard: runs.sort((a, b) => a.time - b.time).slice(0, 10) })
74
+
75
+ with `boardKey: 'leaderboard'`, `sort: 'asc'`, `title: 'Best Times'`.
@@ -0,0 +1,82 @@
1
+ {
2
+ "version": 1,
3
+ "components": [
4
+ {
5
+ "name": "core-schema::Name",
6
+ "data": {
7
+ "512": {
8
+ "json": {
9
+ "value": "Leaderboard"
10
+ }
11
+ },
12
+ "513": {
13
+ "json": {
14
+ "value": "Board Text"
15
+ }
16
+ }
17
+ }
18
+ },
19
+ {
20
+ "name": "core::Transform",
21
+ "data": {
22
+ "512": {
23
+ "json": {
24
+ "position": { "x": 0, "y": 0, "z": 0 },
25
+ "rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
26
+ "scale": { "x": 2, "y": 2, "z": 2 }
27
+ }
28
+ },
29
+ "513": {
30
+ "json": {
31
+ "position": { "x": 0, "y": 0.45, "z": 0.02 },
32
+ "rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
33
+ "scale": { "x": 1, "y": 1, "z": 1 },
34
+ "parent": 512
35
+ }
36
+ }
37
+ }
38
+ },
39
+ {
40
+ "name": "core::GltfContainer",
41
+ "data": {
42
+ "512": {
43
+ "json": {
44
+ "src": "{assetPath}/board-panel.glb",
45
+ "visibleMeshesCollisionMask": 0,
46
+ "invisibleMeshesCollisionMask": 0
47
+ }
48
+ }
49
+ }
50
+ },
51
+ {
52
+ "name": "core::TextShape",
53
+ "data": {
54
+ "513": {
55
+ "json": {
56
+ "text": "LEADERBOARD\n\nNothing to show yet — set the board key your game writes to.",
57
+ "fontSize": 0.8,
58
+ "textColor": { "r": 0.08, "g": 0.09, "b": 0.11, "a": 1 },
59
+ "outlineColor": { "r": 1, "g": 1, "b": 1 },
60
+ "outlineWidth": 0
61
+ }
62
+ }
63
+ }
64
+ },
65
+ {
66
+ "name": "asset-packs::Script",
67
+ "data": {
68
+ "512": {
69
+ "json": {
70
+ "value": [
71
+ {
72
+ "path": "{assetPath}/scripts/leaderboard.ts",
73
+ "priority": 0,
74
+ "layout": "{\"params\":{},\"actions\":[]}"
75
+ }
76
+ ]
77
+ }
78
+ }
79
+ }
80
+ }
81
+ ]
82
+ }