@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,35 @@
1
+ {
2
+ "id": "493daede-2afd-4c10-9c0d-6aefb25629a8",
3
+ "name": "Leaderboard",
4
+ "description": "Shows one of your game's boards — point it at the game.state key your game writes the rows to. Works with: Game Flow.",
5
+ "category": "custom",
6
+ "group": "Multiplayer Server",
7
+ "tags": [
8
+ "multiplayer",
9
+ "server",
10
+ "leaderboard",
11
+ "score",
12
+ "ranking",
13
+ "board"
14
+ ],
15
+ "requiresSdk": "auth-server",
16
+ "drivenBy": {
17
+ "rule": "This panel only paints what it reads — the server writes the rows into the synced state key named in Board Key, and every client draws them in order.",
18
+ "code": "game.setState({ leaderboard: [{ player, score }] })",
19
+ "next": "Press New script below and write that line inside the isServer() block it comes with, using the same key you set in Board Key above."
20
+ },
21
+ "version": "1.1.0",
22
+ "changelog": [
23
+ {
24
+ "version": "1.0.0",
25
+ "notes": "Initial release: a panel that reads the game.state key you name in boardKey and paints its top rows, so the board a Script keeps is the board every player sees and a late joiner gets it from the snapshot."
26
+ },
27
+ {
28
+ "version": "1.1.0",
29
+ "notes": "The placed panel's Script card now shows the setState line that writes the rows it paints, under its own settings."
30
+ }
31
+ ],
32
+ "origin": {
33
+ "source": "builtin"
34
+ }
35
+ }
@@ -0,0 +1,66 @@
1
+ // A board on a panel. It reads one `game.state` key and paints it — nothing
2
+ // here decides a score, and nothing here stores one. The server writes the rows
3
+ // (see ai.md in this folder for the idiom); two boards pointed at two keys are
4
+ // two independent boards in one scene, and two pointed at the same key show the
5
+ // same thing on both walls.
6
+ //
7
+ // The panel is ordinary scene content: swap board-panel.glb for your own model
8
+ // and keep (or restyle) the text child — the script writes only its `text`, so
9
+ // font, size and colour stay exactly as authored.
10
+ import { TextShape, Transform, engine, type Entity } from '@dcl/sdk/ecs'
11
+ import { isServer } from '@dcl/sdk/network'
12
+ import { game } from '~runtime/game'
13
+ import { boardRows, clampRows, renderPanel } from './pure/board'
14
+
15
+ const EMPTY = 'Nothing to show yet — set the board key the server writes to.'
16
+
17
+ export class Leaderboard {
18
+ private panel: Entity | null = null
19
+ private painted = ''
20
+
21
+ constructor(
22
+ public src: string,
23
+ public entity: Entity,
24
+ /** The panel's title. */
25
+ public title: string = 'Leaderboard',
26
+ /** The game.state key this board shows. The server writes the rows there. */
27
+ public boardKey: string = 'leaderboard',
28
+ /** Which score wins: highest keeps points, lowest keeps best times. */
29
+ public sort: 'desc' | 'asc' = 'desc',
30
+ /** How many places the panel lists. */
31
+ public rows: number = 8
32
+ ) {}
33
+
34
+ start(): void {
35
+ if (isServer()) { return }
36
+ this.panel = this.findPanel()
37
+ game.onStateChange(() => this.repaint())
38
+ this.repaint()
39
+ }
40
+
41
+ private repaint(): void {
42
+ this.paint(
43
+ renderPanel(this.title, boardRows(game.state[this.boardKey], this.sort, clampRows(this.rows)), this.sort, EMPTY)
44
+ )
45
+ }
46
+
47
+ // The text lives on a child so the model can be replaced without touching it.
48
+ private findPanel(): Entity {
49
+ for (const [entity, transform] of engine.getEntitiesWith(Transform)) {
50
+ if (transform.parent === this.entity && TextShape.getOrNull(entity) !== null) return entity
51
+ }
52
+ return this.entity
53
+ }
54
+
55
+ private paint(text: string): void {
56
+ const panel = this.panel
57
+ if (panel === null || text === this.painted) return
58
+ this.painted = text
59
+ const shape = TextShape.getMutableOrNull(panel)
60
+ if (shape !== null) {
61
+ shape.text = text
62
+ return
63
+ }
64
+ TextShape.createOrReplace(panel, { text, fontSize: 0.8 })
65
+ }
66
+ }
@@ -0,0 +1,71 @@
1
+ // The board's reading and rendering, with no SDK and no I/O.
2
+ //
3
+ // A Leaderboard is a pure reader: the server writes rows into a `game.state` key
4
+ // and this turns them into panel text. Creators name those fields themselves —
5
+ // one game writes `{ player, seconds }`, the next `{ p, pts }` — so a row is
6
+ // anything with a player-ish string and a number, and anything else is skipped
7
+ // rather than painted as [object Object].
8
+
9
+ export type BoardSort = 'desc' | 'asc'
10
+
11
+ export interface BoardRow {
12
+ rank: number
13
+ player: string
14
+ score: number
15
+ }
16
+
17
+ const PLAYER_FIELDS = ['player', 'p', 'address', 'wallet', 'name']
18
+ const SCORE_FIELDS = ['score', 'points', 'pts', 'value', 'seconds', 'time', 'best']
19
+
20
+ export const MAX_ROWS = 25
21
+
22
+ function isRecord(value: unknown): value is Record<string, unknown> {
23
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
24
+ }
25
+
26
+ /** Wallets are long; a board row reads better with the tail. */
27
+ export function shortName(player: string): string {
28
+ return player.startsWith('0x') && player.length > 10 ? `${player.slice(0, 6)}…${player.slice(-4)}` : player
29
+ }
30
+
31
+ /** Whole seconds as m:ss — how an 'asc' board's times want to read. */
32
+ export function formatScore(score: number, sort: BoardSort): string {
33
+ if (sort === 'desc') return `${Math.round(score)}`
34
+ const total = Math.max(0, Math.floor(score))
35
+ const rest = total % 60
36
+ return `${Math.floor(total / 60)}:${rest < 10 ? '0' : ''}${rest}`
37
+ }
38
+
39
+ export function clampRows(value: number): number {
40
+ if (!Number.isFinite(value)) return 8
41
+ return Math.min(MAX_ROWS, Math.max(1, Math.round(value)))
42
+ }
43
+
44
+ /**
45
+ * The visible rows of whatever sits under the board key. Sorting here rather than
46
+ * trusting the writer's order is what lets one key feed a "highest wins" board and
47
+ * a "lowest wins" board at once.
48
+ */
49
+ export function boardRows(value: unknown, sort: BoardSort, limit: number): BoardRow[] {
50
+ if (!Array.isArray(value)) return []
51
+ const rows: { player: string; score: number }[] = []
52
+ for (const item of value) {
53
+ if (!isRecord(item)) continue
54
+ const playerKey = PLAYER_FIELDS.find((key) => typeof item[key] === 'string' && item[key] !== '')
55
+ const scoreKey = SCORE_FIELDS.find((key) => typeof item[key] === 'number' && Number.isFinite(item[key]))
56
+ if (playerKey === undefined || scoreKey === undefined) continue
57
+ rows.push({ player: String(item[playerKey]), score: Number(item[scoreKey]) })
58
+ }
59
+ rows.sort((a, b) => (sort === 'desc' ? b.score - a.score : a.score - b.score))
60
+ return rows.slice(0, clampRows(limit)).map((row, index) => ({ rank: index + 1, ...row }))
61
+ }
62
+
63
+ export function renderPanel(title: string, rows: BoardRow[], sort: BoardSort, placeholder: string): string {
64
+ const lines = [title.trim().toUpperCase(), '']
65
+ if (rows.length === 0) {
66
+ lines.push(placeholder)
67
+ return lines.join('\n')
68
+ }
69
+ for (const row of rows) lines.push(`${row.rank}. ${shortName(row.player)} ${formatScore(row.score, sort)}`)
70
+ return lines.join('\n')
71
+ }
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}/Couch_01.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": "Loveseat"
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.79,
43
+ "y": 0.08,
44
+ "z": 0.56
45
+ },
46
+ "rotation": {
47
+ "x": 0,
48
+ "y": -0.7071067690849304,
49
+ "z": 0,
50
+ "w": 0.7071067690849304
51
+ },
52
+ "scale": {
53
+ "x": 1,
54
+ "y": 1,
55
+ "z": 1
56
+ },
57
+ "parent": 0
58
+ }
59
+ },
60
+ "514": {
61
+ "json": {
62
+ "position": {
63
+ "x": -0.79,
64
+ "y": 0.08,
65
+ "z": -0.4
66
+ },
67
+ "rotation": {
68
+ "x": 0,
69
+ "y": -0.7071067690849304,
70
+ "z": 0,
71
+ "w": 0.7071067690849304
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,34 @@
1
+ {
2
+ "id": "78050d8f-ee51-4db7-b060-14e7a0b48939",
3
+ "name": "Loveseat",
4
+ "description": "A two-seat sofa. 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.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
@@ -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}/ModernSofa.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": "Modern Sofa"
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": -1.0235,
43
+ "y": 0.1354,
44
+ "z": 0.0251
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.7763,
64
+ "y": 0.1354,
65
+ "z": 0.0293
66
+ },
67
+ "rotation": {
68
+ "x": 0,
69
+ "y": 0.07845907658338547,
70
+ "z": 0,
71
+ "w": 0.9969173669815063
72
+ },
73
+ "scale": {
74
+ "x": 1,
75
+ "y": 1,
76
+ "z": 1
77
+ },
78
+ "parent": 0
79
+ }
80
+ }
81
+ }
82
+ },
83
+ {
84
+ "name": "asset-packs::Script",
85
+ "data": {
86
+ "0": {
87
+ "json": {
88
+ "value": [
89
+ {
90
+ "path": "{assetPath}/scripts/seat.ts",
91
+ "priority": 0,
92
+ "layout": "{\"params\":{},\"actions\":[]}"
93
+ }
94
+ ]
95
+ }
96
+ }
97
+ }
98
+ }
99
+ ]
100
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "id": "14706421-460b-4161-b06c-8ce7db73ad11",
3
+ "name": "Modern Sofa",
4
+ "description": "A low modern sofa, 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
+ }