using System;
using System.IO;
using HarmonyLib;
using NeoModLoader.api;
using NeoModLoader.services;
using UnityEngine;
namespace IdleSpectator;
///
/// Idle Spectator entry: I key AFK camera director over WorldLog events.
///
public class ModClass : MonoBehaviour, IMod, IConfigurable, ILocalizable
{
private ModDeclare _declare;
private GameObject _gameObject;
private Harmony _harmony;
private ModConfig _config;
/// Absolute path to this mod's folder (parent of mod.json).
public static string ModFolder { get; private set; }
public static ModClass Instance { get; private set; }
public ModDeclare GetDeclaration()
{
return _declare;
}
public GameObject GetGameObject()
{
return _gameObject;
}
public string GetUrl()
{
return "";
}
public ModConfig GetConfig()
{
return _config;
}
public string GetLocaleFilesDirectory(ModDeclare pModDeclare)
{
return Path.Combine(pModDeclare.FolderPath, "Locales");
}
public void OnLoad(ModDeclare pModDecl, GameObject pGameObject)
{
Instance = this;
_declare = pModDecl;
_gameObject = pGameObject;
ModFolder = pModDecl.FolderPath;
_config = ModSettings.Load(pModDecl);
InterestScoringConfig.LoadFromModFolder(ModFolder);
EventCatalogConfig.LoadFromModFolder(ModFolder);
TryArmAutoloadSlot(pModDecl);
_harmony = new Harmony(pModDecl.UID);
ApplyHarmonyPatches(pModDecl.Name);
LogService.LogInfo($"[{pModDecl.Name}]: Harmony patches applied");
LogService.LogInfo($"[{pModDecl.Name}]: Press I for Idle Spectator, L for Lore");
Chronicle.EnsureWindow();
}
private void ApplyHarmonyPatches(string modName)
{
Exception firstFailure = null;
foreach (Type type in typeof(ModClass).Assembly.GetTypes())
{
try
{
new PatchClassProcessor(_harmony, type).Patch();
}
catch (Exception ex)
{
LogService.LogError($"[{modName}]: Harmony failed on {type.FullName}: {ex}");
if (ex.InnerException != null)
{
LogService.LogError($"[{modName}]: Inner: {ex.InnerException}");
}
if (firstFailure == null)
{
firstFailure = ex;
}
}
}
if (firstFailure != null)
{
throw firstFailure;
}
}
///
/// Dev/agent helper: .harness/autoload_slot containing a slot number (1-5)
/// sets Config.load_save_on_start so Box of Magic (slot 2) can come up after reboot.
///
private static void TryArmAutoloadSlot(ModDeclare declare)
{
try
{
string path = Path.Combine(declare.FolderPath, ".harness", "autoload_slot");
if (!File.Exists(path))
{
return;
}
string raw = File.ReadAllText(path).Trim();
if (!int.TryParse(raw, out int slot) || slot < 1 || slot > 5)
{
return;
}
var slotFi = AccessTools.Field(typeof(Config), "load_save_on_start_slot");
var onFi = AccessTools.Field(typeof(Config), "load_save_on_start");
if (slotFi == null || onFi == null)
{
return;
}
slotFi.SetValue(null, slot);
onFi.SetValue(null, true);
LogService.LogInfo($"[{declare.Name}]: autoload_slot armed slot={slot}");
}
catch (Exception ex)
{
LogService.LogInfo($"[{declare.Name}]: autoload_slot failed: {ex.Message}");
}
}
private void Update()
{
AgentHarness.Update();
SpectatorMode.PollInput();
Chronicle.PollInput();
IdleHitchProbe.BeginSlice();
InterestDirector.Update();
IdleHitchProbe.EndDirector();
IdleHitchProbe.BeginSlice();
if (!AgentHarness.FreezeDirector)
{
SpeciesDiscovery.Update();
}
IdleHitchProbe.EndDiscovery();
IdleHitchProbe.BeginSlice();
WatchCaption.Update();
IdleHitchProbe.EndCaption();
IdleHitchProbe.BeginSlice();
Chronicle.Update();
GraveMarkers.Update();
InspectUi.Update();
FocusRelationshipArrows.PinFocusActor();
StateProbe.Update();
AutoTest.Update();
IdleHitchProbe.EndOther();
IdleHitchProbe.AfterFrame();
}
}