worldbox-observer-mod/IdleSpectator/Events/Patches/BuildingEventPatches.cs
DazedAnon 5cdea1930a Enhance combat handling and introduce frequency-based scoring adjustments.
- Implement new commands for marking combat cold and pushing interest arcs
- Update InterestDirector to manage forced cold combat states for peers
- Introduce frequency adjustments for scoring based on recent arc representation
- Refactor scoring weights to include combat urgency and frequency penalties
- Increment version to 0.28.38 in mod.json and scoring-model.json
2026-07-17 19:27:07 -05:00

172 lines
5 KiB
C#

using System;
using ai.behaviours;
using HarmonyLib;
using UnityEngine;
namespace IdleSpectator;
/// <summary>Building damage/destroy/eat/complete interest patches from mutation discovery.</summary>
[HarmonyPatch]
public static class BuildingEventPatches
{
[HarmonyPatch(typeof(BehDealDamageToTargetBuilding), nameof(BehDealDamageToTargetBuilding.execute))]
[HarmonyPostfix]
public static void PostfixDamageBuilding(Actor pActor, BehResult __result)
{
// Routine siege chips are Layer B noise. Camera waits for consume.
_ = pActor;
_ = __result;
}
[HarmonyPatch(typeof(BehConsumeTargetBuilding), nameof(BehConsumeTargetBuilding.execute))]
[HarmonyPostfix]
public static void PostfixConsumeBuilding(Actor pActor, BehResult __result)
{
if (pActor == null || !pActor.isAlive() || __result == BehResult.Stop)
{
return;
}
Building food = null;
try
{
food = pActor.beh_building_target;
}
catch
{
food = null;
}
string label = EventReason.BuildingEat(pActor, food);
bool fruitForage = IsFruitForage(food);
// Fruit bushes are high-rate ambient - fill-band strength so they do not crowd combat/story.
// Real settlement eats (granary/etc.) stay camera-competitive.
float strength = fruitForage ? 42f : 78f;
string category = fruitForage ? "Forage" : "Spectacle";
string assetId = fruitForage ? "building_consume_fruit" : "building_consume";
Emit(assetId, strength, category, pActor, label);
}
[HarmonyPatch(typeof(Building), "startDestroyBuilding")]
[HarmonyPostfix]
public static void PostfixStartDestroy(Building __instance)
{
// Ambient object/building falls are Layer B only.
// Nearest-unit attach + "A Tree falls near X" was winning Watching then blanking via identity_filter.
// Intentional spectacle remains building_consume (actor-led eat).
_ = __instance;
}
/// <summary>
/// Civ construction finished (houses, temples, statues).
/// Nature/tree completes stay silent - not settlement spectacle.
/// </summary>
[HarmonyPatch(typeof(Building), nameof(Building.completeConstruction))]
[HarmonyPostfix]
public static void PostfixCompleteConstruction(Building __instance)
{
if (!AgentHarness.LiveFeedsAllowed || __instance?.asset == null)
{
return;
}
if (!LibraryAssetNames.IsCivBuilding(__instance.asset))
{
return;
}
string buildingId = "";
try
{
buildingId = __instance.asset.id ?? "";
}
catch
{
buildingId = "";
}
bool spectacle = LibraryAssetNames.IsSpectacleBuilding(buildingId, __instance.asset);
float strength = spectacle ? 86f : 72f;
Vector3 pos = BuildingWorldPos(__instance);
Actor follow = WorldActivityScanner.FindNearestAliveUnit(pos, 48f);
string label = EventReason.BuildingComplete(follow, buildingId, spectacle);
string key = "building:complete:"
+ (string.IsNullOrEmpty(buildingId) ? "unknown" : buildingId)
+ ":"
+ EventFeedUtil.SafeId(follow);
EventFeedUtil.Register(
key,
"building",
"Settlement",
label,
string.IsNullOrEmpty(buildingId) ? "building_complete" : buildingId,
strength,
pos,
follow,
locationOnly: follow == null,
minWatch: 6f,
maxWatch: spectacle ? 28f : 18f);
}
private static bool IsFruitForage(Building food)
{
if (food?.asset == null)
{
return false;
}
try
{
string type = food.asset.type ?? "";
return string.Equals(type, "type_fruits", StringComparison.OrdinalIgnoreCase);
}
catch
{
return false;
}
}
private static void Emit(string eventId, float strength, string category, Actor actor, string label)
{
if (!AgentHarness.LiveFeedsAllowed)
{
return;
}
string key = "building:" + eventId + ":" + EventFeedUtil.SafeId(actor);
EventFeedUtil.Register(
key,
"building",
string.IsNullOrEmpty(category) ? "Spectacle" : category,
label,
eventId,
strength,
actor.current_position,
actor);
}
private static Vector3 BuildingWorldPos(Building building)
{
try
{
if (building.current_tile != null)
{
return building.current_tile.posV3;
}
}
catch
{
// fall through
}
try
{
return building.current_position;
}
catch
{
return Vector3.zero;
}
}
}