This commit is contained in:
2025-12-13 21:46:58 +01:00
parent e636ad6e19
commit dc50bf2892
4 changed files with 96 additions and 31 deletions

View File

@@ -102,6 +102,7 @@ namespace KCM
public static void Connect(string ip) public static void Connect(string ip)
{ {
Main.helper.Log("Trying to connect to: " + ip); Main.helper.Log("Trying to connect to: " + ip);
try { Application.runInBackground = true; } catch { }
client.Connect(ip, useMessageHandlers: false); client.Connect(ip, useMessageHandlers: false);
} }

44
Main.cs
View File

@@ -942,26 +942,14 @@ namespace KCM
[HarmonyPatch(typeof(Player), "AddBuilding")] [HarmonyPatch(typeof(Player), "AddBuilding")]
public class PlayerAddBuildingHook 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) public static bool Prefix(Player __instance, Building b)
{ {
try try
{ {
if (KCClient.client.IsConnected) if (KCClient.client.IsConnected)
{ {
LogStep(true);
__instance.Buildings.Add(b); __instance.Buildings.Add(b);
IResourceStorage[] storages = b.GetComponents<IResourceStorage>(); IResourceStorage[] storages = b.GetComponents<IResourceStorage>();
LogStep();
for (int i = 0; i < storages.Length; i++) for (int i = 0; i < storages.Length; i++)
{ {
bool flag = !storages[i].IsPrivate(); bool flag = !storages[i].IsPrivate();
@@ -970,50 +958,38 @@ namespace KCM
FreeResourceManager.inst.AddResourceStorage(storages[i]); FreeResourceManager.inst.AddResourceStorage(storages[i]);
} }
} }
LogStep();
int landMass = b.LandMass(); int landMass = b.LandMass();
Home res = b.GetComponent<Home>(); Home res = b.GetComponent<Home>();
bool flag2 = res != null; bool flag2 = res != null;
LogStep();
if (flag2) if (flag2)
{ {
__instance.Residentials.Add(res); __instance.Residentials.Add(res);
__instance.ResidentialsPerLandmass[landMass].Add(res); __instance.ResidentialsPerLandmass[landMass].Add(res);
} }
WagePayer wagePayer = b.GetComponent<WagePayer>(); WagePayer wagePayer = b.GetComponent<WagePayer>();
LogStep();
bool flag3 = wagePayer != null; bool flag3 = wagePayer != null;
if (flag3) if (flag3)
{ {
__instance.WagePayers.Add(wagePayer); __instance.WagePayers.Add(wagePayer);
} }
RadiusBonus radiusBonus = b.GetComponent<RadiusBonus>(); RadiusBonus radiusBonus = b.GetComponent<RadiusBonus>();
LogStep();
bool flag4 = radiusBonus != null; bool flag4 = radiusBonus != null;
if (flag4) if (flag4)
{ {
__instance.RadiusBonuses.Add(radiusBonus); __instance.RadiusBonuses.Add(radiusBonus);
} }
LogStep();
var globalBuildingRegistry = __instance.GetType().GetField("globalBuildingRegistry", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(__instance) as ArrayExt<Player.BuildingRegistry>; 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>; 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>>; var unbuiltBuildingsPerLandmass = __instance.GetType().GetField("unbuiltBuildingsPerLandmass", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(__instance) as ArrayExt<ArrayExt<Building>>;
LogStep();
__instance.AddToRegistry(globalBuildingRegistry, b); __instance.AddToRegistry(globalBuildingRegistry, b);
LogStep();
__instance.AddToRegistry(landMassBuildingRegistry.data[landMass].registry, b); __instance.AddToRegistry(landMassBuildingRegistry.data[landMass].registry, b);
LogStep();
landMassBuildingRegistry.data[landMass].buildings.Add(b); landMassBuildingRegistry.data[landMass].buildings.Add(b);
LogStep();
bool flag5 = !b.IsBuilt(); bool flag5 = !b.IsBuilt();
if (flag5) if (flag5)
{ {
unbuiltBuildingsPerLandmass.data[landMass].Add(b); unbuiltBuildingsPerLandmass.data[landMass].Add(b);
} }
LogStep();
return false; return false;
@@ -1270,6 +1246,8 @@ namespace KCM
{ {
private static long lastTime = 0; private static long lastTime = 0;
private static long lastClientBlockLogTime = 0; private static long lastClientBlockLogTime = 0;
private static long lastHostPauseTraceLogTime = 0;
private static int lastSentSpeed = -1;
public static bool Prefix(int idx, ref bool __state) public static bool Prefix(int idx, ref bool __state)
{ {
@@ -1299,8 +1277,23 @@ namespace KCM
if (!calledFromPacket) if (!calledFromPacket)
{ {
long now = DateTimeOffset.Now.ToUnixTimeMilliseconds(); long now = DateTimeOffset.Now.ToUnixTimeMilliseconds();
if ((now - lastTime) >= 250) // Set speed spam fix / hack // Ensure that real speed changes are always propagated, even if they happen in quick succession (eg. pause/unpause).
if (idx != lastSentSpeed || (now - lastTime) >= 250) // Set speed spam fix / hack
__state = true; __state = true;
// Diagnostics for "random pause": log a stack trace when the host hits speed 0 from local code.
if (idx == 0 && (now - lastHostPauseTraceLogTime) >= 2000)
{
lastHostPauseTraceLogTime = now;
try
{
Main.helper.Log("Host speed set to 0 (pause). Call stack:");
Main.helper.Log(new StackTrace(2, false).ToString());
}
catch
{
}
}
} }
return true; return true;
@@ -1320,6 +1313,7 @@ namespace KCM
}.Send(); }.Send();
lastTime = DateTimeOffset.Now.ToUnixTimeMilliseconds(); lastTime = DateTimeOffset.Now.ToUnixTimeMilliseconds();
lastSentSpeed = idx;
} }
} }
} }

View File

@@ -22,27 +22,72 @@ namespace KCM.StateManagement.BuildingState
{ {
try try
{ {
Observer observer = (Observer)sender; Observer observer = sender as Observer;
if (observer == null)
return;
Building building = (Building)observer.state; Building building = observer.state as Building;
if (building == null)
return;
//Main.helper.Log("Should send building network update for: " + building.UniqueName); //Main.helper.Log("Should send building network update for: " + building.UniqueName);
var t = building.transform;
if (t == null)
return;
Quaternion rotation = t.rotation;
Vector3 globalPosition = t.position;
Vector3 localPosition = t.localPosition;
if (t.childCount > 0)
{
try
{
var child = t.GetChild(0);
if (child != null)
{
rotation = child.rotation;
localPosition = child.localPosition;
}
}
catch
{
}
}
float resourceProgress = 0f;
try
{
var field = building.GetType().GetField("resourceProgress", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (field != null)
{
object value = field.GetValue(building);
if (value is float)
resourceProgress = (float)value;
else if (value != null)
resourceProgress = Convert.ToSingle(value);
}
}
catch
{
}
new BuildingStatePacket() new BuildingStatePacket()
{ {
customName = building.customName, customName = building.customName,
guid = building.guid, guid = building.guid,
uniqueName = building.UniqueName, uniqueName = building.UniqueName,
rotation = building.transform.GetChild(0).rotation, rotation = rotation,
globalPosition = building.transform.position, globalPosition = globalPosition,
localPosition = building.transform.GetChild(0).localPosition, localPosition = localPosition,
built = building.IsBuilt(), built = building.IsBuilt(),
placed = building.IsPlaced(), placed = building.IsPlaced(),
open = building.Open, open = building.Open,
doBuildAnimation = building.doBuildAnimation, doBuildAnimation = building.doBuildAnimation,
constructionPaused = building.constructionPaused, constructionPaused = building.constructionPaused,
constructionProgress = building.constructionProgress, constructionProgress = building.constructionProgress,
resourceProgress = (float)building.GetType().GetField("resourceProgress", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(building), resourceProgress = resourceProgress,
life = building.Life, life = building.Life,
ModifiedMaxLife = building.ModifiedMaxLife, ModifiedMaxLife = building.ModifiedMaxLife,
yearBuilt = building.YearBuilt, yearBuilt = building.YearBuilt,

View File

@@ -128,6 +128,31 @@ namespace KCM.StateManagement.Observers
if (this.state == null) if (this.state == null)
return; return;
// Unity uses "fake null" for destroyed objects. Since our state is stored as object,
// we must explicitly detect that case to avoid exceptions + log spam.
try
{
UnityEngine.Object unityObj = this.state as UnityEngine.Object;
if (this.state is UnityEngine.Object && unityObj == null)
{
try { StateObserver.observers.Remove(this.state.GetHashCode()); } catch { }
try
{
if (observerObject != null)
UnityEngine.Object.Destroy(observerObject);
else
UnityEngine.Object.Destroy(this.gameObject);
}
catch
{
}
return;
}
}
catch
{
}
if (!(currentMs - lastUpdate > updateInterval)) // Don't run if the update interval hasn't passed (default 100 milliseconds); if (!(currentMs - lastUpdate > updateInterval)) // Don't run if the update interval hasn't passed (default 100 milliseconds);
return; return;