From 5716fe6b3168fb5c131bfd145469f4827a19fb73 Mon Sep 17 00:00:00 2001 From: "nicogo.eth" Date: Wed, 8 Oct 2025 19:55:31 +0200 Subject: [PATCH 1/5] a little auto refactor --- Multibonk/Game/EventHandlerExecutor.cs | 1 - Multibonk/Game/GameFunctions.cs | 1 - Multibonk/Game/GamePatchFlags.cs | 2 +- Multibonk/Game/Handlers/GameDispatcher.cs | 4 +- ...dateNetworkPlayerAnimationsEventHandler.cs | 6 +- .../NetworkNotify/GameLoadedEventHandler.cs | 5 - .../PlayerMovementEventHandler.cs | 6 +- Multibonk/Game/Patches/MainMenuPatches.cs | 5 +- Multibonk/Multibonk.cs | 20 +- Multibonk/Multibonk.csproj | 907 +++++++++--------- Multibonk/Networking/Comms/Base/Connection.cs | 6 +- Multibonk/Networking/Comms/Base/IProtocol.cs | 4 +- .../Comms/Base/Packet/GameLoadedPacket.cs | 3 +- .../Base/Packet/LobbyPlayerListPacket.cs | 2 +- .../Comms/Base/Packet/PlayerMovedPacket.cs | 4 +- .../Packet/PlayerSelectedCharacterPacket.cs | 4 +- Multibonk/Networking/Comms/Base/PacketId.cs | 18 +- .../Handlers/LobbyPlayerListPacketHandler.cs | 6 +- .../Handlers/PlayerRotatedPacketHandler.cs | 8 +- .../PlayerSelectedCharacterPacketHandler.cs | 4 +- .../Handlers/SpawnPlayerPacketHandler.cs | 7 +- .../Client/Handlers/StartGamePacketHandler.cs | 10 +- .../Networking/Comms/Client/NetworkClient.cs | 11 +- Multibonk/Networking/Comms/NetworkService.cs | 4 +- .../Handlers/GameLoadedPacketHandler.cs | 2 +- .../Handlers/PlayerMovePacketHandler.cs | 10 +- .../Handlers/PlayerRotatePacketHandler.cs | 8 +- .../Handlers/SelectCharacterPacketHandler.cs | 6 +- Multibonk/Networking/Comms/Server/Listener.cs | 7 +- .../Comms/Server/Protocols/ServerProtocol.cs | 3 +- Multibonk/Networking/Lobby/LobbyContext.cs | 3 +- Multibonk/Properties/MelonInfo.cs | 2 +- Multibonk/UserInterface/UIManager.cs | 13 +- Multibonk/UserInterface/Utils.cs | 3 +- .../UserInterface/Window/ClientLobbyWindow.cs | 5 +- .../UserInterface/Window/ConnectionWindow.cs | 10 +- Multibonk/UserInterface/WindowBase.cs | 8 +- 37 files changed, 551 insertions(+), 577 deletions(-) diff --git a/Multibonk/Game/EventHandlerExecutor.cs b/Multibonk/Game/EventHandlerExecutor.cs index e5ef6d5..11f7d9c 100644 --- a/Multibonk/Game/EventHandlerExecutor.cs +++ b/Multibonk/Game/EventHandlerExecutor.cs @@ -1,5 +1,4 @@ using Multibonk.Game.Handlers; -using UnityEngine.UIElements; namespace Multibonk.Game { diff --git a/Multibonk/Game/GameFunctions.cs b/Multibonk/Game/GameFunctions.cs index 23fff51..fbf6301 100644 --- a/Multibonk/Game/GameFunctions.cs +++ b/Multibonk/Game/GameFunctions.cs @@ -101,4 +101,3 @@ public void Rotate(Vector3 rotation) } } } - \ No newline at end of file diff --git a/Multibonk/Game/GamePatchFlags.cs b/Multibonk/Game/GamePatchFlags.cs index 8941104..4f9e5ad 100644 --- a/Multibonk/Game/GamePatchFlags.cs +++ b/Multibonk/Game/GamePatchFlags.cs @@ -15,7 +15,7 @@ public static class GamePatchFlags public static int Seed { get; set; } = _rng.Next(int.MinValue, int.MaxValue); - public static bool AllowStartMapCall { get; set; } = false; + public static bool AllowStartMapCall { get; set; } = false; public static Vector3 LastPlayerPosition { get; set; } public static Quaternion LastPlayerRotation { get; set; } diff --git a/Multibonk/Game/Handlers/GameDispatcher.cs b/Multibonk/Game/Handlers/GameDispatcher.cs index 9b9fe31..d6354d0 100644 --- a/Multibonk/Game/Handlers/GameDispatcher.cs +++ b/Multibonk/Game/Handlers/GameDispatcher.cs @@ -1,5 +1,5 @@ -using System.Collections.Concurrent; -using MelonLoader; +using MelonLoader; +using System.Collections.Concurrent; namespace Multibonk.Game.Handlers { diff --git a/Multibonk/Game/Handlers/Logic/UpdateNetworkPlayerAnimationsEventHandler.cs b/Multibonk/Game/Handlers/Logic/UpdateNetworkPlayerAnimationsEventHandler.cs index 842afc4..2a6ea3b 100644 --- a/Multibonk/Game/Handlers/Logic/UpdateNetworkPlayerAnimationsEventHandler.cs +++ b/Multibonk/Game/Handlers/Logic/UpdateNetworkPlayerAnimationsEventHandler.cs @@ -6,15 +6,15 @@ namespace Multibonk.Game.Handlers.Logic { public class UpdateNetworkPlayerAnimationsEventHandler : GameEventHandler { - public UpdateNetworkPlayerAnimationsEventHandler() - { + public UpdateNetworkPlayerAnimationsEventHandler() + { } public override void FixedUpdate() { - foreach(var player in GamePatchFlags.PlayersCache.Values) + foreach (var player in GamePatchFlags.PlayersCache.Values) { if (player.PlayerObject.IsNullOrDestroyed()) continue; diff --git a/Multibonk/Game/Handlers/NetworkNotify/GameLoadedEventHandler.cs b/Multibonk/Game/Handlers/NetworkNotify/GameLoadedEventHandler.cs index 02a111c..a859268 100644 --- a/Multibonk/Game/Handlers/NetworkNotify/GameLoadedEventHandler.cs +++ b/Multibonk/Game/Handlers/NetworkNotify/GameLoadedEventHandler.cs @@ -1,12 +1,7 @@ using Il2Cpp; using Il2CppAssets.Scripts.Actors.Player; -using Il2CppInterop.Runtime.InteropTypes.Arrays; -using MelonLoader; using Multibonk.Networking.Comms.Base.Packet; using Multibonk.Networking.Lobby; -using UnityEngine; -using static Il2Cpp.AnimatedMeshScriptableObject; -using static MelonLoader.MelonLaunchOptions; namespace Multibonk.Game.Handlers.NetworkNotify { diff --git a/Multibonk/Game/Handlers/NetworkNotify/PlayerMovementEventHandler.cs b/Multibonk/Game/Handlers/NetworkNotify/PlayerMovementEventHandler.cs index 6337e1b..bc65f79 100644 --- a/Multibonk/Game/Handlers/NetworkNotify/PlayerMovementEventHandler.cs +++ b/Multibonk/Game/Handlers/NetworkNotify/PlayerMovementEventHandler.cs @@ -1,7 +1,7 @@ -using Multibonk.Networking.Comms.Base.Packet.Multibonk.Networking.Comms.Base.Packet; -using Multibonk.Networking.Comms.Base.Packet; -using Multibonk.Networking.Lobby; +using Multibonk.Networking.Comms.Base.Packet; +using Multibonk.Networking.Comms.Base.Packet.Multibonk.Networking.Comms.Base.Packet; using Multibonk.Networking.Comms.Multibonk.Networking.Comms; +using Multibonk.Networking.Lobby; namespace Multibonk.Game.Handlers.NetworkNotify { diff --git a/Multibonk/Game/Patches/MainMenuPatches.cs b/Multibonk/Game/Patches/MainMenuPatches.cs index 51f4a6c..bab43a0 100644 --- a/Multibonk/Game/Patches/MainMenuPatches.cs +++ b/Multibonk/Game/Patches/MainMenuPatches.cs @@ -1,7 +1,4 @@ - - -using System.Runtime.CompilerServices; -using HarmonyLib; +using HarmonyLib; using Il2Cpp; using Il2CppAssets.Scripts.Actors.Player; using Il2CppRewired.Utils; diff --git a/Multibonk/Multibonk.cs b/Multibonk/Multibonk.cs index 9d1344f..fa78acd 100644 --- a/Multibonk/Multibonk.cs +++ b/Multibonk/Multibonk.cs @@ -1,17 +1,17 @@ using MelonLoader; -using Multibonk.UserInterface.Window; using Microsoft.Extensions.DependencyInjection; -using Multibonk.Networking.Lobby; -using Multibonk.Networking.Comms.Server.Protocols; +using Multibonk.Game; +using Multibonk.Game.Handlers; +using Multibonk.Game.Handlers.Logic; +using Multibonk.Game.Handlers.NetworkNotify; +using Multibonk.Networking.Comms.Base; +using Multibonk.Networking.Comms.Client.Handlers; using Multibonk.Networking.Comms.Client.Protocols; using Multibonk.Networking.Comms.Multibonk.Networking.Comms; using Multibonk.Networking.Comms.Server.Handlers; -using Multibonk.Networking.Comms.Client.Handlers; -using Multibonk.Game.Handlers; -using Multibonk.Game; -using Multibonk.Networking.Comms.Base; -using Multibonk.Game.Handlers.NetworkNotify; -using Multibonk.Game.Handlers.Logic; +using Multibonk.Networking.Comms.Server.Protocols; +using Multibonk.Networking.Lobby; +using Multibonk.UserInterface.Window; namespace Multibonk { @@ -22,7 +22,7 @@ public class MultibonkMod : MelonMod public override void OnGUI() { - if(manager != null) + if (manager != null) manager.OnGUI(); } diff --git a/Multibonk/Multibonk.csproj b/Multibonk/Multibonk.csproj index 5403dd2..309c8f2 100644 --- a/Multibonk/Multibonk.csproj +++ b/Multibonk/Multibonk.csproj @@ -1,462 +1,463 @@  - - net6.0 - enable - disable - + + net6.0 + enable + disable + C:\Program Files (x86)\Steam\steamapps\common\Megabonk\Mods + - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\0Harmony.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\AsmResolver.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\AsmResolver.DotNet.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\AsmResolver.PE.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\AsmResolver.PE.File.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Assembly-CSharp.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\AssetRipper.Primitives.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\AssetsTools.NET.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\bHapticsLib.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Iced.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppCoffee.UIParticle.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2Cppcom.rlabrecque.steamworks.net.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppDiscord.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Il2CppInterop.Common.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Il2CppInterop.Generator.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Il2CppInterop.HarmonySupport.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Il2CppInterop.Runtime.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppMK.Toon.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppMono.Security.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2Cppmscorlib.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppNewtonsoft.Json.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppRewired_Core.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppRewired_Windows.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppSystem.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppSystem.Configuration.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppSystem.Core.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppSystem.Data.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppSystem.Drawing.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppSystem.Numerics.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppSystem.Runtime.Serialization.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppSystem.Xml.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppSystem.Xml.Linq.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2Cpp__Generated.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\IndexRange.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\MelonLoader.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\MelonLoader.NativeHost.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Microsoft.Bcl.AsyncInterfaces.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Microsoft.Diagnostics.NETCore.Client.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Microsoft.Diagnostics.Runtime.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Microsoft.Extensions.Configuration.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Microsoft.Extensions.Configuration.Abstractions.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Microsoft.Extensions.Configuration.Binder.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Microsoft.Extensions.DependencyInjection.Abstractions.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Microsoft.Extensions.Logging.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Microsoft.Extensions.Logging.Abstractions.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Microsoft.Extensions.Options.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Microsoft.Extensions.Primitives.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Mono.Cecil.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Mono.Cecil.Mdb.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Mono.Cecil.Pdb.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Mono.Cecil.Rocks.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\MonoMod.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\MonoMod.Backports.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\MonoMod.ILHelpers.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\MonoMod.RuntimeDetour.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\MonoMod.Utils.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Newtonsoft.Json.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\System.Configuration.ConfigurationManager.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\System.Security.Cryptography.ProtectedData.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\System.Security.Permissions.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\System.Windows.Extensions.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Tomlet.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Unity.Addressables.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Unity.Localization.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Unity.Mathematics.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Unity.Postprocessing.Runtime.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Unity.ProBuilder.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Unity.ProBuilder.KdTree.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Unity.ProBuilder.Poly2Tri.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Unity.ResourceManager.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Unity.Splines.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Unity.TextMeshPro.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.AccessibilityModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.AIModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.AndroidJNIModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.AnimationModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.ARModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.AssetBundleModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.AudioModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.ClothModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.CommandStateObserverModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.ContentLoadModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.CoreModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.CrashReportingModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.DirectorModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.DSPGraphModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.GameCenterModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.GIModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.GraphToolsFoundationModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.GridModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.HierarchyCoreModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.HotReloadModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\UnityEngine.Il2CppAssetBundleManager.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\UnityEngine.Il2CppImageConversionManager.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.ImageConversionModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.IMGUIModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.InputForUIModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.InputLegacyModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.InputModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.JSONSerializeModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.LocalizationModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.MarshallingModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.MultiplayerModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.ParticleSystemModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.PerformanceReportingModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.Physics2DModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.PhysicsModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.ProfilerModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.PropertiesModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.RuntimeInitializeOnLoadManagerInitializerModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.ScreenCaptureModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.SharedInternalsModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.SpriteMaskModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.SpriteShapeModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.StreamingModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.SubstanceModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.SubsystemsModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.TerrainModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.TerrainPhysicsModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.TextCoreFontEngineModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.TextCoreTextEngineModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.TextRenderingModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.TilemapModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.TLSModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UI.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UIElementsModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UIModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UmbraModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UnityAnalyticsCommonModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UnityAnalyticsModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UnityConnectModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UnityCurlModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UnityTestProtocolModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UnityWebRequestAssetBundleModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UnityWebRequestAudioModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UnityWebRequestModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UnityWebRequestTextureModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UnityWebRequestWWWModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.VehiclesModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.VFXModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.VideoModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.VRModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.WindModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.XRModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\WebSocketDotNet.dll - - + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\0Harmony.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\AsmResolver.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\AsmResolver.DotNet.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\AsmResolver.PE.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\AsmResolver.PE.File.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Assembly-CSharp.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\AssetRipper.Primitives.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\AssetsTools.NET.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\bHapticsLib.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Iced.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppCoffee.UIParticle.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2Cppcom.rlabrecque.steamworks.net.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppDiscord.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Il2CppInterop.Common.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Il2CppInterop.Generator.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Il2CppInterop.HarmonySupport.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Il2CppInterop.Runtime.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppMK.Toon.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppMono.Security.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2Cppmscorlib.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppNewtonsoft.Json.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppRewired_Core.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppRewired_Windows.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppSystem.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppSystem.Configuration.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppSystem.Core.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppSystem.Data.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppSystem.Drawing.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppSystem.Numerics.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppSystem.Runtime.Serialization.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppSystem.Xml.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2CppSystem.Xml.Linq.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Il2Cpp__Generated.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\IndexRange.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\MelonLoader.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\MelonLoader.NativeHost.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Microsoft.Bcl.AsyncInterfaces.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Microsoft.Diagnostics.NETCore.Client.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Microsoft.Diagnostics.Runtime.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Microsoft.Extensions.Configuration.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Microsoft.Extensions.Configuration.Abstractions.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Microsoft.Extensions.Configuration.Binder.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Microsoft.Extensions.Logging.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Microsoft.Extensions.Logging.Abstractions.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Microsoft.Extensions.Options.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Microsoft.Extensions.Primitives.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Mono.Cecil.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Mono.Cecil.Mdb.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Mono.Cecil.Pdb.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Mono.Cecil.Rocks.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\MonoMod.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\MonoMod.Backports.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\MonoMod.ILHelpers.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\MonoMod.RuntimeDetour.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\MonoMod.Utils.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Newtonsoft.Json.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\System.Configuration.ConfigurationManager.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\System.Security.Cryptography.ProtectedData.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\System.Security.Permissions.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\System.Windows.Extensions.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\Tomlet.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Unity.Addressables.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Unity.Localization.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Unity.Mathematics.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Unity.Postprocessing.Runtime.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Unity.ProBuilder.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Unity.ProBuilder.KdTree.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Unity.ProBuilder.Poly2Tri.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Unity.ResourceManager.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Unity.Splines.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\Unity.TextMeshPro.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.AccessibilityModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.AIModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.AndroidJNIModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.AnimationModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.ARModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.AssetBundleModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.AudioModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.ClothModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.CommandStateObserverModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.ContentLoadModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.CoreModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.CrashReportingModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.DirectorModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.DSPGraphModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.GameCenterModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.GIModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.GraphToolsFoundationModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.GridModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.HierarchyCoreModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.HotReloadModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\UnityEngine.Il2CppAssetBundleManager.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\UnityEngine.Il2CppImageConversionManager.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.ImageConversionModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.IMGUIModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.InputForUIModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.InputLegacyModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.InputModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.JSONSerializeModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.LocalizationModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.MarshallingModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.MultiplayerModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.ParticleSystemModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.PerformanceReportingModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.Physics2DModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.PhysicsModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.ProfilerModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.PropertiesModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.RuntimeInitializeOnLoadManagerInitializerModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.ScreenCaptureModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.SharedInternalsModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.SpriteMaskModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.SpriteShapeModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.StreamingModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.SubstanceModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.SubsystemsModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.TerrainModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.TerrainPhysicsModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.TextCoreFontEngineModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.TextCoreTextEngineModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.TextRenderingModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.TilemapModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.TLSModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UI.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UIElementsModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UIModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UmbraModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UnityAnalyticsCommonModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UnityAnalyticsModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UnityConnectModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UnityCurlModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UnityTestProtocolModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UnityWebRequestAssetBundleModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UnityWebRequestAudioModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UnityWebRequestModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UnityWebRequestTextureModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.UnityWebRequestWWWModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.VehiclesModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.VFXModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.VideoModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.VRModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.WindModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\Il2CppAssemblies\UnityEngine.XRModule.dll + + + ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Megabonk\MelonLoader\net6\WebSocketDotNet.dll + + - - - + + + - - - + + + true - - + + diff --git a/Multibonk/Networking/Comms/Base/Connection.cs b/Multibonk/Networking/Comms/Base/Connection.cs index 4653bd7..63a3f25 100644 --- a/Multibonk/Networking/Comms/Base/Connection.cs +++ b/Multibonk/Networking/Comms/Base/Connection.cs @@ -1,6 +1,6 @@ -using System.Collections.Concurrent; +using MelonLoader; +using System.Collections.Concurrent; using System.Net.Sockets; -using MelonLoader; namespace Multibonk.Networking.Comms.Base { @@ -32,7 +32,7 @@ public Connection(TcpClient client, List packets) { this.client = client ?? throw new ArgumentNullException(nameof(client)); - foreach(var packet in packets) + foreach (var packet in packets) { outgoingPackets.Enqueue(packet); } diff --git a/Multibonk/Networking/Comms/Base/IProtocol.cs b/Multibonk/Networking/Comms/Base/IProtocol.cs index e7e1c9c..2453528 100644 --- a/Multibonk/Networking/Comms/Base/IProtocol.cs +++ b/Multibonk/Networking/Comms/Base/IProtocol.cs @@ -1,6 +1,4 @@ -using System.Net.Sockets; - -namespace Multibonk.Networking.Comms.Base +namespace Multibonk.Networking.Comms.Base { public interface IProtocol { diff --git a/Multibonk/Networking/Comms/Base/Packet/GameLoadedPacket.cs b/Multibonk/Networking/Comms/Base/Packet/GameLoadedPacket.cs index 4de28a7..c680458 100644 --- a/Multibonk/Networking/Comms/Base/Packet/GameLoadedPacket.cs +++ b/Multibonk/Networking/Comms/Base/Packet/GameLoadedPacket.cs @@ -1,5 +1,4 @@ -using System.Threading.Tasks; -using Multibonk.Networking.Comms.Packet.Base.Multibonk.Networking.Comms; +using Multibonk.Networking.Comms.Packet.Base.Multibonk.Networking.Comms; namespace Multibonk.Networking.Comms.Base.Packet { diff --git a/Multibonk/Networking/Comms/Base/Packet/LobbyPlayerListPacket.cs b/Multibonk/Networking/Comms/Base/Packet/LobbyPlayerListPacket.cs index a72039c..714b756 100644 --- a/Multibonk/Networking/Comms/Base/Packet/LobbyPlayerListPacket.cs +++ b/Multibonk/Networking/Comms/Base/Packet/LobbyPlayerListPacket.cs @@ -15,7 +15,7 @@ public SendLobbyPlayerListPacket(List players) foreach (var player in players) { Message.WriteUShort(player.UUID); - Message.WriteString(player.Name); + Message.WriteString(player.Name); Message.WriteString(player.SelectedCharacter); } } diff --git a/Multibonk/Networking/Comms/Base/Packet/PlayerMovedPacket.cs b/Multibonk/Networking/Comms/Base/Packet/PlayerMovedPacket.cs index 5023c04..da4d9ab 100644 --- a/Multibonk/Networking/Comms/Base/Packet/PlayerMovedPacket.cs +++ b/Multibonk/Networking/Comms/Base/Packet/PlayerMovedPacket.cs @@ -1,5 +1,5 @@ -using UnityEngine; -using Multibonk.Networking.Comms.Packet.Base.Multibonk.Networking.Comms; +using Multibonk.Networking.Comms.Packet.Base.Multibonk.Networking.Comms; +using UnityEngine; namespace Multibonk.Networking.Comms.Base.Packet { diff --git a/Multibonk/Networking/Comms/Base/Packet/PlayerSelectedCharacterPacket.cs b/Multibonk/Networking/Comms/Base/Packet/PlayerSelectedCharacterPacket.cs index 0a0cd68..50573bd 100644 --- a/Multibonk/Networking/Comms/Base/Packet/PlayerSelectedCharacterPacket.cs +++ b/Multibonk/Networking/Comms/Base/Packet/PlayerSelectedCharacterPacket.cs @@ -1,6 +1,4 @@ -using System; -using Multibonk.Networking.Comms.Packet.Base; -using Multibonk.Networking.Comms.Packet.Base.Multibonk.Networking.Comms; +using Multibonk.Networking.Comms.Packet.Base.Multibonk.Networking.Comms; namespace Multibonk.Networking.Comms.Base.Packet { diff --git a/Multibonk/Networking/Comms/Base/PacketId.cs b/Multibonk/Networking/Comms/Base/PacketId.cs index c61f47a..73099d2 100644 --- a/Multibonk/Networking/Comms/Base/PacketId.cs +++ b/Multibonk/Networking/Comms/Base/PacketId.cs @@ -2,16 +2,16 @@ { public enum ServerSentPacketId : byte { - LOBBY_PLAYER_LIST_PACKET = 0, - PLAYER_SELECTED_CHARACTER = 1, - START_GAME = 2, - PAUSE_GAME = 3, - UNPAUSE_GAME = 4, - MAP_FINISHED_LOADING = 5, - SPAWN_PLAYER_PACKET = 6, + LOBBY_PLAYER_LIST_PACKET = 0, + PLAYER_SELECTED_CHARACTER = 1, + START_GAME = 2, + PAUSE_GAME = 3, + UNPAUSE_GAME = 4, + MAP_FINISHED_LOADING = 5, + SPAWN_PLAYER_PACKET = 6, - PLAYER_MOVED_PACKET = 7, - PLAYER_ROTATED_PACKET = 8, + PLAYER_MOVED_PACKET = 7, + PLAYER_ROTATED_PACKET = 8, } public enum ClientSentPacketId : byte diff --git a/Multibonk/Networking/Comms/Client/Handlers/LobbyPlayerListPacketHandler.cs b/Multibonk/Networking/Comms/Client/Handlers/LobbyPlayerListPacketHandler.cs index 843556d..379c543 100644 --- a/Multibonk/Networking/Comms/Client/Handlers/LobbyPlayerListPacketHandler.cs +++ b/Multibonk/Networking/Comms/Client/Handlers/LobbyPlayerListPacketHandler.cs @@ -1,7 +1,7 @@ -using Multibonk.Networking.Comms.Packet.Base.Multibonk.Networking.Comms; -using Multibonk.Networking.Lobby; +using Multibonk.Networking.Comms.Base; using Multibonk.Networking.Comms.Base.Packet; -using Multibonk.Networking.Comms.Base; +using Multibonk.Networking.Comms.Packet.Base.Multibonk.Networking.Comms; +using Multibonk.Networking.Lobby; namespace Multibonk.Networking.Comms.Client.Handlers { diff --git a/Multibonk/Networking/Comms/Client/Handlers/PlayerRotatedPacketHandler.cs b/Multibonk/Networking/Comms/Client/Handlers/PlayerRotatedPacketHandler.cs index e7c1060..1773375 100644 --- a/Multibonk/Networking/Comms/Client/Handlers/PlayerRotatedPacketHandler.cs +++ b/Multibonk/Networking/Comms/Client/Handlers/PlayerRotatedPacketHandler.cs @@ -1,10 +1,8 @@ -using Multibonk.Networking.Comms.Base.Packet; +using Multibonk.Game; +using Multibonk.Game.Handlers; using Multibonk.Networking.Comms.Base; +using Multibonk.Networking.Comms.Base.Packet; using Multibonk.Networking.Comms.Packet.Base.Multibonk.Networking.Comms; -using UnityEngine; -using Il2CppRewired.Utils; -using Multibonk.Game.Handlers; -using Multibonk.Game; namespace Multibonk.Networking.Comms.Client.Handlers { diff --git a/Multibonk/Networking/Comms/Client/Handlers/PlayerSelectedCharacterPacketHandler.cs b/Multibonk/Networking/Comms/Client/Handlers/PlayerSelectedCharacterPacketHandler.cs index 5e0e21f..8faf0e0 100644 --- a/Multibonk/Networking/Comms/Client/Handlers/PlayerSelectedCharacterPacketHandler.cs +++ b/Multibonk/Networking/Comms/Client/Handlers/PlayerSelectedCharacterPacketHandler.cs @@ -1,5 +1,5 @@ -using Multibonk.Networking.Comms.Base.Packet; -using Multibonk.Networking.Comms.Base; +using Multibonk.Networking.Comms.Base; +using Multibonk.Networking.Comms.Base.Packet; using Multibonk.Networking.Comms.Packet.Base.Multibonk.Networking.Comms; using Multibonk.Networking.Lobby; diff --git a/Multibonk/Networking/Comms/Client/Handlers/SpawnPlayerPacketHandler.cs b/Multibonk/Networking/Comms/Client/Handlers/SpawnPlayerPacketHandler.cs index a1c3d97..496d30b 100644 --- a/Multibonk/Networking/Comms/Client/Handlers/SpawnPlayerPacketHandler.cs +++ b/Multibonk/Networking/Comms/Client/Handlers/SpawnPlayerPacketHandler.cs @@ -1,9 +1,8 @@ using Multibonk.Game; -using Multibonk.Networking.Comms.Base.Packet; +using Multibonk.Game.Handlers; using Multibonk.Networking.Comms.Base; +using Multibonk.Networking.Comms.Base.Packet; using Multibonk.Networking.Comms.Packet.Base.Multibonk.Networking.Comms; -using UnityEngine; -using Multibonk.Game.Handlers; namespace Multibonk.Networking.Comms.Client.Handlers { @@ -18,7 +17,7 @@ public void Handle(IncomingMessage msg, Connection conn) var packet = new SpawnPlayerPacket(msg); GameDispatcher.Enqueue(() => - { + { GameFunctions.SpawnNetworkPlayer(packet.PlayerId, packet.Character, packet.Position, packet.Rotation); }); } diff --git a/Multibonk/Networking/Comms/Client/Handlers/StartGamePacketHandler.cs b/Multibonk/Networking/Comms/Client/Handlers/StartGamePacketHandler.cs index ae5abcf..9a658fb 100644 --- a/Multibonk/Networking/Comms/Client/Handlers/StartGamePacketHandler.cs +++ b/Multibonk/Networking/Comms/Client/Handlers/StartGamePacketHandler.cs @@ -1,10 +1,10 @@ -using Multibonk.Networking.Comms.Base.Packet; -using Multibonk.Networking.Comms.Base; -using Multibonk.Networking.Comms.Packet.Base.Multibonk.Networking.Comms; +using Il2Cpp; +using MelonLoader; using Multibonk.Game; -using Il2Cpp; using Multibonk.Game.Handlers; -using MelonLoader; +using Multibonk.Networking.Comms.Base; +using Multibonk.Networking.Comms.Base.Packet; +using Multibonk.Networking.Comms.Packet.Base.Multibonk.Networking.Comms; namespace Multibonk.Networking.Comms.Client.Handlers { diff --git a/Multibonk/Networking/Comms/Client/NetworkClient.cs b/Multibonk/Networking/Comms/Client/NetworkClient.cs index a411e95..724f9b4 100644 --- a/Multibonk/Networking/Comms/Client/NetworkClient.cs +++ b/Multibonk/Networking/Comms/Client/NetworkClient.cs @@ -1,5 +1,5 @@ -using System.Net.Sockets; -using Multibonk.Networking.Comms.Base; +using Multibonk.Networking.Comms.Base; +using System.Net.Sockets; namespace Multibonk.Networking.Comms.Client { @@ -7,7 +7,7 @@ public class NetworkClient { private TcpClient tcpClient; private Connection connection; - private IClientProtocol protocol; + private IClientProtocol protocol; public bool IsConnected => tcpClient?.Connected ?? false; @@ -18,7 +18,7 @@ public NetworkClient(IClientProtocol protocol) this.protocol = protocol; connection = new Connection(tcpClient); - } + } private void InternalConnect(string ip, int port) { @@ -40,7 +40,8 @@ public void Connect(string ip, int port) { if (IsConnected) throw new InvalidOperationException("Client already connected."); - new Thread(() => { + new Thread(() => + { InternalConnect(ip, port); }).Start(); } diff --git a/Multibonk/Networking/Comms/NetworkService.cs b/Multibonk/Networking/Comms/NetworkService.cs index 93e9696..ef01dc8 100644 --- a/Multibonk/Networking/Comms/NetworkService.cs +++ b/Multibonk/Networking/Comms/NetworkService.cs @@ -1,11 +1,11 @@ namespace Multibonk.Networking.Comms { - using System; using global::Multibonk.Networking.Comms.Base; using global::Multibonk.Networking.Comms.Client; using global::Multibonk.Networking.Comms.Client.Protocols; using global::Multibonk.Networking.Comms.Server; using global::Multibonk.Networking.Comms.Server.Protocols; + using System; namespace Multibonk.Networking.Comms { @@ -84,7 +84,7 @@ public ServerService(int port, ServerProtocol protocol) public void Stop() => listener.Stop(); } - public interface IExposableClientService + public interface IExposableClientService { void Enqueue(OutgoingPacket packet); } diff --git a/Multibonk/Networking/Comms/Server/Handlers/GameLoadedPacketHandler.cs b/Multibonk/Networking/Comms/Server/Handlers/GameLoadedPacketHandler.cs index ff82f40..b7f6d6f 100644 --- a/Multibonk/Networking/Comms/Server/Handlers/GameLoadedPacketHandler.cs +++ b/Multibonk/Networking/Comms/Server/Handlers/GameLoadedPacketHandler.cs @@ -12,7 +12,7 @@ public GameLoadedPacketHandler() { } public void Handle(IncomingMessage msg, Connection conn) - { + { } } } diff --git a/Multibonk/Networking/Comms/Server/Handlers/PlayerMovePacketHandler.cs b/Multibonk/Networking/Comms/Server/Handlers/PlayerMovePacketHandler.cs index 1e22786..45fae5b 100644 --- a/Multibonk/Networking/Comms/Server/Handlers/PlayerMovePacketHandler.cs +++ b/Multibonk/Networking/Comms/Server/Handlers/PlayerMovePacketHandler.cs @@ -1,12 +1,10 @@ -using Multibonk.Networking.Comms.Base.Packet.Multibonk.Networking.Comms.Base.Packet; +using Multibonk.Game; +using Multibonk.Game.Handlers; using Multibonk.Networking.Comms.Base; +using Multibonk.Networking.Comms.Base.Packet; +using Multibonk.Networking.Comms.Base.Packet.Multibonk.Networking.Comms.Base.Packet; using Multibonk.Networking.Comms.Packet.Base.Multibonk.Networking.Comms; using Multibonk.Networking.Lobby; -using UnityEngine; -using Il2CppRewired.Utils; -using Multibonk.Networking.Comms.Base.Packet; -using Multibonk.Game.Handlers; -using Multibonk.Game; namespace Multibonk.Networking.Comms.Server.Handlers { diff --git a/Multibonk/Networking/Comms/Server/Handlers/PlayerRotatePacketHandler.cs b/Multibonk/Networking/Comms/Server/Handlers/PlayerRotatePacketHandler.cs index b5044a8..bee5d97 100644 --- a/Multibonk/Networking/Comms/Server/Handlers/PlayerRotatePacketHandler.cs +++ b/Multibonk/Networking/Comms/Server/Handlers/PlayerRotatePacketHandler.cs @@ -1,9 +1,9 @@ -using Multibonk.Networking.Comms.Base.Packet; -using Multibonk.Networking.Lobby; +using Multibonk.Game; +using Multibonk.Game.Handlers; using Multibonk.Networking.Comms.Base; +using Multibonk.Networking.Comms.Base.Packet; using Multibonk.Networking.Comms.Packet.Base.Multibonk.Networking.Comms; -using Multibonk.Game.Handlers; -using Multibonk.Game; +using Multibonk.Networking.Lobby; namespace Multibonk.Networking.Comms.Server.Handlers { diff --git a/Multibonk/Networking/Comms/Server/Handlers/SelectCharacterPacketHandler.cs b/Multibonk/Networking/Comms/Server/Handlers/SelectCharacterPacketHandler.cs index dcb223d..7bc34f3 100644 --- a/Multibonk/Networking/Comms/Server/Handlers/SelectCharacterPacketHandler.cs +++ b/Multibonk/Networking/Comms/Server/Handlers/SelectCharacterPacketHandler.cs @@ -1,5 +1,5 @@ -using Multibonk.Networking.Comms.Base.Packet; -using Multibonk.Networking.Comms.Base; +using Multibonk.Networking.Comms.Base; +using Multibonk.Networking.Comms.Base.Packet; using Multibonk.Networking.Comms.Packet.Base.Multibonk.Networking.Comms; using Multibonk.Networking.Lobby; @@ -21,7 +21,7 @@ public void Handle(IncomingMessage msg, Connection conn) var targetPlayer = LobbyContext.GetPlayer(conn); - if (targetPlayer != null) + if (targetPlayer != null) { targetPlayer.SelectedCharacter = packet.CharacterName; diff --git a/Multibonk/Networking/Comms/Server/Listener.cs b/Multibonk/Networking/Comms/Server/Listener.cs index 3b9aa64..0e4aa0a 100644 --- a/Multibonk/Networking/Comms/Server/Listener.cs +++ b/Multibonk/Networking/Comms/Server/Listener.cs @@ -1,6 +1,6 @@ -using System.Net; +using Multibonk.Networking.Comms.Base; +using System.Net; using System.Net.Sockets; -using Multibonk.Networking.Comms.Base; namespace Multibonk.Networking.Comms.Server { @@ -35,7 +35,8 @@ public void Start() if (running) return; running = true; - new Thread(() => { + new Thread(() => + { InternalStart(); }).Start(); } diff --git a/Multibonk/Networking/Comms/Server/Protocols/ServerProtocol.cs b/Multibonk/Networking/Comms/Server/Protocols/ServerProtocol.cs index c2e8c17..1b7bc5f 100644 --- a/Multibonk/Networking/Comms/Server/Protocols/ServerProtocol.cs +++ b/Multibonk/Networking/Comms/Server/Protocols/ServerProtocol.cs @@ -1,6 +1,5 @@ using Multibonk.Networking.Comms.Base; using Multibonk.Networking.Comms.Packet.Base.Multibonk.Networking.Comms; -using Multibonk.Networking.Comms.Server.Handlers; namespace Multibonk.Networking.Comms.Server.Protocols { @@ -17,7 +16,7 @@ public ServerProtocol( IEnumerable packetHandlers ) { - handlers = packetHandlers.ToDictionary(h => h.PacketId); + handlers = packetHandlers.ToDictionary(h => h.PacketId); } public void HandleMessage(Connection conn, byte[] data, int start, int length) diff --git a/Multibonk/Networking/Lobby/LobbyContext.cs b/Multibonk/Networking/Lobby/LobbyContext.cs index 721ce4c..ae290da 100644 --- a/Multibonk/Networking/Lobby/LobbyContext.cs +++ b/Multibonk/Networking/Lobby/LobbyContext.cs @@ -1,5 +1,4 @@ -using Il2Cpp; -using Multibonk.Networking.Comms.Base; +using Multibonk.Networking.Comms.Base; namespace Multibonk.Networking.Lobby { diff --git a/Multibonk/Properties/MelonInfo.cs b/Multibonk/Properties/MelonInfo.cs index cc5cc2d..37d6103 100644 --- a/Multibonk/Properties/MelonInfo.cs +++ b/Multibonk/Properties/MelonInfo.cs @@ -1,5 +1,5 @@ using MelonLoader; using Multibonk; -[assembly: MelonInfo(typeof(MultibonkMod), "Multibonk", "1.0.0", "guilhermeljs")] +[assembly: MelonInfo(typeof(MultibonkMod), "Multibonk", "1.0.0", "guilhermeljs, Nicogo")] [assembly: MelonGame("Ved", "Megabonk")] \ No newline at end of file diff --git a/Multibonk/UserInterface/UIManager.cs b/Multibonk/UserInterface/UIManager.cs index 9046547..557e6c0 100644 --- a/Multibonk/UserInterface/UIManager.cs +++ b/Multibonk/UserInterface/UIManager.cs @@ -1,7 +1,7 @@ -using UnityEngine; -using Multibonk.UserInterface.Window; -using MelonLoader; +using MelonLoader; using Multibonk.Networking.Lobby; +using Multibonk.UserInterface.Window; +using UnityEngine; namespace Multibonk { @@ -69,17 +69,18 @@ LobbyService lobbyService public void OnGUI() { Event e = Event.current; - if(e.rawType == EventType.KeyDown && e.keyCode == KeyCode.F5) + if (e.rawType == EventType.KeyDown && e.keyCode == KeyCode.F5) { _showingMenuBuffer = !_showingMenuBuffer; } - if(e.rawType == EventType.Layout && _showingMenuBuffer != IsShowingMenu) + if (e.rawType == EventType.Layout && _showingMenuBuffer != IsShowingMenu) { IsShowingMenu = _showingMenuBuffer; } - if (IsShowingMenu) { + if (IsShowingMenu) + { switch (currentState) { case UIState.Connection: diff --git a/Multibonk/UserInterface/Utils.cs b/Multibonk/UserInterface/Utils.cs index eb01954..6c9f410 100644 --- a/Multibonk/UserInterface/Utils.cs +++ b/Multibonk/UserInterface/Utils.cs @@ -1,5 +1,4 @@ -using MelonLoader; -using UnityEngine; +using UnityEngine; public static class Utils { diff --git a/Multibonk/UserInterface/Window/ClientLobbyWindow.cs b/Multibonk/UserInterface/Window/ClientLobbyWindow.cs index e407a9b..7fe8e8f 100644 --- a/Multibonk/UserInterface/Window/ClientLobbyWindow.cs +++ b/Multibonk/UserInterface/Window/ClientLobbyWindow.cs @@ -1,5 +1,4 @@ -using MelonLoader; -using Multibonk.Networking.Lobby; +using Multibonk.Networking.Lobby; using UnityEngine; namespace Multibonk.UserInterface.Window @@ -9,7 +8,7 @@ public class ClientLobbyWindow : WindowBase private LobbyContext lobby; public event Action OnLeaveLobby; - public ClientLobbyWindow(LobbyContext lobby) : base(new Rect(50, 50, 300, 200)) + public ClientLobbyWindow(LobbyContext lobby) : base(new Rect(50, 50, 300, 200)) { this.lobby = lobby; } diff --git a/Multibonk/UserInterface/Window/ConnectionWindow.cs b/Multibonk/UserInterface/Window/ConnectionWindow.cs index 6c5b9eb..fa337c0 100644 --- a/Multibonk/UserInterface/Window/ConnectionWindow.cs +++ b/Multibonk/UserInterface/Window/ConnectionWindow.cs @@ -1,13 +1,13 @@ -using MelonLoader; -using UnityEngine; +using UnityEngine; namespace Multibonk.UserInterface.Window { - public class ConnectionWindowEventArgs { + public class ConnectionWindowEventArgs + { public string IP { get; } public string PlayerName { get; } - public ConnectionWindowEventArgs(string playerName, string ip) + public ConnectionWindowEventArgs(string playerName, string ip) { IP = ip; PlayerName = playerName; @@ -24,7 +24,7 @@ public class ConnectionWindow : WindowBase private bool nameIsFocused = false; private bool ipIsFocused = false; - public ConnectionWindow() : base(new Rect(10, 10, 300, 200)) + public ConnectionWindow() : base(new Rect(10, 10, 300, 200)) { ipAddress = Preferences.IpAddress.Value; playerName = Preferences.PlayerName.Value; diff --git a/Multibonk/UserInterface/WindowBase.cs b/Multibonk/UserInterface/WindowBase.cs index 088f21e..a684daa 100644 --- a/Multibonk/UserInterface/WindowBase.cs +++ b/Multibonk/UserInterface/WindowBase.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MelonLoader; -using UnityEngine; +using UnityEngine; namespace Multibonk.UserInterface { From 6fa9476df90b45a0e1383cd71f940c897b163816 Mon Sep 17 00:00:00 2001 From: "nicogo.eth" Date: Wed, 8 Oct 2025 20:10:47 +0200 Subject: [PATCH 2/5] Add LaunchSettings --- Multibonk/Properties/launchSettings.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Multibonk/Properties/launchSettings.json diff --git a/Multibonk/Properties/launchSettings.json b/Multibonk/Properties/launchSettings.json new file mode 100644 index 0000000..109d642 --- /dev/null +++ b/Multibonk/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "StartMB": { + "commandName": "Executable", + "executablePath": "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Megabonk\\Megabonk.exe" + } + } +} \ No newline at end of file From c6eddeb62749856cb442f8fa10462508925f52cf Mon Sep 17 00:00:00 2001 From: "nicogo.eth" Date: Wed, 8 Oct 2025 22:17:34 +0200 Subject: [PATCH 3/5] Improve Steam invites, UI feedback, and gameplay rule propagation --- Multibonk/Game/GamePatchFlags.cs | 7 + Multibonk/Game/GameplayRulesSnapshot.cs | 53 ++++ .../Logic/GameplayRuleSynchronizer.cs | 48 +++ Multibonk/Multibonk.cs | 7 + Multibonk/Networking/Comms/NetworkService.cs | 24 +- Multibonk/Networking/Lobby/LobbyService.cs | 28 +- Multibonk/Networking/NetworkDefaults.cs | 8 + .../Steam/SteamFriendsReflection.cs | 79 +++++ .../Steam/SteamTunnelCallbackBinder.cs | 282 ++++++++++++++++++ .../Networking/Steam/SteamTunnelService.cs | 146 +++++++++ Multibonk/Preferences.cs | 51 +++- Multibonk/UserInterface/UIManager.cs | 246 ++++++++++++--- .../UserInterface/Window/ClientLobbyWindow.cs | 62 +++- .../UserInterface/Window/ConnectionWindow.cs | 218 ++++++++++++-- .../UserInterface/Window/HostLobbyWindow.cs | 62 +++- .../UserInterface/Window/OptionsWindow.cs | 268 +++++++++++++++++ Todo.md | 14 + 17 files changed, 1514 insertions(+), 89 deletions(-) create mode 100644 Multibonk/Game/GameplayRulesSnapshot.cs create mode 100644 Multibonk/Game/Handlers/Logic/GameplayRuleSynchronizer.cs create mode 100644 Multibonk/Networking/NetworkDefaults.cs create mode 100644 Multibonk/Networking/Steam/SteamFriendsReflection.cs create mode 100644 Multibonk/Networking/Steam/SteamTunnelCallbackBinder.cs create mode 100644 Multibonk/Networking/Steam/SteamTunnelService.cs create mode 100644 Multibonk/UserInterface/Window/OptionsWindow.cs create mode 100644 Todo.md diff --git a/Multibonk/Game/GamePatchFlags.cs b/Multibonk/Game/GamePatchFlags.cs index 4f9e5ad..588ab74 100644 --- a/Multibonk/Game/GamePatchFlags.cs +++ b/Multibonk/Game/GamePatchFlags.cs @@ -19,5 +19,12 @@ public static class GamePatchFlags public static Vector3 LastPlayerPosition { get; set; } public static Quaternion LastPlayerRotation { get; set; } + + public static GameplayRulesSnapshot GameplayRules { get; private set; } = GameplayRulesSnapshot.FromPreferences(); + + public static void SetGameplayRules(GameplayRulesSnapshot snapshot) + { + GameplayRules = snapshot; + } } } diff --git a/Multibonk/Game/GameplayRulesSnapshot.cs b/Multibonk/Game/GameplayRulesSnapshot.cs new file mode 100644 index 0000000..a7fb0d5 --- /dev/null +++ b/Multibonk/Game/GameplayRulesSnapshot.cs @@ -0,0 +1,53 @@ +using System; + +namespace Multibonk.Game +{ + public readonly struct GameplayRulesSnapshot : IEquatable + { + public bool PvpEnabled { get; } + public bool ReviveEnabled { get; } + public float ReviveDelaySeconds { get; } + public Preferences.LootDistributionMode XpSharingMode { get; } + public Preferences.LootDistributionMode GoldSharingMode { get; } + public Preferences.LootDistributionMode ChestSharingMode { get; } + + public GameplayRulesSnapshot( + bool pvpEnabled, + bool reviveEnabled, + float reviveDelaySeconds, + Preferences.LootDistributionMode xpSharingMode, + Preferences.LootDistributionMode goldSharingMode, + Preferences.LootDistributionMode chestSharingMode) + { + PvpEnabled = pvpEnabled; + ReviveEnabled = reviveEnabled; + ReviveDelaySeconds = reviveDelaySeconds; + XpSharingMode = xpSharingMode; + GoldSharingMode = goldSharingMode; + ChestSharingMode = chestSharingMode; + } + + public static GameplayRulesSnapshot FromPreferences() => new GameplayRulesSnapshot( + Preferences.PvpEnabled.Value, + Preferences.ReviveEnabled.Value, + Preferences.ReviveTimeSeconds.Value, + Preferences.GetXpSharingMode(), + Preferences.GetGoldSharingMode(), + Preferences.GetChestSharingMode()); + + public bool Equals(GameplayRulesSnapshot other) => + PvpEnabled == other.PvpEnabled && + ReviveEnabled == other.ReviveEnabled && + Math.Abs(ReviveDelaySeconds - other.ReviveDelaySeconds) < 0.001f && + XpSharingMode == other.XpSharingMode && + GoldSharingMode == other.GoldSharingMode && + ChestSharingMode == other.ChestSharingMode; + + public override bool Equals(object? obj) => obj is GameplayRulesSnapshot other && Equals(other); + + public override int GetHashCode() => HashCode.Combine(PvpEnabled, ReviveEnabled, ReviveDelaySeconds, XpSharingMode, GoldSharingMode, ChestSharingMode); + + public override string ToString() => + $"PvP={(PvpEnabled ? "Enabled" : "Disabled")}, Revive={(ReviveEnabled ? "Enabled" : "Disabled")} ({ReviveDelaySeconds:0.##}s), XP={XpSharingMode}, Gold={GoldSharingMode}, Chest={ChestSharingMode}"; + } +} diff --git a/Multibonk/Game/Handlers/Logic/GameplayRuleSynchronizer.cs b/Multibonk/Game/Handlers/Logic/GameplayRuleSynchronizer.cs new file mode 100644 index 0000000..c44aa0c --- /dev/null +++ b/Multibonk/Game/Handlers/Logic/GameplayRuleSynchronizer.cs @@ -0,0 +1,48 @@ +using MelonLoader; +using UnityEngine; + +namespace Multibonk.Game.Handlers.Logic +{ + public class GameplayRuleSynchronizer : GameEventHandler + { + private GameplayRulesSnapshot lastSnapshot; + private float lastLogTime; + + public GameplayRuleSynchronizer() + { + lastSnapshot = GameplayRulesSnapshot.FromPreferences(); + GamePatchFlags.SetGameplayRules(lastSnapshot); + GameEvents.GameLoadedEvent += ApplyRulesToWorld; + GameEvents.ConfirmMapEvent += ApplyRulesToWorld; + } + + public override void Update() + { + if (Time.frameCount % 60 != 0) + { + return; + } + + var current = GameplayRulesSnapshot.FromPreferences(); + if (current.Equals(lastSnapshot)) + { + return; + } + + lastSnapshot = current; + GamePatchFlags.SetGameplayRules(current); + ApplyRulesToWorld(); + } + + private void ApplyRulesToWorld() + { + GamePatchFlags.SetGameplayRules(lastSnapshot); + + if (Time.time - lastLogTime > 5f) + { + MelonLogger.Msg($"Applied gameplay rules: {lastSnapshot}."); + lastLogTime = Time.time; + } + } + } +} diff --git a/Multibonk/Multibonk.cs b/Multibonk/Multibonk.cs index fa78acd..5a482b4 100644 --- a/Multibonk/Multibonk.cs +++ b/Multibonk/Multibonk.cs @@ -11,6 +11,7 @@ using Multibonk.Networking.Comms.Server.Handlers; using Multibonk.Networking.Comms.Server.Protocols; using Multibonk.Networking.Lobby; +using Multibonk.Networking.Steam; using Multibonk.UserInterface.Window; namespace Multibonk @@ -52,6 +53,7 @@ public override void OnInitializeMelon() services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -76,11 +78,14 @@ public override void OnInitializeMelon() // Packet Handlers cannot call services. Otherwise, it will cause circular dependency services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); @@ -91,6 +96,8 @@ public override void OnInitializeMelon() var _lobbyContext = serviceProvider.GetService(); + serviceProvider.GetService(); + base.OnInitializeMelon(); } } diff --git a/Multibonk/Networking/Comms/NetworkService.cs b/Multibonk/Networking/Comms/NetworkService.cs index ef01dc8..5074356 100644 --- a/Multibonk/Networking/Comms/NetworkService.cs +++ b/Multibonk/Networking/Comms/NetworkService.cs @@ -24,7 +24,7 @@ public class NetworkService private readonly ClientService client; public NetworkService(ServerProtocol serverPrt, ClientProtocol clientPrt) { - server = new ServerService(25565, serverPrt); + server = new ServerService(serverPrt); client = new ClientService(clientPrt); } @@ -46,12 +46,12 @@ public void Disconnect() } - public void StartServer() + public void StartServer(int port) { if (State != NetworkState.None) throw new InvalidOperationException("Network already started."); - server.Start(); + server.Start(port); State = NetworkState.Hosting; } @@ -73,15 +73,25 @@ public IExposableClientService GetClientService() public class ServerService { - private readonly Listener listener; + private readonly ServerProtocol protocol; + private Listener listener; - public ServerService(int port, ServerProtocol protocol) + public ServerService(ServerProtocol protocol) + { + this.protocol = protocol; + } + + public void Start(int port) { listener = new Listener(port, protocol); + listener.Start(); } - public void Start() => listener.Start(); - public void Stop() => listener.Stop(); + public void Stop() + { + listener?.Stop(); + listener = null; + } } public interface IExposableClientService diff --git a/Multibonk/Networking/Lobby/LobbyService.cs b/Multibonk/Networking/Lobby/LobbyService.cs index 34b842c..b21db22 100644 --- a/Multibonk/Networking/Lobby/LobbyService.cs +++ b/Multibonk/Networking/Lobby/LobbyService.cs @@ -1,6 +1,8 @@ -using MelonLoader; +using System; +using MelonLoader; using Multibonk.Networking.Comms.Base.Packet; using Multibonk.Networking.Comms.Multibonk.Networking.Comms; +using Multibonk.Networking.Steam; namespace Multibonk.Networking.Lobby { @@ -8,27 +10,31 @@ public class LobbyService { private NetworkService NetworkService { get; } private LobbyContext CurrentLobby { get; } + private SteamTunnelService SteamTunnelService { get; } - public LobbyService(NetworkService service, LobbyContext context) + public LobbyService(NetworkService service, LobbyContext context, SteamTunnelService steamTunnelService) { NetworkService = service; CurrentLobby = context; + SteamTunnelService = steamTunnelService; } - public void CreateLobby(string myName) + public void CreateLobby(string myName, int port) { - MelonLogger.Msg($"Creating lobby"); + MelonLogger.Msg($"Creating lobby on port {port}"); try { - NetworkService.StartServer(); + NetworkService.StartServer(port); } catch (Exception e) { MelonLogger.Msg($"Failed to start lobby {e.Message}"); - CurrentLobby.TriggerLobbyJoinFailed($"Failed to start lobby {e.Message}"); + CurrentLobby.TriggerLobbyJoinFailed($"Failed to start lobby: {e.Message}"); return; } + SteamTunnelService.ClearEndpoints(); + CurrentLobby.GetPlayers().Clear(); CurrentLobby.SetMyself(new LobbyPlayer(name: myName)); CurrentLobby.SetState(LobbyState.Hosting); @@ -38,6 +44,13 @@ public void CreateLobby(string myName) public void JoinLobby(string ip, int port, string myName) { + if (SteamTunnelService.TryConsumeEndpoint(out var endpoint)) + { + ip = endpoint.Address; + port = endpoint.Port; + MelonLogger.Msg($"Using Steam tunnel endpoint {endpoint}."); + } + MelonLogger.Msg($"Joining lobby {ip}:{port} with the username: {myName}"); try @@ -81,13 +94,12 @@ public void CloseLobby() } finally { + SteamTunnelService.ClearEndpoints(); CurrentLobby.TriggerLobbyClosed(); CurrentLobby.GetPlayers().Clear(); CurrentLobby.SetState(LobbyState.None); LobbyPatchFlags.IsHosting = false; } - } } - } diff --git a/Multibonk/Networking/NetworkDefaults.cs b/Multibonk/Networking/NetworkDefaults.cs new file mode 100644 index 0000000..b6644ab --- /dev/null +++ b/Multibonk/Networking/NetworkDefaults.cs @@ -0,0 +1,8 @@ +namespace Multibonk.Networking +{ + internal static class NetworkDefaults + { + public const string DefaultAddress = "127.0.0.1"; + public const int DefaultPort = 25565; + } +} diff --git a/Multibonk/Networking/Steam/SteamFriendsReflection.cs b/Multibonk/Networking/Steam/SteamFriendsReflection.cs new file mode 100644 index 0000000..78784cf --- /dev/null +++ b/Multibonk/Networking/Steam/SteamFriendsReflection.cs @@ -0,0 +1,79 @@ +using System; +using System.Linq; +using System.Reflection; +using MelonLoader; + +namespace Multibonk.Networking.Steam +{ + internal static class SteamFriendsReflection + { + private static readonly string[] CandidateAssemblies = + { + "com.rlabrecque.steamworks.net", + "Steamworks.NET", + "Assembly-CSharp-firstpass", + "Assembly-CSharp" + }; + + public static Type? LocateSteamFriendsType() + { + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + var type = FindSteamFriendsType(assembly); + if (type != null) + { + return type; + } + } + + foreach (var assemblyName in CandidateAssemblies) + { + var qualifiedName = $"Steamworks.SteamFriends, {assemblyName}"; + try + { + var type = Type.GetType(qualifiedName, throwOnError: false); + if (type != null) + { + return type; + } + } + catch (ReflectionTypeLoadException ex) + { + MelonLogger.Warning($"Failed to inspect '{qualifiedName}': {ex.Message}"); + } + } + + return null; + } + + public static MethodInfo? FindActivateGameOverlay(Type steamFriendsType) + { + try + { + return steamFriendsType.GetMethods(BindingFlags.Public | BindingFlags.Static) + .FirstOrDefault(method => + method.Name == "ActivateGameOverlay" && + method.GetParameters().Length == 1 && + method.GetParameters()[0].ParameterType == typeof(string)); + } + catch (ReflectionTypeLoadException ex) + { + MelonLogger.Warning($"Could not enumerate types from '{steamFriendsType.Assembly.FullName}': {ex.Message}"); + return null; + } + } + + private static Type? FindSteamFriendsType(Assembly assembly) + { + try + { + return assembly.GetTypes().FirstOrDefault(type => type.FullName == "Steamworks.SteamFriends"); + } + catch (ReflectionTypeLoadException ex) + { + MelonLogger.Warning($"Could not enumerate types from '{assembly.FullName}': {ex.Message}"); + return null; + } + } + } +} diff --git a/Multibonk/Networking/Steam/SteamTunnelCallbackBinder.cs b/Multibonk/Networking/Steam/SteamTunnelCallbackBinder.cs new file mode 100644 index 0000000..114dce9 --- /dev/null +++ b/Multibonk/Networking/Steam/SteamTunnelCallbackBinder.cs @@ -0,0 +1,282 @@ +using System; +using System.Reflection; +using MelonLoader; +using Multibonk.Networking; + +namespace Multibonk.Networking.Steam +{ + public class SteamTunnelCallbackBinder + { + private readonly SteamTunnelService steamTunnelService; + private readonly object syncRoot = new(); + + private Delegate? joinRequestedHandler; + private EventInfo? joinRequestedEvent; + private Type? subscribedSteamFriendsType; + private bool missingSteamFriendsLogged; + private bool missingEventLogged; + + public SteamTunnelCallbackBinder(SteamTunnelService steamTunnelService) + { + this.steamTunnelService = steamTunnelService; + AppDomain.CurrentDomain.AssemblyLoad += HandleAssemblyLoaded; + TryBindCallbacks(); + } + + public bool TryBindCallbacks() + { + lock (syncRoot) + { + var steamFriendsType = steamTunnelService.SteamFriendsType ?? SteamFriendsReflection.LocateSteamFriendsType(); + if (steamFriendsType == null) + { + if (!missingSteamFriendsLogged) + { + MelonLogger.Warning("Steam friends API is unavailable; Steam join requests will be ignored until Steamworks initialises."); + missingSteamFriendsLogged = true; + } + return false; + } + + if (subscribedSteamFriendsType == steamFriendsType && joinRequestedHandler != null) + { + return true; + } + + DetachHandlers_NoLock(); + + joinRequestedEvent = steamFriendsType.GetEvent("OnGameRichPresenceJoinRequested", BindingFlags.Public | BindingFlags.Static); + if (joinRequestedEvent == null || joinRequestedEvent.EventHandlerType == null) + { + if (!missingEventLogged) + { + MelonLogger.Warning("SteamFriends.OnGameRichPresenceJoinRequested was not found; Steam invites cannot be consumed until the event becomes available."); + missingEventLogged = true; + } + return false; + } + + var handlerMethod = typeof(SteamTunnelCallbackBinder).GetMethod(nameof(OnGameRichPresenceJoinRequested), BindingFlags.Instance | BindingFlags.NonPublic); + if (handlerMethod == null) + { + MelonLogger.Error("Steam tunnel callback binder lost its handler method."); + return false; + } + + try + { + joinRequestedHandler = Delegate.CreateDelegate(joinRequestedEvent.EventHandlerType, this, handlerMethod); + joinRequestedEvent.AddEventHandler(null, joinRequestedHandler); + subscribedSteamFriendsType = steamFriendsType; + MelonLogger.Msg($"Listening for Steam Rich Presence joins via '{steamFriendsType.Assembly.FullName}'."); + missingSteamFriendsLogged = false; + missingEventLogged = false; + return true; + } + catch (Exception ex) + { + MelonLogger.Error($"Failed to subscribe to Steam join requests: {ex.Message}"); + DetachHandlers_NoLock(); + return false; + } + } + } + + public void DetachHandlers() + { + lock (syncRoot) + { + DetachHandlers_NoLock(); + } + } + + private void OnGameRichPresenceJoinRequested(object rawEvent) + { + if (rawEvent == null) + { + MelonLogger.Warning("Steam signalled a join request without payload; ignoring."); + return; + } + + if (!TryExtractConnectString(rawEvent, out var connectString)) + { + MelonLogger.Warning($"Steam join request payload '{rawEvent.GetType().FullName}' did not expose a connect string."); + return; + } + + if (!TryParseConnectString(connectString, out var address, out var port)) + { + MelonLogger.Warning($"Steam join request did not contain a valid endpoint: '{connectString}'."); + return; + } + + steamTunnelService.RegisterEndpoint(address, port); + } + + private void DetachHandlers_NoLock() + { + if (joinRequestedHandler == null || joinRequestedEvent == null) + { + return; + } + + try + { + joinRequestedEvent.RemoveEventHandler(null, joinRequestedHandler); + } + catch (Exception ex) + { + MelonLogger.Warning($"Failed to detach Steam join handler: {ex.Message}"); + } + finally + { + joinRequestedHandler = null; + joinRequestedEvent = null; + subscribedSteamFriendsType = null; + } + } + + private void HandleAssemblyLoaded(object? sender, AssemblyLoadEventArgs args) + { + TryBindCallbacks(); + } + + private static bool TryExtractConnectString(object rawEvent, out string connectString) + { + var eventType = rawEvent.GetType(); + + var field = eventType.GetField("m_rgchConnect", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + if (field?.GetValue(rawEvent) is string fieldValue && !string.IsNullOrWhiteSpace(fieldValue)) + { + connectString = fieldValue.Trim(); + return true; + } + + var property = eventType.GetProperty("Connect", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + if (property?.GetValue(rawEvent) is string propertyValue && !string.IsNullOrWhiteSpace(propertyValue)) + { + connectString = propertyValue.Trim(); + return true; + } + + var method = eventType.GetMethod("GetConnectString", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + if (method != null && method.GetParameters().Length == 0 && method.ReturnType == typeof(string)) + { + if (method.Invoke(rawEvent, Array.Empty()) is string methodValue && !string.IsNullOrWhiteSpace(methodValue)) + { + connectString = methodValue.Trim(); + return true; + } + } + + connectString = string.Empty; + return false; + } + + private static bool TryParseConnectString(string connectString, out string address, out int port) + { + address = string.Empty; + port = 0; + + var trimmed = connectString.Trim(); + if (trimmed.Length == 0) + { + return false; + } + + var tokens = trimmed.Split(new[] { ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + for (var i = 0; i < tokens.Length; i++) + { + var token = tokens[i]; + if (TryParseToken(token, out address, out port)) + { + return true; + } + + if (IsConnectKeyword(token) && i + 1 < tokens.Length && TryParseToken(tokens[i + 1], out address, out port)) + { + return true; + } + } + + return TryParseToken(trimmed, out address, out port); + } + + private static bool TryParseToken(string token, out string address, out int port) + { + address = string.Empty; + port = 0; + + if (string.IsNullOrWhiteSpace(token)) + { + return false; + } + + var sanitized = StripConnectSyntax(token); + if (string.IsNullOrWhiteSpace(sanitized)) + { + return false; + } + + sanitized = sanitized.Trim('"'); + + if (Uri.TryCreate($"tcp://{sanitized}", UriKind.Absolute, out var uri) && !string.IsNullOrEmpty(uri.Host)) + { + address = uri.Host; + port = uri.IsDefaultPort ? NetworkDefaults.DefaultPort : uri.Port; + return port > 0 && port <= 65535; + } + + var parts = sanitized.Split(':'); + if (parts.Length == 2 && !string.IsNullOrWhiteSpace(parts[0]) && int.TryParse(parts[1], out port)) + { + address = parts[0]; + return port > 0 && port <= 65535; + } + + if (parts.Length == 1 && !string.IsNullOrWhiteSpace(parts[0])) + { + address = parts[0]; + port = NetworkDefaults.DefaultPort; + return true; + } + + return false; + } + + private static string StripConnectSyntax(string token) + { + var sanitized = token.Trim('"').Trim(); + if (sanitized.Length == 0) + { + return sanitized; + } + + if (sanitized.StartsWith("+connect", StringComparison.OrdinalIgnoreCase)) + { + sanitized = sanitized.Substring("+connect".Length); + } + else if (sanitized.StartsWith("connect", StringComparison.OrdinalIgnoreCase)) + { + sanitized = sanitized.Substring("connect".Length); + } + + sanitized = sanitized.TrimStart('=', ':'); + + var equalsIndex = sanitized.LastIndexOf('='); + if (equalsIndex >= 0 && equalsIndex < sanitized.Length - 1) + { + sanitized = sanitized.Substring(equalsIndex + 1); + } + + return sanitized.Trim(); + } + + private static bool IsConnectKeyword(string token) + { + var sanitized = token.Trim(); + return sanitized.Equals("+connect", StringComparison.OrdinalIgnoreCase) || + sanitized.Equals("connect", StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/Multibonk/Networking/Steam/SteamTunnelService.cs b/Multibonk/Networking/Steam/SteamTunnelService.cs new file mode 100644 index 0000000..722b43d --- /dev/null +++ b/Multibonk/Networking/Steam/SteamTunnelService.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Concurrent; +using System.Reflection; +using MelonLoader; + +namespace Multibonk.Networking.Steam +{ + public class SteamTunnelService + { + private readonly ConcurrentQueue pendingEndpoints = new(); + private readonly object syncRoot = new(); + + private MethodInfo? activateOverlayMethod; + private Type? steamFriendsType; + private bool missingSteamFriendsLogged; + private bool missingOverlayMethodLogged; + + public SteamTunnelService() + { + AppDomain.CurrentDomain.AssemblyLoad += HandleAssemblyLoaded; + ResolveSteamOverlay(); + } + + public bool IsOverlayAvailable => activateOverlayMethod != null; + + public bool HasPendingEndpoint => pendingEndpoints.TryPeek(out _); + + internal Type? SteamFriendsType => steamFriendsType; + + public void RegisterEndpoint(string address, int port) + { + if (string.IsNullOrWhiteSpace(address)) + { + MelonLogger.Warning("Ignoring Steam tunnel endpoint with an empty address."); + return; + } + + if (port <= 0 || port > 65535) + { + MelonLogger.Warning($"Ignoring Steam tunnel endpoint with invalid port: {port}."); + return; + } + + pendingEndpoints.Enqueue(new SteamTunnelEndpoint(address, port)); + MelonLogger.Msg($"Registered Steam tunnel endpoint {address}:{port}."); + } + + public bool TryPeekEndpoint(out SteamTunnelEndpoint endpoint) => pendingEndpoints.TryPeek(out endpoint); + + public bool TryConsumeEndpoint(out SteamTunnelEndpoint endpoint) => pendingEndpoints.TryDequeue(out endpoint); + + public void ClearEndpoints() + { + while (pendingEndpoints.TryDequeue(out _)) + { + } + } + + public bool TryOpenFriendsOverlay() + { + if (activateOverlayMethod == null) + { + MelonLogger.Warning("Steam overlay is unavailable. Ensure Steam is running before enabling Steam tunneling."); + return false; + } + + try + { + activateOverlayMethod.Invoke(null, new object[] { "Friends" }); + MelonLogger.Msg("Opened Steam friends overlay for tunneling."); + return true; + } + catch (Exception ex) + { + MelonLogger.Error($"Failed to open Steam overlay: {ex.Message}"); + return false; + } + } + + private void HandleAssemblyLoaded(object? sender, AssemblyLoadEventArgs args) + { + ResolveSteamOverlay(); + } + + private void ResolveSteamOverlay() + { + lock (syncRoot) + { + if (activateOverlayMethod != null) + { + return; + } + + var locatedSteamFriends = SteamFriendsReflection.LocateSteamFriendsType(); + if (locatedSteamFriends == null) + { + if (!missingSteamFriendsLogged) + { + MelonLogger.Warning("Could not locate Steamworks.SteamFriends. Steam tunneling will remain disabled until Steamworks initialises."); + missingSteamFriendsLogged = true; + } + + return; + } + + steamFriendsType = locatedSteamFriends; + + activateOverlayMethod = SteamFriendsReflection.FindActivateGameOverlay(locatedSteamFriends); + if (activateOverlayMethod != null) + { + MelonLogger.Msg($"Steam overlay integration ready using '{locatedSteamFriends.Assembly.FullName}'."); + missingOverlayMethodLogged = false; + return; + } + + if (!missingOverlayMethodLogged) + { + MelonLogger.Warning("Could not locate SteamFriends.ActivateGameOverlay. Steam tunneling will remain disabled until the overlay becomes available."); + missingOverlayMethodLogged = true; + } + } + } + + } + + public readonly struct SteamTunnelEndpoint : IEquatable + { + public string Address { get; } + public int Port { get; } + + public SteamTunnelEndpoint(string address, int port) + { + Address = address; + Port = port; + } + + public override string ToString() => $"{Address}:{Port}"; + + public bool Equals(SteamTunnelEndpoint other) => + string.Equals(Address, other.Address, StringComparison.OrdinalIgnoreCase) && Port == other.Port; + + public override bool Equals(object? obj) => obj is SteamTunnelEndpoint other && Equals(other); + + public override int GetHashCode() => HashCode.Combine(Address.ToLowerInvariant(), Port); + } +} diff --git a/Multibonk/Preferences.cs b/Multibonk/Preferences.cs index 7922ed6..c737655 100644 --- a/Multibonk/Preferences.cs +++ b/Multibonk/Preferences.cs @@ -1,20 +1,63 @@ -using MelonLoader; +using System; +using MelonLoader; namespace Multibonk { public static class Preferences { + public enum LootDistributionMode + { + Shared, + Individual, + Duplicated + } + private static readonly MelonPreferences_Category category; - public static readonly MelonPreferences_Entry IpAddress; public static readonly MelonPreferences_Entry PlayerName; + public static readonly MelonPreferences_Entry PvpEnabled; + public static readonly MelonPreferences_Entry ReviveEnabled; + public static readonly MelonPreferences_Entry ReviveTimeSeconds; + public static readonly MelonPreferences_Entry XpSharingMode; + public static readonly MelonPreferences_Entry GoldSharingMode; + public static readonly MelonPreferences_Entry ChestSharingMode; static Preferences() { category = MelonPreferences.CreateCategory("Multibonk", "General Settings"); - IpAddress = category.CreateEntry("IpAddress", "127.0.0.1", description: "IP address used at connection window"); - PlayerName = category.CreateEntry("PlayerName", "PlayerName", description: "PlayerName used at connection window"); + PlayerName = category.CreateEntry("PlayerName", "PlayerName", description: "Default player name used when connecting to a lobby."); + + PvpEnabled = category.CreateEntry("PvpEnabled", false, description: "Enable player-versus-player damage in multiplayer sessions."); + ReviveEnabled = category.CreateEntry("ReviveEnabled", true, description: "Allow players to revive fallen teammates."); + ReviveTimeSeconds = category.CreateEntry("ReviveTimeSeconds", 5f, description: "Delay, in seconds, before a downed player can be revived."); + + XpSharingMode = category.CreateEntry("XpSharingMode", LootDistributionMode.Shared.ToString(), description: "How experience orbs are distributed between players."); + GoldSharingMode = category.CreateEntry("GoldSharingMode", LootDistributionMode.Shared.ToString(), description: "How collected gold is distributed between players."); + ChestSharingMode = category.CreateEntry("ChestSharingMode", LootDistributionMode.Shared.ToString(), description: "How chest rewards are distributed between players."); + } + + public static LootDistributionMode GetXpSharingMode() => ParseEnumEntry(XpSharingMode, LootDistributionMode.Shared); + + public static void SetXpSharingMode(LootDistributionMode mode) => XpSharingMode.Value = mode.ToString(); + + public static LootDistributionMode GetGoldSharingMode() => ParseEnumEntry(GoldSharingMode, LootDistributionMode.Shared); + + public static void SetGoldSharingMode(LootDistributionMode mode) => GoldSharingMode.Value = mode.ToString(); + + public static LootDistributionMode GetChestSharingMode() => ParseEnumEntry(ChestSharingMode, LootDistributionMode.Shared); + + public static void SetChestSharingMode(LootDistributionMode mode) => ChestSharingMode.Value = mode.ToString(); + + private static LootDistributionMode ParseEnumEntry(MelonPreferences_Entry entry, LootDistributionMode fallback) + { + if (Enum.TryParse(entry.Value, out LootDistributionMode value)) + { + return value; + } + + entry.Value = fallback.ToString(); + return fallback; } } } diff --git a/Multibonk/UserInterface/UIManager.cs b/Multibonk/UserInterface/UIManager.cs index 557e6c0..c117b8b 100644 --- a/Multibonk/UserInterface/UIManager.cs +++ b/Multibonk/UserInterface/UIManager.cs @@ -1,5 +1,7 @@ -using MelonLoader; +using System; +using MelonLoader; using Multibonk.Networking.Lobby; +using Multibonk.Networking.Steam; using Multibonk.UserInterface.Window; using UnityEngine; @@ -22,48 +24,74 @@ public enum UIState public ConnectionWindow connectionWindow; public ClientLobbyWindow clientLobbyWindow; public HostLobbyWindow hostLobbyWindow; + private readonly OptionsWindow optionsWindow; + private readonly LobbyService lobbyService; + private readonly SteamTunnelService steamTunnelService; + + private SteamTunnelEndpoint? displayedSteamEndpoint; + private string steamTunnelStatusMessage = string.Empty; + private bool lastOverlayAvailability; public UIManager( ConnectionWindow connectionWindow, ClientLobbyWindow clientLobbyWindow, HostLobbyWindow hostLobbyWindow, - + OptionsWindow optionsWindow, LobbyContext lobby, - LobbyService lobbyService + LobbyService lobbyService, + SteamTunnelService steamTunnelService ) { this.connectionWindow = connectionWindow; this.clientLobbyWindow = clientLobbyWindow; this.hostLobbyWindow = hostLobbyWindow; + this.optionsWindow = optionsWindow; + this.lobbyService = lobbyService; + this.steamTunnelService = steamTunnelService; - connectionWindow.OnConnectClicked += (args) => + connectionWindow.OnConnectClicked += args => { - var parts = args.IP.Split(':'); - if (parts.Length != 2) return; - - var ip = parts[0]; - if (!int.TryParse(parts[1], out var port)) return; - - MelonLogger.Msg("Connecting to ip " + args.IP); - - lobbyService.JoinLobby(ip, port, args.PlayerName); - + MelonLogger.Msg($"Connecting to ip {args.Endpoint}"); + lobbyService.JoinLobby(args.Address, args.Port, args.PlayerName); }; - connectionWindow.OnStartServerClicked += (args) => + connectionWindow.OnStartServerClicked += args => { - lobbyService.CreateLobby(args.PlayerName); + lobbyService.CreateLobby(args.PlayerName, args.Port); }; + connectionWindow.OnOptionsClicked += ToggleOptions; + connectionWindow.OnSteamOverlayClicked += HandleSteamOverlayRequest; + hostLobbyWindow.OnCloseLobby += () => lobbyService.CloseLobby(); + hostLobbyWindow.OnOptionsClicked += ToggleOptions; + hostLobbyWindow.OnSteamOverlayClicked += HandleSteamOverlayRequest; clientLobbyWindow.OnLeaveLobby += () => lobbyService.CloseLobby(); + clientLobbyWindow.OnOptionsClicked += ToggleOptions; + clientLobbyWindow.OnSteamOverlayClicked += HandleSteamOverlayRequest; + + optionsWindow.OpenSteamOverlayRequested += HandleSteamOverlayRequest; + optionsWindow.PreferencesChanged += () => RefreshSteamTunnelStatus(forceUpdate: true); - lobby.OnLobbyJoin += (_) => SetState(UIState.ClientLobby); - lobby.OnLobbyCreated += (_) => SetState(UIState.HostLobby); - lobby.OnLobbyClosed += (_) => SetState(UIState.Connection); - lobby.OnLobbyJoinFailed += (_) => SetState(UIState.Connection); + lobby.OnLobbyJoin += _ => + { + connectionWindow.SetConnectionError(string.Empty); + SetState(UIState.ClientLobby); + }; + lobby.OnLobbyCreated += _ => + { + connectionWindow.SetConnectionError(string.Empty); + SetState(UIState.HostLobby); + }; + lobby.OnLobbyClosed += _ => + { + connectionWindow.SetConnectionError(string.Empty); + SetState(UIState.Connection); + }; + lobby.OnLobbyJoinFailed += HandleLobbyJoinFailed; + RefreshSteamTunnelStatus(forceUpdate: true); } public void OnGUI() @@ -74,29 +102,47 @@ public void OnGUI() _showingMenuBuffer = !_showingMenuBuffer; } - if (e.rawType == EventType.Layout && _showingMenuBuffer != IsShowingMenu) + if (e.rawType == EventType.Layout) { - IsShowingMenu = _showingMenuBuffer; + if (_showingMenuBuffer != IsShowingMenu) + { + IsShowingMenu = _showingMenuBuffer; + } + + RefreshSteamTunnelStatus(); } - if (IsShowingMenu) + if (!IsShowingMenu) { - switch (currentState) + if (optionsWindow.IsOpen) { - case UIState.Connection: - connectionWindow.Handle(); - break; + optionsWindow.Hide(); + } - case UIState.ClientLobby: - clientLobbyWindow.Handle(); - break; + return; + } - case UIState.HostLobby: - hostLobbyWindow.Handle(); - break; - } + switch (currentState) + { + case UIState.Connection: + connectionWindow.Handle(); + break; + + case UIState.ClientLobby: + clientLobbyWindow.Handle(); + break; + + case UIState.HostLobby: + hostLobbyWindow.Handle(); + break; + } + + if (optionsWindow.IsOpen) + { + optionsWindow.Handle(); } } + public void SetState(UIState newState) { currentState = newState; @@ -104,5 +150,135 @@ public void SetState(UIState newState) public UIState GetState() => currentState; + private void ToggleOptions() + { + if (optionsWindow.IsOpen) + { + optionsWindow.Hide(); + } + else + { + optionsWindow.Show(); + } + } + + private void HandleSteamOverlayRequest() + { + if (!steamTunnelService.TryOpenFriendsOverlay()) + { + RefreshSteamTunnelStatus(forceUpdate: true); + } + } + + private void RefreshSteamTunnelStatus(bool forceUpdate = false) + { + bool overlayAvailable = steamTunnelService.IsOverlayAvailable; + SteamTunnelEndpoint? endpoint = null; + string status; + + if (!overlayAvailable) + { + status = "Steam overlay is unavailable. Make sure Steam is running and overlay access is enabled."; + } + else if (steamTunnelService.TryPeekEndpoint(out var pending)) + { + endpoint = pending; + status = $"Steam invite ready: {pending.Address}:{pending.Port}."; + } + else + { + status = "Open the Steam friends overlay to invite or join friends."; + } + + bool overlayChanged = forceUpdate || overlayAvailable != lastOverlayAvailability; + if (overlayChanged) + { + connectionWindow.SetSteamOverlayAvailability(overlayAvailable); + clientLobbyWindow.SetSteamOverlayAvailability(overlayAvailable); + hostLobbyWindow.SetSteamOverlayAvailability(overlayAvailable); + optionsWindow.SetSteamOverlayAvailability(overlayAvailable); + lastOverlayAvailability = overlayAvailable; + } + + if (forceUpdate || !string.Equals(status, steamTunnelStatusMessage, StringComparison.Ordinal)) + { + steamTunnelStatusMessage = status; + connectionWindow.SetSteamTunnelStatus(status); + clientLobbyWindow.SetSteamTunnelStatus(status); + hostLobbyWindow.SetSteamTunnelStatus(status); + optionsWindow.SetSteamTunnelStatus(status); + } + + if (endpoint.HasValue) + { + bool shouldUpdateEndpoint = forceUpdate || !displayedSteamEndpoint.HasValue || !displayedSteamEndpoint.Value.Equals(endpoint.Value); + if (shouldUpdateEndpoint) + { + displayedSteamEndpoint = endpoint; + connectionWindow.SetIpAddress(endpoint.Value.ToString()); + AttemptAutoJoinFromSteam(endpoint.Value); + } + } + else if (displayedSteamEndpoint.HasValue) + { + displayedSteamEndpoint = null; + } + } + + private void AttemptAutoJoinFromSteam(SteamTunnelEndpoint endpoint) + { + if (currentState != UIState.Connection) + { + return; + } + + if (!steamTunnelService.TryConsumeEndpoint(out var consumed)) + { + return; + } + + var playerName = connectionWindow.GetPlayerName(); + if (string.IsNullOrWhiteSpace(playerName)) + { + playerName = Preferences.PlayerName.Value; + } + + if (string.IsNullOrWhiteSpace(playerName)) + { + playerName = "Player"; + } + + Preferences.PlayerName.Value = playerName; + + var message = $"Connecting to Steam invite {consumed.Address}:{consumed.Port}..."; + steamTunnelStatusMessage = message; + connectionWindow.SetConnectionError(string.Empty); + connectionWindow.SetSteamTunnelStatus(message); + clientLobbyWindow.SetSteamTunnelStatus(message); + hostLobbyWindow.SetSteamTunnelStatus(message); + optionsWindow.SetSteamTunnelStatus(message); + + MelonLogger.Msg($"Automatically joining Steam tunnel endpoint {consumed}."); + lobbyService.JoinLobby(consumed.Address, consumed.Port, playerName); + } + + private void HandleLobbyJoinFailed(string reason) + { + var message = string.IsNullOrWhiteSpace(reason) + ? "Failed to join lobby." + : reason; + + MelonLogger.Warning($"Lobby join failed: {message}"); + + connectionWindow.SetConnectionError(message); + + steamTunnelStatusMessage = message; + connectionWindow.SetSteamTunnelStatus(message); + clientLobbyWindow.SetSteamTunnelStatus(message); + hostLobbyWindow.SetSteamTunnelStatus(message); + optionsWindow.SetSteamTunnelStatus(message); + + SetState(UIState.Connection); + } } -} \ No newline at end of file +} diff --git a/Multibonk/UserInterface/Window/ClientLobbyWindow.cs b/Multibonk/UserInterface/Window/ClientLobbyWindow.cs index 7fe8e8f..ef5e3b4 100644 --- a/Multibonk/UserInterface/Window/ClientLobbyWindow.cs +++ b/Multibonk/UserInterface/Window/ClientLobbyWindow.cs @@ -1,14 +1,20 @@ -using Multibonk.Networking.Lobby; +using System; +using Multibonk.Networking.Lobby; using UnityEngine; namespace Multibonk.UserInterface.Window { public class ClientLobbyWindow : WindowBase { - private LobbyContext lobby; + private readonly LobbyContext lobby; + private bool steamOverlayAvailable; + private string steamTunnelStatus = string.Empty; + public event Action OnLeaveLobby; + public event Action OnOptionsClicked; + public event Action OnSteamOverlayClicked; - public ClientLobbyWindow(LobbyContext lobby) : base(new Rect(50, 50, 300, 200)) + public ClientLobbyWindow(LobbyContext lobby) : base(new Rect(50, 50, 360, 240)) { this.lobby = lobby; } @@ -18,13 +24,44 @@ protected override void RenderWindow(Rect rect) GUILayout.BeginArea(rect, GUI.skin.window); GUI.Box(new Rect(0, 0, rect.width, rect.height), GUIContent.none, GUI.skin.window); - GUILayout.Label("Client Lobby (Hide with F5)", new GUIStyle(GUI.skin.label) { normal = { textColor = Color.white } }); - GUILayout.Label("Connected Players:", new GUIStyle(GUI.skin.label) { normal = { textColor = Color.white } }); + var labelStyle = new GUIStyle(GUI.skin.label) + { + wordWrap = true + }; + labelStyle.normal.textColor = Color.white; + + GUILayout.Label("Client Lobby (Hide with F5)", labelStyle); + GUILayout.Label("Connected Players:", labelStyle); foreach (var player in lobby.GetPlayers()) - GUILayout.Label($"{player.Name} - {player.Ping}ms - {player.SelectedCharacter}", new GUIStyle(GUI.skin.label) { normal = { textColor = Color.white } }); + { + GUILayout.Label($"{player.Name} - {player.Ping}ms - {player.SelectedCharacter}", labelStyle); + } + + if (GUILayout.Button("Leave Lobby")) + { + LeaveLobby(); + } - if (GUILayout.Button("Leave Lobby")) LeaveLobby(); + GUILayout.BeginHorizontal(); + if (GUILayout.Button("Options")) + { + OnOptionsClicked?.Invoke(); + } + + bool originalState = GUI.enabled; + GUI.enabled = steamOverlayAvailable; + if (GUILayout.Button("Steam Friends Overlay")) + { + OnSteamOverlayClicked?.Invoke(); + } + GUI.enabled = originalState; + GUILayout.EndHorizontal(); + + if (!string.IsNullOrEmpty(steamTunnelStatus)) + { + GUILayout.Label(steamTunnelStatus, labelStyle); + } GUILayout.EndArea(); } @@ -33,6 +70,15 @@ private void LeaveLobby() { OnLeaveLobby?.Invoke(); } - } + public void SetSteamOverlayAvailability(bool available) + { + steamOverlayAvailable = available; + } + + public void SetSteamTunnelStatus(string status) + { + steamTunnelStatus = status; + } + } } diff --git a/Multibonk/UserInterface/Window/ConnectionWindow.cs b/Multibonk/UserInterface/Window/ConnectionWindow.cs index fa337c0..1883562 100644 --- a/Multibonk/UserInterface/Window/ConnectionWindow.cs +++ b/Multibonk/UserInterface/Window/ConnectionWindow.cs @@ -1,16 +1,21 @@ -using UnityEngine; +using System; +using UnityEngine; +using Multibonk.Networking; namespace Multibonk.UserInterface.Window { public class ConnectionWindowEventArgs { - public string IP { get; } public string PlayerName { get; } + public string Address { get; } + public int Port { get; } + public string Endpoint => $"{Address}:{Port}"; - public ConnectionWindowEventArgs(string playerName, string ip) + public ConnectionWindowEventArgs(string playerName, string address, int port) { - IP = ip; PlayerName = playerName; + Address = address; + Port = port; } } @@ -18,15 +23,21 @@ public class ConnectionWindow : WindowBase { public event Action OnStartServerClicked; public event Action OnConnectClicked; + public event Action OnOptionsClicked; + public event Action OnSteamOverlayClicked; - private string ipAddress = "127.0.0.1"; + private string ipAddress = $"{NetworkDefaults.DefaultAddress}:{NetworkDefaults.DefaultPort}"; + private string listenPort = NetworkDefaults.DefaultPort.ToString(); private string playerName = "PlayerName"; - private bool nameIsFocused = false; - private bool ipIsFocused = false; + private bool nameIsFocused; + private bool ipIsFocused; + private bool listenPortIsFocused; + private bool steamOverlayAvailable; + private string steamTunnelStatus = string.Empty; + private string connectionErrorMessage = string.Empty; - public ConnectionWindow() : base(new Rect(10, 10, 300, 200)) + public ConnectionWindow() : base(new Rect(10, 10, 340, 260)) { - ipAddress = Preferences.IpAddress.Value; playerName = Preferences.PlayerName.Value; } @@ -35,30 +46,77 @@ protected override void RenderWindow(Rect rect) GUILayout.BeginArea(rect, GUI.skin.window); GUI.Box(new Rect(0, 0, rect.width, rect.height), GUIContent.none, GUI.skin.window); - GUILayout.Label("Multibonk Connection Menu (Hide with F5)", new GUIStyle(GUI.skin.label) { normal = { textColor = Color.white } }); + var labelStyle = new GUIStyle(GUI.skin.label) + { + wordWrap = true + }; + labelStyle.normal.textColor = Color.white; + GUILayout.Label("Multibonk Connection Menu (Hide with F5)", labelStyle); GUILayout.BeginHorizontal(); - GUILayout.Label("Name:", new GUIStyle(GUI.skin.label) { normal = { textColor = Color.white } }); - playerName = Utils.CustomTextField(playerName, ref nameIsFocused, new Rect(0, 0, 150, 20)); + GUILayout.Label("Name:", labelStyle); + playerName = Utils.CustomTextField(playerName, ref nameIsFocused, new Rect(0, 0, 160, 22)); GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(); + GUILayout.Label("IP:", labelStyle); + string newEndpoint = Utils.CustomTextField(ipAddress, ref ipIsFocused, new Rect(0, 0, 160, 22)); + if (!string.Equals(newEndpoint, ipAddress, StringComparison.Ordinal)) + { + ipAddress = newEndpoint; + connectionErrorMessage = string.Empty; + } + GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); - GUILayout.Label("IP:", new GUIStyle(GUI.skin.label) { normal = { textColor = Color.white } }); - ipAddress = Utils.CustomTextField(ipAddress, ref ipIsFocused, new Rect(0, 0, 150, 20)); + GUILayout.Label("Listen Port:", labelStyle); + string newListenPort = Utils.CustomTextField(listenPort, ref listenPortIsFocused, new Rect(0, 0, 80, 22)); + if (!string.Equals(newListenPort, listenPort, StringComparison.Ordinal)) + { + listenPort = newListenPort; + connectionErrorMessage = string.Empty; + } GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(); + if (GUILayout.Button("Options")) + { + OnOptionsClicked?.Invoke(); + } + + bool shouldEnableOverlayButton = steamOverlayAvailable; + bool previousGuiState = GUI.enabled; + GUI.enabled = shouldEnableOverlayButton; + if (GUILayout.Button("Steam Friends Overlay")) + { + OnSteamOverlayClicked?.Invoke(); + } + GUI.enabled = previousGuiState; + GUILayout.EndHorizontal(); + + if (!string.IsNullOrEmpty(steamTunnelStatus)) + { + GUILayout.Label(steamTunnelStatus, labelStyle); + } + + if (!string.IsNullOrEmpty(connectionErrorMessage)) + { + var errorStyle = new GUIStyle(labelStyle) + { + normal = { textColor = Color.red } + }; + GUILayout.Label(connectionErrorMessage, errorStyle); + } + if (GUILayout.Button("Start Server")) { - Preferences.IpAddress.Value = ipAddress; Preferences.PlayerName.Value = playerName; OnStartServer(); } if (GUILayout.Button("Connect")) { - Preferences.IpAddress.Value = ipAddress; Preferences.PlayerName.Value = playerName; OnConnect(); } @@ -66,7 +124,129 @@ protected override void RenderWindow(Rect rect) GUILayout.EndArea(); } - private void OnStartServer() => OnStartServerClicked?.Invoke(new ConnectionWindowEventArgs(playerName, ipAddress)); - private void OnConnect() => OnConnectClicked?.Invoke(new ConnectionWindowEventArgs(playerName, ipAddress)); + private void OnStartServer() + { + if (!TryParseListenPort(listenPort, out var port, out var error)) + { + connectionErrorMessage = error; + return; + } + + listenPort = port.ToString(); + connectionErrorMessage = string.Empty; + OnStartServerClicked?.Invoke(new ConnectionWindowEventArgs(playerName, NetworkDefaults.DefaultAddress, port)); + } + + private void OnConnect() + { + if (!TryParseEndpoint(ipAddress, out var address, out var port, out var error)) + { + connectionErrorMessage = error; + return; + } + + connectionErrorMessage = string.Empty; + OnConnectClicked?.Invoke(new ConnectionWindowEventArgs(playerName, address, port)); + } + + public void SetSteamOverlayAvailability(bool isAvailable) + { + steamOverlayAvailable = isAvailable; + } + + public void SetSteamTunnelStatus(string status) + { + steamTunnelStatus = status; + } + + public void SetIpAddress(string address) + { + if (!string.IsNullOrWhiteSpace(address)) + { + ipAddress = address; + connectionErrorMessage = string.Empty; + } + } + + public string GetPlayerName() => playerName; + + public void SetConnectionError(string message) + { + connectionErrorMessage = message ?? string.Empty; + } + + private bool TryParseEndpoint(string input, out string address, out int port, out string error) + { + address = string.Empty; + port = NetworkDefaults.DefaultPort; + error = string.Empty; + + if (string.IsNullOrWhiteSpace(input)) + { + error = "Enter a server address to connect."; + return false; + } + + string trimmed = input.Trim(); + + if (Uri.TryCreate($"tcp://{trimmed}", UriKind.Absolute, out var uri) && !string.IsNullOrEmpty(uri.Host)) + { + address = uri.Host; + port = uri.IsDefaultPort ? NetworkDefaults.DefaultPort : uri.Port; + } + else + { + var parts = trimmed.Split(':'); + if (parts.Length == 1) + { + address = parts[0]; + port = NetworkDefaults.DefaultPort; + } + else if (parts.Length == 2 && int.TryParse(parts[1], out var parsedPort)) + { + address = parts[0]; + port = parsedPort; + } + else + { + error = "Use the format host:port (example: 127.0.0.1:25565)."; + return false; + } + } + + if (string.IsNullOrWhiteSpace(address)) + { + error = "Server address cannot be empty."; + return false; + } + + if (port <= 0 || port > 65535) + { + error = "Port must be between 1 and 65535."; + return false; + } + + return true; + } + + private bool TryParseListenPort(string input, out int port, out string error) + { + error = string.Empty; + + if (string.IsNullOrWhiteSpace(input)) + { + port = NetworkDefaults.DefaultPort; + return true; + } + + if (!int.TryParse(input.Trim(), out port) || port <= 0 || port > 65535) + { + port = NetworkDefaults.DefaultPort; + error = "Listen port must be between 1 and 65535."; + return false; + } + + return true; + } } -} \ No newline at end of file +} diff --git a/Multibonk/UserInterface/Window/HostLobbyWindow.cs b/Multibonk/UserInterface/Window/HostLobbyWindow.cs index 3ebfaa6..3b3e0e6 100644 --- a/Multibonk/UserInterface/Window/HostLobbyWindow.cs +++ b/Multibonk/UserInterface/Window/HostLobbyWindow.cs @@ -1,14 +1,20 @@ -using Multibonk.Networking.Lobby; +using System; +using Multibonk.Networking.Lobby; using UnityEngine; namespace Multibonk.UserInterface.Window { public class HostLobbyWindow : WindowBase { - private LobbyContext LobbyContext { get; } + private readonly LobbyContext LobbyContext; + private bool steamOverlayAvailable; + private string steamTunnelStatus = string.Empty; + public event Action OnCloseLobby; + public event Action OnOptionsClicked; + public event Action OnSteamOverlayClicked; - public HostLobbyWindow(LobbyContext context) : base(new Rect(50, 50, 400, 300)) + public HostLobbyWindow(LobbyContext context) : base(new Rect(50, 50, 420, 280)) { LobbyContext = context; } @@ -18,21 +24,61 @@ protected override void RenderWindow(Rect rect) GUILayout.BeginArea(rect, GUI.skin.window); GUI.Box(new Rect(0, 0, rect.width, rect.height), GUIContent.none, GUI.skin.window); - GUILayout.Label("Host Lobby (Hide with F5)", new GUIStyle(GUI.skin.label) { normal = { textColor = Color.white } }); - GUILayout.Label("Connected Players:", new GUIStyle(GUI.skin.label) { normal = { textColor = Color.white } }); + var labelStyle = new GUIStyle(GUI.skin.label) + { + wordWrap = true + }; + labelStyle.normal.textColor = Color.white; + + GUILayout.Label("Host Lobby (Hide with F5)", labelStyle); + GUILayout.Label("Connected Players:", labelStyle); foreach (var player in LobbyContext.GetPlayers()) - GUILayout.Label($"{player.Name} - {player.Ping}ms - {player.SelectedCharacter}", new GUIStyle(GUI.skin.label) { normal = { textColor = Color.white } }); + { + GUILayout.Label($"{player.Name} - {player.Ping}ms - {player.SelectedCharacter}", labelStyle); + } + + GUILayout.BeginHorizontal(); + if (GUILayout.Button("Leave Lobby")) + { + CloseLobby(); + } - if (GUILayout.Button("Leave Lobby")) CloseLobby(); + if (GUILayout.Button("Options")) + { + OnOptionsClicked?.Invoke(); + } + + bool originalState = GUI.enabled; + GUI.enabled = steamOverlayAvailable; + if (GUILayout.Button("Steam Friends Overlay")) + { + OnSteamOverlayClicked?.Invoke(); + } + GUI.enabled = originalState; + GUILayout.EndHorizontal(); + + if (!string.IsNullOrEmpty(steamTunnelStatus)) + { + GUILayout.Label(steamTunnelStatus, labelStyle); + } GUILayout.EndArea(); } - private void CloseLobby() { OnCloseLobby?.Invoke(); } + + public void SetSteamOverlayAvailability(bool available) + { + steamOverlayAvailable = available; + } + + public void SetSteamTunnelStatus(string status) + { + steamTunnelStatus = status; + } } } diff --git a/Multibonk/UserInterface/Window/OptionsWindow.cs b/Multibonk/UserInterface/Window/OptionsWindow.cs new file mode 100644 index 0000000..bf598f2 --- /dev/null +++ b/Multibonk/UserInterface/Window/OptionsWindow.cs @@ -0,0 +1,268 @@ +using System; +using System.Globalization; +using UnityEngine; + +namespace Multibonk.UserInterface.Window +{ + public class OptionsWindow : WindowBase + { + private bool isOpen; + private bool pvpEnabled; + private bool reviveEnabled; + private string reviveDelayInput = string.Empty; + private float reviveDelaySeconds; + private string reviveDelayError = string.Empty; + private Preferences.LootDistributionMode xpMode; + private Preferences.LootDistributionMode goldMode; + private Preferences.LootDistributionMode chestMode; + private bool steamOverlayAvailable; + private string steamTunnelStatus = string.Empty; + + private GUISkin cachedSkin; + private GUIStyle windowLabelStyle; + private GUIStyle sectionTitleStyle; + private GUIStyle descriptionLabelStyle; + private GUIStyle toggleStyle; + private GUIStyle errorLabelStyle; + + public event Action PreferencesChanged; + public event Action OpenSteamOverlayRequested; + + public OptionsWindow() : base(new Rect(80, 80, 520, 420)) + { + RefreshFromPreferences(); + } + + public bool IsOpen => isOpen; + + public void Show() + { + RefreshFromPreferences(); + isOpen = true; + } + + public void Hide() + { + isOpen = false; + } + + public void SetSteamOverlayAvailability(bool available) + { + steamOverlayAvailable = available; + } + + public void SetSteamTunnelStatus(string status) + { + steamTunnelStatus = status; + } + + protected override void RenderWindow(Rect rect) + { + if (!isOpen) + { + return; + } + + GUILayout.BeginArea(rect, GUI.skin.window); + GUI.Box(new Rect(0, 0, rect.width, rect.height), GUIContent.none, GUI.skin.window); + + EnsureStyles(); + + var labelStyle = windowLabelStyle; + + GUILayout.Label("Gameplay Options", labelStyle); + + DrawToggleSection("Player Versus Player", "Allow players to damage each other.", ref pvpEnabled, value => + { + Preferences.PvpEnabled.Value = value; + }); + + DrawToggleSection("Revive Mechanics", "Allow teammates to revive each other when they go down.", ref reviveEnabled, value => + { + Preferences.ReviveEnabled.Value = value; + }); + + GUILayout.BeginHorizontal(); + GUILayout.Label("Revive delay (seconds):", labelStyle, GUILayout.Width(180)); + GUI.enabled = reviveEnabled; + string newInput = GUILayout.TextField(reviveDelayInput, GUILayout.Width(80)); + GUI.enabled = true; + + if (newInput != reviveDelayInput) + { + reviveDelayInput = newInput; + if (float.TryParse(newInput, NumberStyles.Float, CultureInfo.InvariantCulture, out var seconds) && seconds > 0f) + { + reviveDelaySeconds = seconds; + Preferences.ReviveTimeSeconds.Value = seconds; + PreferencesChanged?.Invoke(); + reviveDelayError = string.Empty; + } + else if (reviveEnabled) + { + reviveDelayError = "Enter a positive number (example: 5 or 7.5)."; + } + else + { + reviveDelayError = string.Empty; + } + } + + if (!string.IsNullOrEmpty(reviveDelayError) && reviveEnabled) + { + GUILayout.Label(reviveDelayError, errorLabelStyle); + } + + GUILayout.EndHorizontal(); + GUILayout.Space(10f); + + DrawDistributionSection("Experience sharing", ref xpMode, Preferences.SetXpSharingMode, + "Shared: everyone gains XP together.", + "Individual: only the collector gains XP.", + "Duplicated: each drop spawns for every player."); + + GUILayout.Space(6f); + + DrawDistributionSection("Gold sharing", ref goldMode, Preferences.SetGoldSharingMode, + "Shared: the team uses one shared wallet.", + "Individual: everyone keeps their own gold.", + "Duplicated: pickups reward every player equally."); + + GUILayout.Space(6f); + + DrawDistributionSection("Chest rewards", ref chestMode, Preferences.SetChestSharingMode, + "Shared: every chest rewards the whole team.", + "Individual: each player must open the chest themselves.", + "Duplicated: each chest can be opened once per player."); + + GUILayout.Space(12f); + + DrawSteamOverlaySection(); + + GUILayout.FlexibleSpace(); + + if (GUILayout.Button("Close")) + { + Hide(); + } + + GUILayout.EndArea(); + } + + private void DrawToggleSection(string title, string description, ref bool cache, Action setter) + { + bool newValue = GUILayout.Toggle(cache, title, toggleStyle); + if (newValue != cache) + { + cache = newValue; + setter(cache); + PreferencesChanged?.Invoke(); + } + + GUILayout.Label(description, descriptionLabelStyle); + } + + private void DrawDistributionSection(string title, + ref Preferences.LootDistributionMode cache, + Action setter, + string sharedDescription, + string individualDescription, + string duplicatedDescription) + { + GUILayout.Label(title, sectionTitleStyle); + + string[] labels = { "Shared", "Individual", "Duplicated" }; + int currentIndex = (int)cache; + int selectedIndex = GUILayout.Toolbar(currentIndex, labels); + if (selectedIndex != currentIndex) + { + cache = (Preferences.LootDistributionMode)selectedIndex; + setter(cache); + PreferencesChanged?.Invoke(); + } + + string description = cache switch + { + Preferences.LootDistributionMode.Shared => sharedDescription, + Preferences.LootDistributionMode.Individual => individualDescription, + _ => duplicatedDescription + }; + + GUILayout.Label(description, descriptionLabelStyle); + } + + private void RefreshFromPreferences() + { + pvpEnabled = Preferences.PvpEnabled.Value; + reviveEnabled = Preferences.ReviveEnabled.Value; + reviveDelaySeconds = Preferences.ReviveTimeSeconds.Value; + reviveDelayInput = reviveDelaySeconds.ToString("0.##", CultureInfo.InvariantCulture); + xpMode = Preferences.GetXpSharingMode(); + goldMode = Preferences.GetGoldSharingMode(); + chestMode = Preferences.GetChestSharingMode(); + reviveDelayError = string.Empty; + } + + private void DrawSteamOverlaySection() + { + GUILayout.Label("Steam tunneling", sectionTitleStyle); + GUILayout.Label("Use the Steam overlay to discover and join friend lobbies.", descriptionLabelStyle); + + if (!string.IsNullOrEmpty(steamTunnelStatus)) + { + GUILayout.Label(steamTunnelStatus, descriptionLabelStyle); + } + + bool previous = GUI.enabled; + GUI.enabled = steamOverlayAvailable; + if (GUILayout.Button("Open Steam Friends Overlay")) + { + OpenSteamOverlayRequested?.Invoke(); + } + GUI.enabled = previous; + } + + private void EnsureStyles() + { + if (cachedSkin == GUI.skin && windowLabelStyle != null && sectionTitleStyle != null && + descriptionLabelStyle != null && toggleStyle != null && errorLabelStyle != null) + { + return; + } + + cachedSkin = GUI.skin; + + windowLabelStyle = new GUIStyle(GUI.skin.label) + { + wordWrap = true + }; + windowLabelStyle.normal.textColor = Color.white; + + sectionTitleStyle = new GUIStyle(GUI.skin.label) + { + fontStyle = FontStyle.Bold + }; + sectionTitleStyle.normal.textColor = Color.white; + + descriptionLabelStyle = new GUIStyle(windowLabelStyle); + + toggleStyle = new GUIStyle(GUI.skin.toggle); + ApplyWhiteText(toggleStyle); + + errorLabelStyle = new GUIStyle(windowLabelStyle); + errorLabelStyle.normal.textColor = Color.red; + } + + private static void ApplyWhiteText(GUIStyle style) + { + style.normal.textColor = Color.white; + style.onNormal.textColor = Color.white; + style.hover.textColor = Color.white; + style.onHover.textColor = Color.white; + style.active.textColor = Color.white; + style.onActive.textColor = Color.white; + style.focused.textColor = Color.white; + style.onFocused.textColor = Color.white; + } + } +} diff --git a/Todo.md b/Todo.md new file mode 100644 index 0000000..2105934 --- /dev/null +++ b/Todo.md @@ -0,0 +1,14 @@ +# TODO + +## Steam tunneling +- [x] Wire the `SteamTunnelService.RegisterEndpoint` API into the Steam callbacks so tunnel invitations actually populate the queue; right now nothing ever enqueues endpoints, so overlay joins can't work end-to-end. +- [x] Listen for new assembly loads and retry locating `SteamFriends` so the overlay hooks survive late Steamworks initialization instead of only probing during startup. +- [x] Consume accepted tunnel endpoints automatically and initiate a connection from the Connection screen so players do not have to press Connect after joining via overlay. + +## UI polish +- [x] Surface lobby join failures and server-creation errors inside the UI rather than silently snapping back to the connection screen. +- [x] Expose the listen port separately from the address so hosts can communicate connection info without editing the combined endpoint field. +- [x] Cache GUIStyle instances in `OptionsWindow` to cut down on per-frame allocations once the layout stabilizes. + +## Gameplay rules +- [x] Validate on the gameplay layer that the preference flags (PvP, revive, loot distribution) propagate to the actual game systems; the UI updates the preferences, but the runtime hooks have not been audited yet. From e9fbabd44fcb3141ad2d70541ffd48004933a544 Mon Sep 17 00:00:00 2001 From: "nicogo.eth" Date: Wed, 8 Oct 2025 22:32:36 +0200 Subject: [PATCH 4/5] Implement custom GUI inputs --- Multibonk/UserInterface/Utils.cs | 116 ++++++++++++++++-- .../UserInterface/Window/OptionsWindow.cs | 6 +- 2 files changed, 112 insertions(+), 10 deletions(-) diff --git a/Multibonk/UserInterface/Utils.cs b/Multibonk/UserInterface/Utils.cs index 6c9f410..6f74475 100644 --- a/Multibonk/UserInterface/Utils.cs +++ b/Multibonk/UserInterface/Utils.cs @@ -1,7 +1,12 @@ -using UnityEngine; +using System.Collections.Generic; +using UnityEngine; public static class Utils { + private static GUISkin toolbarCachedSkin; + private static GUIStyle toolbarButtonStyle; + private static GUIStyle toolbarButtonSelectedStyle; + public static void HandleWindowDrag(ref Rect window, ref bool dragging, ref Vector2 dragOffset) { Event e = Event.current; @@ -29,20 +34,31 @@ public static void HandleTextFieldInput(ref string text, ref bool isFocused, Rec { Event e = Event.current; + if (!GUI.enabled) + { + isFocused = false; + return; + } if (e.type == EventType.MouseDown) { isFocused = rect.Contains(e.mousePosition); - } - if (isFocused && e.type == EventType.KeyDown) { - if (e.keyCode == KeyCode.Backspace && text.Length > 0) + if ((e.keyCode == KeyCode.Backspace || e.keyCode == KeyCode.Delete) && text.Length > 0) + { text = text.Substring(0, text.Length - 1); + } + else if (e.keyCode == KeyCode.Return || e.keyCode == KeyCode.KeypadEnter || e.keyCode == KeyCode.Escape || e.keyCode == KeyCode.Tab) + { + isFocused = false; + } else if (e.character != '\0' && !char.IsControl(e.character)) + { text += e.character; + } e.Use(); } @@ -50,11 +66,95 @@ public static void HandleTextFieldInput(ref string text, ref bool isFocused, Rec public static string CustomTextField(string currentText, ref bool isFocused, Rect rect) { - // not working - Rect calculated = GUILayoutUtility.GetRect(new GUIContent(currentText), GUI.skin.textField, GUILayout.Width(rect.width), GUILayout.Height(rect.height)); + currentText ??= string.Empty; + + var options = new List(); + if (rect.width > 0f) + { + options.Add(GUILayout.Width(rect.width)); + } + else + { + options.Add(GUILayout.ExpandWidth(true)); + } + + if (rect.height > 0f) + { + options.Add(GUILayout.Height(rect.height)); + } + + GUIStyle style = GUI.skin.textField; + Rect calculated = GUILayoutUtility.GetRect(new GUIContent(currentText), style, options.ToArray()); HandleTextFieldInput(ref currentText, ref isFocused, calculated); - GUI.Box(calculated, currentText); + + GUI.Box(calculated, currentText, style); return currentText; } -} \ No newline at end of file + + public static int CustomToolbar(int selectedIndex, string[] labels, params GUILayoutOption[] options) + { + if (labels == null || labels.Length == 0) + { + return selectedIndex; + } + + EnsureToolbarStyles(); + + int newIndex = selectedIndex; + GUILayout.BeginHorizontal(options); + for (int i = 0; i < labels.Length; i++) + { + bool isSelected = i == selectedIndex; + GUIStyle style = isSelected ? toolbarButtonSelectedStyle : toolbarButtonStyle; + bool pressed = GUILayout.Toggle(isSelected, labels[i] ?? string.Empty, style); + if (pressed && !isSelected) + { + newIndex = i; + } + } + GUILayout.EndHorizontal(); + + return newIndex; + } + + private static void EnsureToolbarStyles() + { + if (toolbarCachedSkin == GUI.skin && toolbarButtonStyle != null && toolbarButtonSelectedStyle != null) + { + return; + } + + toolbarCachedSkin = GUI.skin; + toolbarButtonStyle = new GUIStyle(GUI.skin.button); + toolbarButtonSelectedStyle = new GUIStyle(GUI.skin.button); + + CopyState(toolbarButtonSelectedStyle.active, toolbarButtonSelectedStyle.normal); + CopyState(toolbarButtonSelectedStyle.active, toolbarButtonSelectedStyle.hover); + CopyState(toolbarButtonSelectedStyle.active, toolbarButtonSelectedStyle.focused); + CopyState(toolbarButtonSelectedStyle.active, toolbarButtonSelectedStyle.onNormal); + CopyState(toolbarButtonSelectedStyle.active, toolbarButtonSelectedStyle.onHover); + CopyState(toolbarButtonSelectedStyle.active, toolbarButtonSelectedStyle.onFocused); + CopyState(toolbarButtonSelectedStyle.active, toolbarButtonSelectedStyle.onActive); + + if (toolbarButtonSelectedStyle.normal.background == null && toolbarButtonStyle.active.background != null) + { + toolbarButtonSelectedStyle.normal.background = toolbarButtonStyle.active.background; + } + if (toolbarButtonSelectedStyle.normal.textColor == default) + { + toolbarButtonSelectedStyle.normal.textColor = toolbarButtonStyle.active.textColor; + } + } + + private static void CopyState(GUIStyleState source, GUIStyleState destination) + { + if (source == null || destination == null) + { + return; + } + + destination.background = source.background; + destination.textColor = source.textColor; + } +} diff --git a/Multibonk/UserInterface/Window/OptionsWindow.cs b/Multibonk/UserInterface/Window/OptionsWindow.cs index bf598f2..dbd04df 100644 --- a/Multibonk/UserInterface/Window/OptionsWindow.cs +++ b/Multibonk/UserInterface/Window/OptionsWindow.cs @@ -12,6 +12,7 @@ public class OptionsWindow : WindowBase private string reviveDelayInput = string.Empty; private float reviveDelaySeconds; private string reviveDelayError = string.Empty; + private bool reviveDelayFieldFocused; private Preferences.LootDistributionMode xpMode; private Preferences.LootDistributionMode goldMode; private Preferences.LootDistributionMode chestMode; @@ -85,7 +86,7 @@ protected override void RenderWindow(Rect rect) GUILayout.BeginHorizontal(); GUILayout.Label("Revive delay (seconds):", labelStyle, GUILayout.Width(180)); GUI.enabled = reviveEnabled; - string newInput = GUILayout.TextField(reviveDelayInput, GUILayout.Width(80)); + string newInput = Utils.CustomTextField(reviveDelayInput, ref reviveDelayFieldFocused, new Rect(0f, 0f, 80f, 22f)); GUI.enabled = true; if (newInput != reviveDelayInput) @@ -173,7 +174,7 @@ private void DrawDistributionSection(string title, string[] labels = { "Shared", "Individual", "Duplicated" }; int currentIndex = (int)cache; - int selectedIndex = GUILayout.Toolbar(currentIndex, labels); + int selectedIndex = Utils.CustomToolbar(currentIndex, labels); if (selectedIndex != currentIndex) { cache = (Preferences.LootDistributionMode)selectedIndex; @@ -197,6 +198,7 @@ private void RefreshFromPreferences() reviveEnabled = Preferences.ReviveEnabled.Value; reviveDelaySeconds = Preferences.ReviveTimeSeconds.Value; reviveDelayInput = reviveDelaySeconds.ToString("0.##", CultureInfo.InvariantCulture); + reviveDelayFieldFocused = false; xpMode = Preferences.GetXpSharingMode(); goldMode = Preferences.GetGoldSharingMode(); chestMode = Preferences.GetChestSharingMode(); From df4736dc2512714e1edda975017d4696dcc14bf3 Mon Sep 17 00:00:00 2001 From: "nicogo.eth" Date: Wed, 8 Oct 2025 22:42:11 +0200 Subject: [PATCH 5/5] Improve options window controls and layout --- Multibonk/UserInterface/Utils.cs | 140 +++++++++++++----- .../UserInterface/Window/OptionsWindow.cs | 21 ++- 2 files changed, 119 insertions(+), 42 deletions(-) diff --git a/Multibonk/UserInterface/Utils.cs b/Multibonk/UserInterface/Utils.cs index 6f74475..61e0d8f 100644 --- a/Multibonk/UserInterface/Utils.cs +++ b/Multibonk/UserInterface/Utils.cs @@ -30,40 +30,6 @@ public static void HandleWindowDrag(ref Rect window, ref bool dragging, ref Vect } } - public static void HandleTextFieldInput(ref string text, ref bool isFocused, Rect rect) - { - Event e = Event.current; - - if (!GUI.enabled) - { - isFocused = false; - return; - } - - if (e.type == EventType.MouseDown) - { - isFocused = rect.Contains(e.mousePosition); - } - - if (isFocused && e.type == EventType.KeyDown) - { - if ((e.keyCode == KeyCode.Backspace || e.keyCode == KeyCode.Delete) && text.Length > 0) - { - text = text.Substring(0, text.Length - 1); - } - else if (e.keyCode == KeyCode.Return || e.keyCode == KeyCode.KeypadEnter || e.keyCode == KeyCode.Escape || e.keyCode == KeyCode.Tab) - { - isFocused = false; - } - else if (e.character != '\0' && !char.IsControl(e.character)) - { - text += e.character; - } - - e.Use(); - } - } - public static string CustomTextField(string currentText, ref bool isFocused, Rect rect) { currentText ??= string.Empty; @@ -84,14 +50,116 @@ public static string CustomTextField(string currentText, ref bool isFocused, Rec } GUIStyle style = GUI.skin.textField; + int controlId = GUIUtility.GetControlID(FocusType.Keyboard); Rect calculated = GUILayoutUtility.GetRect(new GUIContent(currentText), style, options.ToArray()); - HandleTextFieldInput(ref currentText, ref isFocused, calculated); + currentText = HandleTextFieldInput(currentText, calculated, controlId, ref isFocused); + + if (Event.current.type == EventType.Repaint) + { + style.Draw(calculated, new GUIContent(currentText), controlId); + } - GUI.Box(calculated, currentText, style); return currentText; } + private static string HandleTextFieldInput(string text, Rect rect, int controlId, ref bool isFocused) + { + Event e = Event.current; + + switch (e.type) + { + case EventType.MouseDown: + if (GUI.enabled && rect.Contains(e.mousePosition)) + { + GUIUtility.hotControl = controlId; + GUIUtility.keyboardControl = controlId; + isFocused = true; + e.Use(); + } + else if (GUIUtility.keyboardControl == controlId) + { + GUIUtility.keyboardControl = 0; + isFocused = false; + } + break; + + case EventType.MouseDrag: + if (GUIUtility.hotControl == controlId) + { + e.Use(); + } + break; + + case EventType.MouseUp: + if (GUIUtility.hotControl == controlId) + { + GUIUtility.hotControl = 0; + e.Use(); + } + break; + + case EventType.KeyDown: + if (GUIUtility.keyboardControl == controlId && GUI.enabled) + { + switch (e.keyCode) + { + case KeyCode.Backspace: + case KeyCode.Delete: + if (text.Length > 0) + { + text = text.Substring(0, text.Length - 1); + GUI.changed = true; + } + e.Use(); + break; + + case KeyCode.Return: + case KeyCode.KeypadEnter: + case KeyCode.Escape: + case KeyCode.Tab: + GUIUtility.keyboardControl = 0; + isFocused = false; + e.Use(); + break; + + default: + char c = e.character; + if (!char.IsControl(c)) + { + text += c; + GUI.changed = true; + e.Use(); + } + break; + } + } + break; + } + + if (!GUI.enabled && GUIUtility.keyboardControl == controlId) + { + GUIUtility.keyboardControl = 0; + isFocused = false; + } + + if (!GUI.enabled && GUIUtility.hotControl == controlId) + { + GUIUtility.hotControl = 0; + } + + if (GUIUtility.keyboardControl != controlId && isFocused) + { + isFocused = false; + } + else if (GUIUtility.keyboardControl == controlId && !isFocused) + { + isFocused = true; + } + + return text; + } + public static int CustomToolbar(int selectedIndex, string[] labels, params GUILayoutOption[] options) { if (labels == null || labels.Length == 0) diff --git a/Multibonk/UserInterface/Window/OptionsWindow.cs b/Multibonk/UserInterface/Window/OptionsWindow.cs index dbd04df..56512aa 100644 --- a/Multibonk/UserInterface/Window/OptionsWindow.cs +++ b/Multibonk/UserInterface/Window/OptionsWindow.cs @@ -29,7 +29,10 @@ public class OptionsWindow : WindowBase public event Action PreferencesChanged; public event Action OpenSteamOverlayRequested; - public OptionsWindow() : base(new Rect(80, 80, 520, 420)) + private const float WindowWidth = 520f; + private const float WindowHeight = 520f; + + public OptionsWindow() : base(new Rect(80f, 80f, WindowWidth, WindowHeight)) { RefreshFromPreferences(); } @@ -85,9 +88,10 @@ protected override void RenderWindow(Rect rect) GUILayout.BeginHorizontal(); GUILayout.Label("Revive delay (seconds):", labelStyle, GUILayout.Width(180)); + bool previousGuiState = GUI.enabled; GUI.enabled = reviveEnabled; string newInput = Utils.CustomTextField(reviveDelayInput, ref reviveDelayFieldFocused, new Rect(0f, 0f, 80f, 22f)); - GUI.enabled = true; + GUI.enabled = previousGuiState; if (newInput != reviveDelayInput) { @@ -115,28 +119,28 @@ protected override void RenderWindow(Rect rect) } GUILayout.EndHorizontal(); - GUILayout.Space(10f); + AddVerticalSpace(10f); DrawDistributionSection("Experience sharing", ref xpMode, Preferences.SetXpSharingMode, "Shared: everyone gains XP together.", "Individual: only the collector gains XP.", "Duplicated: each drop spawns for every player."); - GUILayout.Space(6f); + AddVerticalSpace(6f); DrawDistributionSection("Gold sharing", ref goldMode, Preferences.SetGoldSharingMode, "Shared: the team uses one shared wallet.", "Individual: everyone keeps their own gold.", "Duplicated: pickups reward every player equally."); - GUILayout.Space(6f); + AddVerticalSpace(6f); DrawDistributionSection("Chest rewards", ref chestMode, Preferences.SetChestSharingMode, "Shared: every chest rewards the whole team.", "Individual: each player must open the chest themselves.", "Duplicated: each chest can be opened once per player."); - GUILayout.Space(12f); + AddVerticalSpace(12f); DrawSteamOverlaySection(); @@ -255,6 +259,11 @@ private void EnsureStyles() errorLabelStyle.normal.textColor = Color.red; } + private static void AddVerticalSpace(float pixels) + { + GUILayoutUtility.GetRect(GUIContent.none, GUIStyle.none, GUILayout.Height(pixels)); + } + private static void ApplyWhiteText(GUIStyle style) { style.normal.textColor = Color.white;