diff --git a/src/basegame/CSM.BaseGame.csproj b/src/basegame/CSM.BaseGame.csproj
index 1d2e91e9..bc4f165a 100644
--- a/src/basegame/CSM.BaseGame.csproj
+++ b/src/basegame/CSM.BaseGame.csproj
@@ -76,6 +76,7 @@
+
@@ -118,6 +119,7 @@
+
@@ -156,6 +158,7 @@
+
@@ -198,6 +201,7 @@
+
diff --git a/src/basegame/Commands/Data/Buildings/BuildingSetVariationCommand.cs b/src/basegame/Commands/Data/Buildings/BuildingSetVariationCommand.cs
new file mode 100644
index 00000000..28906084
--- /dev/null
+++ b/src/basegame/Commands/Data/Buildings/BuildingSetVariationCommand.cs
@@ -0,0 +1,26 @@
+using CSM.API.Commands;
+using ProtoBuf;
+
+namespace CSM.BaseGame.Commands.Data.Buildings
+{
+ ///
+ /// Called when the building variation was changed.
+ ///
+ /// Sent by:
+ /// - BuildingHandler
+ [ProtoContract]
+ public class BuildingSetVariationCommand : CommandBase
+ {
+ ///
+ /// The id of the modified building.
+ ///
+ [ProtoMember(1)]
+ public ushort Building { get; set; }
+
+ ///
+ /// The new variation flags.
+ ///
+ [ProtoMember(2)]
+ public Building.Flags2 Variation { get; set; }
+ }
+}
diff --git a/src/basegame/Commands/Data/Roads/RoadAdjustCommand.cs b/src/basegame/Commands/Data/Roads/RoadAdjustCommand.cs
new file mode 100644
index 00000000..256a8e2b
--- /dev/null
+++ b/src/basegame/Commands/Data/Roads/RoadAdjustCommand.cs
@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+using CSM.API.Commands;
+using ProtoBuf;
+
+namespace CSM.BaseGame.Commands.Data.Roads
+{
+ ///
+ /// Sent when the player adjusts the extents of a road through the route menu.
+ ///
+ /// Sent by: RoadHandler
+ [ProtoContract]
+ public class RoadAdjustCommand : CommandBase
+ {
+ ///
+ /// The segments previously belonging to the edited road.
+ ///
+ [ProtoMember(1)]
+ public HashSet OriginalSegments { get; set; }
+
+ ///
+ /// The segments now belonging to the edited road.
+ ///
+ [ProtoMember(2)]
+ public HashSet IncludedSegments { get; set; }
+
+ ///
+ /// The selected road segment instance.
+ ///
+ [ProtoMember(3)]
+ public InstanceID LastInstance { get; set; }
+ }
+}
diff --git a/src/basegame/Commands/Handler/Buildings/BuildingSetVariationHandler.cs b/src/basegame/Commands/Handler/Buildings/BuildingSetVariationHandler.cs
new file mode 100644
index 00000000..016a5a05
--- /dev/null
+++ b/src/basegame/Commands/Handler/Buildings/BuildingSetVariationHandler.cs
@@ -0,0 +1,21 @@
+using ColossalFramework;
+using CSM.API.Commands;
+using CSM.API.Helpers;
+using CSM.BaseGame.Commands.Data.Buildings;
+
+namespace CSM.BaseGame.Commands.Handler.Buildings
+{
+ public class BuildingSetVariationHandler : CommandHandler
+ {
+ protected override void Handle(BuildingSetVariationCommand command)
+ {
+ IgnoreHelper.Instance.StartIgnore();
+
+ CommonBuildingAI building_ai = Singleton.instance.m_buildings.m_buffer[command.Building].Info.m_buildingAI as CommonBuildingAI;
+ if (building_ai != null)
+ building_ai.ReplaceVariation(command.Building, command.Variation);
+
+ IgnoreHelper.Instance.EndIgnore();
+ }
+ }
+}
diff --git a/src/basegame/Commands/Handler/Roads/RoadAdjustHandler.cs b/src/basegame/Commands/Handler/Roads/RoadAdjustHandler.cs
new file mode 100644
index 00000000..466fc81a
--- /dev/null
+++ b/src/basegame/Commands/Handler/Roads/RoadAdjustHandler.cs
@@ -0,0 +1,42 @@
+using System.Collections.Generic;
+using CSM.API.Commands;
+using CSM.API.Helpers;
+using CSM.BaseGame.Commands.Data.Roads;
+
+namespace CSM.BaseGame.Commands.Handler.Roads
+{
+ public class RoadAdjustHandler : CommandHandler
+ {
+ private NetAdjust _adjustDummy;
+
+ protected override void Handle(RoadAdjustCommand command)
+ {
+ IgnoreHelper.Instance.StartIgnore();
+
+ if (_adjustDummy == null)
+ {
+ SetupAdjustDummy();
+ }
+
+ ReflectionHelper.SetAttr(_adjustDummy, "m_originalSegments", command.OriginalSegments);
+ ReflectionHelper.SetAttr(_adjustDummy, "m_includedSegments", command.IncludedSegments);
+ ReflectionHelper.SetAttr(_adjustDummy, "m_lastInstance", command.LastInstance);
+ ReflectionHelper.SetAttr(_adjustDummy, "m_tempAdjustmentIndex", 0);
+
+ _adjustDummy.ApplyModification(0);
+
+ IgnoreHelper.Instance.EndIgnore();
+ }
+
+ private void SetupAdjustDummy()
+ {
+ _adjustDummy = new NetAdjust();
+ ReflectionHelper.SetAttr(_adjustDummy, "m_pathVisible", true);
+ ReflectionHelper.SetAttr(_adjustDummy, "m_startPath", new FastList());
+ ReflectionHelper.SetAttr(_adjustDummy, "m_endPath", new FastList());
+ ReflectionHelper.SetAttr(_adjustDummy, "m_tempPath", new FastList());
+ ReflectionHelper.SetAttr(_adjustDummy, "m_segmentQueue", new FastList());
+ ReflectionHelper.SetAttr(_adjustDummy, "m_segmentData", new Dictionary());
+ }
+ }
+}
diff --git a/src/basegame/Injections/BuildingHandler.cs b/src/basegame/Injections/BuildingHandler.cs
index e1807b03..e8637275 100644
--- a/src/basegame/Injections/BuildingHandler.cs
+++ b/src/basegame/Injections/BuildingHandler.cs
@@ -423,4 +423,28 @@ public static void Postfix(ushort buildingId, VehicleInfo info)
});
}
}
+
+ [HarmonyPatch(typeof(CommonBuildingAI))]
+ [HarmonyPatch("ReplaceVariation")]
+ public class ReplaceVariation
+ {
+ public static void Prefix(ushort buildingID, Building.Flags2 variation)
+ {
+ if (IgnoreHelper.Instance.IsIgnored())
+ {
+ return;
+ }
+
+ if ((variation & Building.Flags2.SubmeshVariation) == Building.Flags2.None)
+ {
+ return;
+ }
+
+ Command.SendToAll(new BuildingSetVariationCommand
+ {
+ Building = buildingID,
+ Variation = variation,
+ });
+ }
+ }
}
diff --git a/src/basegame/Injections/RoadHandler.cs b/src/basegame/Injections/RoadHandler.cs
index 76329ba7..eb8077ab 100644
--- a/src/basegame/Injections/RoadHandler.cs
+++ b/src/basegame/Injections/RoadHandler.cs
@@ -86,4 +86,27 @@ public static MethodBase TargetMethod()
return ReflectionHelper.GetIteratorTargetMethod(typeof(NetManager), "c__Iterator3", out Type _);
}
}
+
+ [HarmonyPatch(typeof(NetAdjust))]
+ [HarmonyPatch("ApplyModification")]
+ public class ApplyModification
+ {
+ public static void Prefix(int index, bool ___m_pathVisible, int ___m_tempAdjustmentIndex,
+ HashSet ___m_originalSegments, HashSet ___m_includedSegments,
+ InstanceID ___m_lastInstance)
+ {
+ if (IgnoreHelper.Instance.IsIgnored())
+ return;
+
+ if (!___m_pathVisible || ___m_tempAdjustmentIndex != index)
+ return;
+
+ Command.SendToAll(new RoadAdjustCommand()
+ {
+ OriginalSegments = ___m_originalSegments,
+ IncludedSegments = ___m_includedSegments,
+ LastInstance = ___m_lastInstance,
+ });
+ }
+ }
}