- Re-register all resource storages after load to fix missing resources - Refresh building pathing for all players - Teleport villagers to their position to reset stuck pathfinding/AI - Add comprehensive error handling and logging 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
326 lines
13 KiB
C#
326 lines
13 KiB
C#
using Assets.Code;
|
|
using Riptide;
|
|
using Riptide.Transports;
|
|
using Steamworks;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace KCM.LoadSaveOverrides
|
|
{
|
|
[Serializable]
|
|
public class MultiplayerSaveContainer : LoadSaveContainer
|
|
{
|
|
public Dictionary<string, Player.PlayerSaveData> players = new Dictionary<string, Player.PlayerSaveData>();
|
|
public Dictionary<string, string> kingdomNames = new Dictionary<string, string>();
|
|
|
|
public new MultiplayerSaveContainer Pack(object obj)
|
|
{
|
|
this.CameraSaveData = new Cam.CamSaveData().Pack(Cam.inst);
|
|
this.TownNameSaveData = new TownNameUI.TownNameSaveData().Pack(TownNameUI.inst);
|
|
|
|
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)
|
|
{
|
|
Main.helper.Log($"Attempting to pack data for: " + player.name + $"({player.steamId})");
|
|
Main.helper.Log($"{player.inst.ToString()} {player.inst?.gameObject.name}");
|
|
this.players.Add(player.steamId, new Player.PlayerSaveData().Pack(player.inst));
|
|
kingdomNames.Add(player.steamId, player.kingdomName);
|
|
|
|
Main.helper.Log($"{players[player.steamId] == null}");
|
|
}
|
|
|
|
this.WorldSaveData = new World.WorldSaveData().Pack(World.inst);
|
|
this.FishSystemSaveData = new FishSystem.FishSystemSaveData().Pack(FishSystem.inst);
|
|
this.JobSystemSaveData = new JobSystem.JobSystemSaveData().Pack(JobSystem.inst);
|
|
this.FreeResourceManagerSaveData = new FreeResourceManager.FreeResourceManagerSaveData().Pack(FreeResourceManager.inst);
|
|
this.WeatherSaveData = new Weather.WeatherSaveData().Pack(Weather.inst);
|
|
this.FireManagerSaveData = new FireManager.FireManagerSaveData().Pack(FireManager.inst);
|
|
this.DragonSpawnSaveData = new DragonSpawn.DragonSpawnSaveData().Pack(DragonSpawn.inst);
|
|
this.UnitSystemSaveData = new UnitSystem.UnitSystemSaveData().Pack(UnitSystem.inst);
|
|
this.RaidSystemSaveData2 = new RaiderSystem.RaiderSystemSaveData2().Pack(RaiderSystem.inst);
|
|
this.ShipSystemSaveData = new ShipSystem.ShipSystemSaveData().Pack(ShipSystem.inst);
|
|
this.AIBrainsSaveData = new AIBrainsContainer.SaveData().Pack(AIBrainsContainer.inst);
|
|
this.SiegeMonsterSaveData = new SiegeMonster.SiegeMonsterSaveData().Pack(null);
|
|
this.CartSystemSaveData = new CartSystem.CartSystemSaveData().Pack(CartSystem.inst);
|
|
this.SiegeCatapultSystemSaveData = new SiegeCatapultSystem.SiegeCatapultSystemSaveData().Pack(SiegeCatapultSystem.inst);
|
|
this.OrdersManagerSaveData = new OrdersManager.OrdersManagerSaveData().Pack(OrdersManager.inst);
|
|
this.CustomSaveData = LoadSave.CustomSaveData_DontAccessDirectly;
|
|
|
|
return this;
|
|
}
|
|
|
|
public override object Unpack(object obj)
|
|
{
|
|
//original Player reset was up here
|
|
foreach (var kvp in players)
|
|
{
|
|
|
|
KCPlayer player;
|
|
|
|
if (!Main.kCPlayers.TryGetValue(kvp.Key, out player))
|
|
{
|
|
player = new KCPlayer("", 50, kvp.Key);
|
|
player.kingdomName = kingdomNames[kvp.Key];
|
|
|
|
Main.kCPlayers.Add(kvp.Key, player);
|
|
}
|
|
}
|
|
|
|
foreach (var player in Main.kCPlayers.Values)
|
|
player.inst.Reset();
|
|
|
|
|
|
AIBrainsContainer.inst.ClearAIs();
|
|
this.CameraSaveData.Unpack(Cam.inst);
|
|
this.WorldSaveData.Unpack(World.inst);
|
|
|
|
bool flag = this.FishSystemSaveData != null;
|
|
if (flag)
|
|
{
|
|
this.FishSystemSaveData.Unpack(FishSystem.inst);
|
|
}
|
|
this.TownNameSaveData.Unpack(TownNameUI.inst);
|
|
|
|
|
|
//TownNameUI.inst.townName = kingdomNames[Main.PlayerSteamID];
|
|
TownNameUI.inst.SetTownName(kingdomNames[Main.PlayerSteamID]);
|
|
|
|
Main.helper.Log("Unpacking player data");
|
|
|
|
Player.PlayerSaveData clientPlayerData = null;
|
|
|
|
foreach (var kvp in players)
|
|
{
|
|
if (kvp.Key == SteamUser.GetSteamID().ToString())
|
|
{
|
|
Main.helper.Log("Found current client player data. ID: " + SteamUser.GetSteamID().ToString());
|
|
|
|
clientPlayerData = kvp.Value;
|
|
}
|
|
else
|
|
{ // Maybe ??
|
|
Main.helper.Log("Loading player data: " + kvp.Key);
|
|
|
|
|
|
KCPlayer player;
|
|
|
|
if (!Main.kCPlayers.TryGetValue(kvp.Key, out player))
|
|
{
|
|
player = new KCPlayer("", 50, kvp.Key);
|
|
Main.kCPlayers.Add(kvp.Key, player);
|
|
}
|
|
|
|
Player oldPlayer = Player.inst;
|
|
Player.inst = player.inst;
|
|
Main.helper.Log($"Number of landmasses: {World.inst.NumLandMasses}");
|
|
|
|
//Reset was here before unpack
|
|
kvp.Value.Unpack(player.inst);
|
|
|
|
Player.inst = oldPlayer;
|
|
|
|
|
|
player.banner = player.inst.PlayerLandmassOwner.bannerIdx;
|
|
player.kingdomName = TownNameUI.inst.townName;
|
|
}
|
|
}
|
|
|
|
clientPlayerData.Unpack(Player.inst); // Unpack the current client player last so that loading of villagers works correctly.
|
|
|
|
Main.helper.Log("unpacked player data");
|
|
Main.helper.Log("Setting banner and name");
|
|
|
|
var client = Main.kCPlayers[SteamUser.GetSteamID().ToString()];
|
|
|
|
|
|
client.banner = Player.inst.PlayerLandmassOwner.bannerIdx;
|
|
client.kingdomName = TownNameUI.inst.townName;
|
|
|
|
Main.helper.Log("Finished unpacking player data");
|
|
|
|
/*
|
|
* Not even going to bother fixing AI brains save data yet, not in short-term roadmap
|
|
*/
|
|
|
|
/*bool flag2 = this.AIBrainsSaveData != null;
|
|
if (flag2)
|
|
{
|
|
this.AIBrainsSaveData.UnpackPrePlayer(AIBrainsContainer.inst);
|
|
}*/
|
|
|
|
Main.helper.Log("Unpacking free resource manager");
|
|
this.FreeResourceManagerSaveData.Unpack(FreeResourceManager.inst);
|
|
Main.helper.Log("Unpacking job system");
|
|
this.JobSystemSaveData.Unpack(JobSystem.inst);
|
|
Main.helper.Log("Unpacking weather");
|
|
this.WeatherSaveData.Unpack(Weather.inst);
|
|
Main.helper.Log("Unpacking fire manager");
|
|
this.FireManagerSaveData.Unpack(FireManager.inst);
|
|
Main.helper.Log("Unpacking dragon spawn");
|
|
this.DragonSpawnSaveData.Unpack(DragonSpawn.inst);
|
|
Main.helper.Log("Unpacking unit system");
|
|
bool flag3 = this.UnitSystemSaveData != null;
|
|
if (flag3)
|
|
{
|
|
this.UnitSystemSaveData.Unpack(UnitSystem.inst);
|
|
}
|
|
Main.helper.Log("Unpacking siege monster");
|
|
bool flag4 = this.SiegeMonsterSaveData != null;
|
|
if (flag4)
|
|
{
|
|
this.SiegeMonsterSaveData.Unpack(null);
|
|
}
|
|
Main.helper.Log("Unpacking siege catapult system");
|
|
bool flag5 = this.SiegeCatapultSystemSaveData != null;
|
|
if (flag5)
|
|
{
|
|
this.SiegeCatapultSystemSaveData.Unpack(SiegeCatapultSystem.inst);
|
|
}
|
|
Main.helper.Log("Unpacking ship system");
|
|
bool flag6 = this.ShipSystemSaveData != null;
|
|
if (flag6)
|
|
{
|
|
this.ShipSystemSaveData.Unpack(ShipSystem.inst);
|
|
}
|
|
Main.helper.Log("Unpacking cart system");
|
|
bool flag7 = this.CartSystemSaveData != null;
|
|
if (flag7)
|
|
{
|
|
this.CartSystemSaveData.Unpack(CartSystem.inst);
|
|
}
|
|
Main.helper.Log("Unpacking raid system");
|
|
bool flag8 = this.RaidSystemSaveData2 != null;
|
|
if (flag8)
|
|
{
|
|
this.RaidSystemSaveData2.Unpack(RaiderSystem.inst);
|
|
}
|
|
Main.helper.Log("Unpacking orders manager");
|
|
bool flag9 = this.OrdersManagerSaveData != null;
|
|
if (flag9)
|
|
{
|
|
this.OrdersManagerSaveData.Unpack(OrdersManager.inst);
|
|
}
|
|
Main.helper.Log("Unpacking AI brains");
|
|
bool flag10 = this.AIBrainsSaveData != null;
|
|
if (flag10)
|
|
{
|
|
this.AIBrainsSaveData.Unpack(AIBrainsContainer.inst);
|
|
}
|
|
Main.helper.Log("Unpacking custom save data");
|
|
bool flag11 = this.CustomSaveData != null;
|
|
if (flag11)
|
|
{
|
|
LoadSave.CustomSaveData_DontAccessDirectly = this.CustomSaveData;
|
|
}
|
|
Main.helper.Log("Unpacking done");
|
|
|
|
|
|
World.inst.UpscaleFeatures();
|
|
Player.inst.RefreshVisibility(true);
|
|
for (int i = 0; i < Player.inst.Buildings.Count; i++)
|
|
{
|
|
Player.inst.Buildings.data[i].UpdateMaterialSelection();
|
|
}
|
|
|
|
// Player.inst.loadTickDelay = 1;
|
|
Type playerType = typeof(Player);
|
|
FieldInfo loadTickDelayField = playerType.GetField("loadTickDelay", BindingFlags.Instance | BindingFlags.NonPublic);
|
|
if (loadTickDelayField != null)
|
|
{
|
|
loadTickDelayField.SetValue(Player.inst, 1);
|
|
}
|
|
|
|
// UnitSystem.inst.loadTickDelay = 1;
|
|
Type unitSystemType = typeof(UnitSystem);
|
|
loadTickDelayField = unitSystemType.GetField("loadTickDelay", BindingFlags.Instance | BindingFlags.NonPublic);
|
|
if (loadTickDelayField != null)
|
|
{
|
|
loadTickDelayField.SetValue(UnitSystem.inst, 1);
|
|
}
|
|
|
|
// JobSystem.inst.loadTickDelay = 1;
|
|
Type jobSystemType = typeof(JobSystem);
|
|
loadTickDelayField = jobSystemType.GetField("loadTickDelay", BindingFlags.Instance | BindingFlags.NonPublic);
|
|
if (loadTickDelayField != null)
|
|
{
|
|
loadTickDelayField.SetValue(JobSystem.inst, 1);
|
|
}
|
|
|
|
Main.helper.Log($"Setting kingdom name to: {kingdomNames[Main.PlayerSteamID]}");
|
|
TownNameUI.inst.SetTownName(kingdomNames[Main.PlayerSteamID]);
|
|
|
|
// Post-load fixes for multiplayer
|
|
Main.helper.Log("Running post-load multiplayer fixes...");
|
|
|
|
// Fix 1: Re-register all resource storages for all players
|
|
Main.helper.Log("Re-registering resource storages...");
|
|
foreach (var kcPlayer in Main.kCPlayers.Values)
|
|
{
|
|
if (kcPlayer.inst == null) continue;
|
|
|
|
foreach (var building in kcPlayer.inst.Buildings.data)
|
|
{
|
|
if (building == null) continue;
|
|
|
|
// Re-register resource storages
|
|
var storages = building.GetComponents<IResourceStorage>();
|
|
foreach (var storage in storages)
|
|
{
|
|
if (storage != null && !storage.IsPrivate())
|
|
{
|
|
try
|
|
{
|
|
FreeResourceManager.inst.AddResourceStorage(storage);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Main.helper.Log($"Error re-registering storage for {building.UniqueName}: {e.Message}");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Refresh building pathing
|
|
try
|
|
{
|
|
building.BakePathing();
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
|
|
// Fix 2: Refresh all villagers to fix stuck AI
|
|
Main.helper.Log("Refreshing villager states...");
|
|
foreach (var kcPlayer in Main.kCPlayers.Values)
|
|
{
|
|
if (kcPlayer.inst == null) continue;
|
|
|
|
for (int i = 0; i < kcPlayer.inst.Workers.Count; i++)
|
|
{
|
|
var villager = kcPlayer.inst.Workers.data[i];
|
|
if (villager == null) continue;
|
|
|
|
try
|
|
{
|
|
// Teleport to current position to reset pathfinding
|
|
villager.TeleportTo(villager.Pos);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Main.helper.Log($"Error refreshing villager: {e.Message}");
|
|
}
|
|
}
|
|
}
|
|
|
|
Main.helper.Log("Post-load multiplayer fixes complete.");
|
|
|
|
return obj;
|
|
}
|
|
}
|
|
}
|