A small example how to replace the AI of a custom building asset on startup:
In this example the AI extends from PlayerBuildingAI, that means it has basic stats like a construction cost, it can catch fire etc. But of course you can also extend from any other AI (BuildingAI or CommonBuildingAI if you want to start from scratch).
The AI in this example displays a counter value in the building info window. The counter is increased after SimulationStep is called 16 times (SimulationStep is called about once every second on normal speed).
The mod expects a local building asset with the file name "thing" and asset name "Thing".
The AI is replaced before the prefab is initialized. That means there should be no bad side effects.
The mod requires Harmony. Just place 0Harmony.dll in the same folder as the mod dll.
It is not compatible with Loading Screen Mod. If you want to support LSM and LSM Test, take a look at the source code of Custom Animation Loader.
using ColossalFramework.Packaging;
using Harmony;
using ICities;
using UnityEngine;
namespace AiReplace
{
public class AiReplaceMod : IUserMod
{
private const string HarmonyId = "boformer.AiReplace";
private HarmonyInstance _harmony;
public string Name => "AI Replace";
public string Description => "Replaces AI";
public void OnEnabled()
{
if (_harmony != null) return;
Debug.Log("AiReplace Patching...");
_harmony = HarmonyInstance.Create(HarmonyId);
_harmony.PatchAll(GetType().Assembly);
}
public void OnDisabled()
{
_harmony.UnpatchAll(HarmonyId);
_harmony = null;
Debug.Log("AiReplace Reverted...");
}
}
public class MyBuildingAI : PlayerBuildingAI
{
public override string GetLocalizedStats(ushort buildingID, ref Building data)
{
var counter = data.m_customBuffer1;
return $"Counter: {counter}";
}
public override void SimulationStep(ushort buildingID, ref Building data)
{
Debug.Log("SimulationStep!");
if ((SimulationManager.instance.m_currentFrameIndex & 0xFFF) >= 0xF00)
{
Debug.Log("SimulationStep was called 16 times!");
data.m_customBuffer1++;
}
base.SimulationStep(buildingID, ref data);
}
}
// Vanilla Asset Loader
[HarmonyPatch(typeof(PackageDeserializer), "DeserializeGameObject")]
public static class PackageAssetPatch
{
public static void Postfix(Package package, GameObject __result)
{
var prefab = __result.GetComponent<BuildingInfo>();
if (prefab == null) return;
Debug.Log($"Instantiate {package.packageName} {prefab.name}");
// packageName is file name of the .crp file (for local .crp files)
// prefab name is the "Asset Name" you set in Asset Editor + "_Data"
if (package.packageName == "thing" && prefab.name == "Thing_Data")
{
var oldAi = prefab.gameObject.GetComponent<BuildingAI>();
UnityEngine.Object.DestroyImmediate(oldAi);
var newAi = prefab.gameObject.AddComponent<MyBuildingAI>();
Debug.Log($"AI replaced!");
}
}
}
}