using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Threading; using UnityEngine; public class HiresManagerRebirth { private static HiresManagerRebirth instance = null; private static readonly object Locker = new object(); public static bool loadedHires = false; private const string saveFile = "HiresManagerRebirth.dat"; private static ThreadManager.ThreadInfo dataSaveThreadInfo; public static List playerHires = new List(); public static List observers = new List(); private static Dictionary playerHiresDict = new Dictionary(); private static Dictionary observersDict = new Dictionary(); private static float updateCheck = 0f; private static float updateTick = 60; // Variables to break down heavy update operations private static bool isProcessingUpdate = false; private static int updatePhase = 0; private static List spawnedPlayersForProcessing = new List(); // Reused, cleared instead of reallocated public static bool HasInstance => instance != null; public class chunkObservers { public int entityID; public ChunkManager.ChunkObserver observerRef; public chunkObservers() { } public chunkObservers(int entityID, ChunkManager.ChunkObserver observerRef) { this.entityID = entityID; this.observerRef = observerRef; } } public class hireInfo { public int playerID; public int hireID; public string name; public string className; public Vector3 spawnPosition; public Vector3 spawnRotation; public Vector3 reSpawnPosition; public Vector3 reSpawnRotation; public int numKills; public int numMine; public int order; public bool playerSpawned; public hireInfo() { } public hireInfo(int playerID, int hireID, string name, string className, Vector3 spawnPosition, Vector3 spawnRotation, Vector3 reSpawnPosition, Vector3 reSpawnRotation, int numKills, int numMine, int order, bool playerSpawned = false) { this.playerID = playerID; this.hireID = hireID; this.name = name; this.className = className; this.spawnPosition = spawnPosition; this.spawnRotation = spawnRotation; this.reSpawnPosition = reSpawnPosition; this.reSpawnRotation = reSpawnRotation; this.numKills = numKills; this.numMine = numMine; this.order = order; this.playerSpawned = playerSpawned; } } public static HiresManagerRebirth Instance => instance; public static void Init() { instance = new HiresManagerRebirth(); Load(); loadedHires = true; ModEvents.GameUpdate.RegisterHandler(Update); //GameManager.Instance.StartCoroutine(delayExecution(10f)); RebuildDictionaries(); } public static IEnumerator delayExecution(float delay) { while (true) { Update(); yield return new WaitForSeconds(delay); } } private static void RebuildDictionaries() { playerHiresDict.Clear(); for (int i = 0; i < playerHires.Count; i++) { hireInfo hire = playerHires[i]; playerHiresDict[hire.hireID] = hire; } observersDict.Clear(); for (int i = 0; i < observers.Count; i++) { var obs = observers[i]; observersDict[obs.entityID] = obs; } } public static void Update() { //return; if (!HasInstance || !loadedHires) { return; } GameManager gm = GameManager.Instance; ConnectionManager connectionManager = SingletonMonoBehaviour.Instance; float currentTime = Time.time; bool isHordeNight = RebirthUtilities.IsHordeNight(); bool isClient = connectionManager.IsClient; int hiresCount = playerHires.Count; int observersCount = observers.Count; if (isHordeNight) { return; } if (!isProcessingUpdate && hiresCount == 0 && observersCount == 0) { return; } if (isProcessingUpdate) { ProcessScheduledWork(gm, connectionManager, isClient); return; } if (isClient) { return; } //if ((currentTime - updateCheck) > updateTick) { isProcessingUpdate = true; updatePhase = 0; // Clear instead of reassigning a new list spawnedPlayersForProcessing.Clear(); } } private static void ProcessScheduledWork(GameManager gm, ConnectionManager connectionManager, bool isClient) { if (isClient) { CompleteProcessing(); return; } int hiresCount = playerHires.Count; int observersCount = observers.Count; if (hiresCount == 0 && observersCount == 0) { CompleteProcessing(); return; } switch (updatePhase) { case 0: // Observers cleanup phase for (int i = observersCount - 1; i >= 0; i--) { var currentObserver = observers[i]; int entityID = currentObserver.entityID; if (!playerHiresDict.TryGetValue(entityID, out hireInfo hireCheck) || (hireCheck.order != 1 && hireCheck.order != 2)) { gm.RemoveChunkObserver(currentObserver.observerRef); observersDict.Remove(entityID); observers.RemoveAt(i); } } break; case 1: // Process hires and build the list of spawnedPlayers for (int i = 0; i < hiresCount; i++) { hireInfo hire = playerHires[i]; EntityPlayer player = gm.World.GetEntity(hire.playerID) as EntityPlayer; if (player != null && player.IsSpawned() && !player.AttachedToEntity) { // Avoid Contains check if possible. If spawnedPlayersForProcessing is small, it's minor anyway. if (!spawnedPlayersForProcessing.Contains(player)) { spawnedPlayersForProcessing.Add(player); } int order = hire.order; int hireID = hire.hireID; if (order == 1 || order == 2) { if (!observersDict.ContainsKey(hireID)) { ChunkManager.ChunkObserver observerRef = gm.AddChunkObserver(hire.spawnPosition, false, 3, -1); var newObs = new chunkObservers(hireID, observerRef); observers.Add(newObs); observersDict[hireID] = newObs; } } else if (order == 0) { if (observersDict.TryGetValue(hireID, out var obs)) { gm.RemoveChunkObserver(obs.observerRef); observersDict.Remove(hireID); // Instead of RemoveAll, remove manually: for (int idx = observers.Count - 1; idx >= 0; idx--) { if (observers[idx].entityID == hireID) { observers.RemoveAt(idx); } } } } hire.playerSpawned = true; } } break; case 2: int spawnedPlayersCount = spawnedPlayersForProcessing.Count; if (spawnedPlayersCount == 0) break; for (int p = 0; p < spawnedPlayersCount; p++) { EntityPlayer player = spawnedPlayersForProcessing[p]; var playerBuffs = player.Buffs; bool playerHasGod = playerBuffs.HasBuff("god"); bool playerHasDelay10 = playerBuffs.HasBuff("FuriousRamsayDelay-10"); bool playerHasDelay5 = playerBuffs.HasBuff("FuriousRamsayDelay-5"); if (playerHasGod || playerHasDelay10 || playerHasDelay5 || player.AttachedToEntity) continue; Vector3 playerPos = player.position; Vector3 playerRot = player.rotation; Chunk chunkPlayer = (Chunk)gm.World.GetChunkFromWorldPos((int)playerPos.x, (int)playerPos.z); int playerEntityId = player.entityId; int playerHiresCount = playerHires.Count; for (int i = 0; i < playerHiresCount; i++) { hireInfo hire = playerHires[i]; if (hire.playerID == playerEntityId) { int hireEntityID = hire.hireID; Entity hiredNPC = gm.World.GetEntity(hireEntityID); if (hiredNPC == null) { Vector3 respawnPosition; Vector3 respawnRotation = Vector3.zero; Vector3 hireRespawnPos = hire.reSpawnPosition; if (hireRespawnPos != Vector3.zero) { respawnPosition = hireRespawnPos; respawnRotation.y = playerRot.y; } else { SpawnPosition spawnPoint = RebirthUtilities.GetSpawnPoint(player); if (spawnPoint.IsUndef()) { respawnPosition = playerPos; respawnRotation.y = playerRot.y; continue; } else { respawnPosition = spawnPoint.position; respawnRotation.y = playerRot.y; } } Chunk chunkHire = (Chunk)gm.World.GetChunkFromWorldPos((int)respawnPosition.x, (int)respawnPosition.z); if (chunkHire != null && IsAdjacentOrSameChunk(chunkPlayer, chunkHire)) { // Check again if entity is null (redundant, but safe) if (player.world.GetEntity(hireEntityID) == null) { int entityClass = EntityClass.FromString(hire.className); EntityNPCRebirth NewEntity = EntityFactory.CreateEntity(entityClass, respawnPosition, respawnRotation) as EntityNPCRebirth; NewEntity.SetSpawnerSource(EnumSpawnerSource.StaticSpawner); NewEntity._strMyName = hire.name; RebirthUtilities.SetLeaderAndOwner(NewEntity.entityId, playerEntityId); NewEntity.IsEntityUpdatedInUnloadedChunk = true; NewEntity.bWillRespawn = true; NewEntity.bIsChunkObserver = true; NewEntity.belongsPlayerId = playerEntityId; NewEntity.guardPosition = respawnPosition; gm.World.SpawnEntityInWorld(NewEntity); int oldHireID = hire.hireID; hire.hireID = NewEntity.entityId; hire.order = 1; SaveCurrent(); UpdateHireInfo(oldHireID, "newid", "", "", "", NewEntity.entityId); int hireOrder = hire.order; if (hireOrder == (int)EntityUtilities.Orders.None) { NewEntity.Buffs.SetCustomVar("CurrentOrder", (int)EntityUtilities.Orders.Follow); } else if (hireOrder == (int)EntityUtilities.Orders.Follow) { NewEntity.Buffs.SetCustomVar("CurrentOrder", (int)EntityUtilities.Orders.Stay); } else { NewEntity.Buffs.SetCustomVar("CurrentOrder", (int)EntityUtilities.Orders.Stay); } RebirthUtilities.SetLeaderAndOwner(NewEntity.entityId, playerEntityId); bool foundObserver = false; int obsCount = observers.Count; for (int oi = 0; oi < obsCount; oi++) { var o = observers[oi]; if (o.entityID == oldHireID) { o.entityID = NewEntity.entityId; observersDict.Remove(oldHireID); observersDict[NewEntity.entityId] = o; foundObserver = true; break; } } float flMode = player.Buffs.GetCustomVar("varNPCModMode"); float flHalt = player.Buffs.GetCustomVar("varNPCModStopAttacking"); if (flMode == 0) { NewEntity.Buffs.AddBuff("buffNPCModFullControlMode"); } else { NewEntity.Buffs.RemoveBuff("buffNPCModFullControlMode"); } if (flHalt == 1) { NewEntity.Buffs.AddBuff("buffNPCModStopAttacking"); } else { NewEntity.Buffs.RemoveBuff("buffNPCModStopAttacking"); } if (!foundObserver) { ChunkManager.ChunkObserver observerRef = gm.AddChunkObserver(NewEntity.position, false, 3, -1); var newObs = new chunkObservers(NewEntity.entityId, observerRef); observers.Add(newObs); observersDict[NewEntity.entityId] = newObs; } playerHiresDict.Remove(oldHireID); playerHiresDict[NewEntity.entityId] = hire; } } } } } } break; } updatePhase++; if (updatePhase > 2) { CompleteProcessing(); } } private static void CompleteProcessing() { isProcessingUpdate = false; updateCheck = Time.time; } private static bool IsAdjacentOrSameChunk(Chunk chunkPlayer, Chunk chunkHire) { int px = chunkPlayer.ChunkPos.x; int pz = chunkPlayer.ChunkPos.z; int hx = chunkHire.ChunkPos.x; int hz = chunkHire.ChunkPos.z; return (hx == px && hz == pz) || (hx == px + 1 && hz == pz) || (hx == px - 1 && hz == pz) || (hx == px && hz == pz - 1) || (hx == px + 1 && hz == pz - 1) || (hx == px - 1 && hz == pz) || (hx == px && hz == pz + 1) || (hx == px + 1 && hz == pz + 1) || (hx == px - 1 && hz == pz); } public static void RemoveHire(int entityID, bool removeObserver = false) { ConnectionManager connectionManager = SingletonMonoBehaviour.Instance; GameManager gm = GameManager.Instance; int hireCount = playerHires.Count; for (int i = 0; i < hireCount; i++) { hireInfo hire = playerHires[i]; if (hire.hireID == entityID) { bool isServer = connectionManager.IsServer; if (isServer) { if (RebirthUtilities.SendToClient(hire.playerID)) { connectionManager.SendPackage( NetPackageManager.GetPackage().Setup(entityID), false, hire.playerID, -1, -1, null, 192); } playerHiresDict.Remove(entityID); playerHires.RemoveAt(i); if (removeObserver && observersDict.TryGetValue(entityID, out var obs)) { gm.RemoveChunkObserver(obs.observerRef); observersDict.Remove(entityID); // Manual removal instead of RemoveAll() for (int idx = observers.Count - 1; idx >= 0; idx--) { if (observers[idx].entityID == entityID) { observers.RemoveAt(idx); } } } SaveCurrent(); } else { connectionManager.SendToServer( NetPackageManager.GetPackage().Setup(entityID), false); playerHiresDict.Remove(entityID); playerHires.RemoveAt(i); } return; } } if (connectionManager.IsClient) { connectionManager.SendToServer( NetPackageManager.GetPackage().Setup(entityID), false); } } public static void AddHire(int playerID, int hireID, string name, string className, Vector3 spawnPosition, Vector3 spawnRotation, Vector3 reSpawnPosition, Vector3 reSpawnRotation, int numKills, int numMine, int order, bool playerSpawned) { var connectionManager = SingletonMonoBehaviour.Instance; var gm = GameManager.Instance; if (!playerHiresDict.ContainsKey(hireID)) { hireInfo newHire = new hireInfo(playerID, hireID, name, className, spawnPosition, spawnRotation, reSpawnPosition, reSpawnRotation, numKills, numMine, order, playerSpawned); playerHires.Add(newHire); playerHiresDict[hireID] = newHire; EntityAlive entity = gm.World.GetEntity(hireID) as EntityAlive; if (entity != null) { entity.Buffs.SetCustomVar("$Leader", playerID); } } bool isServer = connectionManager.IsServer; if (isServer) { SaveCurrent(); if (RebirthUtilities.SendToClient(playerID)) { connectionManager.SendPackage( NetPackageManager.GetPackage().Setup( playerID, hireID, name, className, spawnPosition, spawnRotation, reSpawnPosition, reSpawnRotation, numKills, numMine, order, playerSpawned), false, playerID, -1, -1, null, 192); } } else { connectionManager.SendToServer( NetPackageManager.GetPackage().Setup( playerID, hireID, name, className, spawnPosition, spawnRotation, reSpawnPosition, reSpawnRotation, numKills, numMine, order, playerSpawned), false); } } public static void UpdateHireInfo(int hiredID, string action, string value, string position = "", string rotation = "", int newhiredID = -1) { var connectionManager = SingletonMonoBehaviour.Instance; bool isServer = connectionManager.IsServer; int hireCount = playerHires.Count; for (int i = 0; i < hireCount; i++) { var hire = playerHires[i]; int currentHireID = hire.hireID; if (currentHireID == hiredID || currentHireID == newhiredID) { bool changed = false; if (action == "newid") { changed = true; } else if (action == "order") { if (value == "follow") { hire.order = 0; changed = true; } else if (value == "stay") { hire.order = 1; hire.spawnPosition = StringParsers.ParseVector3(position); hire.spawnRotation = StringParsers.ParseVector3(rotation); changed = true; } else if (value == "guard") { hire.order = 2; hire.spawnPosition = StringParsers.ParseVector3(position); hire.spawnRotation = StringParsers.ParseVector3(rotation); changed = true; } } else if (action == "reSpawnPosition") { Vector3 parsedPosition = StringParsers.ParseVector3(position); Vector3 parsedRotation = StringParsers.ParseVector3(rotation); hire.reSpawnPosition = parsedPosition; hire.reSpawnRotation = parsedRotation; hire.spawnPosition = parsedPosition; hire.spawnRotation = parsedRotation; changed = true; } if (changed) { if (isServer) { SaveCurrent(); bool sendToClient = RebirthUtilities.SendToClient(hire.playerID); if (sendToClient) { connectionManager.SendPackage( NetPackageManager.GetPackage().Setup( hire.playerID, hiredID, hire.name, hire.spawnPosition, hire.spawnRotation, hire.reSpawnPosition, hire.reSpawnRotation, hire.numKills, hire.numMine, hire.order, hire.playerSpawned, action, value, newhiredID), false, hire.playerID, -1, -1, null, 192); } } else { connectionManager.SendToServer( NetPackageManager.GetPackage().Setup( hire.playerID, hiredID, hire.name, hire.spawnPosition, hire.spawnRotation, hire.reSpawnPosition, hire.reSpawnRotation, hire.numKills, hire.numMine, hire.order, hire.playerSpawned, action, value, newhiredID), false); } } } } } public static bool CheckHires(int playerID, int hireID) { var gm = GameManager.Instance; EntityPlayer player = gm.World.GetEntity(playerID) as EntityPlayer; if (player == null || !player.IsSpawned()) { return true; } if (playerHiresDict.TryGetValue(hireID, out hireInfo hire) && hire.playerSpawned) { // Additional checks if necessary } return false; } public static void Write(BinaryWriter _bw) { int hireCount = playerHires.Count; for (int i = 0; i < hireCount; i++) { hireInfo hire = playerHires[i]; _bw.Write(hire.playerID); _bw.Write(hire.hireID); _bw.Write(hire.name); _bw.Write(hire.className); _bw.Write(hire.spawnPosition.x); _bw.Write(hire.spawnPosition.y); _bw.Write(hire.spawnPosition.z); _bw.Write(hire.spawnRotation.x); _bw.Write(hire.spawnRotation.y); _bw.Write(hire.spawnRotation.z); _bw.Write(hire.reSpawnPosition.x); _bw.Write(hire.reSpawnPosition.y); _bw.Write(hire.reSpawnPosition.z); _bw.Write(hire.reSpawnRotation.x); _bw.Write(hire.reSpawnRotation.y); _bw.Write(hire.reSpawnRotation.z); _bw.Write(hire.numKills); _bw.Write(hire.numMine); _bw.Write(hire.order); _bw.Write(hire.playerSpawned); } } private static void ReadNewFormat(BinaryReader _br) { while (_br.BaseStream.Position < _br.BaseStream.Length) { hireInfo hire = new hireInfo(); hire.playerID = _br.ReadInt32(); hire.hireID = _br.ReadInt32(); hire.name = _br.ReadString(); hire.className = _br.ReadString(); float sx = _br.ReadSingle(); float sy = _br.ReadSingle(); float sz = _br.ReadSingle(); hire.spawnPosition = new Vector3(sx, sy, sz); float rx = _br.ReadSingle(); float ry = _br.ReadSingle(); float rz = _br.ReadSingle(); hire.spawnRotation = new Vector3(rx, ry, rz); float rsx = _br.ReadSingle(); float rsy = _br.ReadSingle(); float rsz = _br.ReadSingle(); hire.reSpawnPosition = new Vector3(rsx, rsy, rsz); float rrx = _br.ReadSingle(); float rry = _br.ReadSingle(); float rrz = _br.ReadSingle(); hire.reSpawnRotation = new Vector3(rrx, rry, rrz); hire.numKills = _br.ReadInt32(); hire.numMine = _br.ReadInt32(); hire.order = _br.ReadInt32(); hire.playerSpawned = _br.ReadBoolean(); playerHires.Add(hire); } } public static void ReadOldFormat(BinaryReader _br) { while (_br.BaseStream.Position != _br.BaseStream.Length) { hireInfo hire = new hireInfo(); hire.playerID = int.Parse(_br.ReadString()); hire.hireID = int.Parse(_br.ReadString()); hire.name = _br.ReadString(); hire.className = _br.ReadString(); hire.spawnPosition = StringParsers.ParseVector3(_br.ReadString()); hire.spawnRotation = StringParsers.ParseVector3(_br.ReadString()); hire.reSpawnPosition = StringParsers.ParseVector3(_br.ReadString()); hire.reSpawnRotation = StringParsers.ParseVector3(_br.ReadString()); hire.numKills = int.Parse(_br.ReadString()); hire.numMine = int.Parse(_br.ReadString()); hire.order = int.Parse(_br.ReadString()); hire.playerSpawned = _br.ReadBoolean(); playerHires.Add(hire); } } private static int saveDataThreaded(ThreadManager.ThreadInfo _threadInfo) { PooledExpandableMemoryStream pooledExpandableMemoryStream = (PooledExpandableMemoryStream)_threadInfo.parameter; string text = string.Format("{0}/{1}", GameIO.GetSaveGameDir(), saveFile); if (!Directory.Exists(GameIO.GetSaveGameDir())) { Directory.CreateDirectory(GameIO.GetSaveGameDir()); } if (File.Exists(text)) { File.Copy(text, string.Format("{0}/{1}", GameIO.GetSaveGameDir(), $"{saveFile}.bak"), true); } pooledExpandableMemoryStream.Position = 0L; StreamUtils.WriteStreamToFile(pooledExpandableMemoryStream, text); pooledExpandableMemoryStream.Dispose(); return -1; } public static void Save() { WaitOnSave(); PooledExpandableMemoryStream pooledExpandableMemoryStream = MemoryPools.poolMemoryStream.AllocSync(true); using (PooledBinaryWriter pooledBinaryWriter = MemoryPools.poolBinaryWriter.AllocSync(false)) { pooledBinaryWriter.SetBaseStream(pooledExpandableMemoryStream); // Write version number for new format pooledBinaryWriter.Write(1); // version = 1 Write(pooledBinaryWriter); } dataSaveThreadInfo = ThreadManager.StartThread("silent_HiresManagerDataSave", null, new ThreadManager.ThreadFunctionLoopDelegate(saveDataThreaded), null, System.Threading.ThreadPriority.Normal, pooledExpandableMemoryStream, null, false); } public static void Load() { string path = string.Format("{0}/{1}", GameIO.GetSaveGameDir(), saveFile); if (Directory.Exists(GameIO.GetSaveGameDir()) && File.Exists(path)) { try { using (FileStream fileStream = File.OpenRead(path)) { using (PooledBinaryReader pooledBinaryReader = MemoryPools.poolBinaryReader.AllocSync(false)) { pooledBinaryReader.SetBaseStream(fileStream); int version; try { version = pooledBinaryReader.ReadInt32(); } catch { pooledBinaryReader.BaseStream.Position = 0; ReadOldFormat(pooledBinaryReader); RebuildDictionaries(); return; } if (version == 1) { ReadNewFormat(pooledBinaryReader); } else { pooledBinaryReader.BaseStream.Position = 0; ReadOldFormat(pooledBinaryReader); } } } } catch { // Attempt backup string backupPath = string.Format("{0}/{1}", GameIO.GetSaveGameDir(), $"{saveFile}.bak"); if (File.Exists(backupPath)) { using (FileStream fileStream2 = File.OpenRead(backupPath)) { using (PooledBinaryReader pooledBinaryReader2 = MemoryPools.poolBinaryReader.AllocSync(false)) { pooledBinaryReader2.SetBaseStream(fileStream2); try { int version = pooledBinaryReader2.ReadInt32(); if (version == 1) ReadNewFormat(pooledBinaryReader2); else { pooledBinaryReader2.BaseStream.Position = 0; ReadOldFormat(pooledBinaryReader2); } } catch { pooledBinaryReader2.BaseStream.Position = 0; ReadOldFormat(pooledBinaryReader2); } } } } } } RebuildDictionaries(); } private static void WaitOnSave() { if (dataSaveThreadInfo != null) { dataSaveThreadInfo.WaitForEnd(); dataSaveThreadInfo = null; } } public static void Cleanup() { if (instance != null) { SaveAndClear(); } } public static void SaveCurrent() { WaitOnSave(); Save(); WaitOnSave(); } private static void SaveAndClear() { WaitOnSave(); Save(); WaitOnSave(); playerHires.Clear(); playerHiresDict.Clear(); observers.Clear(); observersDict.Clear(); instance = null; } }