using Assets.Code;
using Assets.Code.UI;
using Assets.Interface;
using Harmony;
using KCM.Enums;
using KCM.LoadSaveOverrides;
using KCM.Packets.Game;
using KCM.Packets.Game.Dragon;
using KCM.Packets.Game.GameBuilding;
using KCM.Packets.Game.GamePlayer;
using KCM.Packets.Game.GameTrees;
using KCM.Packets.Game.GameVillager;
using KCM.Packets.Game.GameWeather;
using KCM.Packets.Game.GameWorld;
using KCM.Packets.Handlers;
using KCM.Packets.Lobby;
using KCM.StateManagement.BuildingState;
using KCM.StateManagement.Observers;
using KCM.UI;
using Newtonsoft.Json;
using Riptide;
using Riptide.Demos.Steam.PlayerHosted;
using Riptide.Transports.Steam;
using Riptide.Utils;
using Steamworks;
using System;
using System.Collections.Generic;
using System.Configuration.Assemblies;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.AccessControl;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using static ModCompiler;
using static World;

namespace KCM
{
    public class Main : MonoBehaviour
    {
        public static KCModHelper helper;
        public static MenuState menuState = (MenuState)MainMenuMode.State.Uninitialized;

        public static Dictionary<string, KCPlayer> kCPlayers = new Dictionary<string, KCPlayer>();
        public static Dictionary<ushort, string> clientSteamIds = new Dictionary<ushort, string>();

        public static KCPlayer GetPlayerByClientID(ushort clientId)
        {
            if (TryGetPlayerByClientID(clientId, out KCPlayer player))
            {
                return player;
            }
            return null;
        }

        public static bool TryGetPlayerByClientID(ushort clientId, out KCPlayer player)
        {
            player = null;
            if (clientSteamIds.TryGetValue(clientId, out string steamId))
            {
                return kCPlayers.TryGetValue(steamId, out player);
            }
            return false;
        }

        public static Player GetPlayerByTeamID(int teamId) // Need to replace building / production types so that the correct player is used. IResourceStorage and IResourceProvider, and jobs
        {
            try
            {
                var player = kCPlayers.Values.FirstOrDefault(p => p.inst.PlayerLandmassOwner.teamId == teamId).inst;

                return player;
            }
            catch (Exception e)
            {
                if (KCServer.IsRunning || KCClient.client.IsConnected)
                {
                    Main.helper.Log("Failed finding player by teamID: " + teamId + " My teamID is: " + Player.inst.PlayerLandmassOwner.teamId);
                    Main.helper.Log(kCPlayers.Count.ToString());
                    Main.helper.Log(string.Join(", ", kCPlayers.Values.Select(p => p.inst.PlayerLandmassOwner.teamId.ToString())));
                    Main.helper.Log(e.Message);
                    Main.helper.Log(e.StackTrace);
                }
            }
            return Player.inst;
        }

        public static Player GetPlayerByBuilding(Building building)
        {
            try
            {
                var lmo = World.GetLandmassOwner(building.LandMass());

                if (lmo == null) // Return the actual player for the client if the landmass owner is null
                    return Player.inst;

                // Return the player by teamId so that the correct player instance is updated/used on the server
                return GetPlayerByTeamID(building.TeamID());
            }
            catch (Exception e)
            {
                Main.helper.Log("Failed finding player by building: " + building.UniqueName);
                Main.helper.Log(e.Message);
                Main.helper.Log(e.StackTrace);
            }
            return Player.inst;
        }

        public static string PlayerSteamID = SteamUser.GetSteamID().ToString();

        public static KCMSteamManager KCMSteamManager = null;
        public static LobbyManager lobbyManager = null;
        public static SteamServer steamServer = new SteamServer();
        public static Riptide.Transports.Steam.SteamClient steamClient = new Riptide.Transports.Steam.SteamClient(steamServer);

        public static ushort currentClient = 0;

        public static void CleanupMultiplayerSession()
        {
            if (helper == null) return; // Avoid running if mod is not fully initialized

            helper.Log("--- Starting Multiplayer Session Cleanup ---");

            // Disconnect client
            if (KCClient.client != null && KCClient.client.IsConnected)
            {
                helper.Log("Disconnecting client...");
                KCClient.client.Disconnect();
            }

            // Stop server
            if (KCServer.server != null && KCServer.IsRunning)
            {
                helper.Log("Stopping server...");
                KCServer.server.Stop();
            }

            // Clear player lists
            if (kCPlayers.Count > 0 || clientSteamIds.Count > 0)
            {
                helper.Log($"Clearing {kCPlayers.Count} KCPlayer entries and {clientSteamIds.Count} client steam IDs.");
                kCPlayers.Clear();
                clientSteamIds.Clear();
            }

            // Destroy persistent managers
            if (lobbyManager != null)
            {
                helper.Log("Destroying LobbyManager.");
                Destroy(lobbyManager.gameObject);
                lobbyManager = null;
            }

            helper.Log("--- Multiplayer Session Cleanup Finished ---");
        }

        #region "SceneLoaded"
        private void SceneLoaded(KCModHelper helper)
        {
            helper.Log("SceneLoaded run in main");
            RiptideLogger.Initialize(helper.Log, helper.Log, helper.Log, helper.Log, false);

            helper.Log($"{SteamFriends.GetPersonaName()}");


            KCMSteamManager = new GameObject("KCMSteamManager").AddComponent<KCMSteamManager>();
            DontDestroyOnLoad(KCMSteamManager);

            lobbyManager = new GameObject("LobbyManager").AddComponent<LobbyManager>();
            DontDestroyOnLoad(lobbyManager);

            try
            {

                SteamFriends.SetRichPresence("status", "Playing Multiplayer");

                PacketHandler.Initialise();

                Main.helper.Log(JsonConvert.SerializeObject(World.inst.mapSizeDefs, Formatting.Indented));

                // Check if MainMenuUI_T is available
                if (Constants.MainMenuUI_T == null)
                {
                    Main.helper.Log("MainMenuUI_T is null, cannot create Multiplayer button");
                    return;
                }

                var buttonContainer = Constants.MainMenuUI_T.Find("TopLevelUICanvas/TopLevel/Body/ButtonContainer/New");
                if (buttonContainer == null)
                {
                    Main.helper.Log("Button container not found");
                    return;
                }

                KaC_Button serverBrowser = new KaC_Button(buttonContainer.parent)
                {
                    Name = "Multiplayer",
                    Text = "Multiplayer",
                    FirstSibling = true,
                    OnClick = () =>
                    {
                        SfxSystem.PlayUiSelect();
                        TransitionTo(MenuState.ServerBrowser);
                    }
                };
                serverBrowser.Transform.SetSiblingIndex(2);

                var kingdomShare = Constants.MainMenuUI_T.Find("TopLevelUICanvas/TopLevel/Body/ButtonContainer/Kingdom Share");
                if (kingdomShare != null)
                {
                    Destroy(kingdomShare.gameObject);
                }
            }
            catch (Exception ex)
            {
                Main.helper.Log("----------------------- Main exception -----------------------");
                Main.helper.Log(ex.ToString());
                Main.helper.Log("----------------------- Main message -----------------------");
                Main.helper.Log(ex.Message);
                Main.helper.Log("----------------------- Main stacktrace -----------------------");
                Main.helper.Log(ex.StackTrace);
                if (ex.InnerException != null)
                {
                    Main.helper.Log("----------------------- Inner exception -----------------------");
                    Main.helper.Log(ex.InnerException.ToString());
                    Main.helper.Log("----------------------- Inner message -----------------------");
                    Main.helper.Log(ex.InnerException.Message);
                    Main.helper.Log("----------------------- Inner stacktrace -----------------------");
                    Main.helper.Log(ex.InnerException.StackTrace);
                }
            }

        }
        #endregion

        public static int FixedUpdateInterval = 0;

        private void FixedUpdate()
        {
            // send batched building placement info
            /*if (PlaceHook.QueuedBuildings.Count > 0 && (FixedUpdateInterval % 25 == 0))
            {
                foreach (Building building in PlaceHook.QueuedBuildings)
                {
                    new WorldPlace()
                    {
                        uniqueName = building.UniqueName,
                        customName = building.customName,
                        guid = building.guid,
                        rotation = building.transform.GetChild(0).rotation,
                        globalPosition = building.transform.position,
                        localPosition = building.transform.GetChild(0).localPosition,
                        built = building.IsBuilt(),
                        placed = building.IsPlaced(),
                        open = building.Open,
                        doBuildAnimation = building.doBuildAnimation,
                        constructionPaused = building.constructionPaused,
                        constructionProgress = building.constructionProgress,
                        life = building.Life,
                        ModifiedMaxLife = building.ModifiedMaxLife,
                        //CollectForBuild = CollectForBuild,
                        yearBuilt = building.YearBuilt,
                        decayProtection = building.decayProtection,
                        seenByPlayer = building.seenByPlayer
                    }.Send();
                }

                PlaceHook.QueuedBuildings.Clear();
            }*/

            FixedUpdateInterval++;
        }

        #region "TransitionTo"
        public static void TransitionTo(MenuState state)
        {
            try
            {
                ServerBrowser.serverBrowserRef.SetActive(state == MenuState.ServerBrowser);
                ServerBrowser.serverLobbyRef.SetActive(state == MenuState.ServerLobby);

                ServerBrowser.KCMUICanvas.gameObject.SetActive((int)state > 21);
                helper.Log(((int)state > 21).ToString());

                GameState.inst.mainMenuMode.TransitionTo((MainMenuMode.State)state);
            }
            catch (Exception ex)
            {
                Main.helper.Log("----------------------- Main exception -----------------------");
                Main.helper.Log(ex.ToString());
                Main.helper.Log("----------------------- Main message -----------------------");
                Main.helper.Log(ex.Message);
                Main.helper.Log("----------------------- Main stacktrace -----------------------");
                Main.helper.Log(ex.StackTrace);
                if (ex.InnerException != null)
                {
                    Main.helper.Log("----------------------- Inner exception -----------------------");
                    Main.helper.Log(ex.InnerException.ToString());
                    Main.helper.Log("----------------------- Inner message -----------------------");
                    Main.helper.Log(ex.InnerException.Message);
                    Main.helper.Log("----------------------- Inner stacktrace -----------------------");
                    Main.helper.Log(ex.InnerException.StackTrace);
                }
            }
        }
        #endregion

        private void Preload(KCModHelper helper)
        {
            helper.Log("Preload start in main");
            try
            {
                Main.helper = helper;
                helper.Log(helper.modPath);

                var harmony = HarmonyInstance.Create("harmony");
                harmony.PatchAll(Assembly.GetExecutingAssembly());


                helper.Log("Preload run in main");
            }
            catch (Exception ex)
            {
                Main.helper.Log("----------------------- Main exception -----------------------");
                Main.helper.Log(ex.ToString());
                Main.helper.Log("----------------------- Main message -----------------------");
                Main.helper.Log(ex.Message);
                Main.helper.Log("----------------------- Main stacktrace -----------------------");
                Main.helper.Log(ex.StackTrace);
            }
            helper.Log("Preload end in main");
        }

        #region "MainMenu Hooks"

        public static MenuState prevMenuState = MenuState.Uninitialized;

        [HarmonyPatch(typeof(MainMenuMode))]
        [HarmonyPatch("TransitionTo")]
        public class TransitionToHook
        {
            private static void Prefix(MainMenuMode.State newState)
            {
                Main.helper.Log($"Menu set to: {(MenuState)newState}");

                Main.prevMenuState = Main.menuState;

                if (newState != MainMenuMode.State.Uninitialized)
                    Main.menuState = (MenuState)newState;
            }
        }

        [HarmonyPatch(typeof(MainMenuMode))]
        [HarmonyPatch("OnClickedClose")]
        public class OnClickedCloseHook
        {
            private static bool Prefix()
            {
                helper.Log("Transition back");

                TransitionTo(prevMenuState);

                return false;
            }
        }

        [HarmonyPatch(typeof(MainMenuMode))]
        [HarmonyPatch("OnClickedBackToModeSelect")]
        public class OnClickedBackToModeSelectPatch
        {
            private static bool Prefix()
            {
                if (KCClient.client.IsConnected)
                {
                    Main.TransitionTo(MenuState.ServerLobby);
                    SfxSystem.PlayUiCancel();

                    return false;
                }
                else return true;
            }
        }

        [HarmonyPatch(typeof(MainMenuMode))]
        [HarmonyPatch("OnClickedAcceptNameBanner")]
        public class OnClickedAcceptNameBannerPatch
        {
            private static bool Prefix()
            {
                if (KCClient.client.IsConnected)
                {
                    Main.TransitionTo(MenuState.ServerLobby);
                    SfxSystem.PlayUiCancel();

                    return false;
                }
                else return true;
            }
        }
        #endregion

        #region "TownName Hooks"
        [HarmonyPatch(typeof(TownNameUI))]
        [HarmonyPatch("SetTownNameQuiet")]
        public static class TownNameHook
        {
            //A function to run after target function invocation
            private static void Postfix(TownNameUI __instance)
            {
                helper.Log($"name set: {__instance.townName}");

                new KingdomName() { kingdomName = __instance.townName }.Send();
            }
        }
        #endregion

        #region "ChooseBanner Hooks"
        [HarmonyPatch(typeof(ChooseBannerUI))]
        [HarmonyPatch("OnAccept")]
        public class ChooseBannerUIOnAcceptHook
        {
            private static void Postfix()
            {
                if (KCClient.client.IsConnected)
                {
                    var banner = Player.inst.PlayerLandmassOwner.bannerIdx;


                    new PlayerBanner() { banner = banner }.Send();
                    //return true;
                }
                //else return true;
            }
        }
        #endregion


        [HarmonyPatch(typeof(Keep))]
        [HarmonyPatch("OnPlayerPlacement")]
        public class KeepHook
        {
            public static void Postfix()
            {
                // Your code here

                // Get the name of the last method that called OnPlayerPlacement
                List<string> strings = new List<string>();

                for (int i = 1; i < 10; i++)
                {
                    try
                    {
                        string callingMethodName = new StackFrame(i).GetMethod().Name;
                        strings.Add(callingMethodName);
                    }
                    catch
                    {
                        strings.Add("Start");
                        break;
                    }
                }

                strings.Reverse();

                Main.helper.Log($"Last {strings.Count} methods in call tree: {string.Join(" -> ", strings)}");
            }
        }

        #region "GameUI Hooks"
        //GameUI hook for acceptcursorobjplacement
        /*[HarmonyPatch(typeof(GameUI), "AcceptCursorObjPlacement")]
        public class AcceptCursorObjPlacementHook
        {
        }*/
        #endregion

        #region "World Hooks"
        [HarmonyPatch(typeof(World))]
        [HarmonyPatch("Place")]
        public class PlaceHook
        {
            /*public static bool Prefix()
            {
                if (KCClient.client.IsConnected && !KCServer.IsRunning)
                {
                    if (!new StackFrame(3).GetMethod().kingdomName.Contains("HandlePacket"))
                        return false;
                }

                return true;
            }*/

            public static void Postfix(Building PendingObj)
            {
                try
                {
                    if (KCClient.client.IsConnected)
                    {
                        /*string callTree = "";
                        List<string> strings = new List<string>();

                        for (int i = 1; i < 10; i++)
                        {
                            try
                            {
                                string callingMethodName = new StackFrame(i).GetMethod().Name;
                                strings.Add($"{callingMethodName} ({i})");
                            }
                            catch
                            {
                                strings.Add("Start");
                                break;
                            }
                        }

                        strings.Reverse();

                        Main.helper.Log($"WORLDPLACE Last {strings.Count} methods in call tree: {string.Join(" -> ", strings)}");*/

                        if (new StackFrame(3).GetMethod().Name.Contains("HandlePacket") && !new StackFrame(2).GetMethod().Name.Equals("RandomPlacement"))
                            return;

                        Main.helper.Log($"Called by: {new StackFrame(3).GetMethod().Name}");
                        Main.helper.Log($"{KCClient.client.Id} {Main.kCPlayers[PlayerSteamID].name} - Sending building place packet for " + PendingObj.UniqueName);

                        // Need to batch building placements to prevent network spam
                        new WorldPlace()
                        {
                            uniqueName = PendingObj.UniqueName,
                            customName = PendingObj.customName,
                            guid = PendingObj.guid,
                            rotation = PendingObj.transform.GetChild(0).rotation,
                            globalPosition = PendingObj.transform.position,
                            localPosition = PendingObj.transform.GetChild(0).localPosition,
                            built = PendingObj.IsBuilt(),
                            placed = PendingObj.IsPlaced(),
                            open = PendingObj.Open,
                            doBuildAnimation = PendingObj.doBuildAnimation,
                            constructionPaused = PendingObj.constructionPaused,
                            constructionProgress = PendingObj.constructionProgress,
                            life = PendingObj.Life,
                            ModifiedMaxLife = PendingObj.ModifiedMaxLife,
                            //CollectForBuild = CollectForBuild,
                            yearBuilt = PendingObj.YearBuilt,
                            decayProtection = PendingObj.decayProtection,
                            seenByPlayer = PendingObj.seenByPlayer
                        }.Send();
                        //return true;
                    }
                    //else return true;
                }
                catch (Exception e)
                {
                    Main.helper.Log("World Place error");
                    Main.helper.Log(e.Message);
                    Main.helper.Log(e.StackTrace);
                }
            }
        }

        [HarmonyPatch(typeof(World), "RelationBetween")]
        public class WorldRelationBetweenHook
        {
            public static void Prefix(ref int teamIDA, ref int teamIDB)
            {

                //Main.helper.Log($"RelationBetween {teamIDA} and {teamIDB}");

                if (KCClient.client.IsConnected)
                {
                    if (teamIDA == 0 || teamIDB == 0)
                    {
                        if (teamIDA == 0)
                            teamIDA = Player.inst.PlayerLandmassOwner.teamId;

                        if (teamIDB == 0)
                            teamIDB = Player.inst.PlayerLandmassOwner.teamId;
                    }
                }
            }
        }
        #endregion


        #region "Player Hooks"

        [HarmonyPatch(typeof(Player), "Reset")]
        public class PlayerResetHook
        {
            public static bool Prefix(Player __instance)
            {
                if (KCClient.client.IsConnected && __instance.gameObject.name.Contains("Client Player") && !LobbyManager.loadingSave)
                {
                    try
                    {
                        var bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;

                        __instance.GetType().GetField("resetting", bindingFlags).SetValue(__instance, true);
                        //__instance.resetting = true;
                        __instance.GetType().GetField("poorHealthGracePeriod", bindingFlags).SetValue(__instance, 0f);
                        //__instance.poorHealthGracePeriod = 0f;
                        __instance.PlayerLandmassOwner.Gold = 0;
                        __instance.CurrYear = 0;

                        __instance.buildingDamageAnimator.Reset();
                        __instance.fruitSystem.Reset();
                        __instance.fieldSystem.Reset();

                        bool flag = __instance.DamagedList != null;
                        if (flag)
                        {
                            for (int i = 0; i < __instance.DamagedList.Length; i++)
                            {
                                __instance.DamagedList[i].Clear();
                            }
                            __instance.DamagedList = null;
                        }

                        __instance.irrigation.Reset();
                        __instance.ClearRegistry();
                        bool flag2 = __instance.buildingContainer;
                        if (flag2)
                        {
                            Building[] buildings = __instance.buildingContainer.transform.GetComponentsInChildren<Building>();
                            for (int j = 0; j < buildings.Length; j++)
                            {
                                buildings[j].destroyedWhileInPlay = false;
                                UnityEngine.Object.Destroy(buildings[j].gameObject);
                            }
                        }
                        UnityEngine.Object.Destroy(__instance.buildingContainer);
                        __instance.buildingContainer = new GameObject();
                        __instance.buildingContainer.name = "Buildings";

                        for (int k = 0; k < __instance.Workers.Count; k++)
                        {
                            __instance.Workers.data[k].Shutdown();
                        }
                        __instance.Workers.Clear();
                        /*int r = 0;
                        for (int l = 0; l < __instance.Homeless.Count; l++)
                        {
                            bool flag3 = !__instance.Homeless.data[l].shutdown;
                            if (flag3)
                            {
                                r++;
                            }
                        }*/

                        __instance.Homeless.Clear();

                        __instance.Residentials.Clear();
                        __instance.Buildings.Clear();
                        __instance.RadiusBonuses.Clear();
                        __instance.WagePayers.Clear();

                        __instance.timeAtFailHappiness = 0f;
                        __instance.MaxGoldStorage = 0;
                        __instance.KingdomHappiness = 100;
                        ReflectionHelper.ClearPrivateListField<Player.HappinessInfo>(__instance, "landMassHappiness");
                        //__instance.landMassHappiness.Clear();
                        ReflectionHelper.ClearPrivateListField<Player.HealthInfo>(__instance, "landMassHealth");
                        //__instance.landMassHealth.Clear();
                        ReflectionHelper.ClearPrivateListField<Player.IntegrityInfo>(__instance, "landMassIntegrity");
                        //__instance.landMassIntegrity.Clear();
                        //__instance.HealthTimer.ForceExpire(); // TO-DO implement timer
                        __instance.happinessMods.Clear();
                        /*for (int m = 0; m < __instance.plagueDeathInfo.Count; m++)
                        {
                            __instance.plagueDeathInfo[m].deathQueue.Clear();
                            __instance.plagueDeathInfo[m].deaths = 0;
                            __instance.plagueDeathInfo[m].deathTime = 0f;
                        }*/
                        ReflectionHelper.ClearPrivateListField<Villager>(__instance, "OldAgeDeathQueue");
                        //__instance.OldAgeDeathQueue.Clear();
                        __instance.GetType().GetField("deathsThisYear", bindingFlags).SetValue(__instance, 0);
                        //__instance.deathsThisYear = 0;
                        __instance.ResetPerLandMassData();
                        __instance.ResetTaxRates();
                        __instance.ResetCreativeModeOptions();
                        __instance.PlayerLandmassOwner.ReleaseOwnership();

                        ReflectionHelper.ClearPrivateListField<Player.DockOpening>(__instance, "dockOpenings");
                        //__instance.dockOpenings.Clear();
                        __instance.GetType().GetField("resetting", bindingFlags).SetValue(__instance, false);
                        //__instance.resetting = false;
                    }
                    catch (Exception e)
                    {
                        Main.helper.Log("Error in reset player hook");
                        Main.helper.Log(e.Message);
                        Main.helper.Log(e.StackTrace);
                    }

                    return false;
                }

                return true;
            }
        }

        [HarmonyPatch(typeof(Player), "AddBuilding")]
        public class PlayerAddBuildingHook
        {
            static int step = 1;
            static void LogStep(bool reset = false)
            {
                if (reset)
                    step = 1;

                Main.helper.Log(step.ToString());
                step++;
            }

            public static bool Prefix(Player __instance, Building b)
            {
                try
                {
                    if (KCClient.client.IsConnected)
                    {
                        LogStep(true);
                        __instance.Buildings.Add(b);
                        IResourceStorage[] storages = b.GetComponents<IResourceStorage>();
                        LogStep();
                        for (int i = 0; i < storages.Length; i++)
                        {
                            bool flag = !storages[i].IsPrivate();
                            if (flag)
                            {
                                FreeResourceManager.inst.AddResourceStorage(storages[i]);
                            }
                        }
                        LogStep();
                        int landMass = b.LandMass();
                        Home res = b.GetComponent<Home>();
                        bool flag2 = res != null;
                        LogStep();
                        if (flag2)
                        {
                            __instance.Residentials.Add(res);
                            __instance.ResidentialsPerLandmass[landMass].Add(res);
                        }
                        WagePayer wagePayer = b.GetComponent<WagePayer>();
                        LogStep();
                        bool flag3 = wagePayer != null;
                        if (flag3)
                        {
                            __instance.WagePayers.Add(wagePayer);
                        }
                        RadiusBonus radiusBonus = b.GetComponent<RadiusBonus>();
                        LogStep();
                        bool flag4 = radiusBonus != null;
                        if (flag4)
                        {
                            __instance.RadiusBonuses.Add(radiusBonus);
                        }
                        LogStep();
                        var globalBuildingRegistry = __instance.GetType().GetField("globalBuildingRegistry", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(__instance) as ArrayExt<Player.BuildingRegistry>;
                        LogStep();
                        var landMassBuildingRegistry = __instance.GetType().GetField("landMassBuildingRegistry", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(__instance) as ArrayExt<Player.LandMassBuildingRegistry>;
                        LogStep();
                        var unbuiltBuildingsPerLandmass = __instance.GetType().GetField("unbuiltBuildingsPerLandmass", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(__instance) as ArrayExt<ArrayExt<Building>>;
                        LogStep();

                        __instance.AddToRegistry(globalBuildingRegistry, b);
                        LogStep();
                        __instance.AddToRegistry(landMassBuildingRegistry.data[landMass].registry, b);
                        LogStep();
                        landMassBuildingRegistry.data[landMass].buildings.Add(b);
                        LogStep();
                        bool flag5 = !b.IsBuilt();
                        if (flag5)
                        {
                            unbuiltBuildingsPerLandmass.data[landMass].Add(b);
                        }
                        LogStep();


                        return false;
                    }
                }
                catch (Exception e)
                {
                    Main.helper.Log("Error in add building hook");
                    Main.helper.Log(e.Message);
                    Main.helper.Log(e.StackTrace);
                }

                return true;
            }
        }

        [HarmonyPatch(typeof(Player), "SetupInitialWorkers")]
        public class PlayerSetupInitialWorkersHook
        {
            public static void Postfix(Keep keep)
            {
                if (KCClient.client.IsConnected)
                {


                    if (new StackFrame(3).GetMethod().Name.Contains("HandlePacket"))
                        return;

                    new SetupInitialWorkersPacket()
                    {
                        keepGuid = keep.gameObject.GetComponent<Building>().guid
                    }.Send();
                }
            }
        }

        [HarmonyPatch(typeof(VillagerSystem), "AddVillager")]
        public class PlayerAddVillagerHook
        {
            public static void Postfix(Villager __result, Vector3 pos)
            {
                if (KCClient.client.IsConnected)
                {
                    try
                    {
                        if (new StackFrame(3).GetMethod().Name.Contains("HandlePacket"))
                            return;

                        if (Enumerable.Range(0, 4).Select(i => new StackFrame(i).GetMethod()?.Name).Any(name => name?.Contains("unpack") == true)) // If called by unpack in the tree, do not run, since clients already unpacked villager data
                            return;

                        new AddVillagerPacket()
                        {
                            guid = __result.guid,
                        }.Send();
                    }
                    catch (Exception e)
                    {
                        Main.helper.Log("Error in add villager hook");

                        Main.helper.Log(e.Message);
                        Main.helper.Log(e.StackTrace);
                    }
                }
            }
        }

        #endregion


        #region "Tree Hooks"


        [HarmonyPatch(typeof(TreeSystem), "FellTree")]
        public class TreeSystemFellTreeHook
        {
            /*static IEnumerable<MethodBase> TargetMethods()
            {
                var tmeth = typeof(TreeSystem).GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Static);

                return tmeth.Cast<MethodBase>();
            }*/

            public static void Postfix(MethodBase __originalMethod, Cell cell, int idx)
            {
                if (KCClient.client.IsConnected)
                {
                    //Main.helper.Log($"Called by: {new StackFrame(3).GetMethod().kingdomName}");

                    if (new StackFrame(3).GetMethod().Name.Contains("HandlePacket"))
                        return;

                    new FellTree()
                    {
                        idx = idx,
                        x = cell.x,
                        z = cell.z
                    }.Send();
                }
            }
        }

        [HarmonyPatch(typeof(TreeSystem), "ShakeTree")]
        public class TreeSystemShakeTreeHook
        {
            public static void Postfix(MethodBase __originalMethod, int idx)
            {
                if (KCClient.client.IsConnected)
                {
                    //Main.helper.Log($"Called by: {new StackFrame(3).GetMethod().kingdomName}");

                    if (new StackFrame(3).GetMethod().Name.Contains("HandlePacket"))
                        return;

                    new ShakeTree()
                    {
                        idx = idx
                    }.Send();
                }
            }
        }

        [HarmonyPatch(typeof(TreeSystem), "GrowTree")]
        public class TreeSystemGrowTreeHook
        {
            /*public static bool Prefix()
            {
                if (KCClient.client.IsConnected && !KCServer.IsRunning)
                {
                    //Main.helper.Log($"Called by: {new StackFrame(3).GetMethod().kingdomName}");

                    if (!new StackFrame(3).GetMethod().Name.Contains("HandlePacket"))
                        return false;

                    
                }

                return true;
            }*/

            // Only server should send this information
            public static void Postfix(MethodBase __originalMethod, Cell cell)
            {
                if (KCServer.IsRunning)
                {
                    //Main.helper.Log($"Called by: {new StackFrame(3).GetMethod().kingdomName}");

                    if (new StackFrame(3).GetMethod().Name.Contains("HandlePacket"))
                        return;

                    new GrowTree()
                    {
                        X = cell.x,
                        Z = cell.z
                    }.SendToAll(KCClient.client.Id);
                }
            }
        }

        #endregion

        #region "Weather Hooks"
        [HarmonyPatch(typeof(Weather), "ChangeWeather")]
        public class WeatherChangeWeatherHook
        {
            public static void Postfix(MethodBase __originalMethod, Weather.WeatherType type)
            {
                if (KCServer.IsRunning && KCClient.client.IsConnected)
                {
                    //Main.helper.Log($"Called by: {new StackFrame(3).GetMethod().kingdomName}");

                    if (new StackFrame(3).GetMethod().Name.Contains("HandlePacket"))
                        return;

                    if (type != Weather.inst.currentWeather)
                        new ChangeWeather()
                        {
                            weatherType = (int)type
                        }.Send();
                }
            }
        }
        #endregion

        #region "Building Hooks"

        [HarmonyPatch(typeof(Building), "CompleteBuild")]
        public class BuildingCompleteBuildHook
        {
            public static bool Prefix(MethodBase __originalMethod, Building __instance)
            {
                if (KCClient.client.IsConnected)
                {
                    Main.helper.Log("Overridden complete build");
                    Player player = Main.GetPlayerByTeamID(__instance.TeamID());

                    //Main.helper.Log($"Called by: {new StackFrame(3).GetMethod().kingdomName}");
                    typeof(Building).GetField("built", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(__instance, true);

                    __instance.UpdateMaterialSelection();
                    __instance.SendMessage("OnBuilt", SendMessageOptions.DontRequireReceiver);


                    typeof(Building).GetField("yearBuilt", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(__instance, player.CurrYear);

                    typeof(Building).GetMethod("AddAllResourceProviders", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(__instance, null);


                    player.BuildingNowBuilt(__instance);

                    typeof(Building).GetMethod("TryAddJobs", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(__instance, null);
                    __instance.BakePathing();

                    return false;
                }

                return true;
            }
        }

        [HarmonyPatch(typeof(Building), "UpdateConstruction")]
        public class BuildingUpdateHook
        {
            public static void Prefix(Building __instance)
            {
                try
                {
                    if (KCClient.client.IsConnected)
                    {
                        if (__instance.TeamID() == Player.inst.PlayerLandmassOwner.teamId)
                            StateObserver.RegisterObserver(__instance, new string[] {
                                "customName", "guid", "UniqueName", "built", "placed", "open", "doBuildAnimation", "constructionPaused", "constructionProgress", "resourceProgress",
                                "Life", "ModifiedMaxLife", "CollectForBuild", "yearBuilt", "decayProtection", "seenByPlayer",
                            }, BuildingStateManager.BuildingStateChanged, BuildingStateManager.SendBuildingUpdate);

                        //StateObserver.Update(__instance);
                    }
                }
                catch (Exception e)
                {
                    helper.Log(e.ToString());
                    helper.Log(e.Message);
                    helper.Log(e.StackTrace);
                }
            }
        }

        #endregion

        #region "Time Hooks"
        // TimeManager TrySetSpeed hook
        [HarmonyPatch(typeof(SpeedControlUI), "SetSpeed")]
        public class SpeedControlUISetSpeedHook
        {
            private static long lastTime = 0;

            public static bool Prefix()
            {
                if (KCClient.client.IsConnected)
                {
                    if ((DateTimeOffset.Now.ToUnixTimeMilliseconds() - lastTime) < 250) // Set speed spam fix / hack
                        return false;

                    if (!new StackFrame(3).GetMethod().Name.Contains("HandlePacket"))
                        return false;
                }

                return true;
            }

            public static void Postfix(int idx, bool skipNextSfx)
            {
                if (KCClient.client.IsConnected)
                {
                    /*Main.helper.Log($"set speed Called by 0: {new StackFrame(0).GetMethod()} {new StackFrame(0).GetMethod().Name.Contains("HandlePacket")}");
                    Main.helper.Log($"set speed Called by 1: {new StackFrame(1).GetMethod()} {new StackFrame(1).GetMethod().Name.Contains("HandlePacket")}");
                    Main.helper.Log($"set speed Called by 2: {new StackFrame(2).GetMethod()} {new StackFrame(2).GetMethod().Name.Contains("HandlePacket")}");
                    Main.helper.Log($"set speed Called by 3: {new StackFrame(3).GetMethod()} {new StackFrame(3).GetMethod().Name.Contains("HandlePacket")}");*/

                    if (new StackFrame(3).GetMethod().Name.Contains("HandlePacket"))
                        return;

                    new SetSpeed()
                    {
                        speed = idx
                    }.Send();

                    lastTime = DateTimeOffset.Now.ToUnixTimeMilliseconds();
                }
            }
        }
        #endregion

        #region "SteamManager Hook"
        [HarmonyPatch]
        public class SteamManagerAwakeHook
        {
            static IEnumerable<MethodBase> TargetMethods()
            {
                var meth = typeof(SteamManager).GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
                return meth.Cast<MethodBase>();
            }

            public static bool Prefix(MethodBase __originalMethod)
            {
                return false;
            }
        }
        #endregion

        #region "Dragon Hooks"

        #region "Dragon Spawn Hooks"
        [HarmonyPatch(typeof(DragonSpawn), "SpawnSiegeDragon")]
        public class DragonSpawnSpawnSiegeDragonHook
        {
            public static bool Prefix()
            {
                if (KCClient.client.IsConnected && !KCServer.IsRunning && !new StackFrame(3).GetMethod().Name.Contains("HandlePacket"))
                    return false;

                return true;
            }
            public static void Postfix(MethodBase __originalMethod, Vector3 start)
            {
                if (KCClient.client.IsConnected)
                {
                    //Main.helper.Log($"Called by: {new StackFrame(3).GetMethod().kingdomName}");

                    if (new StackFrame(3).GetMethod().Name.Contains("HandlePacket"))
                        return;

                    new SpawnSiegeDragonPacket() { start = start }.Send();
                }
            }
        }

        [HarmonyPatch(typeof(DragonSpawn), "SpawnMamaDragon", new Type[] { typeof(Vector3) })]
        public class DragonSpawnSpawnMamaDragonHook
        {
            public static bool Prefix()
            {
                if (KCClient.client.IsConnected && !KCServer.IsRunning && !new StackFrame(3).GetMethod().Name.Contains("HandlePacket"))
                    return false;

                return true;
            }
            public static void Postfix(MethodBase __originalMethod, Vector3 start)
            {
                if (KCClient.client.IsConnected)
                {
                    //Main.helper.Log($"Called by: {new StackFrame(3).GetMethod().kingdomName}");

                    if (new StackFrame(3).GetMethod().Name.Contains("HandlePacket"))
                        return;

                    new SpawnMamaDragonPacket() { start = start }.Send();
                }
            }
        }

        [HarmonyPatch(typeof(DragonSpawn), "SpawnBabyDragon", new Type[] { typeof(Vector3) })]
        public class DragonSpawnSpawnBabyDragonHook
        {
            public static bool Prefix()
            {
                if (KCClient.client.IsConnected && !KCServer.IsRunning && !new StackFrame(3).GetMethod().Name.Contains("HandlePacket"))
                    return false;

                return true;
            }
            public static void Postfix(MethodBase __originalMethod, Vector3 start)
            {
                if (KCClient.client.IsConnected)
                {
                    //Main.helper.Log($"Called by: {new StackFrame(3).GetMethod().kingdomName}");

                    if (new StackFrame(3).GetMethod().Name.Contains("HandlePacket"))
                        return;

                    new SpawnBabyDragonPacket() { start = start }.Send();
                }
            }
        }
        #endregion

        #endregion

        #region "Villager Hooks"
        [HarmonyPatch(typeof(Villager), "TeleportTo")]
        public class VillagerTeleportToHook
        {
            public static void Postfix(Villager __instance, Vector3 newPos)
            {
                if (KCClient.client.IsConnected)
                {
                    if (new StackFrame(3).GetMethod().Name.Contains("HandlePacket"))
                        return;

                    new VillagerTeleportTo()
                    {
                        guid = __instance.guid,
                        pos = newPos
                    }.Send();
                }
            }
        }
        #endregion

        #region "Job Hooks"

        /*[HarmonyPatch(typeof(Job), "OnEmployeeQuit")]
        public class JobOnEmployeeQuitHook
        {
            public static Player oldPlayer;

            public static void Prefix(Job __instance)
            {
                if (KCClient.client.IsConnected)
                {
                    oldPlayer = Player.inst;

                    Player.inst = Main.GetPlayerByTeamID(World.GetLandmassOwner(__instance.employer.LandMass()).teamId);
                }
            }

            public static void Postfix(Job __instance)
            {
                if (KCClient.client.IsConnected)
                {
                    Player.inst = oldPlayer;
                }
            }
        }*/

        #endregion

        #region "LoadSave Hooks"
        [HarmonyPatch(typeof(LoadSave), "GetSaveDir")]
        public class LoadSaveGetSaveDirHook
        {
            public static bool Prefix(ref string __result)
            {
                Main.helper.Log("Get save dir");
                if (KCClient.client.IsConnected)
                {
                    if (KCServer.IsRunning)
                    {

                    }
                    __result = Application.persistentDataPath + "/Saves/Multiplayer";

                    return false;
                }

                __result = Application.persistentDataPath + "/Saves"; ;
                return true;
            }
        }

        [HarmonyPatch(typeof(LoadSave), "LoadAtPath")]
        public class LoadSaveLoadAtPathHook
        {
            //public static string saveFile = "";
            public static byte[] saveData = new byte[0];

            public static bool Prefix(string path, string filename, bool visitedWorld)
            {
                if (KCServer.IsRunning)
                {
                    Main.helper.Log("Trying to load multiplayer save");
                    LoadSave.LastLoadDirectory = path;
                    path = path + "/" + filename;


                    bool flag = !File.Exists(path);
                    if (!flag)
                    {
                        BinaryFormatter bf = new BinaryFormatter();
                        bf.Binder = new MultiplayerSaveDeserializationBinder();
                        saveData = File.ReadAllBytes(path);
                        Stream file = new FileStream(path, FileMode.Open);
                        try
                        {
                            MultiplayerSaveContainer loadData = (MultiplayerSaveContainer)bf.Deserialize(file);
                            loadData.Unpack(null);
                            Broadcast.OnLoadedEvent.Broadcast(new OnLoadedEvent());
                        }
                        catch (Exception e)
                        {
                            GameState.inst.mainMenuMode.TransitionTo(MainMenuMode.State.LoadError);
                            Main.helper.Log("Error loading save");
                            Main.helper.Log(e.Message);
                            Main.helper.Log(e.StackTrace);
                            throw;
                        }
                        finally
                        {
                            bool flag2 = file != null;
                            if (flag2)
                            {
                                file.Close();
                                file.Dispose();
                            }
                        }
                    }

                    return false;
                }

                return true;
            }
        }

        [HarmonyPatch(typeof(LoadSave), "Load")]
        public class LoadSaveLoadHook
        {
            public static bool memoryStreamHook = false;

            public static byte[] saveBytes = new byte[0];

            public static MultiplayerSaveContainer saveContainer;

            public static bool Prefix()
            {
                if (memoryStreamHook)
                {
                    Main.helper.Log("Attempting to load save from server");

                    try
                    {
                        using (MemoryStream ms = new MemoryStream(saveBytes))
                        {
                            BinaryFormatter bf = new BinaryFormatter();
                            bf.Binder = new MultiplayerSaveDeserializationBinder();
                            saveContainer = (MultiplayerSaveContainer)bf.Deserialize(ms);
                        }

                        Main.helper.Log("Deserialize complete, calling Unpack...");
                        saveContainer.Unpack(null);
                        Main.helper.Log("Unpack complete!");
                    }
                    catch (Exception e)
                    {
                        Main.helper.Log("Error loading save from server");
                        Main.helper.Log(e.Message);
                        Main.helper.Log(e.StackTrace);
                        if (e.InnerException != null)
                        {
                            Main.helper.Log("Inner exception: " + e.InnerException.Message);
                            Main.helper.Log(e.InnerException.StackTrace);
                        }
                    }
                    finally
                    {
                        memoryStreamHook = false;
                    }

                    return false;
                }

                return true;
            }
        }

        [HarmonyPatch(typeof(LoadSave), "Save")]
        public class LoadSaveSaveHook
        {
            private class OutData
            {
                // Token: 0x04002176 RID: 8566
                public string Path;

                // Token: 0x04002177 RID: 8567
                public MultiplayerSaveContainer LoadSaveContainer;
            }

            private static void OutToFile(object data)
            {
                OutData outData = (OutData)data;
                BinaryFormatter bf = new BinaryFormatter();
                Stream file = null;
                try
                {
                    file = new FileStream(outData.Path, FileMode.Create, FileAccess.Write);
                    bf.Serialize(file, outData.LoadSaveContainer);
                }
                catch (Exception e)
                {
                    LoadSave.AppendToLocalErrorLog(string.Concat(new string[]
                    {
                "Problem during save",
                Environment.NewLine,
                e.Message,
                Environment.NewLine,
                e.StackTrace
                    }));
                }
                finally
                {
                    bool flag = file != null;
                    if (flag)
                    {
                        file.Close();
                        file.Dispose();
                    }
                }
            }

            public static bool Prefix(string pathOverride, UnityAction onCompleteCallback, ref Thread __result)
            {
                if (KCServer.IsRunning)
                {
                    Directory.CreateDirectory(LoadSave.GetSaveDir());
                    Guid guid = Guid.NewGuid();
                    string path = (pathOverride != "") ? pathOverride : (LoadSave.GetSaveDir() + "/" + guid);
                    Directory.CreateDirectory(path);
                    Thread thread;
                    try
                    {
                        thread = new Thread(new ParameterizedThreadStart(OutToFile));

                        MultiplayerSaveContainer packedData = new MultiplayerSaveContainer().Pack(null);
                        Broadcast.OnSaveEvent.Broadcast(new OnSaveEvent());
                        thread.Start(new OutData
                        {
                            LoadSaveContainer = packedData,
                            Path = path + "/world"
                        });
                    }
                    catch (Exception e)
                    {
                        //LoadSave.ErrorToKingdomLog(e);
                        Main.helper.Log(e.Message);
                        Main.helper.Log(e.StackTrace);
                        throw;
                    }
                    finally
                    {
                        LoadSave.SaveWorldSummaryData(path);
                    }


                    // Custom banners not implemented yet
                    /*try
                    {
                        bool usingCustomBanner = Player.inst.usingCustomBanner;
                        if (usingCustomBanner)
                        {
                            File.WriteAllBytes(path + "/custombanner.png", Player.inst.customBannerTexture2D.EncodeToPNG());
                        }
                    }
                    catch (Exception e2)
                    {
                        Main.helper.Log(e2.Message);
                    }*/

                    try
                    {
                        World.inst.TakeScreenshot(path + "/cover", new Func<int, int, Texture2D>(World.inst.Func_CaptureWorldShot), onCompleteCallback);
                    }
                    catch (Exception e3)
                    {
                        Main.helper.Log(e3.Message);
                    }
                    bool flag = onCompleteCallback != null;
                    if (flag)
                    {
                        bool flag2 = thread != null && thread.ThreadState == System.Threading.ThreadState.Running;
                        if (flag2)
                        {
                            thread.Join();
                        }
                    }
                    GC.Collect();

                    __result = thread;

                    return false;
                }

                return true;
            }
        }

        [HarmonyPatch(typeof(SaveLoadUI), "ClickLoadItem")]
        public class SaveLoadUIClickedLoadItemHook
        {
            public static bool Prefix(SaveLoadUI __instance, string id)
            {
                if (KCServer.IsRunning)
                {

                    LoadSave.Load(id);
                    TransitionTo(MenuState.ServerLobby);
                    //GameState.inst.SetNewMode(GameState.inst.playingMode);


                    return false;
                }

                return true;
            }
        }

        [HarmonyPatch(typeof(Player.PlayerSaveData), "ProcessBuilding")]
        public class PlayerProcessBuildingHook
        {
            public static bool Prefix(Building.BuildingSaveData structureData, Player p, ref Building __result)
            {
                if (KCClient.client.IsConnected)
                {

                    Building Building = GameState.inst.GetPlaceableByUniqueName(structureData.uniqueName);
                    bool flag = Building;
                    if (flag)
                    {
                        Building building = UnityEngine.Object.Instantiate<Building>(Building);
                        building.transform.position = structureData.globalPosition;
                        building.Init();
                        building.transform.SetParent(p.buildingContainer.transform, true);
                        structureData.Unpack(building);
                        p.AddBuilding(building);

                        Main.helper.Log($"Loading player id: {p.PlayerLandmassOwner.teamId}");
                        Main.helper.Log($"loading building: {building.FriendlyName}");
                        Main.helper.Log($" (teamid: {building.TeamID()})");
                        Main.helper.Log(p.ToString());
                        bool flag2 = building.GetComponent<Keep>() != null && building.TeamID() == p.PlayerLandmassOwner.teamId;
                        Main.helper.Log("Set keep? " + flag2);
                        if (flag2)
                        {
                            p.keep = building.GetComponent<Keep>();
                            Main.helper.Log(p.keep.ToString());
                        }
                        __result = building;
                    }
                    else
                    {
                        Main.helper.Log(structureData.uniqueName + " failed to load correctly");
                        __result = null;
                    }

                    return false;
                }

                return true;
            }
        }

        [HarmonyPatch(typeof(Player.PlayerSaveData), "Pack")]
        public class PlayerSaveDataPackgHook
        {
            public static bool Prefix(Player.PlayerSaveData __instance, Player p, ref Player.PlayerSaveData __result)
            {
                if (KCClient.client.IsConnected)
                {
                    var bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;

                    Main.helper.Log("Running patched player pack method");
                    Main.helper.Log("Saving banner system");
                    __instance.newBannerSystem = true;
                    Main.helper.Log("Saving player creativeMode");
                    __instance.creativeMode = p.creativeMode;

                    //cmo options not used for saving or loading in multiplayer
                    /**for (int i = 0; i < p.cmoOptionsOn.Length; i++)
                    {
                        bool flag = p.cmoOptionsOn[i];
                        if (flag)
                        {
                            __instance.cmoOptions.Add((Player.CreativeOptions)i);
                        }
                    }**/

                    Main.helper.Log("Saving player upgrades");
                    __instance.GetType().GetField("upgrades", bindingFlags).SetValue(__instance, new List<Player.UpgradeType>());


                    Main.helper.Log("Saving player bannerIdx");
                    __instance.bannerIdx = p.PlayerLandmassOwner.bannerIdx;

                    Main.helper.Log("Saving player WorkersArray");
                    __instance.WorkersArray = new Villager.VillagerSaveData[p.Workers.Count];
                    for (int j = 0; j < p.Workers.Count; j++)
                    {
                        bool flag2 = p.Workers.data[j] != null;
                        if (flag2)
                        {
                            __instance.WorkersArray[j] = new Villager.VillagerSaveData().Pack(p.Workers.data[j]);
                        }
                    }

                    Main.helper.Log("Saving player HomelessData");
                    __instance.HomelessData = new List<Guid>();
                    for (int k = 0; k < p.Homeless.Count; k++)
                    {
                        __instance.HomelessData.Add(p.Homeless.data[k].guid);
                    }
                    __instance.structures = new List<Building.BuildingSaveData[]>();
                    __instance.subStructures = new List<Building.BuildingSaveData[]>();

                    Main.helper.Log("Saving player structures");
                    World.inst.ForEachTile(0, 0, World.inst.GridWidth, World.inst.GridHeight, delegate (int x, int z, Cell cell)
                    {
                        bool flag4 = cell.OccupyingStructure.Count > 0;
                        if (flag4)
                        {
                            List<Building.BuildingSaveData> occupyingStructureData = new List<Building.BuildingSaveData>();
                            for (int i3 = 0; i3 < cell.OccupyingStructure.Count; i3++)
                            {
                                var building = cell.OccupyingStructure[i3];
                                bool flag5 = Vector3.Distance(cell.OccupyingStructure[i3].transform.position.xz(), cell.Position.xz()) <= 1E-05f;
                                if (flag5 && building.TeamID() == p.PlayerLandmassOwner.teamId)
                                {
                                    occupyingStructureData.Add(new Building.BuildingSaveData().Pack(cell.OccupyingStructure[i3]));
                                }
                            }
                            bool flag6 = occupyingStructureData.Count > 0;
                            if (flag6)
                            {
                                __instance.structures.Add(occupyingStructureData.ToArray());
                            }
                        }
                        bool flag7 = cell.SubStructure.Count > 0;
                        if (flag7)
                        {
                            List<Building.BuildingSaveData> subStructureData = new List<Building.BuildingSaveData>();
                            for (int i4 = 0; i4 < cell.SubStructure.Count; i4++)
                            {
                                var building = cell.SubStructure[i4];
                                bool flag8 = Vector3.Distance(cell.SubStructure[i4].transform.position.xz(), cell.Position.xz()) <= 1E-05f;
                                if (flag8 && building.TeamID() == p.PlayerLandmassOwner.teamId)
                                {
                                    subStructureData.Add(new Building.BuildingSaveData().Pack(cell.SubStructure[i4]));
                                }
                            }
                            bool flag9 = subStructureData.Count > 0;
                            if (flag9)
                            {
                                __instance.subStructures.Add(subStructureData.ToArray());
                            }
                        }
                    });

                    Main.helper.Log($"Saving town happiness");
                    __instance.TownHappiness = p.KingdomHappiness;

                    Main.helper.Log($"Saving town happiness infos");
                    __instance.happinessInfos = p.GetType().GetField("landMassHappiness", bindingFlags).GetValue(p) as List<Player.HappinessInfo>;

                    Main.helper.Log($"Saving town integrity infos");
                    __instance.integrityInfos = p.GetType().GetField("landMassIntegrity", bindingFlags).GetValue(p) as List<Player.IntegrityInfo>;

                    Main.helper.Log($"Saving town landmass owner");
                    __instance.playerLandmassOwnerSaveData = new LandmassOwner.LandmassOwnerSaveData().Pack(p.PlayerLandmassOwner);

                    Main.helper.Log($"Saving town bDidFirstFire");
                    __instance.bDidFirstFire = (bool)p.GetType().GetField("bDidFirstFire", bindingFlags).GetValue(p);

                    bool flag3 = p.taxRates != null;
                    if (flag3)
                    {

                        Main.helper.Log($"Saving town tax rates");
                        __instance.TaxRates = new float[p.taxRates.Length];
                        Array.Copy(p.taxRates, __instance.TaxRates, p.taxRates.Length);
                    }

                    Main.helper.Log($"Saving difficulty");
                    __instance.Difficulty = p.difficulty;

                    Main.helper.Log($"Saving CurrYear");
                    __instance.CurrYear = p.CurrYear;

                    Main.helper.Log($"Saving timeAtFailHappiness");
                    __instance.timeAtFailHappiness = p.timeAtFailHappiness;

                    Main.helper.Log($"Saving happinessMods");
                    __instance.happinessMods = p.happinessMods;

                    Main.helper.Log($"Saving currConsumption");
                    __instance.currConsumptionList = p.currConsumption;

                    Main.helper.Log($"Saving lastConsumption");
                    __instance.lastConsumptionList = p.lastConsumption;

                    Main.helper.Log($"Saving currProduction");
                    __instance.currProductionList = p.currProduction;

                    Main.helper.Log($"Saving lastProduction");
                    __instance.lastProductionList = p.lastProduction;

                    Main.helper.Log($"Saving landMassNames");
                    __instance.landMassNames = new List<string>();
                    for (int l = 0; l < p.LandMassNames.Count; l++)
                    {
                        __instance.landMassNames.Add(p.LandMassNames[l]);
                    }

                    Main.helper.Log($"Saving JobPriorityOrder");
                    __instance.JobPriorityOrder = new int[p.JobPriorityOrder.Length][];
                    __instance.JobEnabledFlag = new bool[p.JobEnabledFlag.Length][];
                    for (int m = 0; m < p.JobPriorityOrder.Length; m++)
                    {
                        __instance.JobPriorityOrder[m] = new int[p.JobPriorityOrder[m].Length];
                        __instance.JobEnabledFlag[m] = new bool[p.JobEnabledFlag[m].Length];
                        Array.Copy(p.JobPriorityOrder[m], __instance.JobPriorityOrder[m], __instance.JobPriorityOrder[m].Length);
                        Array.Copy(p.JobEnabledFlag[m], __instance.JobEnabledFlag[m], __instance.JobEnabledFlag[m].Length);
                    }

                    Main.helper.Log($"Saving JobFilledAvailable");
                    __instance.JobFilledAvailable = new int[World.inst.NumLandMasses][];
                    __instance.JobCustomMaxEnabledFlag = new bool[World.inst.NumLandMasses][];
                    for (int lm = 0; lm < World.inst.NumLandMasses; lm++)
                    {
                        int numJobTypes = p.JobFilledAvailable.data[lm].GetLength(0);
                        __instance.JobFilledAvailable[lm] = new int[numJobTypes];
                        __instance.JobCustomMaxEnabledFlag[lm] = new bool[numJobTypes];
                        for (int n = 0; n < numJobTypes; n++)
                        {
                            __instance.JobFilledAvailable[lm][n] = p.JobFilledAvailable.data[lm][n, 1];
                        }
                        Array.Copy(p.JobCustomMaxEnabledFlag[lm], __instance.JobCustomMaxEnabledFlag[lm], __instance.JobCustomMaxEnabledFlag[lm].Length);
                    }

                    Main.helper.Log($"Saving CanUseTools");
                    __instance.CanUseTools = new bool[p.CanUseTools.Length][];
                    for (int i2 = 0; i2 < p.CanUseTools.Length; i2++)
                    {
                        __instance.CanUseTools[i2] = new bool[p.CanUseTools[i2].Length];
                        Array.Copy(p.CanUseTools[i2], __instance.CanUseTools[i2], __instance.CanUseTools[i2].Length);
                    }

                    Main.helper.Log($"Saving usedCheats");
                    __instance.usedCheats = p.hasUsedCheats;

                    Main.helper.Log($"Saving nameForOldAgeDeath");
                    __instance.nameForOldAgeDeath = (string)p.GetType().GetField("nameForOldAgeDeath", bindingFlags).GetValue(p);

                    Main.helper.Log($"Saving deathsThisYear");
                    __instance.deathsThisYear = (int)p.GetType().GetField("deathsThisYear", bindingFlags).GetValue(p);

                    Main.helper.Log($"Saving poorHealthGracePeriod");
                    __instance.poorHealthGracePeriod = (float)p.GetType().GetField("poorHealthGracePeriod", bindingFlags).GetValue(p);

                    Main.helper.Log($"Saving dockOpenings");
                    __instance.dockOpenings = p.GetType().GetField("dockOpenings", bindingFlags).GetValue(p) as List<Player.DockOpening>;

                    Main.helper.Log($"Saving tourism");
                    __instance.tourism = p.tourism;


                    __result = __instance;

                    return false;
                }

                return true;
            }
        }

        /*[HarmonyPatch(typeof(Player.PlayerSaveData), "Unpack")]
        public class PlayerSaveDataUnpackHook
        {
            public static bool Prefix(Player.PlayerSaveData __instance, Player p, ref Player __result)
            {
                Main.helper.Log("Running patched player unpack method");
                if (KCClient.client.IsConnected)
                {
                    Main.helper.Log("Running patched unpack method");

                    var bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
                    Main.helper.Log("1");
                    Weather.inst.weatherTimeScale = 0f;
                    p.creativeMode = __instance.creativeMode;
                    p.ResetPerLandMassData();
                    Main.helper.Log("2");
                    bool flag = __instance.JobPriorityOrder != null && __instance.JobPriorityOrder[0].Length == ((int[])p.GetType().GetField("defaultPriorityOrder", bindingFlags).GetValue(p)).Length;
                    if (flag)
                    {
                        Main.helper.Log(__instance.JobPriorityOrder.Length.ToString());
                        Main.helper.Log(__instance.JobEnabledFlag.Length.ToString());
                        Main.helper.Log(p.JobEnabledFlag.Length.ToString());

                        for (int i = 0; i < __instance.JobPriorityOrder.Length; i++)
                        {
                            Array.Copy(__instance.JobPriorityOrder[i], p.JobPriorityOrder[i], __instance.JobPriorityOrder[i].Length);
                            Main.helper.Log("2.1");
                            Array.Copy(__instance.JobEnabledFlag[i], p.JobEnabledFlag[i], __instance.JobPriorityOrder[i].Length);
                            Main.helper.Log("2.2");
                        }
                    }
                    Main.helper.Log("3");
                    bool flag2 = __instance.JobFilledAvailable != null && __instance.JobFilledAvailable.Length == p.JobFilledAvailable.data.Length;
                    if (flag2)
                    {
                        for (int lm = 0; lm < World.inst.NumLandMasses; lm++)
                        {
                            bool flag3 = __instance.JobFilledAvailable[lm].Length != p.JobFilledAvailable.data[lm].Length / 2;
                            if (flag3)
                            {
                                break;
                            }
                            for (int j = 0; j < 38; j++)
                            {
                                p.JobFilledAvailable.data[lm][j, 0] = 0;
                                p.JobFilledAvailable.data[lm][j, 1] = __instance.JobFilledAvailable[lm][j];
                            }
                            Array.Copy(__instance.JobCustomMaxEnabledFlag[lm], p.JobCustomMaxEnabledFlag[lm], __instance.JobCustomMaxEnabledFlag[lm].Length);
                        }
                    }
                    Main.helper.Log("4");
                    // not saving creative info
                    p.ResetCreativeModeOptions();

                    p.KingdomHappiness = __instance.TownHappiness;
                    Main.helper.Log("5");
                    p.GetType().GetField("landMassHappiness", bindingFlags).SetValue(p, __instance.happinessInfos);
                    var landMassHappiness = p.GetType().GetField("landMassHappiness", bindingFlags).GetValue(p) as List<Player.HappinessInfo>;

                    bool flag5 = landMassHappiness == null;
                    if (flag5)
                    {
                        landMassHappiness = new List<Player.HappinessInfo>();
                    }
                    while (landMassHappiness.Count < World.inst.NumLandMasses)
                    {
                        landMassHappiness.Add(new Player.HappinessInfo());
                    }

                    Main.helper.Log("6");
                    p.GetType().GetField("landMassHealth", bindingFlags).SetValue(p, __instance.healthInfos);
                    var landMassHealth = p.GetType().GetField("landMassHealth", bindingFlags).GetValue(p) as List<Player.HealthInfo>;

                    bool flag6 = landMassHealth == null;
                    if (flag6)
                    {
                        landMassHealth = new List<Player.HealthInfo>();
                    }
                    while (landMassHealth.Count < World.inst.NumLandMasses)
                    {
                        landMassHealth.Add(new Player.HealthInfo());
                    }

                    Main.helper.Log("7");

                    p.GetType().GetField("landMassIntegrity", bindingFlags).SetValue(p, __instance.integrityInfos);
                    var landMassIntegrity = p.GetType().GetField("landMassIntegrity", bindingFlags).GetValue(p) as List<Player.IntegrityInfo>;

                    bool flag7 = landMassIntegrity == null;
                    if (flag7)
                    {
                        landMassIntegrity = new List<Player.IntegrityInfo>();
                    }
                    while (landMassIntegrity.Count < World.inst.NumLandMasses)
                    {
                        landMassIntegrity.Add(new Player.IntegrityInfo());
                    }
                    Main.helper.Log("8");
                    p.GetType().GetField("bDidFirstFire", bindingFlags).SetValue(p, __instance.bDidFirstFire);

                    bool flag8 = __instance.TaxRates == null;
                    if (flag8)
                    {
                        p.taxRates = new float[World.inst.NumLandMasses];
                        int l = 0;
                        int m = World.inst.NumLandMasses;
                        while (l < m)
                        {
                            p.taxRates[l] = (float)__instance.TaxRate;
                            l++;
                        }
                    }
                    else
                    {
                        p.taxRates = new float[__instance.TaxRates.Length];
                        Array.Copy(__instance.TaxRates, p.taxRates, __instance.TaxRates.Length);
                    }
                    p.difficulty = __instance.Difficulty;
                    p.CurrYear = __instance.CurrYear;
                    p.timeAtFailHappiness = __instance.timeAtFailHappiness;
                    p.currConsumption = __instance.currConsumptionList;
                    Main.helper.Log("9");
                    while (p.currConsumption.Count < World.inst.NumLandMasses)
                    {
                        p.currConsumption.Add(new Player.Consumption());
                    }
                    p.lastConsumption = __instance.lastConsumptionList;
                    while (p.lastConsumption.Count < World.inst.NumLandMasses)
                    {
                        p.lastConsumption.Add(new Player.Consumption());
                    }
                    p.currProduction = __instance.currProductionList;
                    while (p.currProduction.Count < World.inst.NumLandMasses)
                    {
                        p.currProduction.Add(new Player.Production());
                    }
                    p.lastProduction = __instance.lastProductionList;
                    while (p.lastProduction.Count < World.inst.NumLandMasses)
                    {
                        p.lastProduction.Add(new Player.Production());
                    }
                    p.happinessMods = __instance.happinessMods;
                    Main.helper.Log("10");
                    bool flag9 = p.happinessMods == null;
                    if (flag9)
                    {
                        p.happinessMods = new List<Player.HappinessMod>();
                    }
                    Main.helper.Log("11");
                    bool flag10 = __instance.landMassNames == null;
                    if (flag10)
                    {
                        p.ResetLandMassNames();
                    }
                    else
                    {
                        p.LandMassNames.Clear();
                        for (int n = 0; n < __instance.landMassNames.Count; n++)
                        {
                            p.LandMassNames.Add(__instance.landMassNames[n]);
                        }
                    }
                    Main.helper.Log("12");
                    bool flag11 = __instance.playerLandmassOwnerSaveData != null;
                    if (flag11)
                    {
                        __instance.playerLandmassOwnerSaveData.Unpack(p.PlayerLandmassOwner);
                    }
                    else
                    {

                        var Resources = (ResourceAmount)__instance.GetType().GetField("Resources").GetValue(__instance);

                        p.PlayerLandmassOwner.Gold = Resources.Get(FreeResourceType.Gold);
                        for (int i2 = 0; i2 < __instance.structures.Count; i2++)
                        {
                            Building.BuildingSaveData[] occupyingStructureData = __instance.structures[i2];
                            for (int h = 0; h < occupyingStructureData.Length; h++)
                            {
                                int lIdx = World.inst.GetCellData(occupyingStructureData[h].globalPosition).landMassIdx;
                                bool flag12 = !p.PlayerLandmassOwner.OwnsLandMass(lIdx);
                                if (flag12)
                                {
                                    p.PlayerLandmassOwner.TakeOwnership(lIdx);
                                }
                            }
                        }
                    }
                    Main.helper.Log("13");
                    bool flag13 = __instance.bannerIdx != -1;
                    if (flag13)
                    {
                        p.SetIndexedBanner(__instance.bannerIdx);
                    }
                    bool flag14 = !__instance.newBannerSystem;
                    Main.helper.Log("14");
                    if (flag14)
                    {
                        p.SetIndexedBanner(5);
                        p.SetCustomBannerTexture(World.inst.liverySets[__instance.bannerIdx].bannerMaterial.mainTexture as Texture2D);
                    }
                    bool flag15 = p.PlayerLandmassOwner.Gold < 0;
                    if (flag15)
                    {
                        p.PlayerLandmassOwner.Gold = 0;
                    }

                    Main.helper.Log("15");
                    var upgrades = __instance.GetType().GetField("upgrades", bindingFlags).GetValue(__instance) as List<Player.UpgradeType>;
                    for (int i3 = 0; i3 < upgrades.Count; i3++)
                    {
                        p.PlayerLandmassOwner.AddUpgrade(upgrades[i3]);
                    }
                    p.Workers.Clear();
                    p.Homeless.Clear();

                    Main.helper.Log("16");
                    Main.helper.Log($"Loading {__instance.WorkersArray.Length} workers from workers array for {p.PlayerLandmassOwner.teamId}");
                    bool flag16 = __instance.WorkersArray != null;
                    if (flag16)
                    {

                        Main.helper.Log("17");
                        for (int i4 = 0; i4 < __instance.WorkersArray.Length; i4++)
                        {
                            Villager person = Villager.CreateVillager();
                            person.Pos = __instance.WorkersArray[i4].pos;
                            __instance.WorkersArray[i4].Unpack(person);
                            p.Workers.Add(person);
                        }
                    }
                    else
                    {

                        Main.helper.Log("18");
                        Main.helper.Log($"Loading {__instance.Workers.Count} workers for {p.PlayerLandmassOwner.teamId}");
                        for (int i5 = 0; i5 < __instance.Workers.Count; i5++)
                        {
                            Villager person2 = Villager.CreateVillager();
                            person2.Pos = __instance.Workers[i5].pos;
                            __instance.Workers[i5].Unpack(person2);
                            p.Workers.Add(person2);
                        }
                    }

                    Main.helper.Log("19");
                    for (int i6 = 0; i6 < __instance.HomelessData.Count; i6++)
                    {
                        Villager worker = p.GetWorker(__instance.HomelessData[i6]);
                        bool flag17 = worker != null;
                        if (flag17)
                        {
                            p.Homeless.Add(worker);
                        }
                    }
                    Main.helper.Log("20");

                    /*List<Player.PlayerSaveData.BuildingLoadHelper> buildingsToPlace = new List<Player.PlayerSaveData.BuildingLoadHelper>();
                    for (int i7 = 0; i7 < __instance.structures.Count; i7++)
                    {
                        Building.BuildingSaveData[] occupyingStructureData2 = __instance.structures[i7];
                        for (int h2 = 0; h2 < occupyingStructureData2.Length; h2++)
                        {
                            Building building = __instance.ProcessBuilding(occupyingStructureData2[h2], p);
                            bool flag18 = building != null;
                            if (flag18)
                            {
                                buildingsToPlace.Add(new Player.PlayerSaveData.BuildingLoadHelper(building, occupyingStructureData2[h2], h2));
                            }
                        }
                    }
                    for (int i8 = 0; i8 < __instance.subStructures.Count; i8++)
                    {
                        Building.BuildingSaveData[] occupyingStructureData3 = __instance.subStructures[i8];
                        for (int h3 = 0; h3 < occupyingStructureData3.Length; h3++)
                        {
                            Building building2 = __instance.ProcessBuilding(occupyingStructureData3[h3], p);
                            bool flag19 = building2 != null;
                            if (flag19)
                            {
                                buildingsToPlace.Add(new Player.PlayerSaveData.BuildingLoadHelper(building2, occupyingStructureData3[h3], h3 - 1000));
                            }
                        }
                    }
                    foreach (Player.PlayerSaveData.BuildingLoadHelper buildingHelper in from x in buildingsToPlace
                                                                                        orderby x.priority
                                                                                        select x)
                    {
                        World.inst.PlaceFromLoad(buildingHelper.building);
                        buildingHelper.buildingSaveData.UnpackStage2(buildingHelper.building);
                    }
                    for (int i9 = 0; i9 < p.Workers.Count; i9++)
                    {
                        bool flag20 = p.Workers.data[i9].Residence == null;
                        if (flag20)
                        {
                            bool flag21 = !p.Homeless.Contains(p.Workers.data[i9]);
                            if (flag21)
                            {
                                p.Homeless.Add(p.Workers.data[i9]);
                                Debug.LogError("worker with null residence not in homeless saved data...");
                            }
                        }
                    }
                    for (int i10 = 0; i10 < buildingsToPlace.Count; i10++)
                    {
                        Player.PlayerSaveData.BuildingLoadHelper buildingHelper2 = buildingsToPlace[i10];
                        Road roadComp = buildingHelper2.building.GetComponent<Road>();
                        bool flag22 = roadComp != null;
                        if (flag22)
                        {
                            roadComp.UpdateRotation();
                        }
                        bool flag23 = buildingHelper2.building.uniqueNameHash == Player.PlayerSaveData.aqueductHash;
                        if (flag23)
                        {
                            buildingHelper2.building.GetComponent<Aqueduct>().UpdateRotation();
                        }
                    }
                    Cell[] cellData = World.inst.GetCellsData();
                    for (int i11 = 0; i11 < cellData.Length; i11++)
                    {
                        List<Building> structures = cellData[i11].OccupyingStructure;
                        for (int j2 = 0; j2 < structures.Count; j2++)
                        {
                            bool flag24 = structures[j2].categoryHash == Player.PlayerSaveData.castleblockHash;
                            if (flag24)
                            {
                                structures[j2].GetComponent<CastleBlock>().UpdateStackPostLoad();
                                break;
                            }
                        }
                    }
                    bool flag25 = Player.inst.keep != null;
                    if (flag25)
                    {
                        p.buildingContainer.BroadcastMessage("OnAnyBuildingAdded", Player.inst.keep.GetComponent<Building>(), SendMessageOptions.DontRequireReceiver);
                    }
                    World.inst.SetupInitialPathCosts();
                    Player.inst.irrigation.UpdateIrrigation();
                    Player.inst.CalcMaxResources(null, -1);
                    bool flag26 = __instance.CanUseTools != null;
                    if (flag26)
                    {
                        int i12 = 0;
                        while (i12 < __instance.CanUseTools.Length && i12 < p.CanUseTools.Length)
                        {
                            Array.Copy(__instance.CanUseTools[i12], p.CanUseTools[i12], __instance.CanUseTools[i12].Length);
                            i12++;
                        }
                    }
                    ToolInfo.inst.SetTogglesFromPlayerData();
                    p.hasUsedCheats = __instance.usedCheats;
                    p.checkedForceCreative = false;
                    p.nameForOldAgeDeath = __instance.nameForOldAgeDeath;
                    p.deathsThisYear = __instance.deathsThisYear;
                    World.inst.RebuildVillagerGrid();
                    ArrayExt<Building> keepers = Player.inst.GetBuildingList(World.cemeteryKeeperHash);
                    for (int i13 = 0; i13 < keepers.Count; i13++)
                    {
                        keepers.data[i13].GetComponent<CemeteryKeeper>().RebuildPathData();
                    }
                    p.ChangeHazardPayActive(p.PlayerLandmassOwner.hazardPay, false);
                    p.poorHealthGracePeriod = __instance.poorHealthGracePeriod;
                    p.UpdateFocusedLandmass();
                    PopulationUI.inst.regionName.RefreshRegionName();
                    p.dockOpenings = __instance.dockOpenings;
                    bool flag27 = p.dockOpenings == null;
                    if (flag27)
                    {
                        p.dockOpenings = new List<Player.DockOpening>();
                    }
                    p.IntegrityTimer.ForceExpire();
                    bool flag28 = __instance.tourism != null;
                    if (flag28)
                    {
                        p.tourism = __instance.tourism;
                    }
                    else
                    {
                        p.tourism = new Player.TourismInfo();
                    }


                    __result = p;

                    return false;
                }

                return true;
            }
        }*/

        #endregion

        /**
         * 
         * Find all Player.inst references and reconstruct method with references to client planyer
         * 
         * Instantiating main player object and setting landmass teamid in KCPLayer
         * 
         * E.G instead of Player.inst, it should be Main.kCPlayers[Client].player for example, and the rest of the code is the same
         * 
         * Prefix that sets Player.inst to the right client instance and then calls that instances method?
         * 
         */

        [HarmonyPatch]
        public class PlayerReferencePatch
        {
            static IEnumerable<MethodBase> TargetMethods()
            {
                Assembly assembly = typeof(Player).Assembly;

                Type[] types = new Type[] { typeof(Player)/*, typeof(World), typeof(LandmassOwner), typeof(Keep), typeof(Villager), typeof(DragonSpawn), typeof(DragonController), typeof(Dragon)*/ };

                var methodsInNamespace = types
                    .SelectMany(t => t.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => !m.IsAbstract))
                    .ToList();

                helper.Log("Methods in namespace: " + methodsInNamespace.Count);

                return methodsInNamespace.ToArray().Cast<MethodBase>();
            }

            static IEnumerable<CodeInstruction> Transpiler(MethodBase method, IEnumerable<CodeInstruction> instructions)
            {
                int PlayerInstCount = 0;

                var codes = new List<CodeInstruction>(instructions);
                for (var i = 0; i < codes.Count; i++)
                {
                    if (codes[i].opcode == OpCodes.Ldsfld && codes[i].operand.ToString() == "Player inst")
                    {
                        PlayerInstCount++;

                        codes[i].opcode = (OpCodes.Ldarg_0); // Replace all instance methods static ref with "this" instead of Player.inst

                        // Replace ldsfld Player::inst with the sequence to load from Main.kCPlayers
                        // Step 1: Load Main.kCPlayers onto the evaluation stack.
                        //codes[i] = new CodeInstruction(OpCodes.Ldsfld, typeof(Main).GetField("kCPlayers"));

                        // Step 2: Load the value of Main.PlayerSteamID onto the evaluation stack as the key
                        //codes.Insert(++i, new CodeInstruction(OpCodes.Ldsfld, typeof(Main).GetField("PlayerSteamID")));

                        // Step 3: Call Dictionary<TKey, TValue>.get_Item(TKey key) to get the Player instance.
                        //codes.Insert(++i, new CodeInstruction(OpCodes.Callvirt, typeof(Dictionary<string, KCPlayer>).GetMethod("get_Item")));

                        // Now, access the 'inst' field of the fetched Player instance, if necessary.
                        //codes.Insert(++i, new CodeInstruction(OpCodes.Ldfld, typeof(KCPlayer).GetField("inst")));
                    }
                }

                if (PlayerInstCount > 0)
                    Main.helper.Log($"Found {PlayerInstCount} static Player.inst references in {method.Name}");

                return codes.AsEnumerable();
            }
        }

        [HarmonyPatch]
        public class BuildingPlayerReferencePatch
        {
            static IEnumerable<MethodBase> TargetMethods()
            {
                Assembly assembly = typeof(Building).Assembly;

                Type[] types = new Type[] { typeof(Building) };

                var methodsInNamespace = types
                    .SelectMany(t => t.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => !m.IsAbstract))
                    .ToList();

                helper.Log("Methods in namespace: " + methodsInNamespace.Count);

                return methodsInNamespace.ToArray().Cast<MethodBase>();
            }

            static IEnumerable<CodeInstruction> Transpiler(MethodBase method, IEnumerable<CodeInstruction> instructions)
            {
                int PlayerInstCount = 0;

                var codes = new List<CodeInstruction>(instructions);
                MethodInfo getPlayerByBuildingMethodInfo = typeof(Main).GetMethod("GetPlayerByBuilding", BindingFlags.Static | BindingFlags.Public);

                for (var i = 0; i < codes.Count; i++)
                {
                    if (codes[i].opcode == OpCodes.Ldsfld && codes[i].operand.ToString() == "Player inst")
                    {
                        PlayerInstCount++;

                        // Check if the current instruction is ldsfld Player.inst
                        if (codes[i].opcode == OpCodes.Ldsfld && codes[i].operand.ToString().Contains("Player inst"))
                        {
                            // Replace the instruction sequence
                            // Step 1: Load 'this' for the Building instance
                            codes[i].opcode = OpCodes.Ldarg_0;

                            // Step 2: Call GetPlayerByBuilding(Building instance) static method in Main
                            var callTeamID = new CodeInstruction(OpCodes.Call, getPlayerByBuildingMethodInfo);
                            codes.Insert(++i, callTeamID);
                        }
                    }
                }

                if (PlayerInstCount > 0)
                    Main.helper.Log($"Found {PlayerInstCount} static building Player.inst references in {method.Name}");

                return codes.AsEnumerable();
            }
        }


        [HarmonyPatch]
        public class PlayerPatch
        {
            static IEnumerable<MethodBase> TargetMethods()
            {
                var meth = typeof(Player).GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
                return meth.Cast<MethodBase>();
            }

            public static bool Prefix(MethodBase __originalMethod, Player __instance)
            {
                if (__originalMethod.Name.Equals("Awake") && (KCServer.IsRunning || KCClient.client.IsConnected))
                {
                    helper.Log("Awake run on player instance while server is running");

                    return false;
                }

                if (__originalMethod.Name.Equals("Awake") && __instance.gameObject.name.Contains("Client Player"))
                {
                    helper.Log("Awake run on client instance");
                    try
                    {
                        //___defaultEnabledFlags = new bool[38];
                        //for (int i = 0; i < ___defaultEnabledFlags.Length; i++)
                        //{
                        //    ___defaultEnabledFlags[i] = true;
                        //}
                        //__instance.PlayerLandmassOwner = __instance.gameObject.AddComponent<LandmassOwner>();



                        //helper.Log(__instance.PlayerLandmassOwner.ToString());
                    }
                    catch (Exception e)
                    {
                        helper.Log(e.ToString());
                        helper.Log(e.Message);
                        helper.Log(e.StackTrace);
                    }
                    return false;
                }

                if (__originalMethod.Name.Equals("Update") && __instance.gameObject.name.Contains("Client Player"))
                {
                    //helper.Log("Update run on client instance");
                    try
                    {
                        //___defaultEnabledFlags = new bool[38];
                        //for (int i = 0; i < ___defaultEnabledFlags.Length; i++)
                        //{
                        //    ___defaultEnabledFlags[i] = true;
                        //}
                        //__instance.PlayerLandmassOwner = __instance.gameObject.AddComponent<LandmassOwner>();



                        //helper.Log(__instance.PlayerLandmassOwner.ToString());
                    }
                    catch (Exception e)
                    {
                        helper.Log(e.ToString());
                        helper.Log(e.Message);
                        helper.Log(e.StackTrace);
                    }
                    return false;
                }

                if (__originalMethod.Name.Equals("Update"))
                {
                    //helper.Log($"Update called for: {__instance.gameObject.name}");

                    try
                    {
                        if (KCClient.client.IsConnected && !__instance.gameObject.name.Contains("Client Player"))
                        {
                            StateObserver.RegisterObserver(__instance, new string[] {
                                "bannerIdx", "kingdomHappiness", "landMassHappiness", "landMassIntegrity", "bDidFirstFire", "CurrYear",
                                "timeAtFailHappiness", "hasUsedCheats", "nameForOldAgeDeath", "deathsThisYear", /*"poorHealthGracePeriod",*/
                            });

                            //StateObserver.Update(__instance);
                        }
                    }
                    catch (Exception e)
                    {
                        helper.Log(e.ToString());
                        helper.Log(e.Message);
                        helper.Log(e.StackTrace);
                    }
                    return true;
                }

                return true;
            }

            public static void Postfix(MethodBase __originalMethod, Player __instance)
            {
                if (__originalMethod.Name.Equals("Update"))
                {
                    //helper.Log($"Update called for: {__instance.gameObject.name} in POSTFIX");


                    //helper.Log("CHECKING ALL COMPONENTS IN UPDATE: ");
                    //Component[] components = __instance.gameObject.GetComponents<Component>();

                    //foreach (Component component in components)
                    //{
                    //    helper.Log("--- " + component.GetType().kingdomName);
                    //}
                }
            }
        }

        #region "Unity Log Hooks"

        [HarmonyPatch(typeof(UnityEngine.Debug), "Log", new Type[] { typeof(object) })]
        public class DebugLogPatch
        {
            public static void Prefix(object message)
            {
                if (Main.kCPlayers.Values.Any((p) => message.ToString().StartsWith(p.inst.PlayerLandmassOwner.teamId.ToString())))
                    return;

                //Main.helper.Log($"UNITY 3D DEBUG LOG");
                //Main.helper.Log(message.ToString());
            }
        }

        [HarmonyPatch(typeof(UnityEngine.Debug), "Log", new Type[] { typeof(object), typeof(UnityEngine.Object) })]
        public class DebugLogCPatch
        {
            public static void Prefix(object message, UnityEngine.Object context)
            {
                if (Main.kCPlayers.Values.Any((p) => message.ToString().StartsWith(p.inst.PlayerLandmassOwner.teamId.ToString())))
                    return;

                //Main.helper.Log($"UNITY 3D DEBUG LOG");
                //Main.helper.Log(context.ToString());
                //Main.helper.Log(message.ToString());
            }
        }


        [HarmonyPatch(typeof(UnityEngine.Debug), "LogError", new Type[] { typeof(object) })]
        public class DebugLogErrorPatch
        {
            public static void Prefix(object message)
            {
                if (message.ToString().StartsWith(Player.inst?.PlayerLandmassOwner?.teamId.ToString() ?? ""))
                    return;

                //Main.helper.Log($"UNITY 3D DEBUG LOG ERROR");
                //Main.helper.Log(message.ToString());
            }
        }

        [HarmonyPatch(typeof(UnityEngine.Debug), "LogError", new Type[] { typeof(object), typeof(UnityEngine.Object) })]
        public class DebugLogErrorCPatch
        {
            public static void Prefix(object message, UnityEngine.Object context)
            {
                if (message.ToString().StartsWith(Player.inst?.PlayerLandmassOwner?.teamId.ToString() ?? ""))
                    return;

                //Main.helper.Log($"UNITY 3D DEBUG LOG ERROR");
                //Main.helper.Log(context.ToString());
                //Main.helper.Log(message.ToString());
            }
        }

        [HarmonyPatch(typeof(UnityEngine.Debug), "LogException", new Type[] { typeof(Exception) })]
        public class DebugLogExceptionPatch
        {
            public static void Prefix(Exception exception)
            {
                //Main.helper.Log($"UNITY 3D DEBUG LOG EXCEPTION");
                //Main.helper.Log(exception.Message);
                //Main.helper.Log(exception.StackTrace);
            }
        }

        [HarmonyPatch(typeof(UnityEngine.Debug), "LogException", new Type[] { typeof(Exception), typeof(UnityEngine.Object) })]
        public class DebugLogExceptionCPatch
        {
            public static void Prefix(Exception exception, UnityEngine.Object context)
            {
                //Main.helper.Log($"UNITY 3D DEBUG LOG EXCEPTION");
                //Main.helper.Log(exception.Message);
                //Main.helper.Log(exception.StackTrace);
            }
        }


        [HarmonyPatch(typeof(UnityEngine.Debug), "LogWarning", new Type[] { typeof(object) })]
        public class DebugLogWarningPatch
        {
            public static void Prefix(object message)
            {
                if (message.ToString().Contains("Failed to send 928 byte"))
                    return;


                //Main.helper.Log($"UNITY 3D DEBUG LOG WARNING");
                //Main.helper.Log(message.ToString());
            }
        }

        [HarmonyPatch(typeof(UnityEngine.Debug), "LogWarning", new Type[] { typeof(object), typeof(UnityEngine.Object) })]
        public class DebugLogWarningCPatch
        {
            public static void Prefix(object message, UnityEngine.Object context)
            {
                if (Main.kCPlayers.Values.Any((p) => message.ToString().StartsWith(p.inst.PlayerLandmassOwner.teamId.ToString())))
                    return;

                //Main.helper.Log($"UNITY 3D DEBUG LOG WARNING");
                //Main.helper.Log(context.ToString());
                //Main.helper.Log(message.ToString());
            }
        }

        #endregion
    }

    public class DetailedTransformData
    {
        public string name;
        public string path;
        public Vector3 position;
        public Vector3 localPosition;
        public Quaternion rotation;
        public Quaternion localRotation;
        public Vector3 eulerAngles;
        public Vector3 localEulerAngles;
        public Vector3 localScale;
        public Vector3 lossyScale;
        public List<DetailedTransformData> children;
        public List<string> components;

        public DetailedTransformData(Transform transform, string parentPath = "")
        {
            name = transform.name;
            path = parentPath == "" ? name : parentPath + "/" + name;
            position = transform.position;
            localPosition = transform.localPosition;
            rotation = transform.rotation;
            localRotation = transform.localRotation;
            eulerAngles = transform.eulerAngles;
            localEulerAngles = transform.localEulerAngles;
            localScale = transform.localScale;
            lossyScale = transform.lossyScale;
            children = new List<DetailedTransformData>();

            components = transform.GetComponents<Component>().Select(c => c.GetType().ToString()).ToList();

            foreach (Transform child in transform)
            {
                children.Add(new DetailedTransformData(child, path));
            }
        }
    }

}
