@mml-io/3d-web-experience-client 0.26.1 → 0.27.1

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 (60) hide show
  1. package/build/AvatarType.d.ts.map +1 -0
  2. package/build/ClientEventEmitter.d.ts +32 -0
  3. package/build/ClientEventEmitter.d.ts.map +1 -0
  4. package/build/DefaultAvatarSelectionPlugin.d.ts +36 -0
  5. package/build/DefaultAvatarSelectionPlugin.d.ts.map +1 -0
  6. package/build/DefaultChatPlugin.d.ts +44 -0
  7. package/build/DefaultChatPlugin.d.ts.map +1 -0
  8. package/build/DefaultHUDPlugin.d.ts +18 -0
  9. package/build/DefaultHUDPlugin.d.ts.map +1 -0
  10. package/build/DefaultRespawnButtonPlugin.d.ts +13 -0
  11. package/build/DefaultRespawnButtonPlugin.d.ts.map +1 -0
  12. package/build/DefaultVirtualJoystickPlugin.d.ts +16 -0
  13. package/build/DefaultVirtualJoystickPlugin.d.ts.map +1 -0
  14. package/build/Networked3dWebExperienceClient.d.ts +113 -17
  15. package/build/Networked3dWebExperienceClient.d.ts.map +1 -1
  16. package/build/WorldConnection.d.ts +142 -0
  17. package/build/WorldConnection.d.ts.map +1 -0
  18. package/build/index.d.ts +10 -0
  19. package/build/index.d.ts.map +1 -1
  20. package/build/index.js +2593 -1256
  21. package/build/index.js.map +4 -4
  22. package/build/plugins.d.ts +16 -0
  23. package/build/plugins.d.ts.map +1 -0
  24. package/package.json +25 -12
  25. package/build/avatar-selection-ui/AvatarSelectionUI.d.ts +0 -21
  26. package/build/avatar-selection-ui/AvatarSelectionUI.d.ts.map +0 -1
  27. package/build/avatar-selection-ui/AvatarType.d.ts.map +0 -1
  28. package/build/avatar-selection-ui/components/AvatarPanel/AvatarSectionUIComponent.d.ts +0 -15
  29. package/build/avatar-selection-ui/components/AvatarPanel/AvatarSectionUIComponent.d.ts.map +0 -1
  30. package/build/avatar-selection-ui/index.d.ts +0 -3
  31. package/build/avatar-selection-ui/index.d.ts.map +0 -1
  32. package/build/chat-ui/TextChatUI.d.ts +0 -26
  33. package/build/chat-ui/TextChatUI.d.ts.map +0 -1
  34. package/build/chat-ui/components/ChatPanel/TextChatUIComponent.d.ts +0 -10
  35. package/build/chat-ui/components/ChatPanel/TextChatUIComponent.d.ts.map +0 -1
  36. package/build/chat-ui/components/Input/InputBox.d.ts +0 -10
  37. package/build/chat-ui/components/Input/InputBox.d.ts.map +0 -1
  38. package/build/chat-ui/components/Message/Message.d.ts +0 -10
  39. package/build/chat-ui/components/Message/Message.d.ts.map +0 -1
  40. package/build/chat-ui/components/Messages/Messages.d.ts +0 -13
  41. package/build/chat-ui/components/Messages/Messages.d.ts.map +0 -1
  42. package/build/chat-ui/helpers.d.ts +0 -4
  43. package/build/chat-ui/helpers.d.ts.map +0 -1
  44. package/build/chat-ui/index.d.ts +0 -2
  45. package/build/chat-ui/index.d.ts.map +0 -1
  46. package/build/index.css +0 -875
  47. package/build/index.css.map +0 -7
  48. package/build/src/Networked3dWebExperience.module.css.d.ts +0 -4
  49. package/build/src/Networked3dWebExperience.module.d.css.ts +0 -4
  50. package/build/src/avatar-selection-ui/components/AvatarPanel/AvatarSelectionUIComponent.module.css.d.ts +0 -26
  51. package/build/src/avatar-selection-ui/components/AvatarPanel/AvatarSelectionUIComponent.module.d.css.ts +0 -26
  52. package/build/src/chat-ui/components/ChatPanel/TextChatUIComponent.module.css.d.ts +0 -15
  53. package/build/src/chat-ui/components/ChatPanel/TextChatUIComponent.module.d.css.ts +0 -15
  54. package/build/src/chat-ui/components/Input/InputBox.module.css.d.ts +0 -7
  55. package/build/src/chat-ui/components/Input/InputBox.module.d.css.ts +0 -7
  56. package/build/src/chat-ui/components/Message/Message.module.css.d.ts +0 -5
  57. package/build/src/chat-ui/components/Message/Message.module.d.css.ts +0 -5
  58. package/build/src/chat-ui/components/Messages/Messages.module.css.d.ts +0 -5
  59. package/build/src/chat-ui/components/Messages/Messages.module.d.css.ts +0 -5
  60. /package/build/{avatar-selection-ui/AvatarType.d.ts → AvatarType.d.ts} +0 -0
package/build/index.js CHANGED
@@ -1,4 +1,3 @@
1
- globalThis['__css-content-9df4d1565589ce63a22b21cf2f9d3ed9__']=".AvatarSelectionUIComponent-module__menuButton_VjZ7cq__0261{z-index:102;user-select:none;width:70px;min-height:70px;position:absolute;top:0;right:0}.AvatarSelectionUIComponent-module__input_VjZ7cq__0261{color:#fff;backdrop-filter:blur(10px);background-color:#0006;border:1px solid #fff3;border-radius:8px;outline:none;flex:1;margin-right:6px;padding:6px 8px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;font-size:14px;font-weight:400;transition:all .2s ease-in-out}.AvatarSelectionUIComponent-module__input_VjZ7cq__0261:focus{background-color:#0009;border-color:#fff6;box-shadow:0 0 0 3px #ffffff26}.AvatarSelectionUIComponent-module__input_VjZ7cq__0261::placeholder{color:#ffffff80}.AvatarSelectionUIComponent-module__closeButton_VjZ7cq__0261{backdrop-filter:blur(10px);color:#fffc;cursor:pointer;user-select:none;background:#0009;border:1px solid #fff3;border-radius:50%;justify-content:center;align-items:center;width:44px;height:44px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;font-size:16px;font-weight:500;transition:all .2s ease-in-out;display:flex;position:absolute;top:8px;right:8px}.AvatarSelectionUIComponent-module__closeButton_VjZ7cq__0261:hover{color:#ffffffe6;background:#000c;transform:scale(1.05)}.AvatarSelectionUIComponent-module__closeButton_VjZ7cq__0261:active{transform:scale(.95)}.AvatarSelectionUIComponent-module__openTab_VjZ7cq__0261{backdrop-filter:blur(10px);cursor:pointer;user-select:none;background:#0009;border:1px solid #fff3;border-radius:50%;justify-content:center;align-items:center;width:44px;height:44px;transition:all .2s ease-in-out;display:flex;position:absolute;top:8px;right:8px}.AvatarSelectionUIComponent-module__openTab_VjZ7cq__0261:hover{background:#000c;transform:scale(1.05)}.AvatarSelectionUIComponent-module__openTab_VjZ7cq__0261:active{transform:scale(.95)}.AvatarSelectionUIComponent-module__openTab_VjZ7cq__0261 img{filter:invert(80%);width:24px;height:24px;transition:all .2s ease-in-out}.AvatarSelectionUIComponent-module__avatarSelectionContainer_VjZ7cq__0261{backdrop-filter:blur(20px);z-index:103;user-select:none;background:#000000bf;border:1px solid #fff3;border-radius:16px;width:400px;max-width:78vw;max-height:calc(100vh - 24px);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;position:absolute;top:8px;right:60px;overflow:hidden auto;box-shadow:0 8px 32px #0006,0 0 0 1px #ffffff1a}.AvatarSelectionUIComponent-module__avatarSelectionContainer_VjZ7cq__0261::-webkit-scrollbar{width:8px}.AvatarSelectionUIComponent-module__avatarSelectionContainer_VjZ7cq__0261::-webkit-scrollbar-track{background:none}.AvatarSelectionUIComponent-module__avatarSelectionContainer_VjZ7cq__0261::-webkit-scrollbar-thumb{background:#fff3;border:1px solid #ffffff1a;border-radius:4px}.AvatarSelectionUIComponent-module__avatarSelectionContainer_VjZ7cq__0261::-webkit-scrollbar-thumb:hover{background:#ffffff4d}.AvatarSelectionUIComponent-module__avatarSelectionSection_VjZ7cq__0261{border-bottom:1px solid #ffffff26;padding:10px}.AvatarSelectionUIComponent-module__avatarSelectionSection_VjZ7cq__0261:last-child{border-bottom:none}.AvatarSelectionUIComponent-module__avatarSelectionUi_VjZ7cq__0261{background:#0000004d;border:1px solid #ffffff26;border-radius:12px;grid-template-columns:repeat(auto-fill,minmax(85px,1fr));gap:8px;max-height:320px;padding:10px;display:grid;overflow:hidden auto}.AvatarSelectionUIComponent-module__avatarSelectionUi_VjZ7cq__0261::-webkit-scrollbar{width:6px}.AvatarSelectionUIComponent-module__avatarSelectionUi_VjZ7cq__0261::-webkit-scrollbar-track{background:none}.AvatarSelectionUIComponent-module__avatarSelectionUi_VjZ7cq__0261::-webkit-scrollbar-thumb{background:#ffffff26;border-radius:3px}.AvatarSelectionUIComponent-module__avatarSelectionUiHeader_VjZ7cq__0261{color:#ffffffe6;text-align:center;font-weight:600;position:relative}.AvatarSelectionUIComponent-module__avatarSelectionUiCloseButton_VjZ7cq__0261{position:absolute;top:20px;right:20px}.AvatarSelectionUIComponent-module__avatarSelectionUiAvatar_VjZ7cq__0261{color:#ffffffe6;text-align:center;cursor:pointer;border-radius:8px;padding:4px;transition:all .2s ease-in-out}.AvatarSelectionUIComponent-module__avatarSelectionUiAvatar_VjZ7cq__0261:hover{background:#0000004d;transform:translateY(-2px)}.AvatarSelectionUIComponent-module__avatarSelectionUiAvatarImgContainer_VjZ7cq__0261{border-radius:8px;position:relative;overflow:hidden}.AvatarSelectionUIComponent-module__avatarSelectionNoImage_VjZ7cq__0261{aspect-ratio:1;box-sizing:border-box;background:#0003;border:1px solid #ffffff26;border-radius:8px;justify-content:center;align-items:center;width:100%;display:flex}.AvatarSelectionUIComponent-module__avatarSelectionNoImage_VjZ7cq__0261 img{filter:invert(70%);opacity:.7;width:40%}.AvatarSelectionUIComponent-module__avatarSelectionUiAvatar_VjZ7cq__0261 p{text-overflow:ellipsis;white-space:nowrap;color:#fffc;margin:4px 0 0;font-size:12px;font-weight:500;position:relative;overflow:hidden}.AvatarSelectionUIComponent-module__tooltipText_VjZ7cq__0261{pointer-events:none;z-index:1000;color:#ffffffe6;backdrop-filter:blur(10px);opacity:0;visibility:hidden;white-space:nowrap;background:#000c;border-radius:8px;margin-bottom:4px;padding:4px 6px;font-size:12px;font-weight:500;transition:opacity .2s ease-in-out;position:absolute;bottom:100%;left:50%;transform:translate(-50%)}.AvatarSelectionUIComponent-module__avatarSelectionUiAvatar_VjZ7cq__0261 p:hover+.AvatarSelectionUIComponent-module__tooltipText_VjZ7cq__0261{opacity:1;visibility:visible;transition-delay:.5s}.AvatarSelectionUIComponent-module__avatarSelectionUiAvatarImgContainer_VjZ7cq__0261 .AvatarSelectionUIComponent-module__avatarSelectionUiAvatarImage_VjZ7cq__0261{aspect-ratio:1;border-radius:8px;width:100%;transition:all .2s ease-in-out}.AvatarSelectionUIComponent-module__selectedPill_VjZ7cq__0261{color:#fff;backdrop-filter:blur(10px);background:linear-gradient(135deg,#22c55e,#16a34a);border:1px solid #fff3;border-radius:6px;padding:2px 4px;font-size:11px;font-weight:600;box-shadow:0 2px 4px #0003}.AvatarSelectionUIComponent-module__avatarSelectionUiAvatarImgContainer_VjZ7cq__0261 .AvatarSelectionUIComponent-module__selectedPill_VjZ7cq__0261{z-index:2;position:absolute;top:4px;right:4px}.AvatarSelectionUIComponent-module__avatarSelectionUiAvatar_VjZ7cq__0261 img:hover{opacity:.8;transform:scale(1.05)}.AvatarSelectionUIComponent-module__customAvatarSection_VjZ7cq__0261{color:#ffffffe6;border-bottom:1px solid #ffffff26;padding:10px;position:relative}.AvatarSelectionUIComponent-module__customAvatarSection_VjZ7cq__0261:last-child{border-bottom:none}.AvatarSelectionUIComponent-module__customAvatarSection_VjZ7cq__0261 .AvatarSelectionUIComponent-module__radioGroup_VjZ7cq__0261{flex-wrap:wrap;align-items:center;gap:4px;margin-bottom:8px;display:flex}.AvatarSelectionUIComponent-module__customAvatarSection_VjZ7cq__0261 .AvatarSelectionUIComponent-module__radioItem_VjZ7cq__0261{white-space:nowrap;align-items:center;gap:3px;margin-right:6px;display:flex}.AvatarSelectionUIComponent-module__customAvatarSection_VjZ7cq__0261 label{color:#fffc;cursor:pointer;margin:0;font-size:14px;font-weight:500;transition:color .2s ease-in-out}.AvatarSelectionUIComponent-module__customAvatarSection_VjZ7cq__0261 label:hover{color:#fff}.AvatarSelectionUIComponent-module__customAvatarSection_VjZ7cq__0261 input[type=radio]{appearance:none;cursor:pointer;background:none;border:2px solid #ffffff4d;border-radius:50%;outline:none;width:18px;height:18px;transition:all .2s ease-in-out;position:relative}.AvatarSelectionUIComponent-module__customAvatarSection_VjZ7cq__0261 input[type=radio]:hover{border-color:#ffffff80}.AvatarSelectionUIComponent-module__customAvatarSection_VjZ7cq__0261 input[type=radio]:focus{box-shadow:0 0 0 3px #ffffff1a}.AvatarSelectionUIComponent-module__customAvatarSection_VjZ7cq__0261 input[type=radio]:checked{background:#ffffff1a;border-color:#fffc}.AvatarSelectionUIComponent-module__customAvatarSection_VjZ7cq__0261 input[type=radio]:checked:after{content:\"\";background:#ffffffe6;border-radius:50%;width:8px;height:8px;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.AvatarSelectionUIComponent-module__customAvatarSection_VjZ7cq__0261 input[type=radio][disabled]{opacity:.4;cursor:not-allowed}.AvatarSelectionUIComponent-module__customAvatarInputSection_VjZ7cq__0261{align-items:stretch;display:flex}.AvatarSelectionUIComponent-module__setButton_VjZ7cq__0261{color:#ffffffe6;backdrop-filter:blur(10px);cursor:pointer;background:linear-gradient(135deg,#0009,#0006);border:1px solid #fff3;border-radius:8px;justify-content:center;align-items:center;height:44px;padding-left:16px;padding-right:16px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;font-size:14px;font-weight:500;transition:all .2s ease-in-out;display:flex}.AvatarSelectionUIComponent-module__setButton_VjZ7cq__0261:hover:not(:disabled){background:linear-gradient(135deg,#000c,#0009);border-color:#ffffff4d;transform:translateY(-1px);box-shadow:0 4px 8px #0000004d}.AvatarSelectionUIComponent-module__setButton_VjZ7cq__0261:active:not(:disabled){transform:translateY(0);box-shadow:0 2px 4px #0003}.AvatarSelectionUIComponent-module__setButton_VjZ7cq__0261:disabled{opacity:.5;cursor:not-allowed;transform:none}.AvatarSelectionUIComponent-module__sectionHeading_VjZ7cq__0261{color:#ffffffe6;letter-spacing:-.01em;margin-bottom:8px;font-size:18px;font-weight:600}.AvatarSelectionUIComponent-module__customAvatarSection_VjZ7cq__0261 .AvatarSelectionUIComponent-module__selectedPill_VjZ7cq__0261{margin-left:8px;position:relative;top:-2px}.AvatarSelectionUIComponent-module__displayNameSection_VjZ7cq__0261{color:#ffffffe6;border-bottom:1px solid #ffffff26;padding:10px;position:relative}.AvatarSelectionUIComponent-module__displayNameInputSection_VjZ7cq__0261{align-items:stretch;display:flex}.InputBox-module__inputWrapper_adOgOW__0261{pointer-events:all;align-items:stretch;gap:6px;display:flex}.InputBox-module__chatInput_adOgOW__0261{color:#fff;backdrop-filter:blur(10px);box-sizing:border-box;background:#0006;border:1px solid #fff3;border-radius:8px;outline:none;flex:1;height:36px;padding:0 8px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;font-size:14px;font-weight:400;transition:all .2s ease-in-out}.InputBox-module__chatInput_adOgOW__0261:focus{background:#0009;border-color:#fff6;box-shadow:0 0 0 3px #ffffff26}.InputBox-module__chatInput_adOgOW__0261::placeholder{color:#ffffff80}.InputBox-module__sendButton_adOgOW__0261{color:#ffffffe6;backdrop-filter:blur(10px);cursor:pointer;background:linear-gradient(135deg,#0009,#0006);border:1px solid #fff3;border-radius:8px;outline:none;justify-content:center;align-items:center;width:36px;height:36px;transition:all .2s ease-in-out;display:flex}.InputBox-module__sendButton_adOgOW__0261:hover{background:linear-gradient(135deg,#000c,#0009);border-color:#ffffff4d;transform:translateY(-1px);box-shadow:0 4px 8px #0000004d}.InputBox-module__sendButton_adOgOW__0261:active{transform:translateY(0);box-shadow:0 2px 4px #0003}.InputBox-module__sendButton_adOgOW__0261 .InputBox-module__svgIcon_adOgOW__0261 img{filter:invert(80%);width:18px;height:18px;transition:all .2s ease-in-out}.InputBox-module__sendButton_adOgOW__0261:hover .InputBox-module__svgIcon_adOgOW__0261 img{filter:invert(90%)}.Message-module__messageContainer_ikOQiq__0261{backdrop-filter:blur(8px);word-break:break-word;color:#ffffffe6;direction:ltr;background:#0000004d;border:1px solid #fff3;border-radius:8px;width:fit-content;margin:6px auto 6px 2px;padding:4px 8px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;font-size:14px;transition:all .2s ease-in-out;overflow-x:hidden}.Message-module__messageContainer_ikOQiq__0261:hover{background:#0006;border-color:#ffffff4d}.Message-module__userName_ikOQiq__0261{color:#ffffffe6;margin-right:4px;font-weight:600}.Messages-module__messagesContainer_LXaUUW__0261{z-index:1000;max-height:inherit;scrollbar-width:thin;scrollbar-color:#fff3 transparent;pointer-events:fill;direction:rtl;position:relative;bottom:0;left:-1px;overflow-y:auto}.Messages-module__messagesContainer_LXaUUW__0261::-webkit-scrollbar{width:6px}.Messages-module__messagesContainer_LXaUUW__0261::-webkit-scrollbar-track{background:none}.Messages-module__messagesContainer_LXaUUW__0261::-webkit-scrollbar-thumb{background:#fff3;border:1px solid #ffffff1a;border-radius:3px}.Messages-module__messagesContainer_LXaUUW__0261::-webkit-scrollbar-thumb:hover{background:#ffffff4d}.Messages-module__newMessagesButton_LXaUUW__0261{-webkit-backdrop-filter:blur(10px);color:#fff;cursor:pointer;z-index:1001;direction:ltr;background:#000000bf;border:1px solid #fff3;border-radius:20px;padding:8px 16px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-weight:500;transition:all .2s;position:absolute;bottom:50px;left:50%;transform:translate(-50%);box-shadow:0 4px 12px #0000004d}.Messages-module__newMessagesButton_LXaUUW__0261:hover{background:#000000d9;border-color:#ffffff4d;transform:translate(-50%)translateY(-2px);box-shadow:0 6px 16px #0006}.Messages-module__newMessagesButton_LXaUUW__0261:active{transform:translate(-50%)translateY(0);box-shadow:0 2px 8px #0000004d}.TextChatUIComponent-module__uiHover_gFDdcW__0261{z-index:102;pointer-events:all;width:58px;min-height:58px;position:absolute;bottom:0;left:0}.TextChatUIComponent-module__textChatUi_gFDdcW__0261{flex-direction:row;align-items:flex-end;width:100%;max-width:500px;height:0;display:flex;position:absolute;bottom:0;left:0}.TextChatUIComponent-module__textChat_gFDdcW__0261{z-index:-1;color:#fff;user-select:none;opacity:0;flex-direction:column;justify-content:space-between;width:100%;max-height:500px;margin-bottom:8px;margin-left:60px;padding:5px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;transition:opacity .25s ease-in-out,transform .2s ease-in-out;display:flex;transform:translate(-100%)}.TextChatUIComponent-module__fadeIn_gFDdcW__0261{opacity:1;transform:translate(0)}.TextChatUIComponent-module__fadeOut_gFDdcW__0261{opacity:.6;transform:translate(calc(-100% - 72px))}.TextChatUIComponent-module__controls_gFDdcW__0261{position:absolute;top:2px;right:0}.TextChatUIComponent-module__openTab_gFDdcW__0261{backdrop-filter:blur(10px);cursor:pointer;z-index:102;user-select:none;background:#0009;border:1px solid #fff3;border-radius:50%;justify-content:center;align-items:center;width:44px;height:44px;transition:all .2s ease-in-out;display:flex;position:absolute;bottom:8px;left:8px}.TextChatUIComponent-module__openTab_gFDdcW__0261:hover{background:#000c;border-color:#ffffff4d;transform:scale(1.05)}.TextChatUIComponent-module__openTab_gFDdcW__0261:active{transform:scale(.95)}.TextChatUIComponent-module__openTab_gFDdcW__0261 img{filter:invert(80%);width:24px;height:24px;transition:all .2s ease-in-out}.TextChatUIComponent-module__stickyButton_gFDdcW__0261{z-index:103;backdrop-filter:blur(10px);cursor:pointer;user-select:none;opacity:1;background:#0009;border:1px solid #fff3;border-radius:50%;justify-content:center;align-items:center;width:24px;height:24px;transition:all .2s ease-in-out;display:flex;position:absolute;bottom:38px;left:38px}.TextChatUIComponent-module__stickyButton_gFDdcW__0261:hover{background:#000c;border-color:#ffffff4d;transform:scale(1.05)}.TextChatUIComponent-module__stickyButtonFadeOut_gFDdcW__0261{z-index:103;backdrop-filter:blur(10px);cursor:pointer;user-select:none;opacity:0;pointer-events:none;background:#0009;border:1px solid #fff3;border-radius:50%;justify-content:center;align-items:center;width:24px;height:24px;transition:all .2s ease-in-out;display:flex;position:absolute;bottom:42px;left:42px}.TextChatUIComponent-module__stickyButtonEnabled_gFDdcW__0261{z-index:103;backdrop-filter:blur(10px);cursor:pointer;user-select:none;background:#000c;border:1px solid #22c55e99;border-radius:50%;justify-content:center;align-items:center;width:24px;height:24px;transition:all .2s ease-in-out;display:flex;position:absolute;bottom:42px;left:42px;box-shadow:0 0 8px #22c55e4d}.TextChatUIComponent-module__stickyButtonEnabled_gFDdcW__0261:hover{border-color:#22c55ecc;transform:scale(1.05);box-shadow:0 0 12px #22c55e66}.TextChatUIComponent-module__stickyButton_gFDdcW__0261 img,.TextChatUIComponent-module__stickyButtonFadeOut_gFDdcW__0261 img,.TextChatUIComponent-module__stickyButtonEnabled_gFDdcW__0261 img{filter:invert(70%);width:12px;height:12px;transition:all .2s ease-in-out}.TextChatUIComponent-module__stickyButton_gFDdcW__0261:hover img,.TextChatUIComponent-module__stickyButtonEnabled_gFDdcW__0261:hover img{filter:invert(90%)}.TextChatUIComponent-module__controls_gFDdcW__0261 .TextChatUIComponent-module__closeButton_gFDdcW__0261{color:#fffc;backdrop-filter:blur(10px);cursor:pointer;background:#0009;border:1px solid #fff3;border-radius:50%;justify-content:center;align-items:center;width:28px;height:28px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;font-size:14px;font-weight:500;transition:all .2s ease-in-out;display:flex;position:absolute;top:3px;right:3px}.TextChatUIComponent-module__closeButton_gFDdcW__0261:hover{color:#ffffffe6;background:#000c;border-color:#ffffff4d;transform:scale(1.05)}.TextChatUIComponent-module__closeButton_gFDdcW__0261:active{transform:scale(.95)}.TextChatUIComponent-module__messagesWrapper_gFDdcW__0261{direction:rtl;width:fit-content;max-height:450px;margin-bottom:8px;position:relative}.Networked3dWebExperience-module__respawnButton_7g9l0W__0261{z-index:102;color:#ffffffe6;backdrop-filter:blur(10px);cursor:pointer;user-select:none;background:linear-gradient(135deg,#0009,#0006);border:1px solid #fff3;border-radius:8px;justify-content:center;align-items:center;padding:12px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;font-size:14px;font-weight:500;transition:all .2s ease-in-out;display:flex;position:absolute;top:8px;left:8px}.Networked3dWebExperience-module__respawnButton_7g9l0W__0261:hover{background:linear-gradient(135deg,#000c,#0009);border-color:#ffffff4d;transform:translateY(-1px)}.Networked3dWebExperience-module__respawnButton_7g9l0W__0261:active{transform:translateY(0)}\n";globalThis['__css-digest-9df4d1565589ce63a22b21cf2f9d3ed9__']="9df4d1565589ce63a22b21cf2f9d3ed9";
2
1
  // src/Networked3dWebExperienceClient.ts
3
2
  import {
4
3
  CameraManager,
@@ -8,8 +7,8 @@ import {
8
7
  Key,
9
8
  KeyInputManager,
10
9
  LoadingScreen,
10
+ normalizeSpawnConfiguration,
11
11
  Vect3,
12
- VirtualJoystick,
13
12
  Quat,
14
13
  getSpawnData,
15
14
  CharacterManager,
@@ -17,1054 +16,553 @@ import {
17
16
  createDefaultCharacterControllerValues,
18
17
  createDefaultCameraValues
19
18
  } from "@mml-io/3d-web-client-core";
20
- import { ThreeJSWorldRenderer } from "@mml-io/3d-web-threejs";
21
19
  import {
22
- FROM_SERVER_CHAT_MESSAGE_TYPE,
23
- FROM_CLIENT_CHAT_MESSAGE_TYPE,
24
- UserNetworkingClient,
25
- WebsocketStatus,
26
- parseServerChatMessage,
27
- DeltaNetV01ServerErrors,
28
- SERVER_BROADCAST_MESSAGE_TYPE,
29
- parseServerBroadcastMessage
30
- } from "@mml-io/3d-web-user-networking";
20
+ experienceClientSubProtocols
21
+ } from "@mml-io/3d-web-experience-protocol";
22
+ import { ThreeJSWorldRenderer } from "@mml-io/3d-web-threejs";
31
23
  import { LoadingProgressManager, registerCustomElementsToWindow } from "@mml-io/mml-web";
32
24
 
33
- // src/avatar-selection-ui/AvatarSelectionUI.tsx
34
- import { forwardRef } from "react";
35
- import { flushSync } from "react-dom";
36
- import { createRoot } from "react-dom/client";
37
-
38
- // src/avatar-selection-ui/components/AvatarPanel/AvatarSectionUIComponent.tsx
39
- import {
40
- useRef,
41
- useState
42
- } from "react";
43
-
44
- // src/avatar-selection-ui/icons/Avatar.svg
45
- var Avatar_default = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M224 256A128 128 0 1 0 224 0a128 128 0 1 0 0 256zm-45.7 48C79.8 304 0 383.8 0 482.3C0 498.7 13.3 512 29.7 512l388.6 0c16.4 0 29.7-13.3 29.7-29.7C448 383.8 368.2 304 269.7 304l-91.4 0z"/></svg>\n';
46
-
47
- // esbuild-css-modules-plugin-ns-js::src/avatar-selection-ui/components/AvatarPanel/AvatarSelectionUIComponent.module.css:injector.js
48
- var content = globalThis['__css-content-9df4d1565589ce63a22b21cf2f9d3ed9__'];
49
- var digest = globalThis['__css-digest-9df4d1565589ce63a22b21cf2f9d3ed9__'];
50
- var inject = () => {
51
- setTimeout(() => {
52
- if (!globalThis.document) {
53
- return;
54
- }
55
- let root = globalThis.document.querySelector("head");
56
- if (root && root.shadowRoot) {
57
- root = root.shadowRoot;
58
- }
59
- if (!root) {
60
- root = globalThis.document.head;
61
- }
62
- let container = root.querySelector("#_" + digest);
63
- if (!container) {
64
- container = globalThis.document.createElement("style");
65
- container.id = "_" + digest;
66
- const text = globalThis.document.createTextNode(content);
67
- container.appendChild(text);
68
- root.appendChild(container);
69
- }
70
- }, 0);
71
- };
72
-
73
- // src/avatar-selection-ui/components/AvatarPanel/AvatarSelectionUIComponent.module.css
74
- var AvatarSelectionUIComponent_default = new Proxy({
75
- "avatarSelectionContainer": "AvatarSelectionUIComponent-module__avatarSelectionContainer_VjZ7cq__0261",
76
- "avatarSelectionNoImage": "AvatarSelectionUIComponent-module__avatarSelectionNoImage_VjZ7cq__0261",
77
- "avatarSelectionSection": "AvatarSelectionUIComponent-module__avatarSelectionSection_VjZ7cq__0261",
78
- "avatarSelectionUi": "AvatarSelectionUIComponent-module__avatarSelectionUi_VjZ7cq__0261",
79
- "avatarSelectionUiAvatar": "AvatarSelectionUIComponent-module__avatarSelectionUiAvatar_VjZ7cq__0261",
80
- "avatarSelectionUiAvatarImage": "AvatarSelectionUIComponent-module__avatarSelectionUiAvatarImage_VjZ7cq__0261",
81
- "avatarSelectionUiAvatarImgContainer": "AvatarSelectionUIComponent-module__avatarSelectionUiAvatarImgContainer_VjZ7cq__0261",
82
- "avatarSelectionUiCloseButton": "AvatarSelectionUIComponent-module__avatarSelectionUiCloseButton_VjZ7cq__0261",
83
- "avatarSelectionUiHeader": "AvatarSelectionUIComponent-module__avatarSelectionUiHeader_VjZ7cq__0261",
84
- "closeButton": "AvatarSelectionUIComponent-module__closeButton_VjZ7cq__0261",
85
- "customAvatarInputSection": "AvatarSelectionUIComponent-module__customAvatarInputSection_VjZ7cq__0261",
86
- "customAvatarSection": "AvatarSelectionUIComponent-module__customAvatarSection_VjZ7cq__0261",
87
- "displayNameInputSection": "AvatarSelectionUIComponent-module__displayNameInputSection_VjZ7cq__0261",
88
- "displayNameSection": "AvatarSelectionUIComponent-module__displayNameSection_VjZ7cq__0261",
89
- "input": "AvatarSelectionUIComponent-module__input_VjZ7cq__0261",
90
- "menuButton": "AvatarSelectionUIComponent-module__menuButton_VjZ7cq__0261",
91
- "openTab": "AvatarSelectionUIComponent-module__openTab_VjZ7cq__0261",
92
- "radioGroup": "AvatarSelectionUIComponent-module__radioGroup_VjZ7cq__0261",
93
- "radioItem": "AvatarSelectionUIComponent-module__radioItem_VjZ7cq__0261",
94
- "sectionHeading": "AvatarSelectionUIComponent-module__sectionHeading_VjZ7cq__0261",
95
- "selectedPill": "AvatarSelectionUIComponent-module__selectedPill_VjZ7cq__0261",
96
- "setButton": "AvatarSelectionUIComponent-module__setButton_VjZ7cq__0261",
97
- "tooltipText": "AvatarSelectionUIComponent-module__tooltipText_VjZ7cq__0261"
98
- }, {
99
- get: function(source, key) {
100
- inject();
101
- return source[key];
102
- }
103
- });
104
-
105
- // src/avatar-selection-ui/components/AvatarPanel/AvatarSectionUIComponent.tsx
106
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
107
- function SelectedPill() {
108
- return /* @__PURE__ */ jsx("span", { className: AvatarSelectionUIComponent_default.selectedPill, children: "Selected" });
109
- }
110
- var AvatarSelectionUIComponent = (props) => {
111
- const visibleByDefault = props.visibleByDefault ?? false;
112
- const [isVisible, setIsVisible] = useState(visibleByDefault);
113
- const [selectedAvatar, setSelectedAvatar] = useState(
114
- props.characterDescription
115
- );
116
- const [customAvatarType, setCustomAvatarType] = useState(
117
- 1 /* mmlUrl */
118
- );
119
- const [customAvatarValue, setCustomAvatarValue] = useState("");
120
- const inputRef = useRef(null);
121
- const textareaRef = useRef(null);
122
- const [displayNameValue, setDisplayNameValue] = useState(props.displayName);
123
- const displayNameRef = useRef(null);
124
- const handleRootClick = (e) => {
125
- e.stopPropagation();
126
- };
127
- const selectAvatar = (avatar) => {
128
- setSelectedAvatar(avatar);
129
- props.onUpdateUserAvatar(avatar);
130
- };
131
- const handleInputChange = (e) => {
132
- setCustomAvatarValue(e.target.value);
133
- };
134
- const handleDisplayNameChange = (e) => {
135
- setDisplayNameValue(e.target.value);
136
- };
137
- const setDisplayName = () => {
138
- if (!displayNameValue) {
139
- return;
140
- }
141
- props.onUpdateDisplayName(displayNameValue);
142
- };
143
- const addCustomAvatar = () => {
144
- if (!customAvatarValue) {
145
- return;
146
- }
147
- let newSelectedAvatar;
148
- switch (customAvatarType) {
149
- case 2 /* mml */:
150
- newSelectedAvatar = {
151
- mmlCharacterString: customAvatarValue
152
- };
153
- break;
154
- case 1 /* mmlUrl */:
155
- newSelectedAvatar = {
156
- mmlCharacterUrl: customAvatarValue
157
- };
158
- break;
159
- case 0 /* meshFileUrl */:
160
- newSelectedAvatar = {
161
- meshFileUrl: customAvatarValue
162
- };
163
- break;
164
- }
165
- setSelectedAvatar(newSelectedAvatar);
166
- props.onUpdateUserAvatar(newSelectedAvatar);
167
- };
168
- const handleKeyPress = (e) => {
169
- e.stopPropagation();
170
- };
171
- const handleAvatarInputKeyPress = (e) => {
172
- e.stopPropagation();
173
- if (e.key === "Enter") {
174
- addCustomAvatar();
175
- }
176
- };
177
- const handleDisplayNameKeyPress = (e) => {
178
- e.stopPropagation();
179
- if (e.key === "Enter") {
180
- setDisplayName();
181
- }
182
- };
183
- const handleTypeSwitch = (type) => {
184
- setCustomAvatarType(type);
185
- setCustomAvatarValue("");
186
- };
187
- const getPlaceholderByType = (type) => {
188
- switch (type) {
189
- case 0 /* meshFileUrl */:
190
- return "https://.../avatar.glb";
191
- case 1 /* mmlUrl */:
192
- return "https://.../avatar.html";
193
- case 2 /* mml */:
194
- return '<m-character src="https://link-to-avatar">\n</m-character';
25
+ // src/ClientEventEmitter.ts
26
+ var ClientEventEmitter = class {
27
+ handlers = /* @__PURE__ */ new Map();
28
+ on(event, handler) {
29
+ let set = this.handlers.get(event);
30
+ if (!set) {
31
+ set = /* @__PURE__ */ new Set();
32
+ this.handlers.set(event, set);
33
+ }
34
+ set.add(handler);
35
+ }
36
+ off(event, handler) {
37
+ var _a;
38
+ (_a = this.handlers.get(event)) == null ? void 0 : _a.delete(handler);
39
+ }
40
+ emit(event, ...args) {
41
+ const set = this.handlers.get(event);
42
+ if (!set) return;
43
+ for (const handler of [...set]) {
44
+ handler(...args);
195
45
  }
196
- };
197
- if (!props.availableAvatars.length && !props.allowCustomAvatars && !props.allowCustomDisplayName) {
198
- return null;
199
- }
200
- let recognizedAvatar = false;
201
- return /* @__PURE__ */ jsxs(Fragment, { children: [
202
- /* @__PURE__ */ jsxs("div", { className: AvatarSelectionUIComponent_default.menuButton, onClick: handleRootClick, children: [
203
- !isVisible && /* @__PURE__ */ jsx("div", { className: AvatarSelectionUIComponent_default.openTab, onClick: () => setIsVisible(true), children: /* @__PURE__ */ jsx("img", { src: `data:image/svg+xml;utf8,${encodeURIComponent(Avatar_default)}` }) }),
204
- isVisible && /* @__PURE__ */ jsx("button", { className: AvatarSelectionUIComponent_default.closeButton, onClick: (e) => setIsVisible(false), children: "X" })
205
- ] }),
206
- isVisible && /* @__PURE__ */ jsxs("div", { className: `${AvatarSelectionUIComponent_default.avatarSelectionContainer}`, children: [
207
- props.allowCustomDisplayName && /* @__PURE__ */ jsxs("div", { className: AvatarSelectionUIComponent_default.displayNameSection, children: [
208
- /* @__PURE__ */ jsx("div", { className: AvatarSelectionUIComponent_default.sectionHeading, children: "Display Name" }),
209
- /* @__PURE__ */ jsxs("div", { className: AvatarSelectionUIComponent_default.displayNameInputSection, children: [
210
- /* @__PURE__ */ jsx(
211
- "input",
212
- {
213
- ref: displayNameRef,
214
- className: AvatarSelectionUIComponent_default.input,
215
- value: displayNameValue,
216
- onKeyDown: handleDisplayNameKeyPress,
217
- onChange: handleDisplayNameChange,
218
- placeholder: "Enter your display name"
219
- }
220
- ),
221
- /* @__PURE__ */ jsx(
222
- "button",
223
- {
224
- className: AvatarSelectionUIComponent_default.setButton,
225
- disabled: !displayNameValue,
226
- type: "button",
227
- onClick: setDisplayName,
228
- children: "Set"
229
- }
230
- )
231
- ] })
232
- ] }),
233
- !!props.availableAvatars.length && /* @__PURE__ */ jsxs("div", { className: AvatarSelectionUIComponent_default.avatarSelectionSection, children: [
234
- /* @__PURE__ */ jsx("div", { className: AvatarSelectionUIComponent_default.sectionHeading, children: "Choose your Avatar" }),
235
- /* @__PURE__ */ jsx("div", { className: AvatarSelectionUIComponent_default.avatarSelectionUi, children: props.availableAvatars.map((avatar, index) => {
236
- const isSelected = (selectedAvatar == null ? void 0 : selectedAvatar.meshFileUrl) && (selectedAvatar == null ? void 0 : selectedAvatar.meshFileUrl) === avatar.meshFileUrl || (selectedAvatar == null ? void 0 : selectedAvatar.mmlCharacterUrl) && (selectedAvatar == null ? void 0 : selectedAvatar.mmlCharacterUrl) === avatar.mmlCharacterUrl || (selectedAvatar == null ? void 0 : selectedAvatar.mmlCharacterString) && (selectedAvatar == null ? void 0 : selectedAvatar.mmlCharacterString) === avatar.mmlCharacterString;
237
- if (isSelected) {
238
- recognizedAvatar = true;
239
- }
240
- return /* @__PURE__ */ jsx(
241
- "div",
242
- {
243
- className: AvatarSelectionUIComponent_default.avatarSelectionUiAvatar,
244
- onClick: () => selectAvatar(avatar),
245
- children: /* @__PURE__ */ jsxs("div", { className: AvatarSelectionUIComponent_default.avatarSelectionUiAvatarImgContainer, children: [
246
- isSelected && /* @__PURE__ */ jsx(SelectedPill, {}),
247
- avatar.thumbnailUrl ? /* @__PURE__ */ jsx(
248
- "img",
249
- {
250
- className: AvatarSelectionUIComponent_default.avatarSelectionUiAvatarImage,
251
- src: avatar.thumbnailUrl,
252
- alt: avatar.name
253
- }
254
- ) : /* @__PURE__ */ jsx("div", { className: AvatarSelectionUIComponent_default.avatarSelectionNoImage, children: /* @__PURE__ */ jsx(
255
- "img",
256
- {
257
- alt: avatar.name,
258
- src: `data:image/svg+xml;utf8,${encodeURIComponent(Avatar_default)}`
259
- }
260
- ) }),
261
- /* @__PURE__ */ jsx("p", { children: avatar.name }),
262
- /* @__PURE__ */ jsx("span", { className: AvatarSelectionUIComponent_default.tooltipText, children: avatar.name })
263
- ] })
264
- },
265
- index
266
- );
267
- }) })
268
- ] }),
269
- props.allowCustomAvatars && /* @__PURE__ */ jsxs("div", { className: AvatarSelectionUIComponent_default.customAvatarSection, children: [
270
- /* @__PURE__ */ jsx("div", { className: AvatarSelectionUIComponent_default.sectionHeading, children: "Custom Avatar" }),
271
- /* @__PURE__ */ jsxs("div", { className: AvatarSelectionUIComponent_default.radioGroup, children: [
272
- /* @__PURE__ */ jsxs("div", { className: AvatarSelectionUIComponent_default.radioItem, children: [
273
- /* @__PURE__ */ jsx(
274
- "input",
275
- {
276
- type: "radio",
277
- id: "html",
278
- name: "customAvatarType",
279
- onChange: () => handleTypeSwitch(1 /* mmlUrl */),
280
- checked: customAvatarType === 1 /* mmlUrl */
281
- }
282
- ),
283
- /* @__PURE__ */ jsx("label", { htmlFor: "html", children: "MML URL" })
284
- ] }),
285
- /* @__PURE__ */ jsxs("div", { className: AvatarSelectionUIComponent_default.radioItem, children: [
286
- /* @__PURE__ */ jsx(
287
- "input",
288
- {
289
- type: "radio",
290
- id: "mml",
291
- name: "customAvatarType",
292
- onChange: () => handleTypeSwitch(2 /* mml */),
293
- checked: customAvatarType === 2 /* mml */
294
- }
295
- ),
296
- /* @__PURE__ */ jsx("label", { htmlFor: "mml", children: "MML" })
297
- ] }),
298
- /* @__PURE__ */ jsxs("div", { className: AvatarSelectionUIComponent_default.radioItem, children: [
299
- /* @__PURE__ */ jsx(
300
- "input",
301
- {
302
- type: "radio",
303
- id: "glb",
304
- name: "customAvatarType",
305
- onChange: () => handleTypeSwitch(0 /* meshFileUrl */),
306
- checked: customAvatarType === 0 /* meshFileUrl */
307
- }
308
- ),
309
- /* @__PURE__ */ jsx("label", { htmlFor: "glb", children: "Mesh URL" })
310
- ] }),
311
- !recognizedAvatar && /* @__PURE__ */ jsx(SelectedPill, {})
312
- ] }),
313
- /* @__PURE__ */ jsxs("div", { className: AvatarSelectionUIComponent_default.customAvatarInputSection, children: [
314
- customAvatarType === 2 /* mml */ ? /* @__PURE__ */ jsx(
315
- "textarea",
316
- {
317
- ref: textareaRef,
318
- className: AvatarSelectionUIComponent_default.input,
319
- value: customAvatarValue,
320
- onChange: handleInputChange,
321
- onKeyDown: handleKeyPress,
322
- placeholder: getPlaceholderByType(customAvatarType),
323
- rows: 4
324
- }
325
- ) : /* @__PURE__ */ jsx(
326
- "input",
327
- {
328
- ref: inputRef,
329
- className: AvatarSelectionUIComponent_default.input,
330
- value: customAvatarValue,
331
- onKeyDown: handleAvatarInputKeyPress,
332
- onChange: handleInputChange,
333
- placeholder: getPlaceholderByType(customAvatarType)
334
- }
335
- ),
336
- /* @__PURE__ */ jsx(
337
- "button",
338
- {
339
- className: AvatarSelectionUIComponent_default.setButton,
340
- disabled: !customAvatarValue,
341
- type: "button",
342
- onClick: addCustomAvatar,
343
- children: "Set"
344
- }
345
- )
346
- ] })
347
- ] })
348
- ] })
349
- ] });
350
- };
351
-
352
- // src/avatar-selection-ui/AvatarSelectionUI.tsx
353
- import { jsx as jsx2 } from "react/jsx-runtime";
354
- var ForwardedAvatarSelectionUIComponent = forwardRef(AvatarSelectionUIComponent);
355
- var AvatarSelectionUI = class {
356
- constructor(config) {
357
- this.config = config;
358
- this.config.holderElement.appendChild(this.wrapper);
359
- this.root = createRoot(this.wrapper);
360
- }
361
- root;
362
- wrapper = document.createElement("div");
363
- onUpdateUserAvatar = (avatar) => {
364
- this.config.characterDescription = avatar;
365
- this.config.sendIdentityUpdateToServer(this.config.displayName, avatar);
366
- };
367
- onUpdateDisplayName = (displayName) => {
368
- this.config.displayName = displayName;
369
- this.config.sendIdentityUpdateToServer(displayName, this.config.characterDescription);
370
- };
371
- updateAvatarConfig(avatarConfig) {
372
- this.config = {
373
- ...this.config,
374
- ...avatarConfig
375
- };
376
- this.init();
377
46
  }
378
- updateAllowCustomDisplayName(allowCustomDisplayName) {
379
- this.config = {
380
- ...this.config,
381
- allowCustomDisplayName
382
- };
383
- this.init();
384
- }
385
- init() {
386
- flushSync(
387
- () => this.root.render(
388
- /* @__PURE__ */ jsx2(
389
- ForwardedAvatarSelectionUIComponent,
390
- {
391
- onUpdateUserAvatar: this.onUpdateUserAvatar,
392
- onUpdateDisplayName: this.onUpdateDisplayName,
393
- visibleByDefault: this.config.visibleByDefault,
394
- displayName: this.config.displayName,
395
- characterDescription: this.config.characterDescription,
396
- availableAvatars: this.config.availableAvatars,
397
- allowCustomAvatars: this.config.allowCustomAvatars || false,
398
- allowCustomDisplayName: this.config.allowCustomDisplayName || false
399
- }
400
- )
401
- )
402
- );
47
+ /** Remove all registered event handlers. Called during dispose to prevent leaks. */
48
+ clearAllHandlers() {
49
+ this.handlers.clear();
403
50
  }
404
51
  };
405
52
 
406
- // src/chat-ui/TextChatUI.tsx
407
- import { createRef, forwardRef as forwardRef3 } from "react";
408
- import { flushSync as flushSync2 } from "react-dom";
409
- import { createRoot as createRoot2 } from "react-dom/client";
410
-
411
- // src/chat-ui/components/ChatPanel/TextChatUIComponent.tsx
412
- import {
413
- useCallback as useCallback2,
414
- useEffect as useEffect4,
415
- useImperativeHandle as useImperativeHandle2,
416
- useRef as useRef5,
417
- useState as useState5
418
- } from "react";
419
-
420
- // src/chat-ui/helpers.tsx
421
- import { useEffect, useLayoutEffect, useRef as useRef2 } from "react";
422
- function useClickOutside(cb) {
423
- const ref = useRef2(null);
424
- const refCb = useRef2(cb);
425
- useLayoutEffect(() => {
426
- refCb.current = cb;
427
- });
428
- useEffect(() => {
429
- const handler = (e) => {
430
- const element = ref.current;
431
- if (element && !element.contains(e.target)) {
432
- refCb.current(e);
433
- }
434
- };
435
- document.addEventListener("mousedown", handler);
436
- document.addEventListener("touchstart", handler);
437
- return () => {
438
- document.removeEventListener("mousedown", handler);
439
- document.removeEventListener("touchstart", handler);
440
- };
441
- }, []);
442
- return ref;
443
- }
444
-
445
- // src/chat-ui/icons/Chat.svg
446
- var Chat_default = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M88.2 309.1c9.8-18.3 6.8-40.8-7.5-55.8C59.4 230.9 48 204 48 176c0-63.5 63.8-128 160-128s160 64.5 160 128s-63.8 128-160 128c-13.1 0-25.8-1.3-37.8-3.6c-10.4-2-21.2-.6-30.7 4.2c-4.1 2.1-8.3 4.1-12.6 6c-16 7.2-32.9 13.5-49.9 18c2.8-4.6 5.4-9.1 7.9-13.6c1.1-1.9 2.2-3.9 3.2-5.9zM0 176c0 41.8 17.2 80.1 45.9 110.3c-.9 1.7-1.9 3.5-2.8 5.1c-10.3 18.4-22.3 36.5-36.6 52.1c-6.6 7-8.3 17.2-4.6 25.9C5.8 378.3 14.4 384 24 384c43 0 86.5-13.3 122.7-29.7c4.8-2.2 9.6-4.5 14.2-6.8c15.1 3 30.9 4.5 47.1 4.5c114.9 0 208-78.8 208-176S322.9 0 208 0S0 78.8 0 176zM432 480c16.2 0 31.9-1.6 47.1-4.5c4.6 2.3 9.4 4.6 14.2 6.8C529.5 498.7 573 512 616 512c9.6 0 18.2-5.7 22-14.5c3.8-8.8 2-19-4.6-25.9c-14.2-15.6-26.2-33.7-36.6-52.1c-.9-1.7-1.9-3.4-2.8-5.1C622.8 384.1 640 345.8 640 304c0-94.4-87.9-171.5-198.2-175.8c4.1 15.2 6.2 31.2 6.2 47.8l0 .6c87.2 6.7 144 67.5 144 127.4c0 28-11.4 54.9-32.7 77.2c-14.3 15-17.3 37.6-7.5 55.8c1.1 2 2.2 4 3.2 5.9c2.5 4.5 5.2 9 7.9 13.6c-17-4.5-33.9-10.7-49.9-18c-4.3-1.9-8.5-3.9-12.6-6c-9.5-4.8-20.3-6.2-30.7-4.2c-12.1 2.4-24.7 3.6-37.8 3.6c-61.7 0-110-26.5-136.8-62.3c-16 5.4-32.8 9.4-50 11.8C279 439.8 350 480 432 480z"/></svg>';
447
-
448
- // src/chat-ui/icons/Pin.svg
449
- var Pin_default = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--! Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M32 32C32 14.3 46.3 0 64 0H320c17.7 0 32 14.3 32 32s-14.3 32-32 32H290.5l11.4 148.2c36.7 19.9 65.7 53.2 79.5 94.7l1 3c3.3 9.8 1.6 20.5-4.4 28.8s-15.7 13.3-26 13.3H32c-10.3 0-19.9-4.9-26-13.3s-7.7-19.1-4.4-28.8l1-3c13.8-41.5 42.8-74.8 79.5-94.7L93.5 64H64C46.3 64 32 49.7 32 32zM160 384h64v96c0 17.7-14.3 32-32 32s-32-14.3-32-32V384z"/></svg>';
450
-
451
- // src/chat-ui/components/Input/InputBox.tsx
452
- import { forwardRef as forwardRef2, useImperativeHandle, useRef as useRef3, useState as useState2 } from "react";
453
-
454
- // src/chat-ui/icons/PaperPlane.svg
455
- var PaperPlane_default = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M498.1 5.6c10.1 7 15.4 19.1 13.5 31.2l-64 416c-1.5 9.7-7.4 18.2-16 23s-18.9 5.4-28 1.6L284 427.7l-68.5 74.1c-8.9 9.7-22.9 12.9-35.2 8.1S160 493.2 160 480V396.4c0-4 1.5-7.8 4.2-10.7L331.8 202.8c5.8-6.3 5.6-16-.4-22s-15.7-6.4-22-.7L106 360.8 17.7 316.6C7.1 311.3 .3 300.7 0 288.9s5.9-22.8 16.1-28.7l448-256c10.7-6.1 23.9-5.5 34 1.4z"/></svg>';
456
-
457
- // esbuild-css-modules-plugin-ns-js::src/chat-ui/components/Input/InputBox.module.css:injector.js
458
- var content2 = globalThis['__css-content-9df4d1565589ce63a22b21cf2f9d3ed9__'];
459
- var digest2 = globalThis['__css-digest-9df4d1565589ce63a22b21cf2f9d3ed9__'];
460
- var inject2 = () => {
461
- setTimeout(() => {
462
- if (!globalThis.document) {
463
- return;
464
- }
465
- let root = globalThis.document.querySelector("head");
466
- if (root && root.shadowRoot) {
467
- root = root.shadowRoot;
468
- }
469
- if (!root) {
470
- root = globalThis.document.head;
471
- }
472
- let container = root.querySelector("#_" + digest2);
473
- if (!container) {
474
- container = globalThis.document.createElement("style");
475
- container.id = "_" + digest2;
476
- const text = globalThis.document.createTextNode(content2);
477
- container.appendChild(text);
478
- root.appendChild(container);
53
+ // src/DefaultRespawnButtonPlugin.ts
54
+ var DefaultRespawnButtonPlugin = class {
55
+ client = null;
56
+ container = null;
57
+ button = null;
58
+ mount(container, client) {
59
+ this.client = client;
60
+ this.container = container;
61
+ }
62
+ onConfigChanged(config) {
63
+ if (config.hud === void 0) return;
64
+ const enabled = config.hud !== false && config.hud.respawnButton === true;
65
+ if (enabled && !this.button) {
66
+ this.show();
67
+ } else if (!enabled && this.button) {
68
+ this.hide();
479
69
  }
480
- }, 0);
481
- };
482
-
483
- // src/chat-ui/components/Input/InputBox.module.css
484
- var InputBox_default = new Proxy({
485
- "chatInput": "InputBox-module__chatInput_adOgOW__0261",
486
- "inputWrapper": "InputBox-module__inputWrapper_adOgOW__0261",
487
- "sendButton": "InputBox-module__sendButton_adOgOW__0261",
488
- "svgIcon": "InputBox-module__svgIcon_adOgOW__0261"
489
- }, {
490
- get: function(source, key) {
491
- inject2();
492
- return source[key];
493
- }
494
- });
495
-
496
- // src/chat-ui/components/Input/InputBox.tsx
497
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
498
- var InputBox = forwardRef2(
499
- ({ onSendMessage, hide, setFocus }, ref) => {
500
- const inputRef = useRef3(null);
501
- const buttonRef = useRef3(null);
502
- const [inputValue, setInputValue] = useState2("");
503
- useImperativeHandle(ref, () => ({
504
- focusInput: () => {
505
- if (inputRef.current) inputRef.current.focus();
506
- }
507
- }));
508
- const handleInputChange = (e) => {
509
- setInputValue(e.target.value);
510
- };
511
- const handleSendClick = () => {
512
- if (inputValue.trim() !== "") {
513
- onSendMessage(inputValue.trim());
514
- setInputValue("");
515
- }
516
- };
517
- const handleKeyPress = (e) => {
70
+ }
71
+ dispose() {
72
+ this.hide();
73
+ this.client = null;
74
+ this.container = null;
75
+ }
76
+ show() {
77
+ if (this.button) return;
78
+ this.button = document.createElement("div");
79
+ this.button.textContent = "RESPAWN";
80
+ Object.assign(this.button.style, {
81
+ position: "absolute",
82
+ top: "12px",
83
+ left: "12px",
84
+ zIndex: "102",
85
+ padding: "12px",
86
+ borderRadius: "8px",
87
+ border: "1px solid rgba(255, 255, 255, 0.2)",
88
+ color: "rgba(255, 255, 255, 0.9)",
89
+ background: "linear-gradient(135deg, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.4))",
90
+ backdropFilter: "blur(10px)",
91
+ WebkitBackdropFilter: "blur(10px)",
92
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif",
93
+ fontSize: "14px",
94
+ fontWeight: "500",
95
+ letterSpacing: "0.5px",
96
+ boxShadow: "0 4px 24px rgba(0, 0, 0, 0.4)",
97
+ cursor: "pointer",
98
+ transition: "all 0.2s ease-in-out",
99
+ display: "flex",
100
+ alignItems: "center",
101
+ justifyContent: "center",
102
+ userSelect: "none"
103
+ });
104
+ this.button.addEventListener("mouseenter", () => {
105
+ if (!this.button) return;
106
+ this.button.style.background = "linear-gradient(135deg, rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.6))";
107
+ this.button.style.borderColor = "rgba(255, 255, 255, 0.3)";
108
+ this.button.style.transform = "translateY(-1px)";
109
+ this.button.style.boxShadow = "0 6px 28px rgba(0, 0, 0, 0.5)";
110
+ });
111
+ this.button.addEventListener("mouseleave", () => {
112
+ if (!this.button) return;
113
+ this.button.style.background = "linear-gradient(135deg, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.4))";
114
+ this.button.style.borderColor = "rgba(255, 255, 255, 0.2)";
115
+ this.button.style.transform = "";
116
+ this.button.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.4)";
117
+ });
118
+ this.button.addEventListener("pointerdown", () => {
119
+ if (!this.button) return;
120
+ this.button.style.transform = "translateY(0)";
121
+ this.button.style.boxShadow = "0 2px 12px rgba(0, 0, 0, 0.3)";
122
+ });
123
+ this.button.addEventListener("pointerup", () => {
124
+ if (!this.button) return;
125
+ this.button.style.transform = "translateY(-1px)";
126
+ this.button.style.boxShadow = "0 6px 28px rgba(0, 0, 0, 0.5)";
127
+ });
128
+ this.button.addEventListener("click", () => {
518
129
  var _a;
519
- e.stopPropagation();
520
- if (e.key === "Enter") {
521
- if (((_a = inputRef.current) == null ? void 0 : _a.value.trim().length) === 0) {
522
- if (buttonRef.current) buttonRef.current.focus();
523
- hide();
524
- return;
525
- }
526
- handleSendClick();
527
- }
528
- };
529
- return /* @__PURE__ */ jsxs2("div", { className: InputBox_default.inputWrapper, children: [
530
- /* @__PURE__ */ jsx3(
531
- "input",
532
- {
533
- ref: inputRef,
534
- type: "text",
535
- placeholder: "Type your message here...",
536
- value: inputValue,
537
- onChange: handleInputChange,
538
- className: InputBox_default.chatInput,
539
- onKeyDown: handleKeyPress,
540
- onFocus: setFocus
541
- }
542
- ),
543
- /* @__PURE__ */ jsx3("button", { ref: buttonRef, onClick: handleSendClick, className: InputBox_default.sendButton, children: /* @__PURE__ */ jsx3("div", { className: InputBox_default.svgIcon, children: /* @__PURE__ */ jsx3("img", { src: `data:image/svg+xml;utf8,${encodeURIComponent(PaperPlane_default)}` }) }) })
544
- ] });
130
+ (_a = this.client) == null ? void 0 : _a.respawn();
131
+ });
132
+ (this.container ?? document.body).appendChild(this.button);
133
+ }
134
+ hide() {
135
+ var _a;
136
+ (_a = this.button) == null ? void 0 : _a.remove();
137
+ this.button = null;
545
138
  }
546
- );
547
- InputBox.displayName = "InputBox";
548
-
549
- // src/chat-ui/components/Messages/Messages.tsx
550
- import { useEffect as useEffect3, useRef as useRef4, useState as useState4 } from "react";
551
-
552
- // src/chat-ui/components/Message/Message.tsx
553
- import { useState as useState3, useEffect as useEffect2, useCallback } from "react";
554
-
555
- // esbuild-css-modules-plugin-ns-js::src/chat-ui/components/Message/Message.module.css:injector.js
556
- var content3 = globalThis['__css-content-9df4d1565589ce63a22b21cf2f9d3ed9__'];
557
- var digest3 = globalThis['__css-digest-9df4d1565589ce63a22b21cf2f9d3ed9__'];
558
- var inject3 = () => {
559
- setTimeout(() => {
560
- if (!globalThis.document) {
561
- return;
562
- }
563
- let root = globalThis.document.querySelector("head");
564
- if (root && root.shadowRoot) {
565
- root = root.shadowRoot;
566
- }
567
- if (!root) {
568
- root = globalThis.document.head;
569
- }
570
- let container = root.querySelector("#_" + digest3);
571
- if (!container) {
572
- container = globalThis.document.createElement("style");
573
- container.id = "_" + digest3;
574
- const text = globalThis.document.createTextNode(content3);
575
- container.appendChild(text);
576
- root.appendChild(container);
577
- }
578
- }, 0);
579
- };
580
-
581
- // src/chat-ui/components/Message/Message.module.css
582
- var Message_default = new Proxy({
583
- "messageContainer": "Message-module__messageContainer_ikOQiq__0261",
584
- "userName": "Message-module__userName_ikOQiq__0261"
585
- }, {
586
- get: function(source, key) {
587
- inject3();
588
- return source[key];
589
- }
590
- });
591
-
592
- // src/chat-ui/components/Message/Message.tsx
593
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
594
- function ReverseHash(input) {
595
- const stringLength = input.length;
596
- let hash = 5381;
597
- for (let i = stringLength - 1; i >= 0; i--) {
598
- hash = (hash << 5) + hash + input.charCodeAt(i);
599
- }
600
- return hash;
601
- }
602
- function generateValueFromThresholds(hash, thresholds) {
603
- const selectedThreshold = thresholds[hash % thresholds.length];
604
- const min = Math.min(...selectedThreshold);
605
- const max = Math.max(...selectedThreshold);
606
- const thresholdRange = Math.abs(max - min);
607
- return hash % thresholdRange + min;
608
- }
609
- function hslForString(input, options = DEFAULT_HSL_OPTIONS) {
610
- let hash = Math.abs(ReverseHash("lightness: " + input));
611
- const lightness = options.lightnessThresholds ? generateValueFromThresholds(hash, options.lightnessThresholds) : generateValueFromThresholds(hash, DEFAULT_HSL_OPTIONS.lightnessThresholds);
612
- hash = Math.abs(ReverseHash("saturation:" + input));
613
- const saturation = options.saturationThresholds ? generateValueFromThresholds(hash, options.saturationThresholds) : generateValueFromThresholds(hash, DEFAULT_HSL_OPTIONS.saturationThresholds);
614
- hash = Math.abs(ReverseHash("hue:" + input));
615
- const hue = options.hueThresholds ? generateValueFromThresholds(hash, options.hueThresholds) : generateValueFromThresholds(hash, DEFAULT_HSL_OPTIONS.hueThresholds);
616
- return [hue, saturation, lightness];
617
- }
618
- var Message = ({ username, message, stringToHslOptions }) => {
619
- const [userColors, setUserColors] = useState3(/* @__PURE__ */ new Map());
620
- const generateColorForUsername = useCallback(() => {
621
- const [hue, saturation, lightness] = hslForString(username, stringToHslOptions);
622
- return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
623
- }, [stringToHslOptions, username]);
624
- useEffect2(() => {
625
- if (!userColors.has(username)) {
626
- const color = generateColorForUsername();
627
- setUserColors(new Map(userColors).set(username, color));
628
- }
629
- }, [username, userColors, generateColorForUsername]);
630
- const userColor = userColors.get(username) || "hsl(0, 0%, 0%)";
631
- return /* @__PURE__ */ jsxs3("div", { className: Message_default.messageContainer, children: [
632
- /* @__PURE__ */ jsx4("span", { className: Message_default.userName, style: { color: userColor }, children: username }),
633
- ": ",
634
- message
635
- ] });
636
- };
637
- var Message_default2 = Message;
638
-
639
- // esbuild-css-modules-plugin-ns-js::src/chat-ui/components/Messages/Messages.module.css:injector.js
640
- var content4 = globalThis['__css-content-9df4d1565589ce63a22b21cf2f9d3ed9__'];
641
- var digest4 = globalThis['__css-digest-9df4d1565589ce63a22b21cf2f9d3ed9__'];
642
- var inject4 = () => {
643
- setTimeout(() => {
644
- if (!globalThis.document) {
645
- return;
646
- }
647
- let root = globalThis.document.querySelector("head");
648
- if (root && root.shadowRoot) {
649
- root = root.shadowRoot;
650
- }
651
- if (!root) {
652
- root = globalThis.document.head;
653
- }
654
- let container = root.querySelector("#_" + digest4);
655
- if (!container) {
656
- container = globalThis.document.createElement("style");
657
- container.id = "_" + digest4;
658
- const text = globalThis.document.createTextNode(content4);
659
- container.appendChild(text);
660
- root.appendChild(container);
661
- }
662
- }, 0);
663
- };
664
-
665
- // src/chat-ui/components/Messages/Messages.module.css
666
- var Messages_default = new Proxy({
667
- "messagesContainer": "Messages-module__messagesContainer_LXaUUW__0261",
668
- "newMessagesButton": "Messages-module__newMessagesButton_LXaUUW__0261"
669
- }, {
670
- get: function(source, key) {
671
- inject4();
672
- return source[key];
673
- }
674
- });
675
-
676
- // src/chat-ui/components/Messages/Messages.tsx
677
- import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
678
- var SCROLL_THRESHOLD_PX = 10;
679
- var Messages = ({ messages, stringToHslOptions, shouldAutoScroll }) => {
680
- const messagesEndRef = useRef4(null);
681
- const containerRef = useRef4(null);
682
- const [unreadCount, setUnreadCount] = useState4(0);
683
- const [lastReadMessageIndex, setLastReadMessageIndex] = useState4(0);
684
- const [wasAtBottom, setWasAtBottom] = useState4(true);
685
- const isAtBottom = () => {
686
- if (!containerRef.current) return true;
687
- const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
688
- return Math.abs(scrollHeight - clientHeight - scrollTop) < SCROLL_THRESHOLD_PX;
689
- };
690
- const handleScroll = () => {
691
- const atBottom = isAtBottom();
692
- setWasAtBottom(atBottom);
693
- if (atBottom && unreadCount > 0) {
694
- setUnreadCount(0);
695
- setLastReadMessageIndex(messages.length - 1);
696
- }
697
- };
698
- const scrollToBottom = () => {
699
- if (messagesEndRef.current) {
700
- messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
701
- }
702
- setUnreadCount(0);
703
- setLastReadMessageIndex(messages.length - 1);
704
- setWasAtBottom(true);
705
- };
706
- useEffect3(() => {
707
- if (messages.length === 0) return;
708
- const shouldScroll = shouldAutoScroll || wasAtBottom;
709
- if (shouldScroll) {
710
- setTimeout(() => {
711
- if (!isAtBottom()) {
712
- if (messagesEndRef.current) {
713
- messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
714
- }
715
- }
716
- setWasAtBottom(true);
717
- }, 0);
718
- setUnreadCount(0);
719
- setLastReadMessageIndex(messages.length - 1);
720
- } else {
721
- const newUnreadCount = messages.length - 1 - lastReadMessageIndex;
722
- setUnreadCount(newUnreadCount);
723
- }
724
- }, [messages, shouldAutoScroll, wasAtBottom, lastReadMessageIndex]);
725
- return /* @__PURE__ */ jsxs4(Fragment2, { children: [
726
- /* @__PURE__ */ jsxs4("div", { ref: containerRef, className: Messages_default.messagesContainer, onScroll: handleScroll, children: [
727
- messages.map((msg, index) => /* @__PURE__ */ jsx5(
728
- Message_default2,
729
- {
730
- username: msg.username,
731
- message: msg.message,
732
- stringToHslOptions
733
- },
734
- index
735
- )),
736
- /* @__PURE__ */ jsx5("div", { ref: messagesEndRef })
737
- ] }),
738
- unreadCount > 0 && /* @__PURE__ */ jsxs4("button", { className: Messages_default.newMessagesButton, onClick: scrollToBottom, children: [
739
- unreadCount,
740
- " new message",
741
- unreadCount !== 1 ? "s" : ""
742
- ] })
743
- ] });
744
139
  };
745
140
 
746
- // esbuild-css-modules-plugin-ns-js::src/chat-ui/components/ChatPanel/TextChatUIComponent.module.css:injector.js
747
- var content5 = globalThis['__css-content-9df4d1565589ce63a22b21cf2f9d3ed9__'];
748
- var digest5 = globalThis['__css-digest-9df4d1565589ce63a22b21cf2f9d3ed9__'];
749
- var inject5 = () => {
750
- setTimeout(() => {
751
- if (!globalThis.document) {
752
- return;
753
- }
754
- let root = globalThis.document.querySelector("head");
755
- if (root && root.shadowRoot) {
756
- root = root.shadowRoot;
757
- }
758
- if (!root) {
759
- root = globalThis.document.head;
760
- }
761
- let container = root.querySelector("#_" + digest5);
762
- if (!container) {
763
- container = globalThis.document.createElement("style");
764
- container.id = "_" + digest5;
765
- const text = globalThis.document.createTextNode(content5);
766
- container.appendChild(text);
767
- root.appendChild(container);
768
- }
769
- }, 0);
141
+ // src/DefaultVirtualJoystickPlugin.ts
142
+ import { VirtualJoystick } from "@mml-io/3d-web-client-core";
143
+ var DefaultVirtualJoystickPlugin = class {
144
+ constructor(options = {}) {
145
+ this.options = options;
146
+ }
147
+ wrapper = null;
148
+ joystick = null;
149
+ mount(container, client) {
150
+ this.wrapper = document.createElement("div");
151
+ container.appendChild(this.wrapper);
152
+ this.joystick = new VirtualJoystick(this.wrapper, {
153
+ radius: this.options.radius ?? 70,
154
+ innerRadius: this.options.innerRadius ?? 20,
155
+ mouseSupport: this.options.mouseSupport ?? false
156
+ });
157
+ client.setAdditionalInputProvider(this.joystick);
158
+ }
159
+ dispose() {
160
+ var _a;
161
+ (_a = this.wrapper) == null ? void 0 : _a.remove();
162
+ this.wrapper = null;
163
+ this.joystick = null;
164
+ }
770
165
  };
771
166
 
772
- // src/chat-ui/components/ChatPanel/TextChatUIComponent.module.css
773
- var TextChatUIComponent_default = new Proxy({
774
- "closeButton": "TextChatUIComponent-module__closeButton_gFDdcW__0261",
775
- "controls": "TextChatUIComponent-module__controls_gFDdcW__0261",
776
- "fadeIn": "TextChatUIComponent-module__fadeIn_gFDdcW__0261",
777
- "fadeOut": "TextChatUIComponent-module__fadeOut_gFDdcW__0261",
778
- "messagesWrapper": "TextChatUIComponent-module__messagesWrapper_gFDdcW__0261",
779
- "openTab": "TextChatUIComponent-module__openTab_gFDdcW__0261",
780
- "stickyButton": "TextChatUIComponent-module__stickyButton_gFDdcW__0261",
781
- "stickyButtonEnabled": "TextChatUIComponent-module__stickyButtonEnabled_gFDdcW__0261",
782
- "stickyButtonFadeOut": "TextChatUIComponent-module__stickyButtonFadeOut_gFDdcW__0261",
783
- "textChat": "TextChatUIComponent-module__textChat_gFDdcW__0261",
784
- "textChatUi": "TextChatUIComponent-module__textChatUi_gFDdcW__0261",
785
- "uiHover": "TextChatUIComponent-module__uiHover_gFDdcW__0261"
786
- }, {
787
- get: function(source, key) {
788
- inject5();
789
- return source[key];
790
- }
791
- });
792
-
793
- // src/chat-ui/components/ChatPanel/TextChatUIComponent.tsx
794
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
795
- var MAX_MESSAGES = 50;
796
- var SECONDS_TO_FADE_OUT = 6;
797
- var AUTO_SCROLL_RESET_DELAY_MS = 100;
798
- var ChatUIComponent = (props, ref) => {
799
- const visibleByDefault = props.visibleByDefault ?? true;
800
- const [messages, setMessages] = useState5([]);
801
- const [isVisible, setIsVisible] = useState5(visibleByDefault);
802
- const [isSticky, setSticky] = useState5(visibleByDefault);
803
- const [isFocused, setIsFocused] = useState5(false);
804
- const [isOpenHovered, setOpenHovered] = useState5(false);
805
- const [shouldAutoScroll, setShouldAutoScroll] = useState5(false);
806
- const [panelStyle, setPanelStyle] = useState5(TextChatUIComponent_default.fadeOut);
807
- const [stickyStyle, setStickyStyle] = useState5(TextChatUIComponent_default.stickyButton);
808
- const stickyButtonRef = useRef5(null);
809
- const closeButtonRef = useRef5(null);
810
- const hideTimeoutRef = useRef5(null);
811
- const inputBoxRef = useRef5(null);
812
- const startHideTimeout = useCallback2(() => {
813
- if (hideTimeoutRef.current) clearTimeout(hideTimeoutRef.current);
814
- hideTimeoutRef.current = setTimeout(() => {
815
- if (isVisible) {
816
- setIsVisible(false);
817
- }
818
- }, SECONDS_TO_FADE_OUT * 1e3);
819
- }, [isVisible]);
820
- const handleKeyDown = useCallback2(
821
- (e) => {
822
- if (e.key === "Enter") {
823
- if (!isVisible) setIsVisible(true);
824
- setIsFocused(true);
825
- if (hideTimeoutRef.current) clearTimeout(hideTimeoutRef.current);
826
- if (inputBoxRef.current) inputBoxRef.current.focusInput();
827
- }
828
- },
829
- [isVisible]
830
- );
831
- const handleBlur = useCallback2(() => {
832
- if (isFocused) setIsFocused(false);
833
- startHideTimeout();
834
- if (closeButtonRef.current) closeButtonRef.current.focus();
835
- }, [isFocused, startHideTimeout]);
836
- const hide = () => {
837
- setIsVisible(false);
838
- setIsFocused(false);
839
- if (hideTimeoutRef.current) clearTimeout(hideTimeoutRef.current);
840
- };
841
- const handleRootClick = (e) => {
842
- e.stopPropagation();
843
- setOpenHovered(true);
844
- if (!isVisible) setIsVisible(true);
845
- };
846
- const handleStickyButton = (e) => {
847
- e.stopPropagation();
848
- setSticky(!isSticky);
849
- };
850
- const handleWheel = (e) => {
851
- e.stopPropagation();
852
- };
853
- const handleMouseLeave = () => {
854
- setOpenHovered(false);
855
- if (!isFocused && !isSticky && isVisible) {
856
- startHideTimeout();
857
- }
858
- };
859
- const handleMouseEnter = () => {
860
- setOpenHovered(true);
861
- if (!isVisible) setIsVisible(true);
862
- };
863
- const chatPanelRef = useClickOutside(() => {
864
- if (isFocused) {
865
- setIsFocused(false);
866
- startHideTimeout();
867
- }
868
- });
869
- useEffect4(() => {
870
- if (isVisible && isSticky) {
871
- if (chatPanelRef.current) chatPanelRef.current.style.zIndex = "100";
872
- }
873
- setPanelStyle(isVisible || isFocused || isSticky ? TextChatUIComponent_default.fadeIn : TextChatUIComponent_default.fadeOut);
874
- setStickyStyle(
875
- isSticky ? TextChatUIComponent_default.stickyButtonEnabled : isOpenHovered ? TextChatUIComponent_default.stickyButton : TextChatUIComponent_default.stickyButtonFadeOut
876
- );
877
- if (chatPanelRef.current && chatPanelRef.current.style.zIndex !== "100") {
878
- setTimeout(() => {
879
- if (chatPanelRef.current) chatPanelRef.current.style.zIndex = "100";
880
- }, 2e3);
881
- }
882
- }, [isVisible, isSticky, isFocused, chatPanelRef, isOpenHovered]);
883
- const appendMessages = (username, message) => {
884
- setMessages((prev) => {
885
- const newMessages = [...prev, { username, message }];
886
- return newMessages.length > MAX_MESSAGES ? newMessages.slice(-MAX_MESSAGES) : newMessages;
167
+ // src/WorldConnection.ts
168
+ import {
169
+ experienceProtocolToDeltaNetSubProtocol,
170
+ FROM_CLIENT_CHAT_MESSAGE_TYPE,
171
+ FROM_SERVER_CHAT_MESSAGE_TYPE,
172
+ FROM_SERVER_BROADCAST_MESSAGE_TYPE,
173
+ FROM_SERVER_SESSION_CONFIG_MESSAGE_TYPE,
174
+ FROM_SERVER_WORLD_CONFIG_MESSAGE_TYPE,
175
+ parseServerBroadcastMessage,
176
+ parseServerChatMessage,
177
+ parseSessionConfigPayload,
178
+ parseWorldConfigPayload
179
+ } from "@mml-io/3d-web-experience-protocol";
180
+ import {
181
+ UserNetworkingClient,
182
+ WebsocketStatus,
183
+ DeltaNetServerErrors
184
+ } from "@mml-io/3d-web-user-networking";
185
+ var MAX_CHAT_HISTORY = 100;
186
+ var WorldConnection = class {
187
+ client;
188
+ otherUsers = /* @__PURE__ */ new Map();
189
+ chatHistory = [];
190
+ eventListeners = [];
191
+ myConnectionId = null;
192
+ connected = false;
193
+ username;
194
+ connectPromise;
195
+ resolveConnect;
196
+ rejectConnect;
197
+ connectSettled = false;
198
+ worldConfigResolve = null;
199
+ worldConfigPromise = null;
200
+ latestWorldConfig = null;
201
+ constructor(config) {
202
+ this.connectPromise = new Promise((resolve, reject) => {
203
+ this.resolveConnect = resolve;
204
+ this.rejectConnect = reject;
887
205
  });
888
- };
889
- useImperativeHandle2(ref, () => ({
890
- addMessage: (username, message) => {
891
- appendMessages(username, message);
892
- if (!isVisible) setIsVisible(true);
893
- startHideTimeout();
894
- }
895
- }));
896
- const handleSendMessage = (message) => {
897
- setShouldAutoScroll(true);
898
- props.sendMessageToServer(message);
899
- setTimeout(() => setShouldAutoScroll(false), AUTO_SCROLL_RESET_DELAY_MS);
900
- };
901
- const setFocus = () => setIsFocused(true);
902
- useEffect4(() => {
903
- window.addEventListener("keydown", handleKeyDown, false);
904
- window.addEventListener("blur", handleBlur, false);
905
- return () => {
906
- window.removeEventListener("keydown", handleKeyDown, false);
907
- window.removeEventListener("blur", handleBlur, false);
206
+ this.connectPromise.catch(() => {
207
+ });
208
+ const initialUserState = config.initialUserState ?? {
209
+ userId: "",
210
+ username: null,
211
+ characterDescription: null,
212
+ colors: null
908
213
  };
909
- }, [handleBlur, handleKeyDown]);
910
- return /* @__PURE__ */ jsxs5("div", { className: TextChatUIComponent_default.textChatUi, children: [
911
- /* @__PURE__ */ jsxs5(
912
- "div",
214
+ this.username = initialUserState.username;
215
+ const initialPosition = config.initialPosition ?? { x: 0, y: 0, z: 0 };
216
+ const initialRotation = config.initialRotation ?? { eulerY: 0 };
217
+ this.client = new UserNetworkingClient(
913
218
  {
914
- className: TextChatUIComponent_default.uiHover,
915
- onMouseEnter: handleMouseEnter,
916
- onMouseLeave: handleMouseLeave,
917
- onClick: handleRootClick,
918
- children: [
919
- /* @__PURE__ */ jsx6("div", { className: TextChatUIComponent_default.openTab, onClick: hide, children: /* @__PURE__ */ jsx6("img", { src: `data:image/svg+xml;utf8,${encodeURIComponent(Chat_default)}` }) }),
920
- /* @__PURE__ */ jsx6("div", { ref: stickyButtonRef, className: stickyStyle, onClick: handleStickyButton, children: /* @__PURE__ */ jsx6("img", { src: `data:image/svg+xml;utf8,${encodeURIComponent(Pin_default)}` }) })
921
- ]
922
- }
923
- ),
924
- /* @__PURE__ */ jsxs5(
925
- "div",
926
- {
927
- ref: chatPanelRef,
928
- style: { zIndex: -1 },
929
- id: "text-chat-wrapper",
930
- className: `${TextChatUIComponent_default.textChat} ${panelStyle}`,
931
- onWheel: handleWheel,
932
- children: [
933
- /* @__PURE__ */ jsx6("div", { className: TextChatUIComponent_default.messagesWrapper, children: /* @__PURE__ */ jsx6(
934
- Messages,
935
- {
936
- messages,
937
- stringToHslOptions: props.stringToHslOptions,
938
- shouldAutoScroll
219
+ url: config.url,
220
+ sessionToken: config.sessionToken,
221
+ websocketFactory: config.websocketFactory,
222
+ statusUpdateCallback: (status) => {
223
+ if (status === WebsocketStatus.Connected) {
224
+ this.connected = true;
225
+ console.log("[world] Connected to server");
226
+ if (!this.connectSettled) {
227
+ this.connectSettled = true;
228
+ this.resolveConnect();
939
229
  }
940
- ) }),
941
- /* @__PURE__ */ jsx6(
942
- InputBox,
943
- {
944
- ref: inputBoxRef,
945
- onSendMessage: handleSendMessage,
946
- hide,
947
- setFocus
230
+ this.emitEvent({ type: "connected" });
231
+ } else if (status === WebsocketStatus.Disconnected || status === WebsocketStatus.Reconnecting) {
232
+ this.connected = false;
233
+ this.otherUsers.clear();
234
+ this.myConnectionId = null;
235
+ this.latestWorldConfig = null;
236
+ this.worldConfigPromise = null;
237
+ this.worldConfigResolve = null;
238
+ console.log(
239
+ `[world] ${status === WebsocketStatus.Disconnected ? "Disconnected from" : "Reconnecting to"} server`
240
+ );
241
+ if (status === WebsocketStatus.Disconnected && !this.connectSettled) {
242
+ this.connectSettled = true;
243
+ this.client.stop();
244
+ this.rejectConnect(
245
+ new Error("WebSocket disconnected before connection was established")
246
+ );
247
+ }
248
+ if (status === WebsocketStatus.Reconnecting) {
249
+ this.emitEvent({ type: "reconnecting" });
250
+ }
251
+ this.emitEvent({ type: "disconnected" });
252
+ }
253
+ },
254
+ assignedIdentity: (connectionId) => {
255
+ this.myConnectionId = connectionId;
256
+ console.log(`[world] Assigned connection ID: ${connectionId}`);
257
+ this.emitEvent({ type: "identity_assigned", connectionId });
258
+ },
259
+ onServerError: (error) => {
260
+ console.error(`[world] Server error (${error.errorType}): ${error.message}`);
261
+ this.emitEvent({
262
+ type: "server_error",
263
+ errorType: error.errorType,
264
+ message: error.message
265
+ });
266
+ if (!this.connectSettled) {
267
+ this.connectSettled = true;
268
+ switch (error.errorType) {
269
+ case DeltaNetServerErrors.USER_AUTHENTICATION_FAILED_ERROR_TYPE:
270
+ this.rejectConnect(new Error(`Authentication failed: ${error.message}`));
271
+ break;
272
+ case DeltaNetServerErrors.USER_NETWORKING_CONNECTION_LIMIT_REACHED_ERROR_TYPE:
273
+ this.rejectConnect(new Error(`Connection limit reached: ${error.message}`));
274
+ break;
275
+ case DeltaNetServerErrors.USER_NETWORKING_SERVER_SHUTDOWN_ERROR_TYPE:
276
+ console.error("[world] Server shutting down");
277
+ this.rejectConnect(new Error(`Server shutting down: ${error.message}`));
278
+ break;
279
+ default:
280
+ this.rejectConnect(new Error(error.message));
281
+ }
282
+ } else {
283
+ if (error.errorType === DeltaNetServerErrors.USER_NETWORKING_SERVER_SHUTDOWN_ERROR_TYPE) {
284
+ console.error("[world] Server shutting down");
285
+ }
286
+ }
287
+ },
288
+ onCustomMessage: (customType, contents) => {
289
+ if (customType === FROM_SERVER_CHAT_MESSAGE_TYPE) {
290
+ const parsed = parseServerChatMessage(contents);
291
+ if (parsed instanceof Error) {
292
+ console.error("[world] Invalid chat message:", parsed.message);
293
+ return;
294
+ }
295
+ let username;
296
+ if (parsed.fromConnectionId === this.myConnectionId) {
297
+ username = this.username ?? `User ${parsed.fromConnectionId}`;
298
+ } else {
299
+ const user = this.otherUsers.get(parsed.fromConnectionId);
300
+ username = (user == null ? void 0 : user.username) ?? `User ${parsed.fromConnectionId}`;
301
+ }
302
+ const chatMsg = {
303
+ fromConnectionId: parsed.fromConnectionId,
304
+ userId: parsed.userId,
305
+ username,
306
+ message: parsed.message,
307
+ timestamp: Date.now()
308
+ };
309
+ this.chatHistory.push(chatMsg);
310
+ if (this.chatHistory.length > MAX_CHAT_HISTORY) {
311
+ this.chatHistory.shift();
312
+ }
313
+ console.log(`[chat] ${username}: ${parsed.message}`);
314
+ this.emitEvent({ type: "chat", message: chatMsg });
315
+ } else if (customType === FROM_SERVER_SESSION_CONFIG_MESSAGE_TYPE) {
316
+ const parsedConfig = parseSessionConfigPayload(contents);
317
+ if (parsedConfig instanceof Error) {
318
+ console.error("[world] Invalid session config message:", parsedConfig.message);
319
+ } else {
320
+ console.log("[world] Received session config from server");
321
+ this.emitEvent({ type: "session_config", config: parsedConfig });
322
+ }
323
+ } else if (customType === FROM_SERVER_WORLD_CONFIG_MESSAGE_TYPE) {
324
+ const parsedConfig = parseWorldConfigPayload(contents);
325
+ if (parsedConfig instanceof Error) {
326
+ console.error("[world] Invalid world config message:", parsedConfig.message);
327
+ } else {
328
+ console.log("[world] Received world config from server");
329
+ this.latestWorldConfig = parsedConfig;
330
+ if (this.worldConfigResolve) {
331
+ this.worldConfigResolve(parsedConfig);
332
+ this.worldConfigResolve = null;
333
+ }
334
+ this.emitEvent({ type: "world_config", config: parsedConfig });
335
+ }
336
+ } else if (customType === FROM_SERVER_BROADCAST_MESSAGE_TYPE) {
337
+ const broadcastMessage = parseServerBroadcastMessage(contents);
338
+ if (broadcastMessage instanceof Error) {
339
+ console.error(`[world] Invalid server broadcast message: ${contents}`);
340
+ } else {
341
+ console.log(
342
+ `[world] Server broadcast (${broadcastMessage.broadcastType}):`,
343
+ broadcastMessage.payload
344
+ );
345
+ this.emitEvent({
346
+ type: "server_broadcast",
347
+ broadcastType: broadcastMessage.broadcastType,
348
+ payload: broadcastMessage.payload
349
+ });
350
+ }
351
+ } else {
352
+ console.warn(`[world] Unrecognised custom message type ${customType}`);
353
+ }
354
+ },
355
+ resolveProtocol: experienceProtocolToDeltaNetSubProtocol,
356
+ onUpdate: (update) => {
357
+ var _a, _b, _c, _d, _e, _f, _g, _h;
358
+ for (const id of update.removedConnectionIds) {
359
+ const leaving = this.otherUsers.get(id);
360
+ this.otherUsers.delete(id);
361
+ if (id !== this.myConnectionId) {
362
+ this.emitEvent({
363
+ type: "user_left",
364
+ connectionId: id,
365
+ userId: (leaving == null ? void 0 : leaving.userId) ?? "",
366
+ username: (leaving == null ? void 0 : leaving.username) ?? null
367
+ });
368
+ }
369
+ }
370
+ for (const [id, added] of update.addedConnectionIds) {
371
+ if (id === this.myConnectionId) continue;
372
+ const position = {
373
+ x: added.components.position.x,
374
+ y: added.components.position.y,
375
+ z: added.components.position.z
376
+ };
377
+ const userState = added.userState;
378
+ this.otherUsers.set(id, {
379
+ userId: (userState == null ? void 0 : userState.userId) ?? "",
380
+ username: (userState == null ? void 0 : userState.username) ?? null,
381
+ characterDescription: (userState == null ? void 0 : userState.characterDescription) ?? null,
382
+ colors: (userState == null ? void 0 : userState.colors) ?? null,
383
+ position
384
+ });
385
+ this.emitEvent({
386
+ type: "user_joined",
387
+ connectionId: id,
388
+ userId: (userState == null ? void 0 : userState.userId) ?? "",
389
+ username: (userState == null ? void 0 : userState.username) ?? null,
390
+ position
391
+ });
392
+ }
393
+ for (const [id, updated] of update.updatedUsers) {
394
+ if (id === this.myConnectionId) continue;
395
+ const existing = this.otherUsers.get(id);
396
+ if (existing) {
397
+ if (((_a = updated.userState) == null ? void 0 : _a.userId) !== void 0) {
398
+ existing.userId = updated.userState.userId;
399
+ }
400
+ if (((_b = updated.userState) == null ? void 0 : _b.username) !== void 0) {
401
+ existing.username = updated.userState.username;
402
+ }
403
+ if (((_c = updated.userState) == null ? void 0 : _c.characterDescription) !== void 0) {
404
+ existing.characterDescription = updated.userState.characterDescription;
405
+ }
406
+ if (((_d = updated.userState) == null ? void 0 : _d.colors) !== void 0) {
407
+ existing.colors = updated.userState.colors;
408
+ }
409
+ existing.position = {
410
+ x: updated.components.position.x,
411
+ y: updated.components.position.y,
412
+ z: updated.components.position.z
413
+ };
414
+ } else {
415
+ this.otherUsers.set(id, {
416
+ userId: ((_e = updated.userState) == null ? void 0 : _e.userId) ?? "",
417
+ username: ((_f = updated.userState) == null ? void 0 : _f.username) ?? null,
418
+ characterDescription: ((_g = updated.userState) == null ? void 0 : _g.characterDescription) ?? null,
419
+ colors: ((_h = updated.userState) == null ? void 0 : _h.colors) ?? null,
420
+ position: {
421
+ x: updated.components.position.x,
422
+ y: updated.components.position.y,
423
+ z: updated.components.position.z
424
+ }
425
+ });
948
426
  }
949
- )
950
- ]
427
+ }
428
+ this.emitEvent({ type: "network_update", update });
429
+ }
430
+ },
431
+ initialUserState,
432
+ {
433
+ position: initialPosition,
434
+ rotation: initialRotation,
435
+ state: 0
951
436
  }
952
- )
953
- ] });
954
- };
955
-
956
- // src/chat-ui/TextChatUI.tsx
957
- import { jsx as jsx7 } from "react/jsx-runtime";
958
- var DEFAULT_HUE_RANGES = [[10, 350]];
959
- var DEFAULT_SATURATION_RANGES = [[60, 100]];
960
- var DEFAULT_LIGHTNESS_RANGES = [[65, 75]];
961
- var DEFAULT_HSL_OPTIONS = {
962
- hueThresholds: DEFAULT_HUE_RANGES,
963
- saturationThresholds: DEFAULT_SATURATION_RANGES,
964
- lightnessThresholds: DEFAULT_LIGHTNESS_RANGES
965
- };
966
- var ForwardedChatUIComponent = forwardRef3(ChatUIComponent);
967
- var TextChatUI = class {
968
- constructor(config) {
969
- this.config = config;
970
- this.config.holderElement.appendChild(this.wrapper);
971
- this.root = createRoot2(this.wrapper);
437
+ );
972
438
  }
973
- root;
974
- appRef = createRef();
975
- addTextMessage(username, message) {
976
- if (this.appRef.current) {
977
- this.appRef.current.addMessage(username, message);
439
+ stop() {
440
+ this.client.stop();
441
+ this.eventListeners = [];
442
+ if (!this.connectSettled) {
443
+ this.connectSettled = true;
444
+ this.rejectConnect(new Error("WorldConnection stopped"));
445
+ }
446
+ this.worldConfigResolve = null;
447
+ this.worldConfigPromise = null;
448
+ }
449
+ /**
450
+ * Returns a promise that settles once for the **initial** connection attempt.
451
+ * It resolves when the first `Connected` status is received, or rejects if
452
+ * the connection fails before being established.
453
+ *
454
+ * This promise does **not** re-settle on reconnection. To react to
455
+ * subsequent connection/disconnection events, use {@link addEventListener}
456
+ * and listen for `"connected"` / `"disconnected"` / `"reconnecting"` events.
457
+ */
458
+ waitForConnection() {
459
+ return this.connectPromise;
460
+ }
461
+ /**
462
+ * Wait for the server to send a world config message.
463
+ *
464
+ * The server pushes this over WebSocket after authentication. If the config
465
+ * has already been received, the returned promise resolves immediately with
466
+ * the cached value (so callers never miss a config that arrived before they
467
+ * called this method).
468
+ *
469
+ * Multiple concurrent callers share a single internal promise that resolves
470
+ * when the config arrives. Each caller gets an independent timeout --- if the
471
+ * timeout fires, that caller receives `null` but other callers with longer
472
+ * timeouts (or the shared promise itself) are unaffected.
473
+ *
474
+ * @returns The world config, or `null` if the timeout expires first.
475
+ */
476
+ waitForWorldConfig(timeoutMs = 1e4) {
477
+ if (this.latestWorldConfig) {
478
+ return Promise.resolve(this.latestWorldConfig);
479
+ }
480
+ if (!this.worldConfigPromise) {
481
+ this.worldConfigPromise = new Promise((resolve) => {
482
+ this.worldConfigResolve = resolve;
483
+ });
978
484
  }
485
+ return new Promise((resolve) => {
486
+ const timer = setTimeout(() => {
487
+ resolve(null);
488
+ }, timeoutMs);
489
+ this.worldConfigPromise.then((config) => {
490
+ clearTimeout(timer);
491
+ resolve(config);
492
+ });
493
+ });
979
494
  }
980
- wrapper = document.createElement("div");
981
- dispose() {
982
- this.root.unmount();
983
- this.wrapper.remove();
984
- }
985
- init() {
986
- flushSync2(
987
- () => this.root.render(
988
- /* @__PURE__ */ jsx7(
989
- ForwardedChatUIComponent,
990
- {
991
- ref: this.appRef,
992
- sendMessageToServer: this.config.sendMessageToServerMethod,
993
- visibleByDefault: this.config.visibleByDefault,
994
- stringToHslOptions: this.config.stringToHslOptions
995
- }
996
- )
997
- )
998
- );
495
+ getWorldConfig() {
496
+ return this.latestWorldConfig;
999
497
  }
1000
- };
1001
-
1002
- // esbuild-css-modules-plugin-ns-js::src/Networked3dWebExperience.module.css:injector.js
1003
- var content6 = globalThis['__css-content-9df4d1565589ce63a22b21cf2f9d3ed9__'];
1004
- var digest6 = globalThis['__css-digest-9df4d1565589ce63a22b21cf2f9d3ed9__'];
1005
- var inject6 = () => {
1006
- setTimeout(() => {
1007
- if (!globalThis.document) {
1008
- return;
498
+ sendUpdate(update) {
499
+ if (this.connected) {
500
+ this.client.sendUpdate(update);
1009
501
  }
1010
- let root = globalThis.document.querySelector("head");
1011
- if (root && root.shadowRoot) {
1012
- root = root.shadowRoot;
502
+ }
503
+ getOtherUsers() {
504
+ const result = [];
505
+ for (const [connectionId, user] of this.otherUsers) {
506
+ result.push({ connectionId, ...user });
1013
507
  }
1014
- if (!root) {
1015
- root = globalThis.document.head;
508
+ return result;
509
+ }
510
+ getConnectionId() {
511
+ return this.myConnectionId;
512
+ }
513
+ getUsername() {
514
+ return this.username;
515
+ }
516
+ isConnected() {
517
+ return this.connected;
518
+ }
519
+ sendChatMessage(message) {
520
+ if (!this.connected) return;
521
+ this.client.sendCustomMessage(FROM_CLIENT_CHAT_MESSAGE_TYPE, JSON.stringify({ message }));
522
+ }
523
+ sendCustomMessage(customType, contents) {
524
+ if (!this.connected) return;
525
+ this.client.sendCustomMessage(customType, contents);
526
+ }
527
+ updateUsername(username) {
528
+ this.username = username;
529
+ this.client.updateUsername(username);
530
+ }
531
+ updateCharacterDescription(characterDescription) {
532
+ this.client.updateCharacterDescription(characterDescription);
533
+ }
534
+ updateColors(colors) {
535
+ this.client.updateColors(colors);
536
+ }
537
+ getChatHistory(since) {
538
+ if (since !== void 0) {
539
+ return this.chatHistory.filter((m) => m.timestamp >= since);
1016
540
  }
1017
- let container = root.querySelector("#_" + digest6);
1018
- if (!container) {
1019
- container = globalThis.document.createElement("style");
1020
- container.id = "_" + digest6;
1021
- const text = globalThis.document.createTextNode(content6);
1022
- container.appendChild(text);
1023
- root.appendChild(container);
541
+ return [...this.chatHistory];
542
+ }
543
+ addEventListener(listener) {
544
+ this.eventListeners.push(listener);
545
+ }
546
+ removeEventListener(listener) {
547
+ const idx = this.eventListeners.indexOf(listener);
548
+ if (idx !== -1) this.eventListeners.splice(idx, 1);
549
+ }
550
+ emitEvent(event) {
551
+ for (const listener of [...this.eventListeners]) {
552
+ listener(event);
1024
553
  }
1025
- }, 0);
554
+ }
1026
555
  };
1027
556
 
1028
- // src/Networked3dWebExperience.module.css
1029
- var Networked3dWebExperience_default = new Proxy({
1030
- "respawnButton": "Networked3dWebExperience-module__respawnButton_7g9l0W__0261"
1031
- }, {
1032
- get: function(source, key) {
1033
- inject6();
1034
- return source[key];
1035
- }
1036
- });
1037
-
1038
557
  // src/Networked3dWebExperienceClient.ts
1039
- function normalizeSpawnConfiguration(spawnConfig) {
1040
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
1041
- return {
1042
- spawnPosition: {
1043
- x: ((_a = spawnConfig == null ? void 0 : spawnConfig.spawnPosition) == null ? void 0 : _a.x) ?? 0,
1044
- y: ((_b = spawnConfig == null ? void 0 : spawnConfig.spawnPosition) == null ? void 0 : _b.y) ?? 0,
1045
- z: ((_c = spawnConfig == null ? void 0 : spawnConfig.spawnPosition) == null ? void 0 : _c.z) ?? 0
1046
- },
1047
- spawnPositionVariance: {
1048
- x: ((_d = spawnConfig == null ? void 0 : spawnConfig.spawnPositionVariance) == null ? void 0 : _d.x) ?? 0,
1049
- y: ((_e = spawnConfig == null ? void 0 : spawnConfig.spawnPositionVariance) == null ? void 0 : _e.y) ?? 0,
1050
- z: ((_f = spawnConfig == null ? void 0 : spawnConfig.spawnPositionVariance) == null ? void 0 : _f.z) ?? 0
1051
- },
1052
- spawnYRotation: (spawnConfig == null ? void 0 : spawnConfig.spawnYRotation) ?? 0,
1053
- respawnTrigger: {
1054
- minX: ((_g = spawnConfig == null ? void 0 : spawnConfig.respawnTrigger) == null ? void 0 : _g.minX) ?? Number.NEGATIVE_INFINITY,
1055
- maxX: ((_h = spawnConfig == null ? void 0 : spawnConfig.respawnTrigger) == null ? void 0 : _h.maxX) ?? Number.POSITIVE_INFINITY,
1056
- minY: ((_i = spawnConfig == null ? void 0 : spawnConfig.respawnTrigger) == null ? void 0 : _i.minY) ?? -100,
1057
- maxY: ((_j = spawnConfig == null ? void 0 : spawnConfig.respawnTrigger) == null ? void 0 : _j.maxY) ?? Number.POSITIVE_INFINITY,
1058
- minZ: ((_k = spawnConfig == null ? void 0 : spawnConfig.respawnTrigger) == null ? void 0 : _k.minZ) ?? Number.NEGATIVE_INFINITY,
1059
- maxZ: ((_l = spawnConfig == null ? void 0 : spawnConfig.respawnTrigger) == null ? void 0 : _l.maxZ) ?? Number.POSITIVE_INFINITY
1060
- },
1061
- enableRespawnButton: (spawnConfig == null ? void 0 : spawnConfig.enableRespawnButton) ?? false
1062
- };
1063
- }
1064
- var Networked3dWebExperienceClient = class {
558
+ var Networked3dWebExperienceClient = class _Networked3dWebExperienceClient extends ClientEventEmitter {
1065
559
  constructor(holderElement, config) {
560
+ super();
1066
561
  this.holderElement = holderElement;
1067
562
  this.config = config;
563
+ this.virtualJoystickPlugin = config.virtualJoystickPlugin === null ? null : config.virtualJoystickPlugin ?? new DefaultVirtualJoystickPlugin();
564
+ this.respawnButtonPlugin = config.respawnButtonPlugin === null ? null : config.respawnButtonPlugin ?? new DefaultRespawnButtonPlugin();
565
+ this.plugins = config.plugins ?? [];
1068
566
  this.element = document.createElement("div");
1069
567
  this.element.style.position = "absolute";
1070
568
  this.element.style.width = "100%";
@@ -1077,11 +575,6 @@ var Networked3dWebExperienceClient = class {
1077
575
  this.element.appendChild(this.canvasHolder);
1078
576
  this.collisionsManager = new CollisionsManager();
1079
577
  this.cameraManager = new CameraManager(this.canvasHolder, this.collisionsManager);
1080
- this.virtualJoystick = new VirtualJoystick(this.element, {
1081
- radius: 70,
1082
- innerRadius: 20,
1083
- mouseSupport: false
1084
- });
1085
578
  this.tweakPane = new TweakPane(
1086
579
  this.canvasHolder,
1087
580
  {
@@ -1094,85 +587,108 @@ var Networked3dWebExperienceClient = class {
1094
587
  const spawnData = getSpawnData(this.spawnConfiguration, true);
1095
588
  const spawnRotation = new Quat().setFromEulerXYZ(spawnData.spawnRotation);
1096
589
  const initialNetworkLoadRef = {};
1097
- this.loadingProgressManager.addLoadingAsset(initialNetworkLoadRef, "network", "network");
1098
- this.networkClient = new UserNetworkingClient(
1099
- {
1100
- url: this.config.userNetworkAddress,
1101
- sessionToken: this.config.sessionToken,
1102
- websocketFactory: (url) => new WebSocket(url, "delta-net-v0.1"),
1103
- statusUpdateCallback: (status) => {
1104
- console.log(`Websocket status: ${status}`);
1105
- if (status === WebsocketStatus.Disconnected || status === WebsocketStatus.Reconnecting) {
1106
- this.characterManager.clear();
1107
- this.remoteUserStates.clear();
1108
- this.clientId = null;
1109
- }
1110
- },
1111
- assignedIdentity: (clientId) => {
1112
- console.log(`Assigned ID: ${clientId}`);
1113
- this.clientId = clientId;
1114
- this.characterManager.setLocalClientId(clientId);
590
+ if (!this.config.waitForWorldConfig) {
591
+ this.loadingProgressManager.addLoadingAsset(initialNetworkLoadRef, "network", "network");
592
+ }
593
+ this.worldConnection = new WorldConnection({
594
+ url: this.config.userNetworkAddress,
595
+ sessionToken: this.config.sessionToken,
596
+ websocketFactory: (url) => new WebSocket(url, [...experienceClientSubProtocols]),
597
+ initialUserState: { userId: "", username: null, characterDescription: null, colors: null },
598
+ initialPosition: spawnData.spawnPosition,
599
+ initialRotation: { eulerY: 2 * Math.atan2(spawnRotation.y, spawnRotation.w) }
600
+ });
601
+ this.worldConnection.addEventListener((event) => {
602
+ var _a, _b;
603
+ switch (event.type) {
604
+ case "disconnected":
605
+ case "reconnecting":
606
+ this.characterManager.clear();
607
+ this.remoteUserStates.clear();
608
+ this.connectionId = null;
609
+ this.pendingChatMessages = [];
610
+ break;
611
+ case "identity_assigned":
612
+ console.log(`Assigned ID: ${event.connectionId}`);
613
+ this.connectionId = event.connectionId;
614
+ this.characterManager.setLocalConnectionId(event.connectionId);
1115
615
  if (this.initialLoadCompleted) {
1116
- const spawnData2 = getSpawnData(this.spawnConfiguration, true);
1117
- this.spawnCharacter(spawnData2);
1118
- } else {
616
+ this.spawnCharacter(getSpawnData(this.spawnConfiguration, true));
617
+ } else if (!this.config.waitForWorldConfig) {
1119
618
  this.loadingProgressManager.completedLoadingAsset(initialNetworkLoadRef);
1120
619
  }
1121
- },
1122
- onUpdate: (update) => {
1123
- this.onNetworkUpdate(update);
1124
- },
1125
- onServerError: (error) => {
1126
- switch (error.errorType) {
1127
- case DeltaNetV01ServerErrors.USER_AUTHENTICATION_FAILED_ERROR_TYPE:
1128
- this.disposeWithError(error.message);
1129
- break;
1130
- case DeltaNetV01ServerErrors.USER_NETWORKING_CONNECTION_LIMIT_REACHED_ERROR_TYPE:
1131
- this.disposeWithError(error.message);
1132
- break;
1133
- case DeltaNetV01ServerErrors.USER_NETWORKING_SERVER_SHUTDOWN_ERROR_TYPE:
1134
- this.disposeWithError(error.message || "Server shutdown");
1135
- break;
1136
- default:
1137
- console.error(`Unhandled server error: ${error.message}`);
1138
- this.disposeWithError(error.message);
1139
- }
1140
- },
1141
- onCustomMessage: (customType, contents) => {
1142
- var _a, _b;
1143
- if (customType === SERVER_BROADCAST_MESSAGE_TYPE) {
1144
- const serverBroadcastMessage = parseServerBroadcastMessage(contents);
1145
- if (serverBroadcastMessage instanceof Error) {
1146
- console.error(`Invalid server broadcast message: ${contents}`);
1147
- } else {
1148
- (_b = (_a = this.config).onServerBroadcast) == null ? void 0 : _b.call(_a, serverBroadcastMessage);
1149
- }
1150
- } else if (customType === FROM_SERVER_CHAT_MESSAGE_TYPE) {
1151
- const serverChatMessage = parseServerChatMessage(contents);
1152
- if (serverChatMessage instanceof Error) {
1153
- console.error(`Invalid server chat message: ${contents}`);
1154
- } else {
1155
- this.handleChatMessage(serverChatMessage.fromUserId, serverChatMessage.message);
620
+ break;
621
+ case "server_error":
622
+ this.disposeWithError(event.message);
623
+ break;
624
+ case "session_config":
625
+ if (event.config.authToken !== void 0) {
626
+ this.mmlAuthToken = event.config.authToken;
627
+ if (this.rendererInitialized && this.config.mmlDocuments) {
628
+ this.renderer.setMMLConfiguration(this.config.mmlDocuments, this.mmlAuthToken);
1156
629
  }
1157
- } else {
1158
- console.warn(`Did not recognize custom message type ${customType}`);
1159
630
  }
631
+ break;
632
+ case "world_config": {
633
+ if (this.worldConfigTimeoutId !== null) {
634
+ clearTimeout(this.worldConfigTimeoutId);
635
+ this.worldConfigTimeoutId = null;
636
+ }
637
+ const parsedConfig = event.config;
638
+ const updatable = {};
639
+ if (parsedConfig.enableChat !== void 0) {
640
+ updatable.enableChat = parsedConfig.enableChat;
641
+ }
642
+ if (parsedConfig.mmlDocuments !== void 0) {
643
+ updatable.mmlDocuments = parsedConfig.mmlDocuments;
644
+ }
645
+ if (parsedConfig.environmentConfiguration !== void 0) {
646
+ updatable.environmentConfiguration = parsedConfig.environmentConfiguration;
647
+ }
648
+ if (parsedConfig.spawnConfiguration !== void 0) {
649
+ updatable.spawnConfiguration = parsedConfig.spawnConfiguration;
650
+ }
651
+ if (parsedConfig.avatarConfiguration !== void 0) {
652
+ updatable.avatarConfiguration = parsedConfig.avatarConfiguration;
653
+ }
654
+ if (parsedConfig.allowCustomDisplayName !== void 0) {
655
+ updatable.allowCustomDisplayName = parsedConfig.allowCustomDisplayName;
656
+ }
657
+ if (parsedConfig.enableTweakPane !== void 0) {
658
+ updatable.enableTweakPane = parsedConfig.enableTweakPane;
659
+ }
660
+ if (parsedConfig.allowOrbitalCamera !== void 0) {
661
+ updatable.allowOrbitalCamera = parsedConfig.allowOrbitalCamera;
662
+ }
663
+ if (parsedConfig.postProcessingEnabled !== void 0) {
664
+ updatable.postProcessingEnabled = parsedConfig.postProcessingEnabled;
665
+ }
666
+ if (parsedConfig.hud !== void 0) {
667
+ updatable.hud = parsedConfig.hud;
668
+ }
669
+ this.updateConfig(updatable);
670
+ this.worldConfigReceived = true;
671
+ this.checkReadyForInitialLoad();
672
+ break;
1160
673
  }
1161
- },
1162
- {
1163
- username: null,
1164
- characterDescription: null,
1165
- colors: null
1166
- },
1167
- {
1168
- position: spawnData.spawnPosition,
1169
- rotation: {
1170
- quaternionY: spawnRotation.y,
1171
- quaternionW: spawnRotation.w
1172
- },
1173
- state: 0
674
+ case "server_broadcast":
675
+ (_b = (_a = this.config).onServerBroadcast) == null ? void 0 : _b.call(_a, {
676
+ broadcastType: event.broadcastType,
677
+ payload: event.payload
678
+ });
679
+ break;
680
+ case "chat":
681
+ this.handleChatMessage(
682
+ event.message.fromConnectionId,
683
+ event.message.userId,
684
+ event.message.message
685
+ );
686
+ break;
687
+ case "network_update":
688
+ this.onNetworkUpdate(event.update);
689
+ break;
1174
690
  }
1175
- );
691
+ });
1176
692
  if (this.config.allowOrbitalCamera) {
1177
693
  this.keyInputManager.createKeyBinding(Key.C, () => {
1178
694
  if (document.activeElement === document.body) {
@@ -1185,34 +701,41 @@ var Networked3dWebExperienceClient = class {
1185
701
  collisionsManager: this.collisionsManager,
1186
702
  cameraManager: this.cameraManager,
1187
703
  keyInputManager: this.keyInputManager,
1188
- virtualJoystick: this.virtualJoystick,
1189
704
  remoteUserStates: this.remoteUserStates,
1190
705
  sendUpdate: (characterState) => {
1191
- this.networkClient.sendUpdate(characterState);
706
+ this.worldConnection.sendUpdate(characterState);
1192
707
  },
1193
708
  sendLocalCharacterColors: (colors) => {
1194
- this.networkClient.updateColors(colors);
709
+ this.worldConnection.updateColors(colors);
1195
710
  },
1196
711
  spawnConfiguration: this.spawnConfiguration,
1197
712
  characterControllerValues: this.characterControllerValues,
1198
- characterResolve: (clientId) => {
1199
- return this.resolveCharacterData(clientId);
713
+ characterResolve: (connectionId) => {
714
+ return this.resolveCharacterData(connectionId);
1200
715
  },
1201
716
  updateURLLocation: this.config.updateURLLocation !== false
1202
717
  });
1203
- if (this.spawnConfiguration.enableRespawnButton) {
1204
- this.respawnButton = this.createRespawnButton();
1205
- this.element.appendChild(this.respawnButton);
1206
- }
1207
718
  this.loadingScreen = new LoadingScreen(this.loadingProgressManager, this.config.loadingScreen);
1208
719
  this.element.append(this.loadingScreen.element);
1209
720
  this.loadingProgressManager.addProgressCallback(() => {
721
+ var _a;
1210
722
  const [, completed] = this.loadingProgressManager.toRatio();
1211
723
  if (completed && !this.initialLoadCompleted) {
1212
724
  this.initialLoadCompleted = true;
1213
- this.connectToTextChat();
1214
- this.mountAvatarSelectionUI();
1215
- this.spawnCharacter(spawnData);
725
+ if (this.connectionId === null || this.disposed) return;
726
+ for (const plugin of this.getAllPlugins()) {
727
+ plugin.mount(this.element, this);
728
+ }
729
+ this.emit("configChanged", this.latestConfig);
730
+ for (const plugin of this.getAllPlugins()) {
731
+ (_a = plugin.onConfigChanged) == null ? void 0 : _a.call(plugin, this.latestConfig);
732
+ }
733
+ this.spawnCharacter(getSpawnData(this.spawnConfiguration, true));
734
+ this.emit("ready");
735
+ for (const chatEvent of this.pendingChatMessages) {
736
+ this.emit("chat", chatEvent);
737
+ }
738
+ this.pendingChatMessages = [];
1216
739
  }
1217
740
  });
1218
741
  registerCustomElementsToWindow(window);
@@ -1223,6 +746,7 @@ var Networked3dWebExperienceClient = class {
1223
746
  spawnSun: true,
1224
747
  enableTweakPane: config.enableTweakPane
1225
748
  };
749
+ const mmlDocumentsForRenderer = this.config.waitForWorldConfig ? {} : this.config.mmlDocuments ?? {};
1226
750
  if (this.config.createRenderer) {
1227
751
  this.renderer = this.config.createRenderer({
1228
752
  targetElement: this.canvasHolder,
@@ -1233,10 +757,15 @@ var Networked3dWebExperienceClient = class {
1233
757
  mmlTargetWindow: window,
1234
758
  mmlTargetElement: document.body,
1235
759
  loadingProgressManager: this.loadingProgressManager,
1236
- mmlDocuments: this.config.mmlDocuments ?? {},
1237
- mmlAuthToken: this.config.authToken ?? null,
760
+ mmlDocuments: mmlDocumentsForRenderer,
761
+ mmlAuthToken: this.mmlAuthToken,
1238
762
  onInitialized: () => {
1239
- this.loadingProgressManager.setInitialLoad(true);
763
+ this.rendererInitialized = true;
764
+ if (this.config.waitForWorldConfig) {
765
+ this.checkReadyForInitialLoad();
766
+ } else {
767
+ this.loadingProgressManager.setInitialLoad(true);
768
+ }
1240
769
  }
1241
770
  });
1242
771
  } else {
@@ -1249,18 +778,29 @@ var Networked3dWebExperienceClient = class {
1249
778
  mmlTargetWindow: window,
1250
779
  mmlTargetElement: document.body,
1251
780
  loadingProgressManager: this.loadingProgressManager,
1252
- mmlDocuments: this.config.mmlDocuments ?? {},
1253
- mmlAuthToken: this.config.authToken ?? null
781
+ mmlDocuments: mmlDocumentsForRenderer,
782
+ mmlAuthToken: this.mmlAuthToken
1254
783
  });
1255
- this.loadingProgressManager.setInitialLoad(true);
784
+ this.rendererInitialized = true;
785
+ if (this.config.waitForWorldConfig) {
786
+ this.checkReadyForInitialLoad();
787
+ } else {
788
+ this.loadingProgressManager.setInitialLoad(true);
789
+ }
1256
790
  }
1257
791
  if (this.characterManager.localController) {
1258
792
  this.characterManager.setupTweakPane(this.tweakPane);
1259
793
  }
1260
- const resizeObserver = new ResizeObserver(() => {
794
+ this.resizeObserver = new ResizeObserver(() => {
1261
795
  this.renderer.fitContainer();
1262
796
  });
1263
- resizeObserver.observe(this.element);
797
+ this.resizeObserver.observe(this.element);
798
+ if (this.config.hud !== void 0) {
799
+ this.latestConfig.hud = this.config.hud;
800
+ }
801
+ if (this.config.enableChat !== void 0) {
802
+ this.latestConfig.enableChat = this.config.enableChat;
803
+ }
1264
804
  }
1265
805
  element;
1266
806
  canvasHolder;
@@ -1270,22 +810,31 @@ var Networked3dWebExperienceClient = class {
1270
810
  collisionsManager;
1271
811
  characterManager;
1272
812
  keyInputManager = new KeyInputManager();
1273
- virtualJoystick;
1274
- clientId = null;
1275
- networkClient;
813
+ resizeObserver;
814
+ connectionId = null;
815
+ worldConnection;
1276
816
  remoteUserStates = /* @__PURE__ */ new Map();
1277
817
  userProfiles = /* @__PURE__ */ new Map();
1278
- textChatUI = null;
1279
- avatarSelectionUI = null;
818
+ virtualJoystickPlugin;
819
+ respawnButtonPlugin;
820
+ plugins;
1280
821
  tweakPane;
1281
822
  spawnConfiguration;
1282
823
  cameraValues = createDefaultCameraValues();
1283
824
  characterControllerValues = createDefaultCharacterControllerValues();
1284
825
  initialLoadCompleted = false;
826
+ pendingChatMessages = [];
827
+ latestConfig = {};
1285
828
  loadingProgressManager = new LoadingProgressManager();
1286
829
  loadingScreen;
1287
830
  errorScreen;
1288
- respawnButton = null;
831
+ mmlAuthToken = null;
832
+ rendererInitialized = false;
833
+ worldConfigReceived = false;
834
+ worldConfigTimeoutId = null;
835
+ disposed = false;
836
+ static WORLD_CONFIG_TIMEOUT_MS = 1e4;
837
+ static MAX_PENDING_CHAT_MESSAGES = 100;
1289
838
  // Frame timing
1290
839
  currentRequestAnimationFrame = null;
1291
840
  lastUpdateTimeMs = 0;
@@ -1299,6 +848,7 @@ var Networked3dWebExperienceClient = class {
1299
848
  fov: 0
1300
849
  };
1301
850
  updateConfig(config) {
851
+ var _a;
1302
852
  this.config = {
1303
853
  ...this.config,
1304
854
  ...config
@@ -1320,12 +870,6 @@ var Networked3dWebExperienceClient = class {
1320
870
  };
1321
871
  this.renderer.updateConfig(rendererConfigUpdate);
1322
872
  }
1323
- if (this.avatarSelectionUI) {
1324
- if (config.avatarConfiguration) {
1325
- this.avatarSelectionUI.updateAvatarConfig(config.avatarConfiguration);
1326
- }
1327
- this.avatarSelectionUI.updateAllowCustomDisplayName(config.allowCustomDisplayName || false);
1328
- }
1329
873
  if (config.allowOrbitalCamera !== void 0) {
1330
874
  if (config.allowOrbitalCamera === false) {
1331
875
  this.keyInputManager.removeKeyBinding(Key.C);
@@ -1341,27 +885,21 @@ var Networked3dWebExperienceClient = class {
1341
885
  });
1342
886
  }
1343
887
  }
1344
- if (config.enableChat) {
1345
- if (!config.enableChat && this.textChatUI !== null) {
1346
- this.textChatUI.dispose();
1347
- this.textChatUI = null;
1348
- } else {
1349
- this.connectToTextChat();
888
+ if (config.spawnConfiguration !== void 0) {
889
+ this.spawnConfiguration = normalizeSpawnConfiguration(config.spawnConfiguration);
890
+ if (this.characterManager.localController) {
891
+ this.characterManager.localController.updateSpawnConfig(this.spawnConfiguration);
1350
892
  }
1351
893
  }
1352
- this.spawnConfiguration = normalizeSpawnConfiguration(config.spawnConfiguration);
1353
- if (this.characterManager.localController) {
1354
- this.characterManager.localController.updateSpawnConfig(this.spawnConfiguration);
1355
- }
1356
- if (this.spawnConfiguration.enableRespawnButton && !this.respawnButton) {
1357
- this.respawnButton = this.createRespawnButton();
1358
- this.element.appendChild(this.respawnButton);
1359
- } else if (!this.spawnConfiguration.enableRespawnButton && this.respawnButton) {
1360
- this.respawnButton.remove();
1361
- this.respawnButton = null;
1362
- }
1363
894
  if (config.mmlDocuments !== void 0) {
1364
- this.renderer.setMMLConfiguration(config.mmlDocuments, this.config.authToken ?? null);
895
+ this.renderer.setMMLConfiguration(config.mmlDocuments, this.mmlAuthToken);
896
+ }
897
+ this.latestConfig = { ...this.latestConfig, ...config };
898
+ this.emit("configChanged", config);
899
+ if (this.initialLoadCompleted) {
900
+ for (const plugin of this.getAllPlugins()) {
901
+ (_a = plugin.onConfigChanged) == null ? void 0 : _a.call(plugin, config);
902
+ }
1365
903
  }
1366
904
  }
1367
905
  static createFullscreenHolder() {
@@ -1375,125 +913,118 @@ var Networked3dWebExperienceClient = class {
1375
913
  document.body.appendChild(holder);
1376
914
  return holder;
1377
915
  }
1378
- createRespawnButton() {
1379
- const respawnButton = document.createElement("div");
1380
- respawnButton.className = Networked3dWebExperience_default.respawnButton;
1381
- respawnButton.textContent = "RESPAWN";
1382
- respawnButton.addEventListener("click", () => {
1383
- var _a;
1384
- (_a = this.characterManager.localController) == null ? void 0 : _a.resetPosition();
1385
- });
1386
- return respawnButton;
916
+ getAllPlugins() {
917
+ const result = [];
918
+ if (this.virtualJoystickPlugin) result.push(this.virtualJoystickPlugin);
919
+ if (this.respawnButtonPlugin) result.push(this.respawnButtonPlugin);
920
+ result.push(...this.plugins);
921
+ return result;
922
+ }
923
+ checkReadyForInitialLoad() {
924
+ if (this.rendererInitialized && this.worldConfigReceived) {
925
+ if (this.worldConfigTimeoutId !== null) {
926
+ clearTimeout(this.worldConfigTimeoutId);
927
+ this.worldConfigTimeoutId = null;
928
+ }
929
+ this.loadingProgressManager.setInitialLoad(true);
930
+ } else if (this.rendererInitialized && !this.worldConfigReceived) {
931
+ if (this.worldConfigTimeoutId === null) {
932
+ this.worldConfigTimeoutId = setTimeout(() => {
933
+ this.worldConfigTimeoutId = null;
934
+ if (this.disposed) return;
935
+ if (!this.worldConfigReceived) {
936
+ console.warn(
937
+ `World config not received within ${_Networked3dWebExperienceClient.WORLD_CONFIG_TIMEOUT_MS}ms \u2014 proceeding with default configuration`
938
+ );
939
+ this.worldConfigReceived = true;
940
+ this.loadingProgressManager.setInitialLoad(true);
941
+ }
942
+ }, _Networked3dWebExperienceClient.WORLD_CONFIG_TIMEOUT_MS);
943
+ }
944
+ }
1387
945
  }
1388
- resolveCharacterData(clientId) {
1389
- const user = this.userProfiles.get(clientId);
946
+ resolveCharacterData(connectionId) {
947
+ const user = this.userProfiles.get(connectionId);
1390
948
  if (!user) {
1391
- throw new Error(`Failed to resolve user for clientId ${clientId}`);
949
+ throw new Error(`Failed to resolve user for connectionId ${connectionId}`);
1392
950
  }
1393
951
  return {
952
+ userId: user.userId,
1394
953
  username: user.username,
1395
954
  characterDescription: user.characterDescription,
1396
955
  colors: user.colors
1397
956
  };
1398
957
  }
1399
958
  onNetworkUpdate(update) {
1400
- const { removedUserIds, addedUserIds, updatedUsers } = update;
1401
- for (const clientId of removedUserIds) {
1402
- this.userProfiles.delete(clientId);
1403
- this.remoteUserStates.delete(clientId);
1404
- }
1405
- for (const [clientId, userData] of addedUserIds) {
1406
- this.userProfiles.set(clientId, userData.userState);
1407
- this.remoteUserStates.set(clientId, userData.components);
959
+ const { removedConnectionIds, addedConnectionIds, updatedUsers } = update;
960
+ for (const connId of removedConnectionIds) {
961
+ const profile = this.userProfiles.get(connId);
962
+ this.emit("userLeft", {
963
+ connectionId: connId,
964
+ userId: (profile == null ? void 0 : profile.userId) ?? "",
965
+ username: (profile == null ? void 0 : profile.username) ?? null
966
+ });
967
+ this.userProfiles.delete(connId);
968
+ this.remoteUserStates.delete(connId);
969
+ }
970
+ for (const [connId, userData] of addedConnectionIds) {
971
+ this.userProfiles.set(connId, userData.userState);
972
+ this.remoteUserStates.set(connId, userData.components);
973
+ if (connId !== this.connectionId) {
974
+ this.emit("userJoined", {
975
+ connectionId: connId,
976
+ userId: userData.userState.userId ?? "",
977
+ username: userData.userState.username ?? null
978
+ });
979
+ }
1408
980
  }
1409
- for (const [clientId, userDataUpdate] of updatedUsers) {
981
+ for (const [connId, userDataUpdate] of updatedUsers) {
1410
982
  const userState = userDataUpdate.userState;
1411
983
  if (userState) {
1412
- if (userState.username !== void 0) {
1413
- this.userProfiles.get(clientId).username = userState.username;
1414
- }
1415
- if (userState.characterDescription !== void 0) {
1416
- this.userProfiles.get(clientId).characterDescription = userState.characterDescription;
1417
- }
1418
- if (userState.colors !== void 0) {
1419
- this.userProfiles.get(clientId).colors = userState.colors;
984
+ const profile = this.userProfiles.get(connId);
985
+ if (profile) {
986
+ if (userState.userId !== void 0) {
987
+ profile.userId = userState.userId;
988
+ }
989
+ if (userState.username !== void 0) {
990
+ profile.username = userState.username;
991
+ }
992
+ if (userState.characterDescription !== void 0) {
993
+ profile.characterDescription = userState.characterDescription;
994
+ }
995
+ if (userState.colors !== void 0) {
996
+ profile.colors = userState.colors;
997
+ }
998
+ this.characterManager.networkCharacterInfoUpdated(connId);
1420
999
  }
1421
- this.characterManager.networkCharacterInfoUpdated(clientId);
1422
- }
1423
- this.remoteUserStates.set(clientId, userDataUpdate.components);
1424
- }
1425
- }
1426
- sendIdentityUpdateToServer(displayName, characterDescription) {
1427
- if (!this.clientId) {
1428
- throw new Error("Client ID not set");
1429
- }
1430
- this.networkClient.updateUsername(displayName);
1431
- this.networkClient.updateCharacterDescription(characterDescription);
1432
- }
1433
- handleChatMessage(fromUserId, message) {
1434
- if (this.textChatUI === null) {
1435
- return;
1436
- }
1437
- if (fromUserId === 0) {
1438
- this.textChatUI.addTextMessage("System", message);
1439
- } else {
1440
- const user = this.userProfiles.get(fromUserId);
1441
- if (!user) {
1442
- console.error(`User not found for clientId ${fromUserId}`);
1443
- return;
1444
1000
  }
1445
- const username = user.username ?? `Unknown User ${fromUserId}`;
1446
- this.textChatUI.addTextMessage(username, message);
1447
- this.renderer.addChatBubble(fromUserId, message);
1001
+ this.remoteUserStates.set(connId, userDataUpdate.components);
1448
1002
  }
1449
1003
  }
1450
- connectToTextChat() {
1451
- if (this.clientId === null) {
1452
- return;
1453
- }
1454
- if (this.config.enableChat && this.textChatUI === null) {
1455
- const user = this.userProfiles.get(this.clientId);
1456
- if (!user) {
1457
- throw new Error("User not found");
1458
- }
1459
- const textChatUISettings = {
1460
- holderElement: this.canvasHolder,
1461
- sendMessageToServerMethod: (message) => {
1462
- this.renderer.onChatMessage(message);
1463
- this.networkClient.sendCustomMessage(
1464
- FROM_CLIENT_CHAT_MESSAGE_TYPE,
1465
- JSON.stringify({ message })
1466
- );
1467
- },
1468
- visibleByDefault: this.config.chatVisibleByDefault,
1469
- stringToHslOptions: this.config.userNameToColorOptions
1470
- };
1471
- this.textChatUI = new TextChatUI(textChatUISettings);
1472
- this.textChatUI.init();
1004
+ sendIdentityUpdateToServer(displayName, characterDescription) {
1005
+ if (this.connectionId === null) {
1006
+ throw new Error("Connection ID not set");
1473
1007
  }
1008
+ this.worldConnection.updateUsername(displayName);
1009
+ this.worldConnection.updateCharacterDescription(characterDescription);
1474
1010
  }
1475
- mountAvatarSelectionUI() {
1476
- var _a, _b;
1477
- if (this.clientId === null) {
1478
- throw new Error("Client ID not set");
1479
- }
1480
- const ownIdentity = this.userProfiles.get(this.clientId);
1481
- if (!ownIdentity) {
1482
- throw new Error("Own identity not found");
1011
+ handleChatMessage(fromConnectionId, userId, message) {
1012
+ const isLocal = fromConnectionId === this.connectionId;
1013
+ let username = "System";
1014
+ if (fromConnectionId !== 0) {
1015
+ const user = this.userProfiles.get(fromConnectionId);
1016
+ username = (user == null ? void 0 : user.username) ?? `User ${fromConnectionId}`;
1017
+ this.renderer.addChatBubble(fromConnectionId, message);
1018
+ }
1019
+ const chatEvent = { username, message, fromConnectionId, userId, isLocal };
1020
+ if (!this.initialLoadCompleted) {
1021
+ this.pendingChatMessages.push(chatEvent);
1022
+ if (this.pendingChatMessages.length > _Networked3dWebExperienceClient.MAX_PENDING_CHAT_MESSAGES) {
1023
+ this.pendingChatMessages.shift();
1024
+ }
1025
+ return;
1483
1026
  }
1484
- this.avatarSelectionUI = new AvatarSelectionUI({
1485
- holderElement: this.element,
1486
- visibleByDefault: false,
1487
- displayName: ownIdentity.username ?? `Unknown User ${this.clientId}`,
1488
- characterDescription: ownIdentity.characterDescription ?? {
1489
- meshFileUrl: ""
1490
- },
1491
- sendIdentityUpdateToServer: this.sendIdentityUpdateToServer.bind(this),
1492
- availableAvatars: ((_a = this.config.avatarConfiguration) == null ? void 0 : _a.availableAvatars) ?? [],
1493
- allowCustomAvatars: (_b = this.config.avatarConfiguration) == null ? void 0 : _b.allowCustomAvatars,
1494
- allowCustomDisplayName: this.config.allowCustomDisplayName || false
1495
- });
1496
- this.avatarSelectionUI.init();
1027
+ this.emit("chat", chatEvent);
1497
1028
  }
1498
1029
  update() {
1499
1030
  const currentTimeMs = performance.now();
@@ -1506,7 +1037,7 @@ var Networked3dWebExperienceClient = class {
1506
1037
  this.accumulatedTime += elapsedSeconds;
1507
1038
  let physicsUpdated = false;
1508
1039
  const updatedCharacterDescriptions = [];
1509
- const removedUserIds = [];
1040
+ const removedConnectionIds = [];
1510
1041
  while (this.accumulatedTime >= this.fixedDeltaTime) {
1511
1042
  this.frameCounter++;
1512
1043
  const result = this.characterManager.update(this.fixedDeltaTime, this.frameCounter);
@@ -1515,9 +1046,9 @@ var Networked3dWebExperienceClient = class {
1515
1046
  updatedCharacterDescriptions.push(id);
1516
1047
  }
1517
1048
  }
1518
- for (const id of result.removedUserIds) {
1519
- if (!removedUserIds.includes(id)) {
1520
- removedUserIds.push(id);
1049
+ for (const id of result.removedConnectionIds) {
1050
+ if (!removedConnectionIds.includes(id)) {
1051
+ removedConnectionIds.push(id);
1521
1052
  }
1522
1053
  }
1523
1054
  this.accumulatedTime -= this.fixedDeltaTime;
@@ -1545,9 +1076,9 @@ var Networked3dWebExperienceClient = class {
1545
1076
  const renderState = {
1546
1077
  characters: allCharacterStates,
1547
1078
  updatedCharacterDescriptions,
1548
- removedUserIds,
1079
+ removedConnectionIds,
1549
1080
  cameraTransform: this.cachedCameraTransform,
1550
- localCharacterId: this.characterManager.getLocalClientId(),
1081
+ localCharacterId: this.characterManager.getLocalConnectionId(),
1551
1082
  deltaTimeSeconds: this.fixedDeltaTime
1552
1083
  };
1553
1084
  this.renderer.render(renderState);
@@ -1557,14 +1088,14 @@ var Networked3dWebExperienceClient = class {
1557
1088
  spawnRotation,
1558
1089
  cameraPosition
1559
1090
  }) {
1560
- if (this.clientId === null) {
1561
- throw new Error("Client ID not set");
1091
+ if (this.connectionId === null) {
1092
+ throw new Error("Connection ID not set");
1562
1093
  }
1563
- const ownIdentity = this.userProfiles.get(this.clientId);
1094
+ const ownIdentity = this.userProfiles.get(this.connectionId);
1564
1095
  if (!ownIdentity) {
1565
1096
  throw new Error("Own identity not found");
1566
1097
  }
1567
- this.characterManager.spawnLocalCharacter(this.clientId, spawnPosition, spawnRotation);
1098
+ this.characterManager.spawnLocalCharacter(this.connectionId, spawnPosition, spawnRotation);
1568
1099
  this.characterManager.setupTweakPane(this.tweakPane);
1569
1100
  if (cameraPosition !== null) {
1570
1101
  const cameraState = this.cameraManager.getMainCameraState();
@@ -1579,11 +1110,155 @@ var Networked3dWebExperienceClient = class {
1579
1110
  this.errorScreen = new ErrorScreen("An error occurred", message);
1580
1111
  this.element.append(this.errorScreen.element);
1581
1112
  }
1113
+ // ---------------------------------------------------------------------------
1114
+ // Public API — these methods allow wrapper code (e.g. the CLI client or
1115
+ // custom scripts) to inspect and interact with the running experience.
1116
+ // ---------------------------------------------------------------------------
1117
+ /**
1118
+ * Add a plugin at runtime. If the experience has already loaded, the plugin
1119
+ * is mounted immediately and receives the latest config. Otherwise it will
1120
+ * be mounted alongside the other plugins when loading completes.
1121
+ */
1122
+ addPlugin(plugin) {
1123
+ var _a;
1124
+ this.plugins.push(plugin);
1125
+ if (this.initialLoadCompleted && !this.disposed) {
1126
+ plugin.mount(this.element, this);
1127
+ if (Object.keys(this.latestConfig).length > 0) {
1128
+ (_a = plugin.onConfigChanged) == null ? void 0 : _a.call(plugin, this.latestConfig);
1129
+ }
1130
+ }
1131
+ }
1132
+ /**
1133
+ * Remove a previously added plugin. Calls `dispose()` on the plugin if the
1134
+ * experience has already loaded (i.e. it was mounted).
1135
+ */
1136
+ removePlugin(plugin) {
1137
+ const index = this.plugins.indexOf(plugin);
1138
+ if (index === -1) return;
1139
+ this.plugins.splice(index, 1);
1140
+ if (this.initialLoadCompleted) {
1141
+ plugin.dispose();
1142
+ }
1143
+ }
1144
+ /** Set an additional input provider (e.g. virtual joystick) for character control. */
1145
+ setAdditionalInputProvider(inputProvider) {
1146
+ this.characterManager.setAdditionalInputProvider(inputProvider);
1147
+ }
1148
+ /** Returns the current spawn configuration (position, respawn trigger, etc.). */
1149
+ getSpawnConfiguration() {
1150
+ return this.spawnConfiguration;
1151
+ }
1152
+ /** Returns the local client's connection ID, or null before authentication. */
1153
+ getConnectionId() {
1154
+ return this.connectionId;
1155
+ }
1156
+ /** Returns position and username of the local character, or null if not yet spawned. */
1157
+ getLocalCharacterState() {
1158
+ if (this.connectionId === null) return null;
1159
+ const localController = this.characterManager.localController;
1160
+ if (!localController) return null;
1161
+ const state = localController.networkState;
1162
+ const profile = this.userProfiles.get(this.connectionId);
1163
+ return {
1164
+ connectionId: this.connectionId,
1165
+ userId: (profile == null ? void 0 : profile.userId) ?? "",
1166
+ position: { x: state.position.x, y: state.position.y, z: state.position.z },
1167
+ username: (profile == null ? void 0 : profile.username) ?? ""
1168
+ };
1169
+ }
1170
+ /**
1171
+ * Returns all connected users (including local). Each entry contains
1172
+ * the user's connection ID, userId, position, and username.
1173
+ */
1174
+ getCharacterStates() {
1175
+ var _a, _b, _c;
1176
+ const result = /* @__PURE__ */ new Map();
1177
+ if (this.connectionId !== null) {
1178
+ const local = this.getLocalCharacterState();
1179
+ if (local) {
1180
+ result.set(this.connectionId, { ...local, isLocal: true });
1181
+ }
1182
+ }
1183
+ for (const [connId, update] of this.remoteUserStates) {
1184
+ if (connId === this.connectionId) continue;
1185
+ const profile = this.userProfiles.get(connId);
1186
+ result.set(connId, {
1187
+ connectionId: connId,
1188
+ userId: (profile == null ? void 0 : profile.userId) ?? "",
1189
+ position: {
1190
+ x: ((_a = update.position) == null ? void 0 : _a.x) ?? 0,
1191
+ y: ((_b = update.position) == null ? void 0 : _b.y) ?? 0,
1192
+ z: ((_c = update.position) == null ? void 0 : _c.z) ?? 0
1193
+ },
1194
+ username: (profile == null ? void 0 : profile.username) ?? "",
1195
+ isLocal: false
1196
+ });
1197
+ }
1198
+ return result;
1199
+ }
1200
+ /** Update the character's avatar. */
1201
+ selectAvatar(characterDescription) {
1202
+ if (this.connectionId === null) return;
1203
+ const profile = this.userProfiles.get(this.connectionId);
1204
+ const displayName = (profile == null ? void 0 : profile.username) ?? "";
1205
+ this.sendIdentityUpdateToServer(displayName, characterDescription);
1206
+ }
1207
+ /** Update the character's display name. */
1208
+ setDisplayName(name) {
1209
+ if (this.connectionId === null) return;
1210
+ const profile = this.userProfiles.get(this.connectionId);
1211
+ if (!(profile == null ? void 0 : profile.characterDescription)) return;
1212
+ this.sendIdentityUpdateToServer(name, profile.characterDescription);
1213
+ }
1214
+ /** Respawn the local character at the configured spawn point. */
1215
+ respawn() {
1216
+ var _a;
1217
+ (_a = this.characterManager.localController) == null ? void 0 : _a.resetPosition();
1218
+ }
1219
+ /** Send a chat message. */
1220
+ sendChatMessage(message) {
1221
+ if (this.connectionId === null) return;
1222
+ this.renderer.onChatMessage(message);
1223
+ this.worldConnection.sendChatMessage(message);
1224
+ }
1225
+ /** Access the underlying world connection. */
1226
+ getWorldConnection() {
1227
+ return this.worldConnection;
1228
+ }
1229
+ /** Access the renderer. */
1230
+ getRenderer() {
1231
+ return this.renderer;
1232
+ }
1233
+ /** Access the character manager (local/remote character state, spawning). */
1234
+ getCharacterManager() {
1235
+ return this.characterManager;
1236
+ }
1237
+ /** Access the camera manager. */
1238
+ getCameraManager() {
1239
+ return this.cameraManager;
1240
+ }
1241
+ /** Look up a user's profile (username, avatar, colors) by connection ID. */
1242
+ getUserProfile(connectionId) {
1243
+ return this.userProfiles.get(connectionId) ?? null;
1244
+ }
1245
+ /** Get all user profiles. */
1246
+ getUserProfiles() {
1247
+ return this.userProfiles;
1248
+ }
1582
1249
  dispose() {
1583
- var _a, _b;
1250
+ var _a;
1251
+ this.disposed = true;
1252
+ this.resizeObserver.disconnect();
1253
+ if (this.worldConfigTimeoutId !== null) {
1254
+ clearTimeout(this.worldConfigTimeoutId);
1255
+ this.worldConfigTimeoutId = null;
1256
+ }
1584
1257
  this.characterManager.dispose();
1585
- this.networkClient.stop();
1586
- (_a = this.textChatUI) == null ? void 0 : _a.dispose();
1258
+ this.worldConnection.stop();
1259
+ for (const plugin of this.getAllPlugins()) {
1260
+ plugin.dispose();
1261
+ }
1587
1262
  this.tweakPane.dispose();
1588
1263
  this.renderer.dispose();
1589
1264
  if (this.currentRequestAnimationFrame !== null) {
@@ -1592,10 +1267,1672 @@ var Networked3dWebExperienceClient = class {
1592
1267
  }
1593
1268
  this.cameraManager.dispose();
1594
1269
  this.loadingScreen.dispose();
1595
- (_b = this.errorScreen) == null ? void 0 : _b.dispose();
1270
+ (_a = this.errorScreen) == null ? void 0 : _a.dispose();
1271
+ this.emit("disposed");
1272
+ this.clearAllHandlers();
1273
+ }
1274
+ };
1275
+
1276
+ // src/DefaultChatPlugin.ts
1277
+ var MAX_MESSAGES = 200;
1278
+ var NEAR_DISTANCE = 10;
1279
+ var FAR_DISTANCE = 100;
1280
+ var FILTER_THRESHOLDS = [0, 15, 30, 60];
1281
+ var PASSIVE_MAX = 5;
1282
+ var PASSIVE_LINGER_MS = 8e3;
1283
+ var PASSIVE_FADE_MS = 600;
1284
+ var ACCENT = "#ffffff";
1285
+ var PANEL_BG = "rgba(10, 14, 23, 0.85)";
1286
+ var BORDER = "1px solid rgba(255,255,255,0.08)";
1287
+ var BORDER_RADIUS = "8px";
1288
+ function getDistanceColor(distance) {
1289
+ if (distance <= 15) return "#ffffff";
1290
+ if (distance <= 50) return "#7a8ba6";
1291
+ return "#4a5568";
1292
+ }
1293
+ function getProximityOpacity(distance) {
1294
+ if (distance <= NEAR_DISTANCE) return 1;
1295
+ if (distance >= FAR_DISTANCE) return 0.35;
1296
+ return 1 - (distance - NEAR_DISTANCE) / (FAR_DISTANCE - NEAR_DISTANCE) * 0.65;
1297
+ }
1298
+ function getProximityFontSize(distance) {
1299
+ if (distance <= NEAR_DISTANCE) return 14;
1300
+ if (distance >= FAR_DISTANCE) return 11;
1301
+ return 14 - (distance - NEAR_DISTANCE) / (FAR_DISTANCE - NEAR_DISTANCE) * 3;
1302
+ }
1303
+ var DefaultChatPlugin = class {
1304
+ container = null;
1305
+ scrollStyle = null;
1306
+ client = null;
1307
+ enabled = true;
1308
+ messages = [];
1309
+ nextId = 0;
1310
+ panelOpen = false;
1311
+ unreadCount = 0;
1312
+ filterIndex = 0;
1313
+ proximityMode = false;
1314
+ userScrolledUp = false;
1315
+ // DOM references
1316
+ toggleBtn;
1317
+ unreadBadge;
1318
+ passiveArea;
1319
+ panel;
1320
+ messageArea;
1321
+ input;
1322
+ filterPill;
1323
+ proxPill;
1324
+ globalKeydownHandler = null;
1325
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1326
+ chatHandler = null;
1327
+ mount(_container, client) {
1328
+ this.client = client;
1329
+ this.container = document.createElement("div");
1330
+ Object.assign(this.container.style, {
1331
+ position: "absolute",
1332
+ top: "0",
1333
+ left: "0",
1334
+ width: "100%",
1335
+ height: "100%",
1336
+ pointerEvents: "none",
1337
+ zIndex: "10001",
1338
+ fontFamily: "'Segoe UI', system-ui, -apple-system, sans-serif",
1339
+ fontSize: "13px",
1340
+ color: "#e0e0e0"
1341
+ });
1342
+ _container.appendChild(this.container);
1343
+ this.buildToggleButton();
1344
+ this.buildPassiveArea();
1345
+ this.buildPanel();
1346
+ this.chatHandler = (msg) => {
1347
+ const distance = msg.isLocal ? 0 : this.calculateDistance(msg.fromConnectionId);
1348
+ this.addMessageToUI(msg.username, msg.message, distance, msg.fromConnectionId, msg.isLocal);
1349
+ };
1350
+ client.on("chat", this.chatHandler);
1351
+ }
1352
+ onConfigChanged(config) {
1353
+ if (config.enableChat === void 0 || !this.container) return;
1354
+ this.enabled = config.enableChat;
1355
+ this.container.style.display = this.enabled ? "" : "none";
1356
+ if (!this.enabled && this.panelOpen) {
1357
+ this.setPanelOpen(false);
1358
+ }
1359
+ }
1360
+ dispose() {
1361
+ var _a, _b;
1362
+ if (this.globalKeydownHandler) {
1363
+ document.removeEventListener("keydown", this.globalKeydownHandler);
1364
+ this.globalKeydownHandler = null;
1365
+ }
1366
+ if (this.chatHandler && this.client) {
1367
+ this.client.off("chat", this.chatHandler);
1368
+ this.chatHandler = null;
1369
+ }
1370
+ (_a = this.scrollStyle) == null ? void 0 : _a.remove();
1371
+ (_b = this.container) == null ? void 0 : _b.remove();
1372
+ this.container = null;
1373
+ this.client = null;
1374
+ }
1375
+ buildToggleButton() {
1376
+ this.toggleBtn = document.createElement("div");
1377
+ this.toggleBtn.dataset.testid = "chat-toggle";
1378
+ Object.assign(this.toggleBtn.style, {
1379
+ position: "absolute",
1380
+ bottom: "12px",
1381
+ left: "12px",
1382
+ width: "44px",
1383
+ height: "44px",
1384
+ borderRadius: "50%",
1385
+ background: PANEL_BG,
1386
+ border: BORDER,
1387
+ backdropFilter: "blur(12px)",
1388
+ WebkitBackdropFilter: "blur(12px)",
1389
+ display: "flex",
1390
+ alignItems: "center",
1391
+ justifyContent: "center",
1392
+ cursor: "pointer",
1393
+ pointerEvents: "auto",
1394
+ boxShadow: "0 4px 24px rgba(0,0,0,0.4)",
1395
+ transition: "all 0.15s ease",
1396
+ color: ACCENT,
1397
+ fontSize: "20px",
1398
+ userSelect: "none"
1399
+ });
1400
+ this.toggleBtn.textContent = "\u{1F4AC}";
1401
+ this.container.appendChild(this.toggleBtn);
1402
+ this.unreadBadge = document.createElement("span");
1403
+ Object.assign(this.unreadBadge.style, {
1404
+ position: "absolute",
1405
+ top: "-4px",
1406
+ right: "-4px",
1407
+ background: ACCENT,
1408
+ color: "#0a0e17",
1409
+ fontSize: "10px",
1410
+ fontWeight: "700",
1411
+ borderRadius: "50%",
1412
+ width: "18px",
1413
+ height: "18px",
1414
+ display: "none",
1415
+ alignItems: "center",
1416
+ justifyContent: "center",
1417
+ lineHeight: "18px",
1418
+ textAlign: "center"
1419
+ });
1420
+ this.toggleBtn.appendChild(this.unreadBadge);
1421
+ this.toggleBtn.addEventListener("click", () => this.setPanelOpen(true));
1422
+ }
1423
+ buildPassiveArea() {
1424
+ this.passiveArea = document.createElement("div");
1425
+ this.passiveArea.dataset.testid = "chat-passive-area";
1426
+ Object.assign(this.passiveArea.style, {
1427
+ position: "absolute",
1428
+ bottom: "64px",
1429
+ left: "12px",
1430
+ width: "360px",
1431
+ display: "flex",
1432
+ flexDirection: "column",
1433
+ gap: "2px",
1434
+ pointerEvents: "none"
1435
+ });
1436
+ this.container.appendChild(this.passiveArea);
1437
+ }
1438
+ buildPanel() {
1439
+ this.panel = document.createElement("div");
1440
+ this.panel.dataset.testid = "chat-panel";
1441
+ Object.assign(this.panel.style, {
1442
+ position: "absolute",
1443
+ bottom: "12px",
1444
+ left: "12px",
1445
+ width: "360px",
1446
+ maxHeight: "440px",
1447
+ display: "none",
1448
+ flexDirection: "column",
1449
+ background: PANEL_BG,
1450
+ border: BORDER,
1451
+ borderRadius: BORDER_RADIUS,
1452
+ backdropFilter: "blur(12px)",
1453
+ WebkitBackdropFilter: "blur(12px)",
1454
+ boxShadow: "0 4px 24px rgba(0,0,0,0.4)",
1455
+ pointerEvents: "auto",
1456
+ overflow: "hidden"
1457
+ });
1458
+ this.container.appendChild(this.panel);
1459
+ const header = document.createElement("div");
1460
+ Object.assign(header.style, {
1461
+ display: "flex",
1462
+ alignItems: "center",
1463
+ justifyContent: "space-between",
1464
+ padding: "10px 12px",
1465
+ borderBottom: BORDER,
1466
+ flexShrink: "0"
1467
+ });
1468
+ this.panel.appendChild(header);
1469
+ const headerLeft = document.createElement("div");
1470
+ Object.assign(headerLeft.style, { display: "flex", alignItems: "center", gap: "10px" });
1471
+ header.appendChild(headerLeft);
1472
+ const chatLabel = document.createElement("span");
1473
+ chatLabel.textContent = "CHAT";
1474
+ Object.assign(chatLabel.style, {
1475
+ fontWeight: "600",
1476
+ fontSize: "12px",
1477
+ letterSpacing: "0.5px",
1478
+ textTransform: "uppercase",
1479
+ color: ACCENT
1480
+ });
1481
+ headerLeft.appendChild(chatLabel);
1482
+ const headerRight = document.createElement("div");
1483
+ Object.assign(headerRight.style, { display: "flex", alignItems: "center", gap: "6px" });
1484
+ header.appendChild(headerRight);
1485
+ const pillBase = {
1486
+ fontSize: "10px",
1487
+ padding: "2px 8px",
1488
+ borderRadius: "4px",
1489
+ cursor: "pointer",
1490
+ userSelect: "none",
1491
+ transition: "all 0.15s ease",
1492
+ fontWeight: "600",
1493
+ letterSpacing: "0.3px"
1494
+ };
1495
+ this.filterPill = document.createElement("span");
1496
+ Object.assign(this.filterPill.style, {
1497
+ ...pillBase,
1498
+ border: "1px solid rgba(255,255,255,0.08)",
1499
+ background: "transparent",
1500
+ color: "rgba(255,255,255,0.4)"
1501
+ });
1502
+ this.filterPill.textContent = "ALL";
1503
+ this.filterPill.title = "Filter by distance";
1504
+ headerRight.appendChild(this.filterPill);
1505
+ this.proxPill = document.createElement("span");
1506
+ Object.assign(this.proxPill.style, {
1507
+ ...pillBase,
1508
+ border: "1px solid rgba(255,255,255,0.08)",
1509
+ background: "transparent",
1510
+ color: "rgba(255,255,255,0.4)"
1511
+ });
1512
+ this.proxPill.textContent = "PROX";
1513
+ this.proxPill.title = "Proximity prominence";
1514
+ headerRight.appendChild(this.proxPill);
1515
+ const closeBtn = document.createElement("span");
1516
+ Object.assign(closeBtn.style, {
1517
+ ...pillBase,
1518
+ border: "none",
1519
+ background: "transparent",
1520
+ color: "rgba(255,255,255,0.3)",
1521
+ fontSize: "14px",
1522
+ padding: "0 4px",
1523
+ cursor: "pointer",
1524
+ marginLeft: "4px"
1525
+ });
1526
+ closeBtn.textContent = "\u2715";
1527
+ closeBtn.title = "Close chat";
1528
+ headerRight.appendChild(closeBtn);
1529
+ this.messageArea = document.createElement("div");
1530
+ Object.assign(this.messageArea.style, {
1531
+ flex: "1",
1532
+ overflowY: "auto",
1533
+ padding: "4px 0",
1534
+ minHeight: "0",
1535
+ maxHeight: "340px"
1536
+ });
1537
+ this.scrollStyle = document.createElement("style");
1538
+ this.scrollStyle.textContent = `
1539
+ .default-chat-messages::-webkit-scrollbar { width: 4px; }
1540
+ .default-chat-messages::-webkit-scrollbar-track { background: transparent; }
1541
+ .default-chat-messages::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.15); border-radius: 2px; }
1542
+ `;
1543
+ document.head.appendChild(this.scrollStyle);
1544
+ this.messageArea.className = "default-chat-messages";
1545
+ this.panel.appendChild(this.messageArea);
1546
+ const inputArea = document.createElement("div");
1547
+ Object.assign(inputArea.style, {
1548
+ display: "flex",
1549
+ padding: "8px 12px",
1550
+ gap: "8px",
1551
+ borderTop: BORDER,
1552
+ flexShrink: "0"
1553
+ });
1554
+ this.panel.appendChild(inputArea);
1555
+ this.input = document.createElement("input");
1556
+ this.input.dataset.testid = "chat-input";
1557
+ this.input.type = "text";
1558
+ this.input.placeholder = "Type a message...";
1559
+ this.input.maxLength = 500;
1560
+ Object.assign(this.input.style, {
1561
+ flex: "1",
1562
+ background: "rgba(255,255,255,0.06)",
1563
+ border: "1px solid rgba(255,255,255,0.08)",
1564
+ borderRadius: "6px",
1565
+ padding: "6px 10px",
1566
+ color: "#e0e0e0",
1567
+ fontSize: "13px",
1568
+ fontFamily: "'Segoe UI', system-ui, -apple-system, sans-serif",
1569
+ outline: "none"
1570
+ });
1571
+ inputArea.appendChild(this.input);
1572
+ const sendBtn = document.createElement("button");
1573
+ sendBtn.dataset.testid = "chat-send";
1574
+ sendBtn.textContent = "\u27A4";
1575
+ Object.assign(sendBtn.style, {
1576
+ background: ACCENT,
1577
+ border: "none",
1578
+ borderRadius: "6px",
1579
+ width: "32px",
1580
+ height: "32px",
1581
+ color: "#0a0e17",
1582
+ fontSize: "14px",
1583
+ cursor: "pointer",
1584
+ display: "flex",
1585
+ alignItems: "center",
1586
+ justifyContent: "center",
1587
+ flexShrink: "0",
1588
+ transition: "opacity 0.15s ease"
1589
+ });
1590
+ inputArea.appendChild(sendBtn);
1591
+ closeBtn.addEventListener("click", () => this.setPanelOpen(false));
1592
+ this.filterPill.addEventListener("click", () => {
1593
+ this.filterIndex = (this.filterIndex + 1) % FILTER_THRESHOLDS.length;
1594
+ this.updateFilterPill();
1595
+ this.rerenderMessages();
1596
+ });
1597
+ this.proxPill.addEventListener("click", () => {
1598
+ this.proximityMode = !this.proximityMode;
1599
+ this.updateProxPill();
1600
+ this.rerenderMessages();
1601
+ });
1602
+ this.messageArea.addEventListener("scroll", () => {
1603
+ const threshold = 30;
1604
+ const diff = this.messageArea.scrollHeight - this.messageArea.scrollTop - this.messageArea.clientHeight;
1605
+ this.userScrolledUp = diff >= threshold;
1606
+ });
1607
+ this.input.addEventListener("keydown", (e) => {
1608
+ e.stopPropagation();
1609
+ if (e.key === "Enter" && this.input.value.trim()) {
1610
+ this.sendMessage(this.input.value.trim());
1611
+ this.input.value = "";
1612
+ }
1613
+ if (e.key === "Escape") {
1614
+ this.input.blur();
1615
+ this.setPanelOpen(false);
1616
+ }
1617
+ });
1618
+ this.input.addEventListener("keyup", (e) => e.stopPropagation());
1619
+ this.input.addEventListener("keypress", (e) => e.stopPropagation());
1620
+ sendBtn.addEventListener("click", () => {
1621
+ if (this.input.value.trim()) {
1622
+ this.sendMessage(this.input.value.trim());
1623
+ this.input.value = "";
1624
+ }
1625
+ this.input.focus();
1626
+ });
1627
+ this.globalKeydownHandler = (e) => {
1628
+ if (!this.enabled) return;
1629
+ const active = document.activeElement;
1630
+ const isInputFocused = active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement;
1631
+ if (e.key === "Enter" && !isInputFocused) {
1632
+ e.preventDefault();
1633
+ if (!this.panelOpen) this.setPanelOpen(true);
1634
+ setTimeout(() => this.input.focus(), 0);
1635
+ }
1636
+ };
1637
+ document.addEventListener("keydown", this.globalKeydownHandler);
1638
+ }
1639
+ sendMessage(text) {
1640
+ var _a;
1641
+ (_a = this.client) == null ? void 0 : _a.sendChatMessage(text);
1642
+ }
1643
+ calculateDistance(fromConnectionId) {
1644
+ if (!this.client) return null;
1645
+ const states = this.client.getCharacterStates();
1646
+ let localPos = null;
1647
+ let senderPos = null;
1648
+ for (const s of states.values()) {
1649
+ if (s.isLocal) localPos = s.position;
1650
+ if (s.connectionId === fromConnectionId) senderPos = s.position;
1651
+ }
1652
+ if (!localPos || !senderPos) return null;
1653
+ const dx = senderPos.x - localPos.x;
1654
+ const dz = senderPos.z - localPos.z;
1655
+ return Math.sqrt(dx * dx + dz * dz);
1656
+ }
1657
+ setPanelOpen(open) {
1658
+ this.panelOpen = open;
1659
+ this.panel.style.display = open ? "flex" : "none";
1660
+ this.toggleBtn.style.display = open ? "none" : "flex";
1661
+ this.passiveArea.style.display = open ? "none" : "flex";
1662
+ if (open) {
1663
+ this.unreadCount = 0;
1664
+ this.updateUnreadBadge();
1665
+ this.scrollToBottom(true);
1666
+ }
1667
+ }
1668
+ updateUnreadBadge() {
1669
+ if (this.unreadCount > 0 && !this.panelOpen) {
1670
+ this.unreadBadge.textContent = this.unreadCount > 9 ? "9+" : String(this.unreadCount);
1671
+ this.unreadBadge.style.display = "flex";
1672
+ } else {
1673
+ this.unreadBadge.style.display = "none";
1674
+ }
1675
+ }
1676
+ updateFilterPill() {
1677
+ const threshold = FILTER_THRESHOLDS[this.filterIndex];
1678
+ if (threshold === 0) {
1679
+ this.filterPill.textContent = "ALL";
1680
+ Object.assign(this.filterPill.style, {
1681
+ background: "transparent",
1682
+ color: "rgba(255,255,255,0.4)",
1683
+ borderColor: "rgba(255,255,255,0.08)"
1684
+ });
1685
+ } else {
1686
+ this.filterPill.textContent = `< ${threshold}m`;
1687
+ Object.assign(this.filterPill.style, {
1688
+ background: "rgba(255,255,255,0.08)",
1689
+ color: ACCENT,
1690
+ borderColor: "rgba(255,255,255,0.15)"
1691
+ });
1692
+ }
1693
+ }
1694
+ updateProxPill() {
1695
+ if (this.proximityMode) {
1696
+ Object.assign(this.proxPill.style, {
1697
+ background: "rgba(255,255,255,0.08)",
1698
+ color: ACCENT,
1699
+ borderColor: "rgba(255,255,255,0.15)"
1700
+ });
1701
+ } else {
1702
+ Object.assign(this.proxPill.style, {
1703
+ background: "transparent",
1704
+ color: "rgba(255,255,255,0.4)",
1705
+ borderColor: "rgba(255,255,255,0.08)"
1706
+ });
1707
+ }
1708
+ }
1709
+ shouldShowMessage(msg) {
1710
+ const threshold = FILTER_THRESHOLDS[this.filterIndex];
1711
+ if (threshold === 0) return true;
1712
+ if (msg.fromConnectionId === 0) return true;
1713
+ if (msg.isLocal) return true;
1714
+ if (msg.distance === null) return false;
1715
+ return msg.distance <= threshold;
1716
+ }
1717
+ scrollToBottom(force = false) {
1718
+ if (force || !this.userScrolledUp) {
1719
+ this.messageArea.scrollTop = this.messageArea.scrollHeight;
1720
+ }
1721
+ }
1722
+ createMessageElement(msg) {
1723
+ const row = document.createElement("div");
1724
+ Object.assign(row.style, {
1725
+ padding: "3px 12px",
1726
+ display: "flex",
1727
+ alignItems: "flex-start",
1728
+ gap: "8px",
1729
+ transition: "opacity 0.15s ease"
1730
+ });
1731
+ if (this.proximityMode && msg.distance !== null && msg.fromConnectionId !== 0) {
1732
+ row.style.opacity = String(getProximityOpacity(msg.distance));
1733
+ row.style.fontSize = `${getProximityFontSize(msg.distance)}px`;
1734
+ }
1735
+ const badge = document.createElement("span");
1736
+ Object.assign(badge.style, {
1737
+ fontSize: "9px",
1738
+ padding: "1px 5px",
1739
+ borderRadius: "4px",
1740
+ fontWeight: "600",
1741
+ flexShrink: "0",
1742
+ minWidth: "32px",
1743
+ textAlign: "center",
1744
+ lineHeight: "16px",
1745
+ marginTop: "2px"
1746
+ });
1747
+ if (msg.fromConnectionId === 0) {
1748
+ badge.textContent = "SYS";
1749
+ badge.style.color = "#f0c040";
1750
+ badge.style.background = "rgba(240,192,64,0.12)";
1751
+ } else if (msg.isLocal) {
1752
+ badge.textContent = "YOU";
1753
+ badge.style.color = ACCENT;
1754
+ badge.style.background = "rgba(255,255,255,0.08)";
1755
+ } else if (msg.distance !== null) {
1756
+ badge.textContent = `${Math.round(msg.distance)}m`;
1757
+ const c = getDistanceColor(msg.distance);
1758
+ badge.style.color = c;
1759
+ badge.style.background = c + "1f";
1760
+ } else {
1761
+ badge.textContent = "?";
1762
+ badge.style.color = "rgba(255,255,255,0.3)";
1763
+ badge.style.background = "rgba(255,255,255,0.05)";
1764
+ }
1765
+ row.appendChild(badge);
1766
+ const content = document.createElement("span");
1767
+ Object.assign(content.style, { wordBreak: "break-word", lineHeight: "1.4" });
1768
+ const usernameSpan = document.createElement("span");
1769
+ usernameSpan.textContent = msg.username;
1770
+ Object.assign(usernameSpan.style, {
1771
+ fontWeight: "600",
1772
+ color: msg.isLocal ? ACCENT : "#ffffff",
1773
+ marginRight: "6px"
1774
+ });
1775
+ content.appendChild(usernameSpan);
1776
+ content.appendChild(document.createTextNode(msg.message));
1777
+ row.appendChild(content);
1778
+ return row;
1779
+ }
1780
+ rerenderMessages() {
1781
+ this.messageArea.innerHTML = "";
1782
+ for (const msg of this.messages) {
1783
+ const el = this.createMessageElement(msg);
1784
+ if (!this.shouldShowMessage(msg)) el.style.display = "none";
1785
+ this.messageArea.appendChild(el);
1786
+ }
1787
+ this.scrollToBottom(true);
1788
+ }
1789
+ addPassiveMessage(msg) {
1790
+ const el = document.createElement("div");
1791
+ Object.assign(el.style, {
1792
+ padding: "3px 10px",
1793
+ background: "rgba(10, 14, 23, 0.6)",
1794
+ borderRadius: "6px",
1795
+ fontSize: "13px",
1796
+ lineHeight: "1.4",
1797
+ opacity: "0",
1798
+ transition: `opacity ${PASSIVE_FADE_MS}ms ease`,
1799
+ whiteSpace: "normal",
1800
+ wordBreak: "break-word"
1801
+ });
1802
+ const usernameSpan = document.createElement("span");
1803
+ usernameSpan.textContent = msg.username;
1804
+ Object.assign(usernameSpan.style, {
1805
+ fontWeight: "600",
1806
+ color: msg.isLocal ? ACCENT : "#ffffff",
1807
+ marginRight: "6px"
1808
+ });
1809
+ el.appendChild(usernameSpan);
1810
+ el.appendChild(document.createTextNode(msg.message));
1811
+ this.passiveArea.appendChild(el);
1812
+ requestAnimationFrame(() => {
1813
+ el.style.opacity = "0.9";
1814
+ });
1815
+ while (this.passiveArea.children.length > PASSIVE_MAX) {
1816
+ this.passiveArea.removeChild(this.passiveArea.firstChild);
1817
+ }
1818
+ setTimeout(() => {
1819
+ el.style.opacity = "0";
1820
+ setTimeout(() => {
1821
+ if (el.parentNode === this.passiveArea) this.passiveArea.removeChild(el);
1822
+ }, PASSIVE_FADE_MS);
1823
+ }, PASSIVE_LINGER_MS);
1824
+ }
1825
+ addMessageToUI(username, message, distance, fromConnectionId, isLocal) {
1826
+ const msg = {
1827
+ id: this.nextId++,
1828
+ username,
1829
+ message,
1830
+ distance,
1831
+ fromConnectionId,
1832
+ isLocal,
1833
+ timestamp: Date.now()
1834
+ };
1835
+ this.messages.push(msg);
1836
+ if (this.messages.length > MAX_MESSAGES) {
1837
+ this.messages.shift();
1838
+ if (this.messageArea.firstChild) this.messageArea.removeChild(this.messageArea.firstChild);
1839
+ }
1840
+ const el = this.createMessageElement(msg);
1841
+ if (!this.shouldShowMessage(msg)) el.style.display = "none";
1842
+ this.messageArea.appendChild(el);
1843
+ this.scrollToBottom();
1844
+ if (!this.panelOpen) {
1845
+ this.unreadCount++;
1846
+ this.updateUnreadBadge();
1847
+ this.addPassiveMessage(msg);
1848
+ }
1849
+ }
1850
+ };
1851
+
1852
+ // src/DefaultAvatarSelectionPlugin.ts
1853
+ var ACCENT2 = "#ffffff";
1854
+ var PANEL_BG2 = "rgba(10, 14, 23, 0.85)";
1855
+ var BORDER2 = "1px solid rgba(255,255,255,0.08)";
1856
+ var BORDER_RADIUS2 = "8px";
1857
+ var DefaultAvatarSelectionPlugin = class {
1858
+ container = null;
1859
+ scrollStyle = null;
1860
+ client = null;
1861
+ panelOpen = false;
1862
+ selectedIndex = -1;
1863
+ avatars = [];
1864
+ allowCustomAvatars = false;
1865
+ allowCustomDisplayName = false;
1866
+ customAvatarType = "mmlUrl" /* mmlUrl */;
1867
+ // DOM references
1868
+ toggleBtn;
1869
+ panel;
1870
+ grid;
1871
+ nameInput = null;
1872
+ customInput = null;
1873
+ customSection = null;
1874
+ escapeHandler = null;
1875
+ mount(_container, client) {
1876
+ this.client = client;
1877
+ this.container = document.createElement("div");
1878
+ Object.assign(this.container.style, {
1879
+ position: "absolute",
1880
+ top: "0",
1881
+ left: "0",
1882
+ width: "100%",
1883
+ height: "100%",
1884
+ pointerEvents: "none",
1885
+ zIndex: "10001",
1886
+ fontFamily: "'Segoe UI', system-ui, -apple-system, sans-serif",
1887
+ fontSize: "13px",
1888
+ color: "#e0e0e0"
1889
+ });
1890
+ _container.appendChild(this.container);
1891
+ this.buildToggleButton();
1892
+ this.buildPanel();
1893
+ }
1894
+ onConfigChanged(config) {
1895
+ const cfg = config;
1896
+ let needsRebuild = false;
1897
+ if (cfg.avatarConfiguration) {
1898
+ this.avatars = cfg.avatarConfiguration.availableAvatars ?? [];
1899
+ this.allowCustomAvatars = cfg.avatarConfiguration.allowCustomAvatars ?? false;
1900
+ needsRebuild = true;
1901
+ }
1902
+ if (cfg.allowCustomDisplayName !== void 0) {
1903
+ this.allowCustomDisplayName = cfg.allowCustomDisplayName || false;
1904
+ needsRebuild = true;
1905
+ }
1906
+ if (needsRebuild) {
1907
+ this.rebuildPanel();
1908
+ }
1909
+ }
1910
+ dispose() {
1911
+ var _a, _b;
1912
+ if (this.escapeHandler) {
1913
+ document.removeEventListener("keydown", this.escapeHandler);
1914
+ this.escapeHandler = null;
1915
+ }
1916
+ (_a = this.scrollStyle) == null ? void 0 : _a.remove();
1917
+ (_b = this.container) == null ? void 0 : _b.remove();
1918
+ this.container = null;
1919
+ this.client = null;
1920
+ }
1921
+ buildToggleButton() {
1922
+ this.toggleBtn = document.createElement("div");
1923
+ Object.assign(this.toggleBtn.style, {
1924
+ position: "absolute",
1925
+ top: "12px",
1926
+ right: "12px",
1927
+ width: "44px",
1928
+ height: "44px",
1929
+ borderRadius: "50%",
1930
+ background: PANEL_BG2,
1931
+ border: BORDER2,
1932
+ backdropFilter: "blur(12px)",
1933
+ WebkitBackdropFilter: "blur(12px)",
1934
+ display: "flex",
1935
+ alignItems: "center",
1936
+ justifyContent: "center",
1937
+ cursor: "pointer",
1938
+ pointerEvents: "auto",
1939
+ boxShadow: "0 4px 24px rgba(0,0,0,0.4)",
1940
+ transition: "all 0.15s ease",
1941
+ color: ACCENT2,
1942
+ fontSize: "20px",
1943
+ userSelect: "none"
1944
+ });
1945
+ this.toggleBtn.textContent = "\u{1F464}";
1946
+ this.toggleBtn.title = "Avatar & Display Name";
1947
+ this.container.appendChild(this.toggleBtn);
1948
+ this.toggleBtn.addEventListener("click", () => this.setPanelOpen(true));
1949
+ }
1950
+ rebuildPanel() {
1951
+ var _a;
1952
+ const wasOpen = this.panelOpen;
1953
+ this.panel.remove();
1954
+ (_a = this.scrollStyle) == null ? void 0 : _a.remove();
1955
+ this.buildPanel();
1956
+ if (wasOpen) this.setPanelOpen(true);
1957
+ }
1958
+ buildPanel() {
1959
+ this.panel = document.createElement("div");
1960
+ Object.assign(this.panel.style, {
1961
+ position: "absolute",
1962
+ top: "12px",
1963
+ right: "12px",
1964
+ width: "340px",
1965
+ maxHeight: "calc(100vh - 24px)",
1966
+ display: "none",
1967
+ flexDirection: "column",
1968
+ background: PANEL_BG2,
1969
+ border: BORDER2,
1970
+ borderRadius: BORDER_RADIUS2,
1971
+ backdropFilter: "blur(12px)",
1972
+ WebkitBackdropFilter: "blur(12px)",
1973
+ boxShadow: "0 4px 24px rgba(0,0,0,0.4)",
1974
+ pointerEvents: "auto",
1975
+ overflow: "hidden"
1976
+ });
1977
+ this.container.appendChild(this.panel);
1978
+ const header = document.createElement("div");
1979
+ Object.assign(header.style, {
1980
+ display: "flex",
1981
+ alignItems: "center",
1982
+ justifyContent: "space-between",
1983
+ padding: "10px 12px",
1984
+ borderBottom: BORDER2,
1985
+ flexShrink: "0"
1986
+ });
1987
+ this.panel.appendChild(header);
1988
+ const headerLabel = document.createElement("span");
1989
+ headerLabel.textContent = "AVATAR";
1990
+ Object.assign(headerLabel.style, {
1991
+ fontWeight: "600",
1992
+ fontSize: "12px",
1993
+ letterSpacing: "0.5px",
1994
+ textTransform: "uppercase",
1995
+ color: ACCENT2
1996
+ });
1997
+ header.appendChild(headerLabel);
1998
+ const closeBtn = document.createElement("span");
1999
+ Object.assign(closeBtn.style, {
2000
+ fontSize: "14px",
2001
+ color: "rgba(255,255,255,0.3)",
2002
+ cursor: "pointer",
2003
+ padding: "0 4px",
2004
+ userSelect: "none"
2005
+ });
2006
+ closeBtn.textContent = "\u2715";
2007
+ closeBtn.title = "Close";
2008
+ header.appendChild(closeBtn);
2009
+ closeBtn.addEventListener("click", () => this.setPanelOpen(false));
2010
+ this.nameInput = null;
2011
+ if (this.allowCustomDisplayName) {
2012
+ const nameSection = document.createElement("div");
2013
+ Object.assign(nameSection.style, {
2014
+ padding: "8px 12px",
2015
+ borderBottom: BORDER2,
2016
+ flexShrink: "0"
2017
+ });
2018
+ this.panel.appendChild(nameSection);
2019
+ const nameLabel = document.createElement("div");
2020
+ nameLabel.textContent = "Display Name";
2021
+ Object.assign(nameLabel.style, {
2022
+ fontSize: "10px",
2023
+ fontWeight: "600",
2024
+ letterSpacing: "0.5px",
2025
+ textTransform: "uppercase",
2026
+ color: "rgba(255,255,255,0.4)",
2027
+ marginBottom: "6px"
2028
+ });
2029
+ nameSection.appendChild(nameLabel);
2030
+ this.nameInput = document.createElement("input");
2031
+ this.nameInput.type = "text";
2032
+ this.nameInput.placeholder = "Enter display name...";
2033
+ this.nameInput.maxLength = 30;
2034
+ Object.assign(this.nameInput.style, {
2035
+ width: "100%",
2036
+ boxSizing: "border-box",
2037
+ background: "rgba(255,255,255,0.06)",
2038
+ border: "1px solid rgba(255,255,255,0.08)",
2039
+ borderRadius: "6px",
2040
+ padding: "6px 10px",
2041
+ color: "#e0e0e0",
2042
+ fontSize: "13px",
2043
+ fontFamily: "'Segoe UI', system-ui, -apple-system, sans-serif",
2044
+ outline: "none"
2045
+ });
2046
+ nameSection.appendChild(this.nameInput);
2047
+ this.nameInput.addEventListener("keydown", (e) => e.stopPropagation());
2048
+ this.nameInput.addEventListener("keyup", (e) => e.stopPropagation());
2049
+ this.nameInput.addEventListener("keypress", (e) => e.stopPropagation());
2050
+ }
2051
+ const gridScroll = document.createElement("div");
2052
+ Object.assign(gridScroll.style, {
2053
+ flex: "1",
2054
+ overflowY: "auto",
2055
+ padding: "8px",
2056
+ minHeight: "0"
2057
+ });
2058
+ this.scrollStyle = document.createElement("style");
2059
+ this.scrollStyle.textContent = `
2060
+ .default-avatar-grid::-webkit-scrollbar { width: 4px; }
2061
+ .default-avatar-grid::-webkit-scrollbar-track { background: transparent; }
2062
+ .default-avatar-grid::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.15); border-radius: 2px; }
2063
+ `;
2064
+ document.head.appendChild(this.scrollStyle);
2065
+ gridScroll.className = "default-avatar-grid";
2066
+ this.panel.appendChild(gridScroll);
2067
+ this.grid = document.createElement("div");
2068
+ Object.assign(this.grid.style, {
2069
+ display: "grid",
2070
+ gridTemplateColumns: "repeat(auto-fill, minmax(70px, 1fr))",
2071
+ gap: "6px"
2072
+ });
2073
+ gridScroll.appendChild(this.grid);
2074
+ this.customInput = null;
2075
+ this.customSection = null;
2076
+ if (this.allowCustomAvatars) {
2077
+ this.customSection = document.createElement("div");
2078
+ Object.assign(this.customSection.style, {
2079
+ padding: "8px 12px",
2080
+ borderTop: BORDER2,
2081
+ flexShrink: "0"
2082
+ });
2083
+ this.panel.appendChild(this.customSection);
2084
+ const customLabel = document.createElement("div");
2085
+ customLabel.textContent = "Custom Avatar";
2086
+ Object.assign(customLabel.style, {
2087
+ fontSize: "10px",
2088
+ fontWeight: "600",
2089
+ letterSpacing: "0.5px",
2090
+ textTransform: "uppercase",
2091
+ color: "rgba(255,255,255,0.4)",
2092
+ marginBottom: "6px"
2093
+ });
2094
+ this.customSection.appendChild(customLabel);
2095
+ this.buildCustomAvatarTypeSelector(this.customSection);
2096
+ this.buildCustomAvatarInput(this.customSection);
2097
+ }
2098
+ const applyArea = document.createElement("div");
2099
+ Object.assign(applyArea.style, {
2100
+ padding: "8px 12px",
2101
+ borderTop: BORDER2,
2102
+ flexShrink: "0"
2103
+ });
2104
+ this.panel.appendChild(applyArea);
2105
+ const applyBtn = document.createElement("button");
2106
+ applyBtn.textContent = "Apply";
2107
+ Object.assign(applyBtn.style, {
2108
+ width: "100%",
2109
+ background: ACCENT2,
2110
+ border: "none",
2111
+ borderRadius: "6px",
2112
+ padding: "8px",
2113
+ color: "#0a0e17",
2114
+ fontSize: "13px",
2115
+ fontWeight: "600",
2116
+ cursor: "pointer",
2117
+ transition: "opacity 0.15s ease",
2118
+ fontFamily: "'Segoe UI', system-ui, -apple-system, sans-serif"
2119
+ });
2120
+ applyArea.appendChild(applyBtn);
2121
+ applyBtn.addEventListener("click", () => this.applyChanges());
2122
+ this.escapeHandler = (e) => {
2123
+ if (e.key === "Escape" && this.panelOpen) this.setPanelOpen(false);
2124
+ };
2125
+ document.addEventListener("keydown", this.escapeHandler);
2126
+ this.renderGrid();
2127
+ }
2128
+ renderGrid() {
2129
+ this.grid.innerHTML = "";
2130
+ this.avatars.forEach((avatar, idx) => {
2131
+ const cell = document.createElement("div");
2132
+ const isSelected = idx === this.selectedIndex;
2133
+ Object.assign(cell.style, {
2134
+ aspectRatio: "1",
2135
+ borderRadius: "6px",
2136
+ border: isSelected ? `2px solid ${ACCENT2}` : "2px solid rgba(255,255,255,0.06)",
2137
+ background: isSelected ? "rgba(255,255,255,0.08)" : "rgba(255,255,255,0.04)",
2138
+ cursor: "pointer",
2139
+ display: "flex",
2140
+ flexDirection: "column",
2141
+ alignItems: "center",
2142
+ justifyContent: "center",
2143
+ overflow: "hidden",
2144
+ transition: "all 0.1s ease",
2145
+ position: "relative"
2146
+ });
2147
+ if (avatar.thumbnailUrl) {
2148
+ const img = document.createElement("img");
2149
+ img.src = avatar.thumbnailUrl;
2150
+ Object.assign(img.style, {
2151
+ width: "100%",
2152
+ height: "100%",
2153
+ objectFit: "cover",
2154
+ borderRadius: "4px"
2155
+ });
2156
+ img.onerror = () => {
2157
+ img.remove();
2158
+ this.addTextLabel(cell, avatar, idx);
2159
+ };
2160
+ cell.appendChild(img);
2161
+ } else {
2162
+ this.addTextLabel(cell, avatar, idx);
2163
+ }
2164
+ cell.addEventListener("click", () => {
2165
+ this.selectedIndex = idx;
2166
+ this.renderGrid();
2167
+ });
2168
+ cell.addEventListener("mouseenter", () => {
2169
+ if (idx !== this.selectedIndex) {
2170
+ cell.style.borderColor = "rgba(255,255,255,0.15)";
2171
+ cell.style.background = "rgba(255,255,255,0.06)";
2172
+ }
2173
+ });
2174
+ cell.addEventListener("mouseleave", () => {
2175
+ if (idx !== this.selectedIndex) {
2176
+ cell.style.borderColor = "rgba(255,255,255,0.06)";
2177
+ cell.style.background = "rgba(255,255,255,0.04)";
2178
+ }
2179
+ });
2180
+ this.grid.appendChild(cell);
2181
+ });
2182
+ }
2183
+ buildCustomAvatarTypeSelector(parent) {
2184
+ const radioGroup = document.createElement("div");
2185
+ Object.assign(radioGroup.style, {
2186
+ display: "flex",
2187
+ gap: "12px",
2188
+ marginBottom: "8px"
2189
+ });
2190
+ parent.appendChild(radioGroup);
2191
+ const types = [
2192
+ { type: "mmlUrl" /* mmlUrl */, label: "MML URL" },
2193
+ { type: "mml" /* mml */, label: "MML" },
2194
+ { type: "meshFileUrl" /* meshFileUrl */, label: "Mesh URL" }
2195
+ ];
2196
+ for (const { type, label } of types) {
2197
+ const item = document.createElement("label");
2198
+ Object.assign(item.style, {
2199
+ display: "flex",
2200
+ alignItems: "center",
2201
+ gap: "4px",
2202
+ cursor: "pointer",
2203
+ fontSize: "12px",
2204
+ color: "rgba(255,255,255,0.7)"
2205
+ });
2206
+ const radio = document.createElement("input");
2207
+ radio.type = "radio";
2208
+ radio.name = "default-avatar-custom-type";
2209
+ radio.checked = this.customAvatarType === type;
2210
+ radio.addEventListener("change", () => {
2211
+ this.customAvatarType = type;
2212
+ this.rebuildCustomInput();
2213
+ });
2214
+ item.appendChild(radio);
2215
+ item.appendChild(document.createTextNode(label));
2216
+ radioGroup.appendChild(item);
2217
+ }
2218
+ }
2219
+ getCustomPlaceholder() {
2220
+ switch (this.customAvatarType) {
2221
+ case "meshFileUrl" /* meshFileUrl */:
2222
+ return "https://example.com/avatar.glb";
2223
+ case "mmlUrl" /* mmlUrl */:
2224
+ return "https://example.com/avatar.html";
2225
+ case "mml" /* mml */:
2226
+ return '<m-character src="https://...">\n</m-character>';
2227
+ }
2228
+ }
2229
+ buildCustomAvatarInput(parent) {
2230
+ const inputStyle = {
2231
+ width: "100%",
2232
+ boxSizing: "border-box",
2233
+ background: "rgba(255,255,255,0.06)",
2234
+ border: "1px solid rgba(255,255,255,0.08)",
2235
+ borderRadius: "6px",
2236
+ padding: "6px 10px",
2237
+ color: "#e0e0e0",
2238
+ fontSize: "12px",
2239
+ fontFamily: "'Segoe UI', system-ui, -apple-system, sans-serif",
2240
+ outline: "none",
2241
+ resize: "vertical"
2242
+ };
2243
+ if (this.customAvatarType === "mml" /* mml */) {
2244
+ const textarea = document.createElement("textarea");
2245
+ textarea.rows = 4;
2246
+ textarea.placeholder = this.getCustomPlaceholder();
2247
+ Object.assign(textarea.style, inputStyle);
2248
+ this.customInput = textarea;
2249
+ } else {
2250
+ const input = document.createElement("input");
2251
+ input.type = "text";
2252
+ input.placeholder = this.getCustomPlaceholder();
2253
+ Object.assign(input.style, inputStyle);
2254
+ this.customInput = input;
2255
+ }
2256
+ this.customInput.addEventListener("input", () => {
2257
+ if (this.customInput.value.trim()) {
2258
+ this.selectedIndex = -1;
2259
+ this.renderGrid();
2260
+ }
2261
+ });
2262
+ this.customInput.addEventListener("keydown", (e) => e.stopPropagation());
2263
+ this.customInput.addEventListener("keyup", (e) => e.stopPropagation());
2264
+ this.customInput.addEventListener("keypress", (e) => e.stopPropagation());
2265
+ parent.appendChild(this.customInput);
2266
+ }
2267
+ rebuildCustomInput() {
2268
+ var _a;
2269
+ if (!this.customSection) return;
2270
+ (_a = this.customInput) == null ? void 0 : _a.remove();
2271
+ this.buildCustomAvatarInput(this.customSection);
2272
+ }
2273
+ addTextLabel(cell, avatar, idx) {
2274
+ const label = document.createElement("span");
2275
+ label.textContent = avatar.name || `#${idx + 1}`;
2276
+ Object.assign(label.style, {
2277
+ fontSize: "10px",
2278
+ color: "rgba(255,255,255,0.5)",
2279
+ textAlign: "center",
2280
+ padding: "4px",
2281
+ wordBreak: "break-word"
2282
+ });
2283
+ cell.appendChild(label);
2284
+ }
2285
+ setPanelOpen(open) {
2286
+ this.panelOpen = open;
2287
+ this.panel.style.display = open ? "flex" : "none";
2288
+ this.toggleBtn.style.display = open ? "none" : "flex";
2289
+ if (open) this.syncCurrentState();
2290
+ }
2291
+ syncCurrentState() {
2292
+ if (!this.client) return;
2293
+ const connectionId = this.client.getConnectionId();
2294
+ if (connectionId === null) return;
2295
+ const profile = this.client.getUserProfile(connectionId);
2296
+ if (!profile) return;
2297
+ if (this.nameInput && profile.username) {
2298
+ this.nameInput.value = profile.username;
2299
+ }
2300
+ const desc = profile.characterDescription;
2301
+ this.selectedIndex = this.avatars.findIndex(
2302
+ (a) => a.meshFileUrl && a.meshFileUrl === (desc == null ? void 0 : desc.meshFileUrl) || a.mmlCharacterUrl && a.mmlCharacterUrl === (desc == null ? void 0 : desc.mmlCharacterUrl) || a.mmlCharacterString && a.mmlCharacterString === (desc == null ? void 0 : desc.mmlCharacterString)
2303
+ );
2304
+ if (this.customInput && this.selectedIndex === -1 && desc) {
2305
+ if (desc.mmlCharacterString) {
2306
+ this.customAvatarType = "mml" /* mml */;
2307
+ this.rebuildCustomInput();
2308
+ this.customInput.value = desc.mmlCharacterString;
2309
+ } else if (desc.mmlCharacterUrl) {
2310
+ this.customAvatarType = "mmlUrl" /* mmlUrl */;
2311
+ this.rebuildCustomInput();
2312
+ this.customInput.value = desc.mmlCharacterUrl;
2313
+ } else if (desc.meshFileUrl) {
2314
+ this.customAvatarType = "meshFileUrl" /* meshFileUrl */;
2315
+ this.rebuildCustomInput();
2316
+ this.customInput.value = desc.meshFileUrl;
2317
+ }
2318
+ }
2319
+ this.renderGrid();
2320
+ }
2321
+ applyChanges() {
2322
+ var _a;
2323
+ if (!this.client) return;
2324
+ let characterDescription = null;
2325
+ if (this.selectedIndex >= 0 && this.selectedIndex < this.avatars.length) {
2326
+ const avatar = this.avatars[this.selectedIndex];
2327
+ if (avatar.meshFileUrl) {
2328
+ characterDescription = { meshFileUrl: avatar.meshFileUrl };
2329
+ } else if (avatar.mmlCharacterUrl) {
2330
+ characterDescription = { mmlCharacterUrl: avatar.mmlCharacterUrl };
2331
+ } else if (avatar.mmlCharacterString) {
2332
+ characterDescription = { mmlCharacterString: avatar.mmlCharacterString };
2333
+ }
2334
+ } else if ((_a = this.customInput) == null ? void 0 : _a.value.trim()) {
2335
+ const value = this.customInput.value.trim();
2336
+ switch (this.customAvatarType) {
2337
+ case "mml" /* mml */:
2338
+ characterDescription = { mmlCharacterString: value };
2339
+ break;
2340
+ case "mmlUrl" /* mmlUrl */:
2341
+ characterDescription = { mmlCharacterUrl: value };
2342
+ break;
2343
+ case "meshFileUrl" /* meshFileUrl */:
2344
+ characterDescription = { meshFileUrl: value };
2345
+ break;
2346
+ }
2347
+ }
2348
+ if (characterDescription) {
2349
+ this.client.selectAvatar(characterDescription);
2350
+ }
2351
+ if (this.nameInput) {
2352
+ const newName = this.nameInput.value.trim();
2353
+ if (newName) this.client.setDisplayName(newName);
2354
+ }
2355
+ this.setPanelOpen(false);
2356
+ }
2357
+ };
2358
+
2359
+ // src/DefaultHUDPlugin.ts
2360
+ var WORLD_HALF = 105;
2361
+ var MINIMAP_SIZE = 180;
2362
+ var VIEW_RADIUS = WORLD_HALF;
2363
+ var GRID_SPACING = 30;
2364
+ var PLAYER_LIST_UPDATE_MS = 500;
2365
+ var MINIMAP_UPDATE_MS = 100;
2366
+ var ACCENT3 = "#ffffff";
2367
+ var PANEL_BG3 = "rgba(10, 14, 23, 0.85)";
2368
+ var BORDER3 = "1px solid rgba(255,255,255,0.08)";
2369
+ var BORDER_RADIUS3 = "8px";
2370
+ function createHUD(mountTarget, client, options = {}) {
2371
+ const showMinimap = options.minimap !== false;
2372
+ const showPlayerList = options.playerList !== false;
2373
+ const getCharacterStates = () => client.getCharacterStates();
2374
+ const getCameraYaw = () => {
2375
+ const camPos = client.getCameraManager().getCameraPosition();
2376
+ const states = getCharacterStates();
2377
+ const local = Array.from(states.values()).find((p) => p.isLocal);
2378
+ if (!local) return 0;
2379
+ return Math.atan2(camPos.x - local.position.x, camPos.z - local.position.z);
2380
+ };
2381
+ const container = document.createElement("div");
2382
+ Object.assign(container.style, {
2383
+ position: "absolute",
2384
+ top: "0",
2385
+ left: "0",
2386
+ width: "100%",
2387
+ height: "100%",
2388
+ pointerEvents: "none",
2389
+ zIndex: "10000",
2390
+ fontFamily: "'Segoe UI', system-ui, -apple-system, sans-serif",
2391
+ fontSize: "13px",
2392
+ color: "#e0e0e0"
2393
+ });
2394
+ mountTarget.appendChild(container);
2395
+ const SMALL_SCREEN_PX = 768;
2396
+ const group = document.createElement("div");
2397
+ Object.assign(group.style, {
2398
+ position: "absolute",
2399
+ bottom: "12px",
2400
+ right: "12px",
2401
+ display: "flex",
2402
+ flexDirection: "column",
2403
+ gap: "6px",
2404
+ pointerEvents: "none",
2405
+ alignItems: "flex-end"
2406
+ });
2407
+ container.appendChild(group);
2408
+ let hudExpanded = window.innerWidth >= SMALL_SCREEN_PX;
2409
+ const hudToggle = document.createElement("div");
2410
+ Object.assign(hudToggle.style, {
2411
+ width: "36px",
2412
+ height: "36px",
2413
+ borderRadius: "50%",
2414
+ background: PANEL_BG3,
2415
+ border: BORDER3,
2416
+ backdropFilter: "blur(12px)",
2417
+ WebkitBackdropFilter: "blur(12px)",
2418
+ display: "flex",
2419
+ alignItems: "center",
2420
+ justifyContent: "center",
2421
+ cursor: "pointer",
2422
+ pointerEvents: "auto",
2423
+ boxShadow: "0 4px 24px rgba(0,0,0,0.4)",
2424
+ fontSize: "14px",
2425
+ color: ACCENT3,
2426
+ userSelect: "none",
2427
+ flexShrink: "0"
2428
+ });
2429
+ hudToggle.title = "Toggle HUD";
2430
+ hudToggle.textContent = "\u{1F5FA}";
2431
+ group.appendChild(hudToggle);
2432
+ const hudContent = document.createElement("div");
2433
+ Object.assign(hudContent.style, {
2434
+ display: "flex",
2435
+ flexDirection: "column",
2436
+ gap: "6px",
2437
+ pointerEvents: "none"
2438
+ });
2439
+ group.appendChild(hudContent);
2440
+ function setHudExpanded(expanded) {
2441
+ hudExpanded = expanded;
2442
+ hudContent.style.display = expanded ? "flex" : "none";
2443
+ hudToggle.style.display = expanded && window.innerWidth >= SMALL_SCREEN_PX ? "none" : "flex";
2444
+ }
2445
+ hudToggle.addEventListener("click", () => setHudExpanded(!hudExpanded));
2446
+ const resizeHandler = () => {
2447
+ const isSmall = window.innerWidth < SMALL_SCREEN_PX;
2448
+ hudToggle.style.display = !hudExpanded || isSmall ? "flex" : "none";
2449
+ };
2450
+ window.addEventListener("resize", resizeHandler);
2451
+ setHudExpanded(hudExpanded);
2452
+ let highlightedPlayerId = null;
2453
+ let playerListInterval = null;
2454
+ if (showPlayerList) {
2455
+ let updatePlayerList = function() {
2456
+ const states = getCharacterStates();
2457
+ const players = Array.from(states.values()).sort((a, b) => {
2458
+ if (a.isLocal !== b.isLocal) return a.isLocal ? -1 : 1;
2459
+ return (a.username || "").localeCompare(b.username || "");
2460
+ });
2461
+ headerTitle.textContent = `Players (${players.length})`;
2462
+ const names = players.map((p) => `${p.connectionId}:${p.username || ""}:${p.isLocal}`).join(",");
2463
+ if (listBody.dataset.hash === names) return;
2464
+ listBody.dataset.hash = names;
2465
+ listBody.innerHTML = "";
2466
+ for (const player of players) {
2467
+ const row = document.createElement("div");
2468
+ Object.assign(row.style, {
2469
+ padding: "5px 12px",
2470
+ display: "flex",
2471
+ alignItems: "center",
2472
+ gap: "8px",
2473
+ cursor: player.isLocal ? "default" : "pointer",
2474
+ transition: "background 0.1s ease"
2475
+ });
2476
+ if (!player.isLocal) {
2477
+ row.addEventListener("mouseenter", () => {
2478
+ highlightedPlayerId = player.connectionId;
2479
+ row.style.background = "rgba(255,255,255,0.06)";
2480
+ });
2481
+ row.addEventListener("mouseleave", () => {
2482
+ highlightedPlayerId = null;
2483
+ row.style.background = "";
2484
+ });
2485
+ row.addEventListener("click", () => {
2486
+ const currentStates = getCharacterStates();
2487
+ const local = Array.from(currentStates.values()).find((p) => p.isLocal);
2488
+ const target = currentStates.get(player.connectionId);
2489
+ if (local && target) {
2490
+ const dx = target.position.x - local.position.x;
2491
+ const dz = target.position.z - local.position.z;
2492
+ const angleToTarget = Math.atan2(dz, dx);
2493
+ client.getCameraManager().setOrbitAngle(angleToTarget + Math.PI);
2494
+ }
2495
+ });
2496
+ }
2497
+ const dot = document.createElement("span");
2498
+ Object.assign(dot.style, {
2499
+ width: "6px",
2500
+ height: "6px",
2501
+ borderRadius: "50%",
2502
+ background: player.isLocal ? ACCENT3 : "#7a8ba6",
2503
+ flexShrink: "0"
2504
+ });
2505
+ row.appendChild(dot);
2506
+ const name = document.createElement("span");
2507
+ name.textContent = player.username || "Unknown";
2508
+ Object.assign(name.style, {
2509
+ overflow: "hidden",
2510
+ textOverflow: "ellipsis",
2511
+ whiteSpace: "nowrap",
2512
+ color: player.isLocal ? "#ffffff" : "#b0bec5",
2513
+ fontWeight: player.isLocal ? "600" : "400",
2514
+ fontSize: "12px"
2515
+ });
2516
+ row.appendChild(name);
2517
+ if (player.isLocal) {
2518
+ const youBadge = document.createElement("span");
2519
+ youBadge.textContent = "you";
2520
+ Object.assign(youBadge.style, {
2521
+ fontSize: "9px",
2522
+ color: ACCENT3,
2523
+ background: "rgba(255,255,255,0.08)",
2524
+ padding: "1px 5px",
2525
+ borderRadius: "4px",
2526
+ marginLeft: "auto",
2527
+ fontWeight: "600",
2528
+ letterSpacing: "0.5px",
2529
+ textTransform: "uppercase",
2530
+ flexShrink: "0"
2531
+ });
2532
+ row.appendChild(youBadge);
2533
+ }
2534
+ listBody.appendChild(row);
2535
+ }
2536
+ };
2537
+ const playerList = document.createElement("div");
2538
+ Object.assign(playerList.style, {
2539
+ width: `${MINIMAP_SIZE}px`,
2540
+ background: PANEL_BG3,
2541
+ border: BORDER3,
2542
+ borderRadius: BORDER_RADIUS3,
2543
+ backdropFilter: "blur(12px)",
2544
+ WebkitBackdropFilter: "blur(12px)",
2545
+ pointerEvents: "auto",
2546
+ overflow: "hidden",
2547
+ boxShadow: "0 4px 24px rgba(0,0,0,0.4)"
2548
+ });
2549
+ hudContent.appendChild(playerList);
2550
+ const header = document.createElement("div");
2551
+ Object.assign(header.style, {
2552
+ display: "flex",
2553
+ alignItems: "center",
2554
+ justifyContent: "space-between",
2555
+ padding: "10px 12px",
2556
+ cursor: "pointer",
2557
+ userSelect: "none",
2558
+ borderBottom: BORDER3
2559
+ });
2560
+ playerList.appendChild(header);
2561
+ const headerTitle = document.createElement("span");
2562
+ headerTitle.textContent = "Players (0)";
2563
+ Object.assign(headerTitle.style, {
2564
+ fontWeight: "600",
2565
+ fontSize: "12px",
2566
+ letterSpacing: "0.5px",
2567
+ textTransform: "uppercase",
2568
+ color: ACCENT3
2569
+ });
2570
+ header.appendChild(headerTitle);
2571
+ const chevron = document.createElement("span");
2572
+ chevron.textContent = "\u25B2";
2573
+ Object.assign(chevron.style, {
2574
+ fontSize: "10px",
2575
+ color: "rgba(255,255,255,0.4)",
2576
+ transition: "transform 0.2s ease"
2577
+ });
2578
+ header.appendChild(chevron);
2579
+ const listBody = document.createElement("div");
2580
+ const reservedPx = 16 + MINIMAP_SIZE + 6 + 40 + 2 + 16;
2581
+ Object.assign(listBody.style, {
2582
+ maxHeight: `calc(100vh - ${reservedPx}px)`,
2583
+ overflowY: "auto",
2584
+ padding: "4px 0"
2585
+ });
2586
+ playerList.appendChild(listBody);
2587
+ let collapsed = false;
2588
+ header.addEventListener("click", () => {
2589
+ collapsed = !collapsed;
2590
+ listBody.style.display = collapsed ? "none" : "block";
2591
+ chevron.style.transform = collapsed ? "rotate(180deg)" : "rotate(0deg)";
2592
+ if (collapsed) highlightedPlayerId = null;
2593
+ });
2594
+ listBody.addEventListener("mouseleave", () => {
2595
+ highlightedPlayerId = null;
2596
+ });
2597
+ playerListInterval = setInterval(updatePlayerList, PLAYER_LIST_UPDATE_MS);
2598
+ updatePlayerList();
2599
+ }
2600
+ let minimapRafId = null;
2601
+ if (showMinimap) {
2602
+ let offsetToCanvas = function(dx, dz, rotation) {
2603
+ let rx, ry;
2604
+ if (rotation !== 0) {
2605
+ const cos = Math.cos(rotation);
2606
+ const sin = Math.sin(rotation);
2607
+ rx = dx * cos - dz * sin;
2608
+ ry = dx * sin + dz * cos;
2609
+ } else {
2610
+ rx = dx;
2611
+ ry = dz;
2612
+ }
2613
+ return [C + rx * frameScale, C + ry * frameScale];
2614
+ }, drawGrid = function(playerX, playerZ, rotation, viewRadius) {
2615
+ ctx.strokeStyle = "rgba(255,255,255,0.05)";
2616
+ ctx.lineWidth = 1;
2617
+ const visibleRange = viewRadius * 1.42;
2618
+ const minGX = Math.floor((playerX - visibleRange) / GRID_SPACING) * GRID_SPACING;
2619
+ const maxGX = Math.ceil((playerX + visibleRange) / GRID_SPACING) * GRID_SPACING;
2620
+ const minGZ = Math.floor((playerZ - visibleRange) / GRID_SPACING) * GRID_SPACING;
2621
+ const maxGZ = Math.ceil((playerZ + visibleRange) / GRID_SPACING) * GRID_SPACING;
2622
+ for (let wx = minGX; wx <= maxGX; wx += GRID_SPACING) {
2623
+ const dx = wx - playerX;
2624
+ const [x1, y1] = offsetToCanvas(dx, -visibleRange, rotation);
2625
+ const [x2, y2] = offsetToCanvas(dx, visibleRange, rotation);
2626
+ ctx.beginPath();
2627
+ ctx.moveTo(x1, y1);
2628
+ ctx.lineTo(x2, y2);
2629
+ ctx.stroke();
2630
+ }
2631
+ for (let wz = minGZ; wz <= maxGZ; wz += GRID_SPACING) {
2632
+ const dz = wz - playerZ;
2633
+ const [x1, y1] = offsetToCanvas(-visibleRange, dz, rotation);
2634
+ const [x2, y2] = offsetToCanvas(visibleRange, dz, rotation);
2635
+ ctx.beginPath();
2636
+ ctx.moveTo(x1, y1);
2637
+ ctx.lineTo(x2, y2);
2638
+ ctx.stroke();
2639
+ }
2640
+ ctx.strokeStyle = "rgba(255,255,255,0.1)";
2641
+ const [ox1, oy1] = offsetToCanvas(-playerX, -visibleRange, rotation);
2642
+ const [ox2, oy2] = offsetToCanvas(-playerX, visibleRange, rotation);
2643
+ ctx.beginPath();
2644
+ ctx.moveTo(ox1, oy1);
2645
+ ctx.lineTo(ox2, oy2);
2646
+ ctx.stroke();
2647
+ const [zx1, zy1] = offsetToCanvas(-visibleRange, -playerZ, rotation);
2648
+ const [zx2, zy2] = offsetToCanvas(visibleRange, -playerZ, rotation);
2649
+ ctx.beginPath();
2650
+ ctx.moveTo(zx1, zy1);
2651
+ ctx.lineTo(zx2, zy2);
2652
+ ctx.stroke();
2653
+ }, drawNorthIndicator = function(rotation, viewRadius) {
2654
+ const [nx, ny] = offsetToCanvas(0, -viewRadius * 0.88, rotation);
2655
+ if (nx < 0 || nx > S || ny < 0 || ny > S) return;
2656
+ ctx.fillStyle = "rgba(255,255,255,0.35)";
2657
+ ctx.font = "bold 18px 'Segoe UI', system-ui, sans-serif";
2658
+ ctx.textAlign = "center";
2659
+ ctx.textBaseline = "middle";
2660
+ ctx.fillText("N", nx, ny);
2661
+ }, updateMinimap = function() {
2662
+ const states = getCharacterStates();
2663
+ ctx.clearRect(0, 0, S, S);
2664
+ ctx.strokeStyle = "rgba(255,255,255,0.06)";
2665
+ ctx.lineWidth = 2;
2666
+ ctx.strokeRect(1, 1, S - 2, S - 2);
2667
+ const local = Array.from(states.values()).find((p) => p.isLocal) ?? null;
2668
+ const playerX = (local == null ? void 0 : local.position.x) ?? 0;
2669
+ const playerZ = (local == null ? void 0 : local.position.z) ?? 0;
2670
+ let effectiveViewRadius = VIEW_RADIUS;
2671
+ if (highlightedPlayerId !== null) {
2672
+ const highlighted = states.get(highlightedPlayerId);
2673
+ if (highlighted) {
2674
+ const dx = highlighted.position.x - playerX;
2675
+ const dz = highlighted.position.z - playerZ;
2676
+ const dist = Math.sqrt(dx * dx + dz * dz);
2677
+ if (dist > VIEW_RADIUS * 0.85) {
2678
+ effectiveViewRadius = dist * 1.25;
2679
+ }
2680
+ }
2681
+ }
2682
+ frameScale = S / (effectiveViewRadius * 2);
2683
+ const cameraYaw = getCameraYaw();
2684
+ const rotation = cameraOriented ? cameraYaw : 0;
2685
+ drawGrid(playerX, playerZ, rotation, effectiveViewRadius);
2686
+ drawNorthIndicator(rotation, effectiveViewRadius);
2687
+ const players = Array.from(states.values());
2688
+ const remotes = players.filter((p) => !p.isLocal);
2689
+ for (const p of remotes) {
2690
+ const dx = p.position.x - playerX;
2691
+ const dz = p.position.z - playerZ;
2692
+ const [mx, my] = offsetToCanvas(dx, dz, rotation);
2693
+ if (mx < -10 || mx > S + 10 || my < -10 || my > S + 10) continue;
2694
+ const isHighlighted = p.connectionId === highlightedPlayerId;
2695
+ if (isHighlighted) {
2696
+ ctx.beginPath();
2697
+ ctx.moveTo(C, C);
2698
+ ctx.lineTo(mx, my);
2699
+ ctx.strokeStyle = "rgba(255, 255, 255, 0.15)";
2700
+ ctx.lineWidth = 2;
2701
+ ctx.setLineDash([6, 4]);
2702
+ ctx.stroke();
2703
+ ctx.setLineDash([]);
2704
+ ctx.beginPath();
2705
+ ctx.arc(mx, my, 14, 0, Math.PI * 2);
2706
+ ctx.fillStyle = "rgba(255, 255, 255, 0.1)";
2707
+ ctx.fill();
2708
+ ctx.beginPath();
2709
+ ctx.arc(mx, my, 9, 0, Math.PI * 2);
2710
+ ctx.strokeStyle = ACCENT3;
2711
+ ctx.lineWidth = 2;
2712
+ ctx.stroke();
2713
+ ctx.beginPath();
2714
+ ctx.arc(mx, my, 6, 0, Math.PI * 2);
2715
+ ctx.fillStyle = ACCENT3;
2716
+ ctx.fill();
2717
+ } else {
2718
+ ctx.beginPath();
2719
+ ctx.arc(mx, my, 5, 0, Math.PI * 2);
2720
+ ctx.fillStyle = "#7a8ba6";
2721
+ ctx.fill();
2722
+ }
2723
+ }
2724
+ ctx.beginPath();
2725
+ ctx.arc(C, C, 12, 0, Math.PI * 2);
2726
+ ctx.fillStyle = "rgba(255, 255, 255, 0.12)";
2727
+ ctx.fill();
2728
+ ctx.beginPath();
2729
+ ctx.arc(C, C, 7, 0, Math.PI * 2);
2730
+ ctx.fillStyle = ACCENT3;
2731
+ ctx.fill();
2732
+ ctx.beginPath();
2733
+ ctx.arc(C, C, 3, 0, Math.PI * 2);
2734
+ ctx.fillStyle = "#ffffff";
2735
+ ctx.fill();
2736
+ coordLabel.textContent = `${Math.round(playerX)}, ${Math.round(playerZ)}`;
2737
+ }, minimapLoop = function(now) {
2738
+ if (now - lastMinimapUpdate >= MINIMAP_UPDATE_MS) {
2739
+ updateMinimap();
2740
+ lastMinimapUpdate = now;
2741
+ }
2742
+ minimapRafId = requestAnimationFrame(minimapLoop);
2743
+ };
2744
+ let cameraOriented = true;
2745
+ const minimapContainer = document.createElement("div");
2746
+ Object.assign(minimapContainer.style, {
2747
+ position: "relative",
2748
+ width: `${MINIMAP_SIZE}px`,
2749
+ height: `${MINIMAP_SIZE}px`,
2750
+ background: PANEL_BG3,
2751
+ border: BORDER3,
2752
+ borderRadius: BORDER_RADIUS3,
2753
+ backdropFilter: "blur(12px)",
2754
+ WebkitBackdropFilter: "blur(12px)",
2755
+ pointerEvents: "auto",
2756
+ overflow: "hidden",
2757
+ boxShadow: "0 4px 24px rgba(0,0,0,0.4)"
2758
+ });
2759
+ hudContent.appendChild(minimapContainer);
2760
+ const canvas = document.createElement("canvas");
2761
+ canvas.width = MINIMAP_SIZE * 2;
2762
+ canvas.height = MINIMAP_SIZE * 2;
2763
+ Object.assign(canvas.style, {
2764
+ width: `${MINIMAP_SIZE}px`,
2765
+ height: `${MINIMAP_SIZE}px`,
2766
+ display: "block"
2767
+ });
2768
+ minimapContainer.appendChild(canvas);
2769
+ const ctx = canvas.getContext("2d");
2770
+ const S = canvas.width;
2771
+ const C = S / 2;
2772
+ let frameScale = S / (VIEW_RADIUS * 2);
2773
+ const toggleBtn = document.createElement("div");
2774
+ Object.assign(toggleBtn.style, {
2775
+ position: "absolute",
2776
+ top: "6px",
2777
+ right: "6px",
2778
+ width: "22px",
2779
+ height: "22px",
2780
+ display: "flex",
2781
+ alignItems: "center",
2782
+ justifyContent: "center",
2783
+ background: "rgba(255,255,255,0.08)",
2784
+ borderRadius: "4px",
2785
+ cursor: "pointer",
2786
+ userSelect: "none",
2787
+ fontSize: "11px",
2788
+ fontWeight: "700",
2789
+ color: ACCENT3,
2790
+ zIndex: "2",
2791
+ lineHeight: "1"
2792
+ });
2793
+ toggleBtn.title = "Toggle north-oriented / camera-oriented";
2794
+ toggleBtn.textContent = "C";
2795
+ minimapContainer.appendChild(toggleBtn);
2796
+ toggleBtn.addEventListener("click", () => {
2797
+ cameraOriented = !cameraOriented;
2798
+ toggleBtn.textContent = cameraOriented ? "C" : "N";
2799
+ });
2800
+ const coordLabel = document.createElement("div");
2801
+ Object.assign(coordLabel.style, {
2802
+ position: "absolute",
2803
+ bottom: "5px",
2804
+ left: "7px",
2805
+ fontSize: "10px",
2806
+ fontFamily: "'SF Mono', 'Cascadia Code', 'Consolas', monospace",
2807
+ color: "rgba(255,255,255,0.45)",
2808
+ zIndex: "2",
2809
+ pointerEvents: "none",
2810
+ textShadow: "0 1px 3px rgba(0,0,0,0.8)"
2811
+ });
2812
+ coordLabel.textContent = "0, 0";
2813
+ minimapContainer.appendChild(coordLabel);
2814
+ const tooltip = document.createElement("div");
2815
+ Object.assign(tooltip.style, {
2816
+ position: "absolute",
2817
+ background: "rgba(0,0,0,0.85)",
2818
+ color: "#fff",
2819
+ padding: "3px 7px",
2820
+ borderRadius: "4px",
2821
+ fontSize: "10px",
2822
+ pointerEvents: "none",
2823
+ display: "none",
2824
+ whiteSpace: "nowrap",
2825
+ zIndex: "3"
2826
+ });
2827
+ minimapContainer.appendChild(tooltip);
2828
+ canvas.addEventListener("mousemove", (e) => {
2829
+ const states = getCharacterStates();
2830
+ const rect = canvas.getBoundingClientRect();
2831
+ const scaleX = canvas.width / rect.width;
2832
+ const scaleY = canvas.height / rect.height;
2833
+ const cx = (e.clientX - rect.left) * scaleX;
2834
+ const cy = (e.clientY - rect.top) * scaleY;
2835
+ const local = Array.from(states.values()).find((p) => p.isLocal) ?? null;
2836
+ const playerX = (local == null ? void 0 : local.position.x) ?? 0;
2837
+ const playerZ = (local == null ? void 0 : local.position.z) ?? 0;
2838
+ const rotation = cameraOriented ? getCameraYaw() : 0;
2839
+ let found = null;
2840
+ for (const p of states.values()) {
2841
+ const dx = p.position.x - playerX;
2842
+ const dz = p.position.z - playerZ;
2843
+ const [mx, my] = p.isLocal ? [C, C] : offsetToCanvas(dx, dz, rotation);
2844
+ const ddx = cx - mx;
2845
+ const ddy = cy - my;
2846
+ if (ddx * ddx + ddy * ddy < 15 * 15) {
2847
+ found = p.username || "Unknown";
2848
+ break;
2849
+ }
2850
+ }
2851
+ if (found) {
2852
+ tooltip.textContent = found;
2853
+ tooltip.style.display = "block";
2854
+ tooltip.style.left = `${e.clientX - rect.left + 10}px`;
2855
+ tooltip.style.top = `${e.clientY - rect.top - 20}px`;
2856
+ } else {
2857
+ tooltip.style.display = "none";
2858
+ }
2859
+ });
2860
+ canvas.addEventListener("mouseleave", () => {
2861
+ tooltip.style.display = "none";
2862
+ });
2863
+ let lastMinimapUpdate = 0;
2864
+ minimapRafId = requestAnimationFrame(minimapLoop);
2865
+ updateMinimap();
2866
+ }
2867
+ return {
2868
+ dispose() {
2869
+ if (playerListInterval !== null) clearInterval(playerListInterval);
2870
+ if (minimapRafId !== null) cancelAnimationFrame(minimapRafId);
2871
+ window.removeEventListener("resize", resizeHandler);
2872
+ container.remove();
2873
+ }
2874
+ };
2875
+ }
2876
+ var DefaultHUDPlugin = class {
2877
+ hud = null;
2878
+ container = null;
2879
+ client = null;
2880
+ currentMinimap = true;
2881
+ currentPlayerList = true;
2882
+ // Tracks whether the HUD has been explicitly configured or default-created,
2883
+ // so that later partial config updates without a `hud` key are no-ops.
2884
+ hudInitialized = false;
2885
+ mount(container, client) {
2886
+ this.container = container;
2887
+ this.client = client;
2888
+ }
2889
+ onConfigChanged(config) {
2890
+ var _a, _b;
2891
+ if (config.hud === void 0) {
2892
+ if (!this.hudInitialized && !this.hud && this.container && this.client) {
2893
+ this.hudInitialized = true;
2894
+ this.hud = createHUD(this.container, this.client, {
2895
+ minimap: this.currentMinimap,
2896
+ playerList: this.currentPlayerList
2897
+ });
2898
+ }
2899
+ return;
2900
+ }
2901
+ this.hudInitialized = true;
2902
+ if (config.hud === false) {
2903
+ (_a = this.hud) == null ? void 0 : _a.dispose();
2904
+ this.hud = null;
2905
+ return;
2906
+ }
2907
+ const newMinimap = config.hud.minimap !== false;
2908
+ const newPlayerList = config.hud.playerList !== false;
2909
+ if (newMinimap !== this.currentMinimap || newPlayerList !== this.currentPlayerList || !this.hud) {
2910
+ (_b = this.hud) == null ? void 0 : _b.dispose();
2911
+ this.currentMinimap = newMinimap;
2912
+ this.currentPlayerList = newPlayerList;
2913
+ if (this.container && this.client) {
2914
+ this.hud = createHUD(this.container, this.client, {
2915
+ minimap: this.currentMinimap,
2916
+ playerList: this.currentPlayerList
2917
+ });
2918
+ }
2919
+ }
2920
+ }
2921
+ dispose() {
2922
+ var _a;
2923
+ (_a = this.hud) == null ? void 0 : _a.dispose();
2924
+ this.hud = null;
2925
+ this.container = null;
2926
+ this.client = null;
1596
2927
  }
1597
2928
  };
1598
2929
  export {
1599
- Networked3dWebExperienceClient
2930
+ DefaultAvatarSelectionPlugin,
2931
+ DefaultChatPlugin,
2932
+ DefaultHUDPlugin,
2933
+ DefaultRespawnButtonPlugin,
2934
+ DefaultVirtualJoystickPlugin,
2935
+ Networked3dWebExperienceClient,
2936
+ WorldConnection
1600
2937
  };
1601
2938
  //# sourceMappingURL=index.js.map