diff --git a/Assets/PurrLobby/Editor/LobbyManagerEditor.cs b/Assets/PurrLobby/Editor/LobbyManagerEditor.cs index 8949778..54b6776 100644 --- a/Assets/PurrLobby/Editor/LobbyManagerEditor.cs +++ b/Assets/PurrLobby/Editor/LobbyManagerEditor.cs @@ -1,5 +1,7 @@ #if UNITY_EDITOR +using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using UnityEditor; using UnityEngine; @@ -9,11 +11,22 @@ namespace PurrLobby.Editor [CustomEditor(typeof(LobbyManager))] public class LobbyManagerEditor : UnityEditor.Editor { + private bool showLobbyCodeEncoder = false; private bool showCreateRoomArgs = false; private bool showSearchRoomArgs = false; private bool showEvents = false; private bool showRoomStatus = true; private Dictionary memberFoldouts = new Dictionary(); + private string[] encoderNames; + + private void OnEnable() + { + var encoderTypes = LobbyCode.GetEncoderTypes(); + if (encoderTypes.Count > 0) + { + encoderNames = encoderTypes.Select(t => t.Name).ToArray(); + } + } public override void OnInspectorGUI() { @@ -23,8 +36,12 @@ public override void OnInspectorGUI() EditorGUILayout.Space(); + DrawEncoderDropdown(); + + EditorGUILayout.Space(); + DrawCreateRoomArgs(); - + EditorGUILayout.Space(); DrawSearchRoomArgs(); @@ -40,7 +57,7 @@ public override void OnInspectorGUI() private void DrawProviderDropdown(LobbyManager lobbyManager) { - var providers = FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None); + var providers = FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None); var providerOptions = new List(); foreach (var provider in providers) { @@ -73,6 +90,37 @@ private void DrawProviderDropdown(LobbyManager lobbyManager) } } + private void DrawEncoderDropdown() + { + var serializedObject = new SerializedObject(target); + var lobbyCodeProp = serializedObject.FindProperty("lobbyCodeEncoderType"); + if (lobbyCodeProp != null) + { + showLobbyCodeEncoder = EditorGUILayout.Foldout(showLobbyCodeEncoder, "Lobby Code Encoder", true); + if (showLobbyCodeEncoder) + { + EditorGUI.indentLevel++; + if (encoderNames.Length == 0) + { + EditorGUILayout.HelpBox($"No implementation of {nameof(IBaseEncoder)} found.", MessageType.Warning); + return; + } + + EditorGUI.BeginChangeCheck(); + var encoderSelectedIndex = Array.IndexOf(encoderNames, lobbyCodeProp.stringValue); + encoderSelectedIndex = EditorGUILayout.Popup("Type", encoderSelectedIndex, encoderNames); + if (EditorGUI.EndChangeCheck()) + { + lobbyCodeProp.stringValue = encoderNames[encoderSelectedIndex]; + serializedObject.ApplyModifiedProperties(); + } + EditorGUI.indentLevel--; + } + } + + serializedObject.ApplyModifiedProperties(); + } + private void DrawCreateRoomArgs() { var serializedObject = new SerializedObject(target); @@ -159,13 +207,13 @@ private void DrawRoomStatus(LobbyManager lobbyManager) if (!lobbyManager) return; - + var currentRoom = lobbyManager.CurrentLobby; if (currentRoom.IsValid) { EditorGUILayout.LabelField("Room ID:", currentRoom.LobbyId); - if(!string.IsNullOrWhiteSpace(currentRoom.LobbyCode)) + if (!string.IsNullOrWhiteSpace(currentRoom.LobbyCode)) { EditorGUILayout.LabelField("Lobby Code:", currentRoom.LobbyCode); } diff --git a/Assets/PurrLobby/LobbyScenes/LobbySample.unity b/Assets/PurrLobby/LobbyScenes/LobbySample.unity index 107e76f..04b2c98 100644 --- a/Assets/PurrLobby/LobbyScenes/LobbySample.unity +++ b/Assets/PurrLobby/LobbyScenes/LobbySample.unity @@ -2401,6 +2401,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: currentProvider: {fileID: 1097043114} + lobbyCodeEncoderType: Base36Encoder createRoomArgs: maxPlayers: 5 roomProperties: diff --git a/Assets/PurrLobby/Runtime/Lobby/Lobby.cs b/Assets/PurrLobby/Runtime/Lobby/Lobby.cs index d2b4c4a..b862f1a 100644 --- a/Assets/PurrLobby/Runtime/Lobby/Lobby.cs +++ b/Assets/PurrLobby/Runtime/Lobby/Lobby.cs @@ -40,6 +40,7 @@ public static Lobby Create(string name, string lobbyId, int maxPlayers, bool isO Name = name, IsValid = true, LobbyId = lobbyId, + LobbyCode = LobbyCode.Encode(uint.Parse(lobbyId)), MaxPlayers = maxPlayers, Properties = properties ?? new Dictionary(), IsOwner = isOwner, diff --git a/Assets/PurrLobby/Runtime/Lobby/LobbyCode.cs b/Assets/PurrLobby/Runtime/Lobby/LobbyCode.cs new file mode 100644 index 0000000..810f43d --- /dev/null +++ b/Assets/PurrLobby/Runtime/Lobby/LobbyCode.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace PurrLobby +{ + public static class LobbyCode + { + private static IBaseEncoder encoder; + public static string Encode(ulong value) + { + if (encoder == null) + { + return value.ToString(); + } + return encoder.Encode(value); + } + public static ulong Decode(string value) + { + if (encoder == null) + { + return ulong.Parse(value); + } + return encoder.Decode(value); + } + + public static List GetEncoderTypes() => + AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(a => a.GetTypes()) + .Where(t => typeof(IBaseEncoder).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract) + .ToList(); + + public static void AssignEncoder(string name) + { + if (string.IsNullOrEmpty(name)) + { + return; + } + var types = GetEncoderTypes(); + var type = types.FirstOrDefault(t => t.Name == name); + if (type == null) + { + return; + } + encoder = (IBaseEncoder)Activator.CreateInstance(type); + } + } +} \ No newline at end of file diff --git a/Assets/PurrLobby/Runtime/Lobby/LobbyCode.cs.meta b/Assets/PurrLobby/Runtime/Lobby/LobbyCode.cs.meta new file mode 100644 index 0000000..f336376 --- /dev/null +++ b/Assets/PurrLobby/Runtime/Lobby/LobbyCode.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ea608e60a23615f47af8a5a0b5251053 \ No newline at end of file diff --git a/Assets/PurrLobby/Runtime/Lobby/LobbyManager.cs b/Assets/PurrLobby/Runtime/Lobby/LobbyManager.cs index cfb297d..c99b086 100644 --- a/Assets/PurrLobby/Runtime/Lobby/LobbyManager.cs +++ b/Assets/PurrLobby/Runtime/Lobby/LobbyManager.cs @@ -11,6 +11,7 @@ namespace PurrLobby public class LobbyManager : MonoBehaviour { [SerializeField] private MonoBehaviour currentProvider; + [SerializeField] private string lobbyCodeEncoderType; private ILobbyProvider _currentProvider; private readonly Queue _delayedActions = new Queue(); @@ -64,6 +65,8 @@ private void Awake() else PurrLogger.LogWarning("No lobby provider assigned to LobbyManager."); + LobbyCode.AssignEncoder(lobbyCodeEncoderType); + SetupDataHolder(); } @@ -268,6 +271,16 @@ public void LeaveLobby(string lobbyId) OnRoomLeft?.Invoke(); }); } + + /// + /// Join the lobby with the given lobby code + /// + /// lobby code of the lobby to join + public void JoinLobbyByCode(string lobbyCode) + { + var roomId = LobbyCode.Decode(lobbyCode); + JoinLobby(roomId.ToString()); + } /// /// Join the lobby with the given ID diff --git a/Assets/PurrLobby/Runtime/Misc/Encoders.meta b/Assets/PurrLobby/Runtime/Misc/Encoders.meta new file mode 100644 index 0000000..97b63e2 --- /dev/null +++ b/Assets/PurrLobby/Runtime/Misc/Encoders.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5c0f9035c43177b4f992fe6b08bab529 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PurrLobby/Runtime/Misc/Encoders/Base36Encoder.cs b/Assets/PurrLobby/Runtime/Misc/Encoders/Base36Encoder.cs new file mode 100644 index 0000000..3f62b96 --- /dev/null +++ b/Assets/PurrLobby/Runtime/Misc/Encoders/Base36Encoder.cs @@ -0,0 +1,52 @@ +using System; + +namespace PurrLobby +{ + public class Base36Encoder : IBaseEncoder + { + private const int MaxBase36Length = 13; // ceil(log36(ulong.MaxValue)) + private const string Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + public string Encode(ulong value) + { + if (value == 0) + { + return "0"; + } + + var result = ""; + while (value > 0) + { + result = Chars[(int)(value % 36)] + result; + value /= 36; + } + return result; + } + + public ulong Decode(string value) + { + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentException("Value cannot be null or empty."); + } + + value = value.ToUpper().Trim(); + if (value.Length > MaxBase36Length) + { + throw new FormatException($"Input exceeds maximum Base36 length ({MaxBase36Length})."); + } + + ulong result = 0; + foreach (var c in value) + { + var digit = Chars.IndexOf(c); + if (digit < 0) + { + throw new FormatException($"Invalid Base36 character: {c}"); + } + result = result * 36 + (ulong)digit; + } + return result; + } + } +} \ No newline at end of file diff --git a/Assets/PurrLobby/Runtime/Misc/Encoders/Base36Encoder.cs.meta b/Assets/PurrLobby/Runtime/Misc/Encoders/Base36Encoder.cs.meta new file mode 100644 index 0000000..912762f --- /dev/null +++ b/Assets/PurrLobby/Runtime/Misc/Encoders/Base36Encoder.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2ccd612de43709a45b011ebcd9adb91d \ No newline at end of file diff --git a/Assets/PurrLobby/Runtime/Misc/Encoders/Base62Encoder.cs b/Assets/PurrLobby/Runtime/Misc/Encoders/Base62Encoder.cs new file mode 100644 index 0000000..60899de --- /dev/null +++ b/Assets/PurrLobby/Runtime/Misc/Encoders/Base62Encoder.cs @@ -0,0 +1,51 @@ +using System; + +namespace PurrLobby +{ + public class Base62Encoder : IBaseEncoder + { + private const int MaxBase62Length = 11; // ceil(log62(ulong.MaxValue)) + private const string Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + public string Encode(ulong value) + { + if (value == 0) + { + return "0"; + } + + var result = ""; + while (value > 0) + { + result = Chars[(int)(value % 62)] + result; + value /= 62; + } + return result; + } + + public ulong Decode(string value) + { + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentException("Value cannot be null or empty."); + } + + if (value.Length > MaxBase62Length) + { + throw new FormatException($"Input exceeds maximum Base62 length ({MaxBase62Length})."); + } + + ulong result = 0; + foreach (var c in value) + { + var digit = Chars.IndexOf(c); + if (digit < 0) + { + throw new FormatException($"Invalid Base62 character: {c}"); + } + result = result * 62 + (ulong)digit; + } + return result; + } + } +} \ No newline at end of file diff --git a/Assets/PurrLobby/Runtime/Misc/Encoders/Base62Encoder.cs.meta b/Assets/PurrLobby/Runtime/Misc/Encoders/Base62Encoder.cs.meta new file mode 100644 index 0000000..e8fc536 --- /dev/null +++ b/Assets/PurrLobby/Runtime/Misc/Encoders/Base62Encoder.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3b6986bb8c055a2459b407a27decd1b1 \ No newline at end of file diff --git a/Assets/PurrLobby/Runtime/Misc/Encoders/IBaseEncoder.cs b/Assets/PurrLobby/Runtime/Misc/Encoders/IBaseEncoder.cs new file mode 100644 index 0000000..8a10f0f --- /dev/null +++ b/Assets/PurrLobby/Runtime/Misc/Encoders/IBaseEncoder.cs @@ -0,0 +1,8 @@ +namespace PurrLobby +{ + public interface IBaseEncoder + { + public string Encode(ulong value); + public ulong Decode(string value); + } +} \ No newline at end of file diff --git a/Assets/PurrLobby/Runtime/Misc/Encoders/IBaseEncoder.cs.meta b/Assets/PurrLobby/Runtime/Misc/Encoders/IBaseEncoder.cs.meta new file mode 100644 index 0000000..91bae2b --- /dev/null +++ b/Assets/PurrLobby/Runtime/Misc/Encoders/IBaseEncoder.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a01f3e2b3475440408fe722c2488a963 \ No newline at end of file diff --git a/Assets/PurrLobby/Runtime/Misc/UI/JoinButton.cs b/Assets/PurrLobby/Runtime/Misc/UI/JoinButton.cs index 2d72ea0..1a42f2f 100644 --- a/Assets/PurrLobby/Runtime/Misc/UI/JoinButton.cs +++ b/Assets/PurrLobby/Runtime/Misc/UI/JoinButton.cs @@ -19,7 +19,7 @@ public void JoinRoom() } onStartJoin?.Invoke(); - lobbyManager.JoinLobby(roomIdInput.text); + lobbyManager.JoinLobbyByCode(roomIdInput.text); } } } diff --git a/Assets/PurrLobby/Runtime/Misc/UI/LobbyEntry.cs b/Assets/PurrLobby/Runtime/Misc/UI/LobbyEntry.cs index 1c1d3ce..8f62ef1 100644 --- a/Assets/PurrLobby/Runtime/Misc/UI/LobbyEntry.cs +++ b/Assets/PurrLobby/Runtime/Misc/UI/LobbyEntry.cs @@ -21,7 +21,7 @@ public void Init(Lobby room, LobbyManager lobbyManager) public void OnClick() { - _lobbyManager.JoinLobby(_room.LobbyId); + _lobbyManager.JoinLobbyByCode(_room.LobbyCode); } } } diff --git a/Assets/PurrLobby/Runtime/Providers/SteamLobbyProvider.cs b/Assets/PurrLobby/Runtime/Providers/SteamLobbyProvider.cs index 7ab06f4..c8af870 100644 --- a/Assets/PurrLobby/Runtime/Providers/SteamLobbyProvider.cs +++ b/Assets/PurrLobby/Runtime/Providers/SteamLobbyProvider.cs @@ -113,6 +113,7 @@ public async Task CreateLobbyAsync(int maxPlayers, Dictionary JoinLobbyAsync(string lobbyId) var lobby = LobbyFactory.Create( Steamworks.SteamMatchmaking.GetLobbyData(_currentLobby, "Name"), lobbyId, + LobbyCode.Encode(cLobbyId.m_SteamID), Steamworks.SteamMatchmaking.GetLobbyMemberLimit(_currentLobby), false, GetLobbyUsers(cLobbyId), @@ -570,6 +572,7 @@ private void OnLobbyDataUpdate(Steamworks.LobbyDataUpdate_t callback) var updatedLobby = LobbyFactory.Create( Steamworks.SteamMatchmaking.GetLobbyData(_currentLobby, "Name"), _currentLobby.m_SteamID.ToString(), + LobbyCode.Encode(_currentLobby.m_SteamID), Steamworks.SteamMatchmaking.GetLobbyMemberLimit(_currentLobby), isOwner, updatedLobbyUsers, @@ -640,6 +643,7 @@ private void OnLobbyChatUpdate(Steamworks.LobbyChatUpdate_t callback) var updatedLobby = LobbyFactory.Create( data, _currentLobby.m_SteamID.ToString(), + LobbyCode.Encode(_currentLobby.m_SteamID), Steamworks.SteamMatchmaking.GetLobbyMemberLimit(_currentLobby), isOwner, updatedLobbyUsers, diff --git a/Assets/PurrLobby/Runtime/ViewManagement/Views/LobbyView.cs b/Assets/PurrLobby/Runtime/ViewManagement/Views/LobbyView.cs index ffc1002..dea88fb 100644 --- a/Assets/PurrLobby/Runtime/ViewManagement/Views/LobbyView.cs +++ b/Assets/PurrLobby/Runtime/ViewManagement/Views/LobbyView.cs @@ -9,7 +9,7 @@ public class LobbyView : View public override void OnShow() { - codeButton.Init(lobbyManager.CurrentLobby.LobbyId); + codeButton.Init(lobbyManager.CurrentLobby.LobbyCode); } } }