gg.easy.airship 0.1.2126 → 0.1.2127

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.
@@ -112,6 +112,11 @@ namespace Airship.Editor {
112
112
  [SerializeField] internal List<ComponentScriptAssetData> scripts = new();
113
113
  [SerializeField] internal List<ComponentData> components = new();
114
114
 
115
+ /// <summary>
116
+ /// Returns if the Database is empty - if it is, it's likely the project is new or freshly pulled
117
+ /// </summary>
118
+ internal static bool isEmpty => instance.scripts.Count == 0 && instance.components.Count == 0;
119
+
115
120
  /// <summary>
116
121
  /// Gets or creates the script asset data in the artifact database for the given script
117
122
  /// </summary>
@@ -322,11 +322,19 @@ namespace Airship.Editor {
322
322
  private static void OnComponentReconcile(AirshipReconcileEventData eventData) {
323
323
  // Components must have guids
324
324
  if (string.IsNullOrEmpty(eventData.Component.guid)) eventData.Component.guid = Guid.NewGuid().ToString();
325
+
326
+ // If an initial setup (e.g. first pull, or new template project)
327
+ if (AirshipLocalArtifactDatabase.isEmpty) {
328
+ // We can run a default reconcile, it wont matter tbh.
329
+ var component = eventData.Component;
330
+ ReconcileComponent(component);
331
+ component.componentHash = component.scriptHash;
332
+ return;
333
+ }
325
334
 
326
335
  if (ReconcilerVersion == ReconcilerVersion.Version2) {
327
336
  var component = eventData.Component;
328
-
329
-
337
+
330
338
  var isPrefab = PrefabUtility.IsPartOfAnyPrefab(component);
331
339
  var prefabOriginalComponent = PrefabUtility.GetCorrespondingObjectFromOriginalSource(component);
332
340
  if (isPrefab && prefabOriginalComponent.script != null) {
@@ -1,11 +1,11 @@
1
1
  // ReSharper disable InconsistentNaming
2
2
  namespace Code {
3
3
  public static class AirshipConst {
4
- public const int playerVersion = 3;
4
+ public const int playerVersion = 4;
5
5
 
6
6
  /// <summary>
7
7
  /// The server will kick clients that have a playerVersion lower than this value.
8
8
  /// </summary>
9
- public const int minAcceptedPlayerVersionOnServer = 3;
9
+ public const int minAcceptedPlayerVersionOnServer = 4;
10
10
  }
11
11
  }
@@ -106,6 +106,16 @@ public class SocketManager : Singleton<SocketManager> {
106
106
  await Awaitable.MainThreadAsync();
107
107
  Instance.OnDisconnected?.Invoke(s);
108
108
  };
109
+
110
+ Instance.socket.OnError += async (sender, s) => {
111
+ if (s.StartsWith("User does not have \"GC Edge\" access")) {
112
+ Debug.Log("User does not have \"GC Edge\" access");
113
+ await Awaitable.MainThreadAsync();
114
+ CrossSceneState.kickForceLogout = true;
115
+ TransferManager.Instance.Disconnect(true, "User does not have permission to access Airship");
116
+ }
117
+ };
118
+
109
119
  }
110
120
 
111
121
  if (!Instance.socket.Connected) {
@@ -19,6 +19,7 @@ public static class CrossSceneState
19
19
  public static ServerTransferData ServerTransferData;
20
20
  public static bool UseLocalBundles = false;
21
21
  public static string kickMessage = "";
22
+ public static bool kickForceLogout = false;
22
23
  public static bool disconnectKicked = false;
23
24
 
24
25
  [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
@@ -14,4 +14,4 @@ namespace Code.Http {
14
14
  return headers[headerName];
15
15
  }
16
16
  }
17
- }
17
+ }
@@ -147,6 +147,7 @@ namespace Luau {
147
147
  [typeof(Scrollbar)] = LuauContextAll,
148
148
  [typeof(ScrollRect)] = LuauContextAll,
149
149
  [typeof(Text)] = LuauContextAll,
150
+ [typeof(RectMask2D)] = LuauContextAll,
150
151
 
151
152
  // Particles
152
153
  [typeof(ParticleSystem)] = LuauContextAll,
@@ -5,13 +5,35 @@ using UnityEngine.SceneManagement;
5
5
 
6
6
  public class DisconnectedScreen : MonoBehaviour {
7
7
  public TMP_Text reasonText;
8
+ public GameObject continueButton;
9
+ public GameObject logoutButton;
10
+ public GameObject quitButton;
8
11
 
9
12
  private void Start() {
10
13
  this.reasonText.text = CrossSceneState.kickMessage;
11
14
  Cursor.lockState = CursorLockMode.None;
15
+ if (CrossSceneState.kickForceLogout) {
16
+ CrossSceneState.kickForceLogout = false;
17
+ this.continueButton.SetActive(false);
18
+ this.logoutButton.SetActive(true);
19
+ this.quitButton.SetActive(true);
20
+ } else {
21
+ this.continueButton.SetActive(true);
22
+ this.logoutButton.SetActive(false);
23
+ this.quitButton.SetActive(false);
24
+ }
12
25
  }
13
26
 
14
27
  public void ContinueButton_OnClick() {
15
28
  SceneManager.LoadScene("MainMenu");
16
29
  }
30
+
31
+ public void QuitButton_OnClick() {
32
+ Application.Quit();
33
+ }
34
+
35
+ public void LogoutButton_OnClick() {
36
+ AuthManager.ClearSavedAccount();
37
+ SceneManager.LoadScene("Login");
38
+ }
17
39
  }
@@ -11,153 +11,155 @@ public class CameraScreenshotResponse{
11
11
  public string extension = "";
12
12
  }
13
13
 
14
- [LuauAPI(LuauContext.Protected)]
15
- public class InternalCameraScreenshotRecorder : Singleton<InternalCameraScreenshotRecorder> {
16
- public enum SaveFolder {
17
- ApplicationData,
18
- PicturesFolder,
19
- Documents,
20
- }
21
-
22
- public SaveFolder saveFolder = SaveFolder.PicturesFolder;
23
- public bool shouldSaveCaptures = true;
24
- public int resWidth = 1920;
25
- public int resHeight = 1080;
26
-
27
- private const int resDepth = 24;
28
- private static Texture2D screenShot;
29
- private static RenderTexture rt;
30
-
31
- public delegate void OnPictureTaken(Texture2D screenshot);
32
- public static OnPictureTaken onPictureTaken;
33
-
34
- public static Texture2D GetScreenshotTexture {
35
- get {
36
- return screenShot;
14
+ namespace Assets.Code.Misc {
15
+ [LuauAPI(LuauContext.Protected)]
16
+ public class InternalCameraScreenshotRecorder : Singleton<InternalCameraScreenshotRecorder> {
17
+ public enum SaveFolder {
18
+ ApplicationData,
19
+ PicturesFolder,
20
+ Documents,
37
21
  }
38
- }
39
-
40
- public string ScreenShotName(int width, int height, bool png) {
41
- return FolderName + string.Format("screen_{0}x{1}_{2}.{3}",
42
- width, height,
43
- System.DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"), png?"png":"jpg");
44
- }
45
-
46
- public string ScreenShotName(string filename, bool png) {
47
- return FolderName + filename + (png?".png":".jpg");
48
- }
49
-
50
- public string FolderName{
51
- get {
52
- string folderPath = Application.persistentDataPath;
53
- switch (saveFolder) {
54
- case SaveFolder.PicturesFolder:
55
- folderPath = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
56
- break;
57
- case SaveFolder.Documents:
58
- folderPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
59
- break;
60
- };
61
- return string.Format("{0}/Airship/", folderPath);
22
+
23
+ public SaveFolder saveFolder = SaveFolder.PicturesFolder;
24
+ public bool shouldSaveCaptures = true;
25
+ public int resWidth = 1920;
26
+ public int resHeight = 1080;
27
+
28
+ private const int resDepth = 24;
29
+ private static Texture2D screenShot;
30
+ private static RenderTexture rt;
31
+
32
+ public delegate void OnPictureTaken(Texture2D screenshot);
33
+ public static OnPictureTaken onPictureTaken;
34
+
35
+ public static Texture2D GetScreenshotTexture {
36
+ get {
37
+ return screenShot;
38
+ }
39
+ }
40
+
41
+ public string ScreenShotName(int width, int height, bool png) {
42
+ return FolderName + string.Format("screen_{0}x{1}_{2}.{3}",
43
+ width, height,
44
+ System.DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"), png?"png":"jpg");
62
45
  }
63
- }
64
46
 
65
- private void InitFolder() {
66
- if (!Directory.Exists (FolderName)) {
67
- Directory.CreateDirectory(FolderName);
47
+ public string ScreenShotName(string filename, bool png) {
48
+ return FolderName + filename + (png?".png":".jpg");
49
+ }
50
+
51
+ public string FolderName{
52
+ get {
53
+ string folderPath = Application.persistentDataPath;
54
+ switch (saveFolder) {
55
+ case SaveFolder.PicturesFolder:
56
+ folderPath = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
57
+ break;
58
+ case SaveFolder.Documents:
59
+ folderPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
60
+ break;
61
+ };
62
+ return string.Format("{0}/Airship/", folderPath);
63
+ }
68
64
  }
69
- }
70
65
 
71
- public static void TakeScreenshot(string fileName = "", int superSampleSize = 1, bool png = true) {
72
- Instance.InitFolder();
73
- Instance.StartCoroutine(Instance.TakeScreenshotCo(fileName, superSampleSize, png));
74
- }
66
+ private void InitFolder() {
67
+ if (!Directory.Exists (FolderName)) {
68
+ Directory.CreateDirectory(FolderName);
69
+ }
70
+ }
75
71
 
76
- private IEnumerator TakeScreenshotCo(string fileName = "", int superSampleSize = 1, bool png = true) {
77
- //Have to capture at end of frame for ScreenCapture to work
78
- yield return new WaitForEndOfFrame();
79
- screenShot = ScreenCapture.CaptureScreenshotAsTexture(superSampleSize);
80
- SaveScreenshot(fileName, superSampleSize, png);
81
- }
72
+ public static void TakeScreenshot(string fileName = "", int superSampleSize = 1, bool png = true) {
73
+ Instance.InitFolder();
74
+ Instance.StartCoroutine(Instance.TakeScreenshotCo(fileName, superSampleSize, png));
75
+ }
82
76
 
77
+ private IEnumerator TakeScreenshotCo(string fileName = "", int superSampleSize = 1, bool png = true) {
78
+ //Have to capture at end of frame for ScreenCapture to work
79
+ yield return new WaitForEndOfFrame();
80
+ screenShot = ScreenCapture.CaptureScreenshotAsTexture(superSampleSize);
81
+ SaveScreenshot(fileName, superSampleSize, png);
82
+ }
83
83
 
84
- public static void TakeCameraScreenshot(Camera camera, string fileName = "", int superSampleSize = 1) {
85
- Instance.InitFolder();
86
- Instance.StartCoroutine(Instance.TakeCameraScreenshotCo(camera, fileName, superSampleSize));
87
- }
88
84
 
89
- public IEnumerator TakeCameraScreenshotCo(Camera camera, string fileName = "", int superSampleSize = 1) {
90
- bool enabled = camera.enabled;
91
-
92
- // Have to capture at end of frame for ScreenCapture to work
93
- yield return new WaitForEndOfFrame();
94
-
95
- try {
96
- screenShot = new Texture2D(resWidth * superSampleSize, resHeight * superSampleSize, TextureFormat.RGB24, false);
97
- rt = new RenderTexture(resWidth, resHeight, resDepth);
98
- camera.enabled = true;
99
- camera.targetTexture = rt;
100
- camera.Render();
101
- RenderTexture.active = rt;
102
- } catch(Exception e) {
103
- Debug.LogError("Error saving: " + e.Message);
85
+ public static void TakeCameraScreenshot(Camera camera, string fileName = "", int superSampleSize = 1) {
86
+ Instance.InitFolder();
87
+ Instance.StartCoroutine(Instance.TakeCameraScreenshotCo(camera, fileName, superSampleSize));
104
88
  }
105
89
 
106
- // Save the render textures data to a texture2D
107
- screenShot.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
108
- camera.targetTexture = null;
109
- RenderTexture.active = null;
110
- rt.Release();
111
- rt = null;
112
-
113
- if (shouldSaveCaptures) {
114
- SaveScreenshot(fileName, superSampleSize, true);
115
- }
90
+ public IEnumerator TakeCameraScreenshotCo(Camera camera, string fileName = "", int superSampleSize = 1) {
91
+ bool enabled = camera.enabled;
92
+
93
+ // Have to capture at end of frame for ScreenCapture to work
94
+ yield return new WaitForEndOfFrame();
116
95
 
117
- if(onPictureTaken != null){
118
- onPictureTaken(screenShot);
96
+ try {
97
+ screenShot = new Texture2D(resWidth * superSampleSize, resHeight * superSampleSize, TextureFormat.RGB24, false);
98
+ rt = new RenderTexture(resWidth, resHeight, resDepth);
99
+ camera.enabled = true;
100
+ camera.targetTexture = rt;
101
+ camera.Render();
102
+ RenderTexture.active = rt;
103
+ } catch(Exception e) {
104
+ Debug.LogError("Error saving: " + e.Message);
105
+ }
106
+
107
+ // Save the render textures data to a texture2D
108
+ screenShot.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
109
+ camera.targetTexture = null;
110
+ RenderTexture.active = null;
111
+ rt.Release();
112
+ rt = null;
113
+
114
+ if (shouldSaveCaptures) {
115
+ SaveScreenshot(fileName, superSampleSize, true);
116
+ }
117
+
118
+ if(onPictureTaken != null){
119
+ onPictureTaken(screenShot);
120
+ }
121
+ camera.enabled = enabled;
119
122
  }
120
- camera.enabled = enabled;
121
- }
122
123
 
123
- private void SaveScreenshot(string fileName, int superSampleSize, bool png) {
124
- if (!screenShot || screenShot.width <= 0) {
125
- return;
124
+ private void SaveScreenshot(string fileName, int superSampleSize, bool png) {
125
+ if (!screenShot || screenShot.width <= 0) {
126
+ return;
127
+ }
128
+ SaveTexture(screenShot, fileName, png);
129
+ screenShot.Apply();
126
130
  }
127
- SaveTexture(screenShot, fileName, png);
128
- screenShot.Apply();
129
- }
130
131
 
131
- public CameraScreenshotResponse SaveRenderTexture(RenderTexture rt, string fileName, bool png){
132
- if(!rt){
133
- return new CameraScreenshotResponse();
132
+ public CameraScreenshotResponse SaveRenderTexture(RenderTexture rt, string fileName, bool png){
133
+ if(!rt){
134
+ return new CameraScreenshotResponse();
135
+ }
136
+ RenderTexture.active = rt;
137
+ var texture = new Texture2D(rt.width, rt.height, TextureFormat.RGB24, false);
138
+ texture.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
139
+ RenderTexture.active = null;
140
+ return SaveTexture(texture, fileName, png);
134
141
  }
135
- RenderTexture.active = rt;
136
- var texture = new Texture2D(rt.width, rt.height, TextureFormat.RGB24, false);
137
- texture.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
138
- RenderTexture.active = null;
139
- return SaveTexture(texture, fileName, png);
140
- }
141
-
142
- public CameraScreenshotResponse SaveTexture(Texture2D texture, string fileName, bool png){
143
- try {
144
- //Debug.Log("Saving Texture size: " + texture.width +", " + texture.height);
145
- string filePath = string.IsNullOrEmpty(fileName)
146
- ? ScreenShotName(texture.width, texture.height, png)
147
- : ScreenShotName(fileName, png);
148
- byte[] bytes = png ? texture.EncodeToPNG() : texture.EncodeToJPG();
149
- string directoryPath = Path.GetDirectoryName(filePath);
150
- if(!Directory.Exists(directoryPath)){
151
- Directory.CreateDirectory(directoryPath);
142
+
143
+ public CameraScreenshotResponse SaveTexture(Texture2D texture, string fileName, bool png){
144
+ try {
145
+ //Debug.Log("Saving Texture size: " + texture.width +", " + texture.height);
146
+ string filePath = string.IsNullOrEmpty(fileName)
147
+ ? ScreenShotName(texture.width, texture.height, png)
148
+ : ScreenShotName(fileName, png);
149
+ byte[] bytes = png ? texture.EncodeToPNG() : texture.EncodeToJPG();
150
+ string directoryPath = Path.GetDirectoryName(filePath);
151
+ if(!Directory.Exists(directoryPath)){
152
+ Directory.CreateDirectory(directoryPath);
153
+ }
154
+ File.WriteAllBytes(filePath, bytes);
155
+ Debug.Log(string.Format("Saved screenshot to: {0}", filePath));
156
+ return new CameraScreenshotResponse(){filesize = bytes.Length, extension = Path.GetExtension(filePath), path = filePath};
157
+ } catch (Exception e) {
158
+ Debug.LogError("Error saving texture: " + e.Message);
152
159
  }
153
- File.WriteAllBytes(filePath, bytes);
154
- Debug.Log(string.Format("Saved screenshot to: {0}", filePath));
155
- return new CameraScreenshotResponse(){filesize = bytes.Length, extension = Path.GetExtension(filePath), path = filePath};
156
- } catch (Exception e) {
157
- Debug.LogError("Error saving texture: " + e.Message);
160
+ return new CameraScreenshotResponse();
158
161
  }
159
- return new CameraScreenshotResponse();
160
- }
162
+ }
161
163
  }
162
164
 
163
165
 
@@ -9,6 +9,7 @@ namespace Code.Platform.Shared {
9
9
  public static string dataStoreService = "https://data-store-service-fxy2zritya-uc.a.run.app";
10
10
  public static string deploymentService = "https://deployment-service-fxy2zritya-uc.a.run.app";
11
11
  public static string analyticsService = "https://analytics-service-fxy2zritya-uc.a.run.app";
12
+ public static string moderationService = "https://moderation-service-fxy2zritya-uc.a.run.app";
12
13
  public static string gameCdn = "https://gcdn-staging.easy.gg";
13
14
  public static string cdn = "https://cdn-staging.easy.gg";
14
15
  public static string mainWeb = "https://staging.airship.gg";
@@ -20,6 +21,7 @@ namespace Code.Platform.Shared {
20
21
  public static string dataStoreService = "https://api-staging.airship.gg/data-store";
21
22
  public static string deploymentService = "https://api-staging.airship.gg/deployment";
22
23
  public static string analyticsService = "https://api-staging.airship.gg/analytics";
24
+ public static string moderationService = "https://api-staging.airship.gg/moderation";
23
25
  public static string gameCdn = "https://gcdn-staging.easy.gg";
24
26
  public static string cdn = "https://cdn-staging.easy.gg";
25
27
  public static string mainWeb = "https://staging.airship.gg";
@@ -33,6 +35,7 @@ namespace Code.Platform.Shared {
33
35
  public static string dataStoreService = "https://data-store-service-hwcvz2epka-uc.a.run.app";
34
36
  public static string deploymentService = "https://deployment-service-hwcvz2epka-uc.a.run.app";
35
37
  public static string analyticsService = "https://analytics-service-hwcvz2epka-uc.a.run.app";
38
+ public static string moderationService = "https://moderation-service-hwcvz2epka-uc.a.run.app";
36
39
  public static string cdn = "https://cdn.airship.gg";
37
40
  public static string gameCdn = "https://gcdn.airship.gg";
38
41
  public static string mainWeb = "https://airship.gg";
@@ -44,6 +47,7 @@ namespace Code.Platform.Shared {
44
47
  public static string dataStoreService = "https://api.airship.gg/data-store";
45
48
  public static string deploymentService = "https://api.airship.gg/deployment";
46
49
  public static string analyticsService = "https://api.airship.gg/analytics";
50
+ public static string moderationService = "https://api.airship.gg/moderation";
47
51
  public static string cdn = "https://cdn.airship.gg";
48
52
  public static string gameCdn = "https://gcdn.airship.gg";
49
53
  public static string mainWeb = "https://airship.gg";
@@ -89,6 +89,7 @@ namespace CsToTs.TypeScript {
89
89
  // }
90
90
  type.GetGenericArguments().ToList().ForEach(t => {
91
91
  if (t.Name == type.Name) return;
92
+ if (t.Name == "InternalCameraScreenshotRecorder") return;
92
93
  PopulateTypeDefinition(t, context);
93
94
  });
94
95
  type = type.GetGenericTypeDefinition();
@@ -138,7 +138,6 @@ public class TypeGenerator : MonoBehaviour
138
138
  typeof(LayoutRebuilder),
139
139
  typeof(RectTransformUtility),
140
140
  typeof(ScrollRect),
141
- typeof(InternalCameraScreenshotRecorder),
142
141
  typeof(Ray),
143
142
  typeof(MaterialPropertyBlock),
144
143
  typeof(DevConsole),
@@ -259,6 +258,7 @@ public class TypeGenerator : MonoBehaviour
259
258
  typeof(AirshipPredictionManager),
260
259
  typeof(VisualGraphComponent),
261
260
  typeof(AccessoryAddMode),
261
+ typeof(RectMask2D),
262
262
 
263
263
  // Steam
264
264
  typeof(AirshipSteamFriendInfo),
@@ -332,6 +332,7 @@ public class TypeGenerator : MonoBehaviour
332
332
  "\\.MonoBehaviour$",
333
333
  "\\.InternalCameraScreenshotRecorder$",
334
334
  "\\.OcclusionCam$",
335
+ "\\.AirshipUniVoiceNetwork$"
335
336
  };
336
337
 
337
338
  var options = new TypeScriptOptions
@@ -35,8 +35,14 @@ namespace Code.VoiceChat {
35
35
  public event Action<short, ChatroomAudioSegment> OnAudioReceived;
36
36
  public event Action<ChatroomAudioSegment> OnAudioBroadcasted;
37
37
 
38
+ // connectionId (int), speakingLevel (float)
39
+ public event Action<object, object> onPlayerSpeakingLevel;
40
+
41
+ // speakingLevel (float)
42
+ public event Action<object> onLocalSpeakingLevel;
43
+
38
44
  // Peer ID management
39
- public short OwnID { get; private set; } = -1;
45
+ public short LocalPeerId { get; private set; } = -1;
40
46
  public List<short> PeerIDs { get; private set; } = new List<short>();
41
47
 
42
48
  // UniVoice peer ID <-> FishNet connection ID mapping
@@ -47,6 +53,7 @@ namespace Code.VoiceChat {
47
53
 
48
54
  private uint audioNonce = 0;
49
55
 
56
+
50
57
  private void OnDisable() {
51
58
  if (this.agent != null) {
52
59
  this.agent.Dispose();
@@ -101,15 +108,15 @@ namespace Code.VoiceChat {
101
108
  void OnDestroy() {
102
109
  // If the client disconnects while own ID is -1, that means
103
110
  // it haven't connected earlier and the connection attempt has failed.
104
- if (OwnID == -1) {
111
+ if (LocalPeerId == -1) {
105
112
  OnChatroomJoinFailed?.Invoke(new Exception("Could not join chatroom"));
106
113
  return;
107
114
  }
108
115
 
109
116
  // This method is *also* called on the server when the server is shutdown.
110
117
  // So we check peer ID to ensure that we're running this only on a peer.
111
- if (OwnID >= 0) {
112
- OwnID = -1;
118
+ if (LocalPeerId >= 0) {
119
+ LocalPeerId = -1;
113
120
  PeerIDs.Clear();
114
121
  peerIdToConnectionIdMap.Clear();
115
122
  OnLeftChatroom?.Invoke();
@@ -124,8 +131,8 @@ namespace Code.VoiceChat {
124
131
  this.Log($"Initialized self with PeerId {assignedPeerId} and peers: {string.Join(", ", existingPeers)}");
125
132
 
126
133
  // Get self ID and fire that joined chatroom event
127
- OwnID = assignedPeerId;
128
- OnJoinedChatroom?.Invoke(OwnID);
134
+ LocalPeerId = assignedPeerId;
135
+ OnJoinedChatroom?.Invoke(LocalPeerId);
129
136
 
130
137
  for (int i = 0; i < existingPeers.Length; i++) {
131
138
  this.peerIdToConnectionIdMap[existingPeers[i]] = existingPeerConnectionIds[i];
@@ -282,23 +289,58 @@ namespace Code.VoiceChat {
282
289
  RpcSendAudioToClient(senderPeerId, bytes, this.audioNonce);
283
290
 
284
291
  if (Application.isEditor) {
285
- var segment = FromByteArray<ChatroomAudioSegment>(bytes);
286
- OnAudioReceived?.Invoke(senderPeerId, segment);
292
+ this.EmitAudioInScene(senderPeerId, bytes);
287
293
  }
288
294
  }
289
295
 
290
296
  [ClientRpc(channel = Channels.Reliable)]
291
297
  void RpcSendAudioToClient(short senderPeerId, byte[] bytes, uint nonce) {
292
298
  // print($"[client] received audio from server for peer {senderPeerId}. Frame={Time.frameCount} Nonce={nonce}");
299
+ this.EmitAudioInScene(senderPeerId, bytes);
300
+ }
301
+
302
+ private void EmitAudioInScene(short senderPeerId, byte[] bytes) {
293
303
  var segment = FromByteArray<ChatroomAudioSegment>(bytes);
304
+
305
+ if (senderPeerId == LocalPeerId && RunCore.IsClient() && NetworkClient.isConnected) {
306
+ // Local speaking
307
+ var speakingLevel = this.ComputeSpeakingLevel(segment.samples);
308
+ onLocalSpeakingLevel?.Invoke(speakingLevel);
309
+ return;
310
+ }
311
+
294
312
  OnAudioReceived?.Invoke(senderPeerId, segment);
313
+
314
+ NetworkConnection connection = this.GetNetworkConnectionFromPeerId(senderPeerId);
315
+ if (connection != null) {
316
+ var speakingLevel = this.ComputeSpeakingLevel(segment.samples);
317
+ onPlayerSpeakingLevel?.Invoke(connection.connectionId, speakingLevel);
318
+ }
295
319
  }
320
+
321
+ private float ComputeSpeakingLevel(float[] samples) {
322
+ float sum = 0f;
323
+ for (int i = 0; i < samples.Length; i++) {
324
+ sum += samples[i] * samples[i];
325
+ }
326
+ float rms = Mathf.Sqrt(sum / samples.Length);
327
+ return Mathf.Clamp01(rms * 10f); // scale up and clamp
328
+ }
329
+
330
+ // public float GetSpeakingLevel(int connectionId) {
331
+ // if (this.playerConnectionIdToSpeakingLevelMap.TryGetValue(connectionId, out var speakingLevel)) {
332
+ // return speakingLevel;
333
+ // }
334
+ // return 0;
335
+ // }
296
336
 
297
337
  public void BroadcastAudioSegment(ChatroomAudioSegment data) {
298
338
  if (!NetworkClient.isConnected) return;
299
339
 
300
340
  if (isClient) {
301
- RpcSendAudioToServer(ToByteArray(data));
341
+ var bytes = ToByteArray(data);
342
+ this.EmitAudioInScene(LocalPeerId, bytes);
343
+ RpcSendAudioToServer(bytes);
302
344
  }
303
345
 
304
346
  OnAudioBroadcasted?.Invoke(data);
@@ -331,7 +373,6 @@ namespace Code.VoiceChat {
331
373
  if (NetworkServer.connections.TryGetValue(peerIdToConnectionIdMap[peerId], out var connection)) {
332
374
  return connection;
333
375
  }
334
-
335
376
  return null;
336
377
  }
337
378