Compare commits
10 Commits
e91ae0fc99
...
b6bcde41b1
| Author | SHA1 | Date | |
|---|---|---|---|
| b6bcde41b1 | |||
| 9b5fb2c632 | |||
| cf76acccf3 | |||
| 9868d30810 | |||
| 5abd025860 | |||
| d7718c1dff | |||
| ea57e0a52c | |||
| c3223d5db9 | |||
| 4871f7c150 | |||
| 3dcb9a85b5 |
10
KCClient.cs
10
KCClient.cs
@@ -19,7 +19,7 @@ namespace KCM
|
|||||||
{
|
{
|
||||||
public class KCClient : MonoBehaviour
|
public class KCClient : MonoBehaviour
|
||||||
{
|
{
|
||||||
public static Client client = new Client(Main.steamClient);
|
public static Client client = new Client();
|
||||||
|
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
@@ -101,7 +101,8 @@ namespace KCM
|
|||||||
|
|
||||||
public static void Connect(string ip)
|
public static void Connect(string ip)
|
||||||
{
|
{
|
||||||
Main.helper.Log("Trying to connect to: " + ip);
|
Main.EnsureNetworking();
|
||||||
|
Main.Log("Trying to connect to: " + ip);
|
||||||
client.Connect(ip, useMessageHandlers: false);
|
client.Connect(ip, useMessageHandlers: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,8 +113,9 @@ namespace KCM
|
|||||||
|
|
||||||
private void Preload(KCModHelper helper)
|
private void Preload(KCModHelper helper)
|
||||||
{
|
{
|
||||||
|
Main.helper = helper;
|
||||||
helper.Log("Preload run in client");
|
Main.EnsureNetworking();
|
||||||
|
Main.Log("Preload run in client");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SceneLoaded(KCModHelper helper)
|
private void SceneLoaded(KCModHelper helper)
|
||||||
|
|||||||
10
KCServer.cs
10
KCServer.cs
@@ -18,7 +18,7 @@ namespace KCM
|
|||||||
{
|
{
|
||||||
public class KCServer : MonoBehaviour
|
public class KCServer : MonoBehaviour
|
||||||
{
|
{
|
||||||
public static Server server = new Server(Main.steamServer);
|
public static Server server = new Server();
|
||||||
public static bool started = false;
|
public static bool started = false;
|
||||||
|
|
||||||
private static readonly Dictionary<ushort, Queue<SaveTransferPacket>> saveTransferQueues = new Dictionary<ushort, Queue<SaveTransferPacket>>();
|
private static readonly Dictionary<ushort, Queue<SaveTransferPacket>> saveTransferQueues = new Dictionary<ushort, Queue<SaveTransferPacket>>();
|
||||||
@@ -33,6 +33,7 @@ namespace KCM
|
|||||||
|
|
||||||
public static void StartServer()
|
public static void StartServer()
|
||||||
{
|
{
|
||||||
|
Main.EnsureNetworking();
|
||||||
server = new Server(Main.steamServer);
|
server = new Server(Main.steamServer);
|
||||||
server.MessageReceived += PacketHandler.HandlePacketServer;
|
server.MessageReceived += PacketHandler.HandlePacketServer;
|
||||||
|
|
||||||
@@ -203,9 +204,10 @@ namespace KCM
|
|||||||
|
|
||||||
private void Preload(KCModHelper helper)
|
private void Preload(KCModHelper helper)
|
||||||
{
|
{
|
||||||
helper.Log("server?");
|
Main.helper = helper;
|
||||||
|
Main.EnsureNetworking();
|
||||||
helper.Log("Preload run in server");
|
Main.Log("server?");
|
||||||
|
Main.Log("Preload run in server");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SceneLoaded(KCModHelper helper)
|
private void SceneLoaded(KCModHelper helper)
|
||||||
|
|||||||
79
LoadSaveOverrides/LoadSaveLoadHooks.cs
Normal file
79
LoadSaveOverrides/LoadSaveLoadHooks.cs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
using Harmony;
|
||||||
|
using KCM.LoadSaveOverrides;
|
||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KCM
|
||||||
|
{
|
||||||
|
public static class LoadSaveLoadAtPathHook
|
||||||
|
{
|
||||||
|
public static byte[] saveData = new byte[1];
|
||||||
|
public static string lastLoadedPath = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LoadSaveLoadHook
|
||||||
|
{
|
||||||
|
public static byte[] saveBytes = null;
|
||||||
|
public static bool memoryStreamHook = false;
|
||||||
|
public static MultiplayerSaveContainer saveContainer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPatch(typeof(LoadSave), "LoadAtPath")]
|
||||||
|
public class LoadSaveLoadAtPathCaptureHook
|
||||||
|
{
|
||||||
|
public static void Prefix(string path)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(path))
|
||||||
|
return;
|
||||||
|
|
||||||
|
LoadSaveLoadAtPathHook.lastLoadedPath = path;
|
||||||
|
|
||||||
|
if (!FileExists(path))
|
||||||
|
return;
|
||||||
|
|
||||||
|
LoadSaveLoadAtPathHook.saveData = ReadAllBytes(path);
|
||||||
|
Main.Log($"Captured save bytes from: {path} ({LoadSaveLoadAtPathHook.saveData.Length} bytes)");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Main.Log("Failed capturing save bytes from LoadSave.LoadAtPath");
|
||||||
|
Main.Log(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool FileExists(string path)
|
||||||
|
{
|
||||||
|
object result = InvokeSystemIoFile("Exists", new Type[] { typeof(string) }, new object[] { path });
|
||||||
|
return result is bool && (bool)result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] ReadAllBytes(string path)
|
||||||
|
{
|
||||||
|
object result = InvokeSystemIoFile("ReadAllBytes", new Type[] { typeof(string) }, new object[] { path });
|
||||||
|
return result as byte[];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object InvokeSystemIoFile(string methodName, Type[] parameterTypes, object[] args)
|
||||||
|
{
|
||||||
|
// Avoid direct references to System.IO in IL (some mod loaders forbid it).
|
||||||
|
const string typeName = "System.IO.File";
|
||||||
|
Type fileType =
|
||||||
|
Type.GetType(typeName) ??
|
||||||
|
Type.GetType(typeName + ", mscorlib") ??
|
||||||
|
Type.GetType(typeName + ", System") ??
|
||||||
|
Type.GetType(typeName + ", System.Runtime") ??
|
||||||
|
Type.GetType(typeName + ", System.Private.CoreLib");
|
||||||
|
|
||||||
|
if (fileType == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var method = fileType.GetMethod(methodName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static, null, parameterTypes, null);
|
||||||
|
if (method == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return method.Invoke(null, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,6 @@ namespace KCM.LoadSaveOverrides
|
|||||||
|
|
||||||
Main.helper.Log($"Saving data for {Main.kCPlayers.Count} ({KCServer.server.ClientCount}) players.");
|
Main.helper.Log($"Saving data for {Main.kCPlayers.Count} ({KCServer.server.ClientCount}) players.");
|
||||||
|
|
||||||
//this.PlayerSaveData = new PlayerSaveDataOverride().Pack(Player.inst);
|
|
||||||
foreach (var player in Main.kCPlayers.Values)
|
foreach (var player in Main.kCPlayers.Values)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -103,10 +102,8 @@ namespace KCM.LoadSaveOverrides
|
|||||||
|
|
||||||
public override object Unpack(object obj)
|
public override object Unpack(object obj)
|
||||||
{
|
{
|
||||||
//original Player reset was up here
|
|
||||||
foreach (var kvp in players)
|
foreach (var kvp in players)
|
||||||
{
|
{
|
||||||
|
|
||||||
KCPlayer player;
|
KCPlayer player;
|
||||||
|
|
||||||
if (!Main.kCPlayers.TryGetValue(kvp.Key, out player))
|
if (!Main.kCPlayers.TryGetValue(kvp.Key, out player))
|
||||||
@@ -121,7 +118,6 @@ namespace KCM.LoadSaveOverrides
|
|||||||
foreach (var player in Main.kCPlayers.Values)
|
foreach (var player in Main.kCPlayers.Values)
|
||||||
player.inst.Reset();
|
player.inst.Reset();
|
||||||
|
|
||||||
|
|
||||||
AIBrainsContainer.inst.ClearAIs();
|
AIBrainsContainer.inst.ClearAIs();
|
||||||
this.CameraSaveData.Unpack(Cam.inst);
|
this.CameraSaveData.Unpack(Cam.inst);
|
||||||
this.WorldSaveData.Unpack(World.inst);
|
this.WorldSaveData.Unpack(World.inst);
|
||||||
@@ -133,10 +129,6 @@ namespace KCM.LoadSaveOverrides
|
|||||||
}
|
}
|
||||||
this.TownNameSaveData.Unpack(TownNameUI.inst);
|
this.TownNameSaveData.Unpack(TownNameUI.inst);
|
||||||
|
|
||||||
|
|
||||||
//TownNameUI.inst.townName = kingdomNames[Main.PlayerSteamID];
|
|
||||||
TownNameUI.inst.SetTownName(kingdomNames[Main.PlayerSteamID]);
|
|
||||||
|
|
||||||
Main.helper.Log("Unpacking player data");
|
Main.helper.Log("Unpacking player data");
|
||||||
|
|
||||||
Player.PlayerSaveData clientPlayerData = null;
|
Player.PlayerSaveData clientPlayerData = null;
|
||||||
@@ -150,10 +142,9 @@ namespace KCM.LoadSaveOverrides
|
|||||||
clientPlayerData = kvp.Value;
|
clientPlayerData = kvp.Value;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{ // Maybe ??
|
{
|
||||||
Main.helper.Log("Loading player data: " + kvp.Key);
|
Main.helper.Log("Loading player data: " + kvp.Key);
|
||||||
|
|
||||||
|
|
||||||
KCPlayer player;
|
KCPlayer player;
|
||||||
|
|
||||||
if (!Main.kCPlayers.TryGetValue(kvp.Key, out player))
|
if (!Main.kCPlayers.TryGetValue(kvp.Key, out player))
|
||||||
@@ -166,52 +157,61 @@ namespace KCM.LoadSaveOverrides
|
|||||||
Player.inst = player.inst;
|
Player.inst = player.inst;
|
||||||
Main.helper.Log($"Number of landmasses: {World.inst.NumLandMasses}");
|
Main.helper.Log($"Number of landmasses: {World.inst.NumLandMasses}");
|
||||||
|
|
||||||
//Reset was here before unpack
|
|
||||||
kvp.Value.Unpack(player.inst);
|
kvp.Value.Unpack(player.inst);
|
||||||
|
|
||||||
Player.inst = oldPlayer;
|
Player.inst = oldPlayer;
|
||||||
|
|
||||||
|
|
||||||
player.banner = player.inst.PlayerLandmassOwner.bannerIdx;
|
player.banner = player.inst.PlayerLandmassOwner.bannerIdx;
|
||||||
player.kingdomName = TownNameUI.inst.townName;
|
player.kingdomName = TownNameUI.inst.townName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clientPlayerData.Unpack(Player.inst); // Unpack the current client player last so that loading of villagers works correctly.
|
clientPlayerData.Unpack(Player.inst);
|
||||||
|
|
||||||
Main.helper.Log("unpacked player data");
|
Main.helper.Log("unpacked player data");
|
||||||
Main.helper.Log("Setting banner and name");
|
Main.helper.Log("Setting banner and name");
|
||||||
|
|
||||||
var client = Main.kCPlayers[SteamUser.GetSteamID().ToString()];
|
var client = Main.kCPlayers[SteamUser.GetSteamID().ToString()];
|
||||||
|
|
||||||
|
|
||||||
client.banner = Player.inst.PlayerLandmassOwner.bannerIdx;
|
client.banner = Player.inst.PlayerLandmassOwner.bannerIdx;
|
||||||
client.kingdomName = TownNameUI.inst.townName;
|
client.kingdomName = TownNameUI.inst.townName;
|
||||||
|
|
||||||
Main.helper.Log("Finished unpacking player data");
|
Main.helper.Log("Finished unpacking player data");
|
||||||
|
|
||||||
// Fix AI brains save/load system to restore villager AI state
|
Main.helper.Log("Unpacking AI brains");
|
||||||
bool flag2 = this.AIBrainsSaveData != null;
|
bool flag10 = this.AIBrainsSaveData != null;
|
||||||
if (flag2)
|
if (flag10)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Main.helper.Log("Unpacking AI brains before player data");
|
this.AIBrainsSaveData.Unpack(AIBrainsContainer.inst);
|
||||||
// Use reflection to call UnpackPrePlayer if it exists
|
Main.helper.Log("AI brains unpacked successfully");
|
||||||
var aiSaveDataType = this.AIBrainsSaveData.GetType();
|
|
||||||
var unpackPrePlayerMethod = aiSaveDataType.GetMethod("UnpackPrePlayer", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
|
|
||||||
if (unpackPrePlayerMethod != null)
|
|
||||||
{
|
|
||||||
unpackPrePlayerMethod.Invoke(this.AIBrainsSaveData, new object[] { AIBrainsContainer.inst });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Main.helper.Log("UnpackPrePlayer method not found, skipping AI brains pre-unpack");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Main.helper.Log("Error unpacking AI brains pre-player: " + e.Message);
|
Main.helper.Log("Error unpacking AI brains: " + e.Message);
|
||||||
|
Main.helper.Log("Attempting to reinitialize AI systems");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AIBrainsContainer.inst.ClearAIs();
|
||||||
|
Main.helper.Log("AI systems reinitialized");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Main.helper.Log("Failed to reinitialize AI systems: " + ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Main.helper.Log("No AI brains save data found, initializing fresh AI");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Main.helper.Log("Fresh AI initialization completed");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Main.helper.Log("Failed fresh AI initialization: " + e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,80 +268,9 @@ namespace KCM.LoadSaveOverrides
|
|||||||
this.OrdersManagerSaveData.Unpack(OrdersManager.inst);
|
this.OrdersManagerSaveData.Unpack(OrdersManager.inst);
|
||||||
}
|
}
|
||||||
Main.helper.Log("Unpacking AI brains");
|
Main.helper.Log("Unpacking AI brains");
|
||||||
bool flag10 = this.AIBrainsSaveData != null;
|
|
||||||
if (flag10)
|
if (flag10)
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
this.AIBrainsSaveData.Unpack(AIBrainsContainer.inst);
|
this.AIBrainsSaveData.Unpack(AIBrainsContainer.inst);
|
||||||
Main.helper.Log("AI brains unpacked successfully");
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Main.helper.Log("Error unpacking AI brains: " + e.Message);
|
|
||||||
Main.helper.Log("Attempting to reinitialize AI systems");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
AIBrainsContainer.inst.ClearAIs();
|
|
||||||
// Force villager system refresh instead of direct brain access
|
|
||||||
if (VillagerSystem.inst != null)
|
|
||||||
{
|
|
||||||
var villagerSystemType = typeof(VillagerSystem);
|
|
||||||
var refreshMethods = villagerSystemType.GetMethods(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
|
|
||||||
.Where(m => m.Name.Contains("Refresh") || m.Name.Contains("Update") || m.Name.Contains("Restart"));
|
|
||||||
|
|
||||||
foreach (var method in refreshMethods)
|
|
||||||
{
|
|
||||||
if (method.GetParameters().Length == 0)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
method.Invoke(VillagerSystem.inst, null);
|
|
||||||
Main.helper.Log($"Called VillagerSystem.{method.Name} for AI reinit");
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Main.helper.Log("AI systems reinitialized");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Main.helper.Log("Failed to reinitialize AI systems: " + ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Main.helper.Log("No AI brains save data found, initializing fresh AI");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Force villager system refresh for fresh initialization
|
|
||||||
if (VillagerSystem.inst != null)
|
|
||||||
{
|
|
||||||
var villagerSystemType = typeof(VillagerSystem);
|
|
||||||
var refreshMethods = villagerSystemType.GetMethods(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
|
|
||||||
.Where(m => m.Name.Contains("Refresh") || m.Name.Contains("Update") || m.Name.Contains("Restart"));
|
|
||||||
|
|
||||||
foreach (var method in refreshMethods)
|
|
||||||
{
|
|
||||||
if (method.GetParameters().Length == 0)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
method.Invoke(VillagerSystem.inst, null);
|
|
||||||
Main.helper.Log($"Called VillagerSystem.{method.Name} for fresh AI");
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Main.helper.Log("Fresh AI initialization completed");
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Main.helper.Log("Failed fresh AI initialization: " + e.Message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Main.helper.Log("Unpacking custom save data");
|
Main.helper.Log("Unpacking custom save data");
|
||||||
bool flag11 = this.CustomSaveData != null;
|
bool flag11 = this.CustomSaveData != null;
|
||||||
@@ -365,7 +294,6 @@ namespace KCM.LoadSaveOverrides
|
|||||||
Main.helper.Log(e.ToString());
|
Main.helper.Log(e.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
World.inst.UpscaleFeatures();
|
World.inst.UpscaleFeatures();
|
||||||
Player.inst.RefreshVisibility(true);
|
Player.inst.RefreshVisibility(true);
|
||||||
for (int i = 0; i < Player.inst.Buildings.Count; i++)
|
for (int i = 0; i < Player.inst.Buildings.Count; i++)
|
||||||
@@ -373,7 +301,6 @@ namespace KCM.LoadSaveOverrides
|
|||||||
Player.inst.Buildings.data[i].UpdateMaterialSelection();
|
Player.inst.Buildings.data[i].UpdateMaterialSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increase loadTickDelay values to ensure proper initialization
|
|
||||||
Type playerType = typeof(Player);
|
Type playerType = typeof(Player);
|
||||||
FieldInfo loadTickDelayField = playerType.GetField("loadTickDelay", BindingFlags.Instance | BindingFlags.NonPublic);
|
FieldInfo loadTickDelayField = playerType.GetField("loadTickDelay", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
if (loadTickDelayField != null)
|
if (loadTickDelayField != null)
|
||||||
@@ -381,7 +308,6 @@ namespace KCM.LoadSaveOverrides
|
|||||||
loadTickDelayField.SetValue(Player.inst, 3);
|
loadTickDelayField.SetValue(Player.inst, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnitSystem.inst.loadTickDelay = 3;
|
|
||||||
Type unitSystemType = typeof(UnitSystem);
|
Type unitSystemType = typeof(UnitSystem);
|
||||||
loadTickDelayField = unitSystemType.GetField("loadTickDelay", BindingFlags.Instance | BindingFlags.NonPublic);
|
loadTickDelayField = unitSystemType.GetField("loadTickDelay", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
if (loadTickDelayField != null)
|
if (loadTickDelayField != null)
|
||||||
@@ -389,7 +315,6 @@ namespace KCM.LoadSaveOverrides
|
|||||||
loadTickDelayField.SetValue(UnitSystem.inst, 3);
|
loadTickDelayField.SetValue(UnitSystem.inst, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
// JobSystem.inst.loadTickDelay = 3;
|
|
||||||
Type jobSystemType = typeof(JobSystem);
|
Type jobSystemType = typeof(JobSystem);
|
||||||
loadTickDelayField = jobSystemType.GetField("loadTickDelay", BindingFlags.Instance | BindingFlags.NonPublic);
|
loadTickDelayField = jobSystemType.GetField("loadTickDelay", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
if (loadTickDelayField != null)
|
if (loadTickDelayField != null)
|
||||||
@@ -397,7 +322,6 @@ namespace KCM.LoadSaveOverrides
|
|||||||
loadTickDelayField.SetValue(JobSystem.inst, 3);
|
loadTickDelayField.SetValue(JobSystem.inst, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
// VillagerSystem.inst.loadTickDelay = 3;
|
|
||||||
Type villagerSystemType = typeof(VillagerSystem);
|
Type villagerSystemType = typeof(VillagerSystem);
|
||||||
loadTickDelayField = villagerSystemType.GetField("loadTickDelay", BindingFlags.Instance | BindingFlags.NonPublic);
|
loadTickDelayField = villagerSystemType.GetField("loadTickDelay", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
if (loadTickDelayField != null)
|
if (loadTickDelayField != null)
|
||||||
@@ -405,202 +329,9 @@ namespace KCM.LoadSaveOverrides
|
|||||||
loadTickDelayField.SetValue(VillagerSystem.inst, 3);
|
loadTickDelayField.SetValue(VillagerSystem.inst, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Force AI system restart after load
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Main.helper.Log("Forcing AI system restart after load");
|
|
||||||
|
|
||||||
// Force villager system refresh instead of direct brain access
|
|
||||||
if (VillagerSystem.inst != null)
|
|
||||||
{
|
|
||||||
var villagerSystemType = typeof(VillagerSystem);
|
|
||||||
var refreshMethods = villagerSystemType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
|
|
||||||
.Where(m => m.Name.Contains("Refresh") || m.Name.Contains("Rebuild") || m.Name.Contains("Update") || m.Name.Contains("Restart"));
|
|
||||||
|
|
||||||
foreach (var method in refreshMethods)
|
|
||||||
{
|
|
||||||
if (method.GetParameters().Length == 0)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
method.Invoke(VillagerSystem.inst, null);
|
|
||||||
Main.helper.Log($"Called VillagerSystem.{method.Name}()");
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force job system refresh
|
|
||||||
if (JobSystem.inst != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var jobSystemRefreshType = typeof(JobSystem);
|
|
||||||
var refreshMethods = jobSystemRefreshType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
|
|
||||||
.Where(m => m.Name.Contains("Refresh") || m.Name.Contains("Rebuild") || m.Name.Contains("Update"));
|
|
||||||
|
|
||||||
foreach (var method in refreshMethods)
|
|
||||||
{
|
|
||||||
if (method.GetParameters().Length == 0)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
method.Invoke(JobSystem.inst, null);
|
|
||||||
Main.helper.Log($"Called JobSystem.{method.Name}()");
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Main.helper.Log($"Error refreshing job system: {e.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Main.helper.Log("AI system restart completed");
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Main.helper.Log($"Error during AI system restart: {e.Message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
Main.helper.Log($"Setting kingdom name to: {kingdomNames[Main.PlayerSteamID]}");
|
Main.helper.Log($"Setting kingdom name to: {kingdomNames[Main.PlayerSteamID]}");
|
||||||
TownNameUI.inst.SetTownName(kingdomNames[Main.PlayerSteamID]);
|
TownNameUI.inst.SetTownName(kingdomNames[Main.PlayerSteamID]);
|
||||||
|
|
||||||
// Perform villager state resync after loading completes
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Main.helper.Log("Starting villager state resync after load");
|
|
||||||
|
|
||||||
// Simple resync without async complications
|
|
||||||
Main.helper.Log("Performing villager resync");
|
|
||||||
|
|
||||||
// Force villager system refresh
|
|
||||||
if (VillagerSystem.inst != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var villagerSystemType = typeof(VillagerSystem);
|
|
||||||
var refreshMethods = villagerSystemType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
|
|
||||||
.Where(m => m.Name.Contains("Refresh") || m.Name.Contains("Update") || m.Name.Contains("Restart"));
|
|
||||||
|
|
||||||
foreach (var method in refreshMethods)
|
|
||||||
{
|
|
||||||
if (method.GetParameters().Length == 0)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
method.Invoke(VillagerSystem.inst, null);
|
|
||||||
Main.helper.Log($"Called VillagerSystem.{method.Name}()");
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Main.helper.Log($"Error refreshing villager system: {e.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force job system refresh
|
|
||||||
if (JobSystem.inst != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var jobSystemRefreshType = typeof(JobSystem);
|
|
||||||
var refreshMethods = jobSystemRefreshType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
|
|
||||||
.Where(m => m.Name.Contains("Refresh") || m.Name.Contains("Update") || m.Name.Contains("Restart"));
|
|
||||||
|
|
||||||
foreach (var method in refreshMethods)
|
|
||||||
{
|
|
||||||
if (method.GetParameters().Length == 0)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
method.Invoke(JobSystem.inst, null);
|
|
||||||
Main.helper.Log($"Called JobSystem.{method.Name}()");
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Main.helper.Log($"Error refreshing job system: {e.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Main.helper.Log("Villager state resync completed");
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Main.helper.Log($"Error during villager resync: {e.Message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure villager is in correct system lists
|
|
||||||
if (v.workerJob != null && Player.inst != null)
|
|
||||||
{
|
|
||||||
if (!Player.inst.Workers.Contains(v))
|
|
||||||
{
|
|
||||||
Player.inst.Workers.Add(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (v.workerJob == null && Player.inst != null)
|
|
||||||
{
|
|
||||||
if (!Player.inst.Homeless.Contains(v))
|
|
||||||
{
|
|
||||||
Player.inst.Homeless.Add(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Main.helper.Log($"Error resyncing villager {i}: {e.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force job system to re-evaluate all jobs
|
|
||||||
if (JobSystem.inst != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var jobSystemType = typeof(JobSystem);
|
|
||||||
var updateMethods = jobSystemType.GetMethods(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
|
|
||||||
.Where(m => (m.Name.Contains("Update") || m.Name.Contains("Refresh")) && m.GetParameters().Length == 0);
|
|
||||||
|
|
||||||
foreach (var method in updateMethods)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
method.Invoke(JobSystem.inst, null);
|
|
||||||
Main.helper.Log($"Called JobSystem.{method.Name} for resync");
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Main.helper.Log($"Error updating job system: {e.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Main.helper.Log("Villager state resync completed");
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Main.helper.Log($"Error in delayed villager resync: {e.Message}");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Main.helper.Log($"Error starting villager resync: {e.Message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,15 +103,15 @@ namespace KCM.Packets.Lobby
|
|||||||
// Handle completed transfer here
|
// Handle completed transfer here
|
||||||
Main.helper.Log("Save Transfer complete!");
|
Main.helper.Log("Save Transfer complete!");
|
||||||
|
|
||||||
LoadSaveLoadHook.saveBytes = saveData;
|
KCM.LoadSaveLoadHook.saveBytes = saveData;
|
||||||
LoadSaveLoadHook.memoryStreamHook = true;
|
KCM.LoadSaveLoadHook.memoryStreamHook = true;
|
||||||
|
|
||||||
LoadSave.Load();
|
LoadSave.Load();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Main.SetMultiplayerSaveLoadInProgress(true);
|
Main.SetMultiplayerSaveLoadInProgress(true);
|
||||||
LoadSaveLoadHook.saveContainer.Unpack(null);
|
KCM.LoadSaveLoadHook.saveContainer.Unpack(null);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,11 +24,12 @@ namespace KCM
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//Main.helper = _helper;
|
Main.helper = _helper;
|
||||||
|
Main.EnsureNetworking();
|
||||||
|
|
||||||
assetBundle = KCModHelper.LoadAssetBundle(_helper.modPath, "serverbrowserpkg");
|
assetBundle = KCModHelper.LoadAssetBundle(_helper.modPath, "serverbrowserpkg");
|
||||||
|
|
||||||
Main.helper.Log(String.Join(", ", assetBundle.GetAllAssetNames()));
|
Main.Log(String.Join(", ", assetBundle.GetAllAssetNames()));
|
||||||
|
|
||||||
serverBrowserPrefab = assetBundle.LoadAsset("assets/workspace/serverbrowser.prefab") as GameObject;
|
serverBrowserPrefab = assetBundle.LoadAsset("assets/workspace/serverbrowser.prefab") as GameObject;
|
||||||
serverEntryItemPrefab = assetBundle.LoadAsset("assets/workspace/serverentryitem.prefab") as GameObject;
|
serverEntryItemPrefab = assetBundle.LoadAsset("assets/workspace/serverentryitem.prefab") as GameObject;
|
||||||
@@ -41,13 +42,13 @@ namespace KCM
|
|||||||
|
|
||||||
modalUIPrefab = assetBundle.LoadAsset("assets/workspace/modalui.prefab") as GameObject;
|
modalUIPrefab = assetBundle.LoadAsset("assets/workspace/modalui.prefab") as GameObject;
|
||||||
|
|
||||||
Main.helper.Log("Loaded assets");
|
Main.Log("Loaded assets");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Main.helper.Log(ex.ToString());
|
Main.Log(ex);
|
||||||
Main.helper.Log(ex.Message);
|
Main.Log(ex.Message);
|
||||||
Main.helper.Log(ex.StackTrace);
|
Main.Log(ex.StackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ public class KCMSteamManager : MonoBehaviour {
|
|||||||
|
|
||||||
[AOT.MonoPInvokeCallback(typeof(SteamAPIWarningMessageHook_t))]
|
[AOT.MonoPInvokeCallback(typeof(SteamAPIWarningMessageHook_t))]
|
||||||
protected static void SteamAPIDebugTextHook(int nSeverity, System.Text.StringBuilder pchDebugText) {
|
protected static void SteamAPIDebugTextHook(int nSeverity, System.Text.StringBuilder pchDebugText) {
|
||||||
Main.helper.Log(pchDebugText.ToString());
|
Main.Log(pchDebugText.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
#if UNITY_2019_3_OR_NEWER
|
#if UNITY_2019_3_OR_NEWER
|
||||||
@@ -64,7 +64,7 @@ public class KCMSteamManager : MonoBehaviour {
|
|||||||
|
|
||||||
protected virtual void Awake() {
|
protected virtual void Awake() {
|
||||||
// Only one instance of SteamManager at a time!
|
// Only one instance of SteamManager at a time!
|
||||||
Main.helper.Log("Steam awake");
|
Main.Log("Steam awake");
|
||||||
if (s_instance != null) {
|
if (s_instance != null) {
|
||||||
Destroy(gameObject);
|
Destroy(gameObject);
|
||||||
return;
|
return;
|
||||||
@@ -76,7 +76,7 @@ public class KCMSteamManager : MonoBehaviour {
|
|||||||
// The most common case where this happens is when SteamManager gets destroyed because of Application.Quit(),
|
// The most common case where this happens is when SteamManager gets destroyed because of Application.Quit(),
|
||||||
// and then some Steamworks code in some other OnDestroy gets called afterwards, creating a new SteamManager.
|
// and then some Steamworks code in some other OnDestroy gets called afterwards, creating a new SteamManager.
|
||||||
// You should never call Steamworks functions in OnDestroy, always prefer OnDisable if possible.
|
// You should never call Steamworks functions in OnDestroy, always prefer OnDisable if possible.
|
||||||
Main.helper.Log("Tried to Initialize the SteamAPI twice in one session!");
|
Main.Log("Tried to Initialize the SteamAPI twice in one session!");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -85,11 +85,11 @@ public class KCMSteamManager : MonoBehaviour {
|
|||||||
DontDestroyOnLoad(gameObject);
|
DontDestroyOnLoad(gameObject);
|
||||||
|
|
||||||
if (!Packsize.Test()) {
|
if (!Packsize.Test()) {
|
||||||
Main.helper.Log("[Steamworks.NET] Packsize Test returned false, the wrong version of Steamworks.NET is being run in this platform.");
|
Main.Log("[Steamworks.NET] Packsize Test returned false, the wrong version of Steamworks.NET is being run in this platform.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!DllCheck.Test()) {
|
if (!DllCheck.Test()) {
|
||||||
Main.helper.Log("[Steamworks.NET] DllCheck Test returned false, One or more of the Steamworks binaries seems to be the wrong version.");
|
Main.Log("[Steamworks.NET] DllCheck Test returned false, One or more of the Steamworks binaries seems to be the wrong version.");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -101,12 +101,12 @@ public class KCMSteamManager : MonoBehaviour {
|
|||||||
// See the Valve documentation for more information: https://partner.steamgames.com/doc/sdk/api#initialization_and_shutdown
|
// See the Valve documentation for more information: https://partner.steamgames.com/doc/sdk/api#initialization_and_shutdown
|
||||||
if (SteamAPI.RestartAppIfNecessary((AppId_t)569480)) {
|
if (SteamAPI.RestartAppIfNecessary((AppId_t)569480)) {
|
||||||
//Application.Quit();
|
//Application.Quit();
|
||||||
Main.helper.Log("Attempted to restart app");
|
Main.Log("Attempted to restart app");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (System.DllNotFoundException e) { // We catch this exception here, as it will be the first occurrence of it.
|
catch (System.DllNotFoundException e) { // We catch this exception here, as it will be the first occurrence of it.
|
||||||
Main.helper.Log("[Steamworks.NET] Could not load [lib]steam_api.dll/so/dylib. It's likely not in the correct location. Refer to the README for more details.\n" + e);
|
Main.Log("[Steamworks.NET] Could not load [lib]steam_api.dll/so/dylib. It's likely not in the correct location. Refer to the README for more details.\n" + e);
|
||||||
|
|
||||||
//Application.Quit();
|
//Application.Quit();
|
||||||
return;
|
return;
|
||||||
@@ -123,7 +123,7 @@ public class KCMSteamManager : MonoBehaviour {
|
|||||||
// https://partner.steamgames.com/doc/sdk/api#initialization_and_shutdown
|
// https://partner.steamgames.com/doc/sdk/api#initialization_and_shutdown
|
||||||
m_bInitialized = SteamAPI.Init();
|
m_bInitialized = SteamAPI.Init();
|
||||||
if (!m_bInitialized) {
|
if (!m_bInitialized) {
|
||||||
Main.helper.Log("[Steamworks.NET] SteamAPI_Init() failed. Refer to Valve's documentation or the comment above this line for more information.");
|
Main.Log("[Steamworks.NET] SteamAPI_Init() failed. Refer to Valve's documentation or the comment above this line for more information.");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -400,7 +400,7 @@ namespace KCM
|
|||||||
SfxSystem.PlayUiSelect();
|
SfxSystem.PlayUiSelect();
|
||||||
|
|
||||||
|
|
||||||
Main.TransitionTo(MenuState.Menu);
|
Main.TransitionTo(KCM.Enums.MenuState.Menu);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
125
UI/MultiplayerMenuInjector.cs
Normal file
125
UI/MultiplayerMenuInjector.cs
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
using Harmony;
|
||||||
|
using I2.Loc;
|
||||||
|
using Riptide.Demos.Steam.PlayerHosted;
|
||||||
|
using TMPro;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace KCM.UI
|
||||||
|
{
|
||||||
|
[HarmonyPatch(typeof(GameState), "SetNewMode")]
|
||||||
|
public static class MultiplayerMenuInjector
|
||||||
|
{
|
||||||
|
private static bool injected;
|
||||||
|
|
||||||
|
public static void Postfix()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
EnsureInjected();
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
Main.Log("MultiplayerMenuInjector failed");
|
||||||
|
Main.Log(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureInjected()
|
||||||
|
{
|
||||||
|
if (injected)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (GameState.inst == null || GameState.inst.mainMenuMode == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
GameObject mainMenuUi = GameState.inst.mainMenuMode.mainMenuUI;
|
||||||
|
if (mainMenuUi == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Transform buttonContainer = mainMenuUi.transform.Find("TopLevelUICanvas/TopLevel/Body/ButtonContainer");
|
||||||
|
if (buttonContainer == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (buttonContainer.Find("KCM_Multiplayer") != null)
|
||||||
|
{
|
||||||
|
injected = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Button template = mainMenuUi.transform.Find("TopLevelUICanvas/TopLevel/Body/ButtonContainer/New")?.GetComponent<Button>();
|
||||||
|
if (template == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Button button = Object.Instantiate(template, buttonContainer);
|
||||||
|
button.name = "KCM_Multiplayer";
|
||||||
|
|
||||||
|
foreach (Localize loc in button.GetComponentsInChildren<Localize>())
|
||||||
|
Object.Destroy(loc);
|
||||||
|
|
||||||
|
button.onClick = new Button.ButtonClickedEvent();
|
||||||
|
|
||||||
|
TextMeshProUGUI label = button.GetComponentInChildren<TextMeshProUGUI>();
|
||||||
|
if (label != null)
|
||||||
|
label.text = "Multiplayer";
|
||||||
|
|
||||||
|
int insertIndex = template.transform.GetSiblingIndex() + 1;
|
||||||
|
button.transform.SetSiblingIndex(insertIndex);
|
||||||
|
|
||||||
|
button.onClick.AddListener(OpenServerBrowser);
|
||||||
|
|
||||||
|
injected = true;
|
||||||
|
Main.Log("Injected Multiplayer menu button");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OpenServerBrowser()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Ensure Steam + lobby manager exist
|
||||||
|
_ = KCMSteamManager.Instance;
|
||||||
|
|
||||||
|
LobbyManager lobbyManager = Object.FindObjectOfType<LobbyManager>();
|
||||||
|
if (lobbyManager == null)
|
||||||
|
{
|
||||||
|
GameObject go = new GameObject("KCM_LobbyManager");
|
||||||
|
Object.DontDestroyOnLoad(go);
|
||||||
|
lobbyManager = go.AddComponent<LobbyManager>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure prefabs loaded
|
||||||
|
if (PrefabManager.serverBrowserPrefab == null || PrefabManager.serverLobbyPrefab == null)
|
||||||
|
{
|
||||||
|
if (Main.helper != null)
|
||||||
|
new PrefabManager().PreScriptLoad(Main.helper);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure UI initialized
|
||||||
|
KCM.ServerBrowser browser = Object.FindObjectOfType<KCM.ServerBrowser>();
|
||||||
|
if (browser == null)
|
||||||
|
{
|
||||||
|
GameObject go = new GameObject("KCM_ServerBrowser");
|
||||||
|
Object.DontDestroyOnLoad(go);
|
||||||
|
browser = go.AddComponent<KCM.ServerBrowser>();
|
||||||
|
}
|
||||||
|
|
||||||
|
browser.EnsureUi();
|
||||||
|
|
||||||
|
if (KCM.ServerBrowser.serverLobbyRef != null)
|
||||||
|
KCM.ServerBrowser.serverLobbyRef.SetActive(false);
|
||||||
|
|
||||||
|
if (KCM.ServerBrowser.serverBrowserRef != null)
|
||||||
|
KCM.ServerBrowser.serverBrowserRef.SetActive(true);
|
||||||
|
|
||||||
|
if (GameState.inst != null && GameState.inst.mainMenuMode != null)
|
||||||
|
GameState.inst.mainMenuMode.mainMenuUI.SetActive(false);
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
Main.Log("Failed opening server browser");
|
||||||
|
Main.Log(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user