using System; using System.Collections; using System.Reflection; using UnityEngine; namespace IdleSpectator; /// /// Registers plot interest from Harmony hooks and World.plots polling. /// Active plots with a target kingdom use sticky PlotCell tips; begin/end stay FixedDwell. /// public static class PlotInterestFeed { public static void Emit(string plotAssetId, Actor subject, string phase) { if (!AgentHarness.LiveFeedsAllowed) { return; } DiscreteEventEntry entry = EventCatalog.Plot.GetOrFallback(plotAssetId); if (!EventCatalog.IsCameraWorthy(entry)) { InterestDropLog.Record("createsInterest=false", "plot"); return; } Actor follow = subject; if (follow == null || !follow.isAlive()) { // Plot without a living author: skip rather than invent a stranger. return; } string label = EventReason.Plot(follow, entry.LabelTemplate, phase, entry.Id); string key = "plot:" + entry.Id + ":" + (phase ?? "active") + ":" + EventFeedUtil.SafeId(follow); EventFeedUtil.Register( key, "plot", entry.Category, label, entry.Id, entry.EventStrength, follow.current_position, follow); } public static void EmitFromPlot(object plot, string phase) { if (plot == null || !AgentHarness.LiveFeedsAllowed) { return; } TryRegisterPlot(plot, phase ?? "active"); } public static void Tick() { if (!AgentHarness.LiveFeedsAllowed) { return; } IEnumerable plots = ResolvePlotsEnumerable(); if (plots == null) { return; } int registered = 0; foreach (object plot in plots) { if (plot == null || registered >= 8) { break; } if (TryRegisterPlot(plot, "active")) { registered++; } } } private static bool TryRegisterPlot(object plot, string phase) { string assetId = ReadPlotAssetId(plot); DiscreteEventEntry entry = EventCatalog.Plot.GetOrFallback(assetId); if (!EventCatalog.IsCameraWorthy(entry)) { InterestDropLog.Record("createsInterest=false", "plot:" + (assetId ?? "")); return false; } string phaseKey = string.IsNullOrEmpty(phase) ? "active" : phase; LiveEnsemble ensemble = null; if (plot is Plot typedPlot && phaseKey == "active") { LiveEnsemble.TryBuildPlot(typedPlot, out ensemble); } Actor follow = ensemble?.Focus ?? ReadPlotAuthor(plot); Actor related = ensemble?.Related; if (follow == null || !follow.isAlive()) { return false; } Vector3 position = Vector3.zero; try { position = follow.current_position; } catch { position = Vector3.zero; } string plotId = ""; if (plot is Plot pId) { try { plotId = pId.getID().ToString(); } catch { plotId = ""; } } string key = "plot:" + phaseKey + ":" + (plotId ?? "") + ":" + (entry.Id ?? assetId ?? "plot") + ":" + EventFeedUtil.SafeId(follow); string label; InterestCompletionKind completion = InterestCompletionKind.FixedDwell; float minWatch = 6f; float maxWatch = 28f; string registerAsset = entry.Id; float strength = entry.EventStrength; if (ensemble != null && phaseKey == "active") { label = EventReason.PlotCell(ensemble); if (string.IsNullOrEmpty(label)) { label = EventReason.Ensemble(ensemble); } completion = InterestCompletionKind.PlotActive; registerAsset = "live_plot"; minWatch = 8f; maxWatch = 40f; strength = Mathf.Max(strength, 86f); } else { label = EventReason.Plot(follow, entry.LabelTemplate, phaseKey, entry.Id); } InterestCandidate registered = EventFeedUtil.Register( key, "plot", entry.Category, label, registerAsset, strength, position, follow, related: related, minWatch: minWatch, maxWatch: maxWatch, completion: completion); if (registered == null) { return false; } if (ensemble != null) { registered.CorrelationKey = key; StickyScoreboard.TryLockFromEnsemble(registered.Sticky, ensemble); StickyScoreboard.Refresh( registered, ensemble, position, StickyScoreboard.PlotTheaterRadius); registered.Label = EventReason.PlotCell(ensemble); if (string.IsNullOrEmpty(registered.Label)) { registered.Label = label; } registered.ParticipantCount = ensemble.ParticipantCount; registered.AssetId = "live_plot"; InterestScoring.ScoreCheap(registered); } return true; } private static IEnumerable ResolvePlotsEnumerable() { try { object world = World.world; if (world == null) { return null; } object plots = world.GetType().GetField("plots")?.GetValue(world) ?? world.GetType().GetProperty("plots")?.GetValue(world, null); if (plots is IEnumerable direct) { return direct; } object manager = world.GetType().GetField("plots_manager")?.GetValue(world) ?? world.GetType().GetProperty("plots_manager")?.GetValue(world, null) ?? typeof(PlotManager); if (manager is Type) { manager = null; } object mapBox = MapBox.instance; if (manager == null && mapBox != null) { manager = mapBox.GetType().GetField("plots")?.GetValue(mapBox) ?? mapBox.GetType().GetProperty("plots")?.GetValue(mapBox, null); } if (manager == null) { return null; } MethodInfo getAll = manager.GetType().GetMethod("getSimpleList") ?? manager.GetType().GetMethod("getList") ?? manager.GetType().GetMethod("getPlots"); if (getAll != null) { return getAll.Invoke(manager, null) as IEnumerable; } return manager as IEnumerable; } catch { return null; } } private static string ReadPlotAssetId(object plot) { try { object asset = plot.GetType().GetField("asset")?.GetValue(plot) ?? plot.GetType().GetProperty("asset")?.GetValue(plot, null) ?? plot.GetType().GetMethod("getAsset")?.Invoke(plot, null); if (asset != null) { object id = asset.GetType().GetField("id")?.GetValue(asset) ?? asset.GetType().GetProperty("id")?.GetValue(asset, null); if (id != null) { return id.ToString(); } } object typeId = plot.GetType().GetField("type")?.GetValue(plot) ?? plot.GetType().GetProperty("type")?.GetValue(plot, null); return typeId?.ToString() ?? "unknown"; } catch { return "unknown"; } } private static Actor ReadPlotAuthor(object plot) { try { object author = plot.GetType().GetField("author")?.GetValue(plot) ?? plot.GetType().GetProperty("author")?.GetValue(plot, null) ?? plot.GetType().GetMethod("getAuthor")?.Invoke(plot, null) ?? plot.GetType().GetField("founder")?.GetValue(plot); return author as Actor; } catch { return null; } } }