sex
This commit is contained in:
@@ -47,6 +47,7 @@ namespace KCM.Enums
|
|||||||
PlaceKeepRandomly = 91,
|
PlaceKeepRandomly = 91,
|
||||||
ResyncRequest = 92,
|
ResyncRequest = 92,
|
||||||
ResourceSnapshot = 93,
|
ResourceSnapshot = 93,
|
||||||
BuildingSnapshot = 94
|
BuildingSnapshot = 94,
|
||||||
|
SpeedRestoreRequest = 95
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
279
Main.cs
279
Main.cs
@@ -59,6 +59,11 @@ namespace KCM
|
|||||||
private static int resetInProgress = 0;
|
private static int resetInProgress = 0;
|
||||||
private static int multiplayerSaveLoadInProgress = 0;
|
private static int multiplayerSaveLoadInProgress = 0;
|
||||||
private static int suppressVillagerTeleportPackets = 0;
|
private static int suppressVillagerTeleportPackets = 0;
|
||||||
|
private static int lastAppliedSpeedIndex = 0;
|
||||||
|
private static long lastSimWatchdogFixMs = 0;
|
||||||
|
private static int lastNonZeroSpeedIndex = 1;
|
||||||
|
private static int forceAllowClientLocalSpeedSetOnce = 0;
|
||||||
|
private static long lastSimStateLogMs = 0;
|
||||||
|
|
||||||
public static bool IsMultiplayerSaveLoadInProgress
|
public static bool IsMultiplayerSaveLoadInProgress
|
||||||
{
|
{
|
||||||
@@ -80,6 +85,143 @@ namespace KCM
|
|||||||
Interlocked.Exchange(ref suppressVillagerTeleportPackets, suppress ? 1 : 0);
|
Interlocked.Exchange(ref suppressVillagerTeleportPackets, suppress ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int LastAppliedSpeedIndex
|
||||||
|
{
|
||||||
|
get { return Volatile.Read(ref lastAppliedSpeedIndex); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int LastNonZeroSpeedIndex
|
||||||
|
{
|
||||||
|
get { return Volatile.Read(ref lastNonZeroSpeedIndex); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetLastAppliedSpeedIndex(int idx)
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref lastAppliedSpeedIndex, idx);
|
||||||
|
if (idx > 0)
|
||||||
|
Interlocked.Exchange(ref lastNonZeroSpeedIndex, idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ForceAllowClientLocalSpeedOnce()
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref forceAllowClientLocalSpeedSetOnce, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ConsumeForceAllowClientLocalSpeedOnce()
|
||||||
|
{
|
||||||
|
return Interlocked.Exchange(ref forceAllowClientLocalSpeedSetOnce, 0) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int TryGetLoadTickDelay(object instance)
|
||||||
|
{
|
||||||
|
if (instance == null)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FieldInfo loadTickDelayField = instance.GetType().GetField("loadTickDelay", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
if (loadTickDelayField != null && loadTickDelayField.FieldType == typeof(int))
|
||||||
|
return (int)loadTickDelayField.GetValue(instance);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void LogSimState(string reason)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
long now = DateTimeOffset.Now.ToUnixTimeMilliseconds();
|
||||||
|
if ((now - lastSimStateLogMs) < 1000)
|
||||||
|
return;
|
||||||
|
|
||||||
|
lastSimStateLogMs = now;
|
||||||
|
|
||||||
|
bool connected = false;
|
||||||
|
try { connected = KCClient.client != null && KCClient.client.IsConnected; } catch { }
|
||||||
|
|
||||||
|
bool focused = true;
|
||||||
|
try { focused = Application.isFocused; } catch { }
|
||||||
|
|
||||||
|
helper?.Log($"SimState ({(reason ?? string.Empty)}): menu={(int)menuState} server={KCServer.IsRunning} connected={connected} focused={focused} timeScale={Time.timeScale:0.###} speedLast={LastAppliedSpeedIndex} speedNonZero={LastNonZeroSpeedIndex}");
|
||||||
|
helper?.Log($"SimState systems: Player={(Player.inst != null)} UnitSys={(UnitSystem.inst != null)} JobSys={(JobSystem.inst != null)} VillagerSys={(VillagerSystem.inst != null)}");
|
||||||
|
|
||||||
|
try { helper?.Log($"SimState enabled: UnitSys={(UnitSystem.inst != null && UnitSystem.inst.enabled)} JobSys={(JobSystem.inst != null && JobSystem.inst.enabled)} VillagerSys={(VillagerSystem.inst != null && VillagerSystem.inst.enabled)}"); } catch { }
|
||||||
|
try { helper?.Log($"SimState loadTickDelay: Player={TryGetLoadTickDelay(Player.inst)} UnitSys={TryGetLoadTickDelay(UnitSystem.inst)} JobSys={TryGetLoadTickDelay(JobSystem.inst)} VillagerSys={TryGetLoadTickDelay(VillagerSystem.inst)}"); } catch { }
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int playerWorkers = Player.inst != null ? Player.inst.Workers.Count : -1;
|
||||||
|
int allVillagers = Villager.villagers != null ? Villager.villagers.Count : -1;
|
||||||
|
helper?.Log($"SimState villagers: playerWorkers={playerWorkers} allVillagers={allVillagers}");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TryForceSetSpeed(int speedIdx, string reason)
|
||||||
|
{
|
||||||
|
if (speedIdx <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (SpeedControlUI.inst == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!KCServer.IsRunning)
|
||||||
|
ForceAllowClientLocalSpeedOnce();
|
||||||
|
|
||||||
|
helper?.Log($"ForceSetSpeed: {speedIdx} ({reason ?? string.Empty})");
|
||||||
|
SpeedControlUI.inst.SetSpeed(speedIdx);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
helper?.Log("ForceSetSpeed failed");
|
||||||
|
helper?.Log(ex.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DumpSimState(string reason)
|
||||||
|
{
|
||||||
|
LogSimState(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Unstuck(string reason)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool connected = KCClient.client != null && KCClient.client.IsConnected;
|
||||||
|
if (!connected && !KCServer.IsRunning)
|
||||||
|
return;
|
||||||
|
|
||||||
|
LogSimState("unstuck:" + (reason ?? string.Empty));
|
||||||
|
|
||||||
|
if (!KCServer.IsRunning)
|
||||||
|
{
|
||||||
|
try { new KCM.Packets.Network.SpeedRestoreRequestPacket { reason = reason ?? string.Empty }.Send(); } catch { }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int speed = LastNonZeroSpeedIndex > 0 ? LastNonZeroSpeedIndex : 1;
|
||||||
|
TryForceSetSpeed(speed, "unstuck:" + (reason ?? string.Empty));
|
||||||
|
}
|
||||||
|
|
||||||
|
try { RunPostLoadRebuild("unstuck:" + (reason ?? string.Empty)); } catch { }
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void ResetMultiplayerState(string reason = null)
|
public static void ResetMultiplayerState(string reason = null)
|
||||||
{
|
{
|
||||||
if (Interlocked.Exchange(ref resetInProgress, 1) == 1)
|
if (Interlocked.Exchange(ref resetInProgress, 1) == 1)
|
||||||
@@ -446,6 +588,123 @@ namespace KCM
|
|||||||
|
|
||||||
public static int FixedUpdateInterval = 0;
|
public static int FixedUpdateInterval = 0;
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool connected = KCClient.client != null && KCClient.client.IsConnected;
|
||||||
|
if (!connected && !KCServer.IsRunning)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Input.GetKeyDown(KeyCode.F8))
|
||||||
|
LogSimState("hotkey:F8");
|
||||||
|
|
||||||
|
if (Input.GetKeyDown(KeyCode.F9) && connected)
|
||||||
|
{
|
||||||
|
if (!KCServer.IsRunning)
|
||||||
|
{
|
||||||
|
try { new KCM.Packets.Network.SpeedRestoreRequestPacket { reason = "hotkey:F9" }.Send(); } catch { }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TryForceSetSpeed(LastNonZeroSpeedIndex > 0 ? LastNonZeroSpeedIndex : 1, "hotkey:F9");
|
||||||
|
}
|
||||||
|
|
||||||
|
try { RunPostLoadRebuild("unstuck:hotkey:F9"); } catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only apply watchdog behavior in playing mode.
|
||||||
|
if ((int)menuState != 200)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If something pauses the game by setting Time.timeScale without going through SpeedControlUI,
|
||||||
|
// villagers + events will appear "stuck". We reapply the last known speed.
|
||||||
|
if (LastAppliedSpeedIndex > 0 && Time.timeScale == 0f)
|
||||||
|
{
|
||||||
|
long now = DateTimeOffset.Now.ToUnixTimeMilliseconds();
|
||||||
|
if ((now - lastSimWatchdogFixMs) >= 2000)
|
||||||
|
{
|
||||||
|
lastSimWatchdogFixMs = now;
|
||||||
|
|
||||||
|
LogSimState("watchdog:timescale0");
|
||||||
|
TryForceSetSpeed(LastAppliedSpeedIndex, "watchdog:timescale0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Best-effort recovery if core simulation systems are disabled after load.
|
||||||
|
try { if (UnitSystem.inst != null && !UnitSystem.inst.enabled) UnitSystem.inst.enabled = true; } catch { }
|
||||||
|
try { if (JobSystem.inst != null && !JobSystem.inst.enabled) JobSystem.inst.enabled = true; } catch { }
|
||||||
|
try { if (VillagerSystem.inst != null && !VillagerSystem.inst.enabled) VillagerSystem.inst.enabled = true; } catch { }
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnApplicationFocus(bool hasFocus)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!hasFocus)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool connected = KCClient.client != null && KCClient.client.IsConnected;
|
||||||
|
if (!connected && !KCServer.IsRunning)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ((int)menuState != 200)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (Time.timeScale != 0f)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int speed = LastAppliedSpeedIndex > 0 ? LastAppliedSpeedIndex : LastNonZeroSpeedIndex;
|
||||||
|
if (speed <= 0)
|
||||||
|
speed = 1;
|
||||||
|
|
||||||
|
LogSimState("focus-gained");
|
||||||
|
TryForceSetSpeed(speed, "focus-gained");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnApplicationPause(bool pauseStatus)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (pauseStatus)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool connected = KCClient.client != null && KCClient.client.IsConnected;
|
||||||
|
if (!connected && !KCServer.IsRunning)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ((int)menuState != 200)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (Time.timeScale != 0f)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int speed = LastAppliedSpeedIndex > 0 ? LastAppliedSpeedIndex : LastNonZeroSpeedIndex;
|
||||||
|
if (speed <= 0)
|
||||||
|
speed = 1;
|
||||||
|
|
||||||
|
LogSimState("unpaused");
|
||||||
|
TryForceSetSpeed(speed, "unpaused");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void FixedUpdate()
|
private void FixedUpdate()
|
||||||
{
|
{
|
||||||
// send batched building placement info
|
// send batched building placement info
|
||||||
@@ -948,6 +1207,15 @@ namespace KCM
|
|||||||
{
|
{
|
||||||
if (KCClient.client.IsConnected)
|
if (KCClient.client.IsConnected)
|
||||||
{
|
{
|
||||||
|
// Only override AddBuilding for remote "Client Player" instances.
|
||||||
|
// The vanilla implementation does important registration work for the local player (jobs, systems, etc).
|
||||||
|
// When loading/unpacking for a remote player we temporarily set Player.inst to that player, so vanilla is safe.
|
||||||
|
if (__instance == null || __instance == Player.inst)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (__instance.gameObject == null || __instance.gameObject.name == null || !__instance.gameObject.name.Contains("Client Player"))
|
||||||
|
return true;
|
||||||
|
|
||||||
__instance.Buildings.Add(b);
|
__instance.Buildings.Add(b);
|
||||||
IResourceStorage[] storages = b.GetComponents<IResourceStorage>();
|
IResourceStorage[] storages = b.GetComponents<IResourceStorage>();
|
||||||
for (int i = 0; i < storages.Length; i++)
|
for (int i = 0; i < storages.Length; i++)
|
||||||
@@ -1262,7 +1530,16 @@ namespace KCM
|
|||||||
if (!KCServer.IsRunning)
|
if (!KCServer.IsRunning)
|
||||||
{
|
{
|
||||||
if (calledFromPacket)
|
if (calledFromPacket)
|
||||||
|
{
|
||||||
|
SetLastAppliedSpeedIndex(idx);
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idx > 0 && ConsumeForceAllowClientLocalSpeedOnce())
|
||||||
|
{
|
||||||
|
SetLastAppliedSpeedIndex(idx);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
long now = DateTimeOffset.Now.ToUnixTimeMilliseconds();
|
long now = DateTimeOffset.Now.ToUnixTimeMilliseconds();
|
||||||
if ((now - lastClientBlockLogTime) >= 2000)
|
if ((now - lastClientBlockLogTime) >= 2000)
|
||||||
@@ -1274,6 +1551,8 @@ namespace KCM
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SetLastAppliedSpeedIndex(idx);
|
||||||
|
|
||||||
if (!calledFromPacket)
|
if (!calledFromPacket)
|
||||||
{
|
{
|
||||||
long now = DateTimeOffset.Now.ToUnixTimeMilliseconds();
|
long now = DateTimeOffset.Now.ToUnixTimeMilliseconds();
|
||||||
|
|||||||
39
Packets/Network/SpeedRestoreRequestPacket.cs
Normal file
39
Packets/Network/SpeedRestoreRequestPacket.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using KCM.Attributes;
|
||||||
|
using KCM.Packets.Game;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace KCM.Packets.Network
|
||||||
|
{
|
||||||
|
[NoServerRelay]
|
||||||
|
public class SpeedRestoreRequestPacket : Packet
|
||||||
|
{
|
||||||
|
public override ushort packetId => (ushort)Enums.Packets.SpeedRestoreRequest;
|
||||||
|
|
||||||
|
public string reason { get; set; }
|
||||||
|
|
||||||
|
public override void HandlePacketClient()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void HandlePacketServer()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!KCServer.IsRunning)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int speed = Main.LastNonZeroSpeedIndex;
|
||||||
|
if (speed <= 0)
|
||||||
|
speed = 1;
|
||||||
|
|
||||||
|
Main.helper.Log($"Speed restore requested by client {clientId} ({reason ?? string.Empty}); broadcasting speed {speed}");
|
||||||
|
new SetSpeed { speed = speed }.SendToAll();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Main.helper.Log("Error handling SpeedRestoreRequestPacket on server");
|
||||||
|
Main.helper.Log(ex.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -219,6 +219,36 @@ namespace KCM
|
|||||||
{
|
{
|
||||||
if (ChatInput.text.Length > 0)
|
if (ChatInput.text.Length > 0)
|
||||||
{
|
{
|
||||||
|
if (ChatInput.text.Trim().Equals("/simdebug", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Main.DumpSimState("chat:/simdebug");
|
||||||
|
LobbyHandler.AddSystemMessage("Sim state logged to output.txt");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatInput.text = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ChatInput.text.Trim().Equals("/unstuck", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Main.Unstuck("chat:/unstuck");
|
||||||
|
LobbyHandler.AddSystemMessage("Unstuck requested.");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatInput.text = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (ChatInput.text.Trim().Equals("/resync", StringComparison.OrdinalIgnoreCase))
|
if (ChatInput.text.Trim().Equals("/resync", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using UnityEngine;
|
||||||
using static KCM.StateManagement.Observers.Observer;
|
using static KCM.StateManagement.Observers.Observer;
|
||||||
|
|
||||||
namespace KCM.StateManagement.BuildingState
|
namespace KCM.StateManagement.BuildingState
|
||||||
|
|||||||
Reference in New Issue
Block a user