using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppSystem.Collections.Generic;
using Microsoft.CodeAnalysis;
using ProtectorateShared;
using SOD.Common;
using SOD.Common.Helpers;
using SOD.Common.Helpers.DialogObjects;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("DetectiveLicense")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.2.0.0")]
[assembly: AssemblyInformationalVersion("1.2.0+0a93c4585cf6d92cd2cc95ba73fb5bdf1b3f6c08")]
[assembly: AssemblyProduct("DetectiveLicense")]
[assembly: AssemblyTitle("DetectiveLicense")]
[assembly: AssemblyVersion("1.2.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
internal sealed class NullableAttribute : Attribute
{
public readonly byte[] NullableFlags;
public NullableAttribute(byte P_0)
{
NullableFlags = new byte[1] { P_0 };
}
public NullableAttribute(byte[] P_0)
{
NullableFlags = P_0;
}
}
[CompilerGenerated]
[Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
internal sealed class NullableContextAttribute : Attribute
{
public readonly byte Flag;
public NullableContextAttribute(byte P_0)
{
Flag = P_0;
}
}
[CompilerGenerated]
[Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace ProtectorateShared
{
internal static class CredentialsCard
{
private const string RootName = "ProtectorateCredentials";
private const string HeaderName = "00_Header";
public static void Refresh(string sectionKey, string title, Func<List<string>> lines)
{
//IL_004c: Unknown result type (might be due to invalid IL or missing references)
//IL_0052: Invalid comparison between Unknown and I4
MainMenuController instance = MainMenuController.Instance;
RectTransform val = ((instance != null) ? instance.mainMenuContainer : null);
if ((Object)(object)val == (Object)null)
{
return;
}
bool num = instance.mainMenuActive && (Object)(object)SessionData.Instance != (Object)null && SessionData.Instance.startedGame && instance.currentComponent != null && (int)instance.currentComponent.component == 1;
Transform val2 = ((Transform)val).Find("ProtectorateCredentials");
if (!num)
{
if ((Object)(object)val2 != (Object)null)
{
((Component)val2).gameObject.SetActive(false);
}
return;
}
if (val2 == null)
{
val2 = CreateRoot((Transform)(object)val, instance);
}
Transform val3 = val2.Find(sectionKey) ?? ((TMP_Text)CreateText(val2, sectionKey, instance, 17f)).transform;
List<string> list = lines();
TextMeshProUGUI component = ((Component)val3).GetComponent<TextMeshProUGUI>();
if (list.Count == 0)
{
((Component)val3).gameObject.SetActive(false);
}
else
{
((TMP_Text)component).text = "<b><color=#E8B04B>" + title + "</color></b>\n" + string.Join("\n", list);
((Component)val3).gameObject.SetActive(true);
}
bool active = false;
List<string> list2 = new List<string>();
for (int i = 0; i < val2.childCount; i++)
{
list2.Add(((Object)val2.GetChild(i)).name);
}
list2.Sort(StringComparer.Ordinal);
for (int j = 0; j < list2.Count; j++)
{
Transform val4 = val2.Find(list2[j]);
val4.SetSiblingIndex(j);
if (list2[j] != "00_Header" && ((Component)val4).gameObject.activeSelf)
{
active = true;
}
}
val2.SetAsLastSibling();
((Component)val2).gameObject.SetActive(active);
}
private static Transform CreateRoot(Transform container, MainMenuController menu)
{
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
//IL_002c: Unknown result type (might be due to invalid IL or missing references)
//IL_0032: Unknown result type (might be due to invalid IL or missing references)
//IL_0033: Unknown result type (might be due to invalid IL or missing references)
//IL_0034: Unknown result type (might be due to invalid IL or missing references)
//IL_003a: Unknown result type (might be due to invalid IL or missing references)
//IL_004a: Unknown result type (might be due to invalid IL or missing references)
//IL_0054: Unknown result type (might be due to invalid IL or missing references)
//IL_006f: Unknown result type (might be due to invalid IL or missing references)
//IL_007f: Unknown result type (might be due to invalid IL or missing references)
//IL_0086: Unknown result type (might be due to invalid IL or missing references)
//IL_008b: Unknown result type (might be due to invalid IL or missing references)
//IL_0093: Unknown result type (might be due to invalid IL or missing references)
//IL_009b: Unknown result type (might be due to invalid IL or missing references)
//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
//IL_00b0: Expected O, but got Unknown
//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
//IL_011e: Unknown result type (might be due to invalid IL or missing references)
GameObject val = new GameObject("ProtectorateCredentials");
RectTransform obj = val.AddComponent<RectTransform>();
((Transform)obj).SetParent(container, false);
Vector2 val2 = default(Vector2);
((Vector2)(ref val2))..ctor(1f, 0f);
obj.pivot = val2;
Vector2 anchorMin = (obj.anchorMax = val2);
obj.anchorMin = anchorMin;
obj.anchoredPosition = new Vector2(-48f, 48f);
Image obj2 = val.AddComponent<Image>();
((Graphic)obj2).color = new Color(0.08f, 0.07f, 0.11f, 0.82f);
((Graphic)obj2).raycastTarget = false;
VerticalLayoutGroup obj3 = val.AddComponent<VerticalLayoutGroup>();
((LayoutGroup)obj3).padding = new RectOffset
{
left = 20,
right = 20,
top = 14,
bottom = 16
};
((HorizontalOrVerticalLayoutGroup)obj3).spacing = 10f;
((HorizontalOrVerticalLayoutGroup)obj3).childControlWidth = true;
((HorizontalOrVerticalLayoutGroup)obj3).childControlHeight = true;
((HorizontalOrVerticalLayoutGroup)obj3).childForceExpandWidth = true;
((HorizontalOrVerticalLayoutGroup)obj3).childForceExpandHeight = false;
ContentSizeFitter obj4 = val.AddComponent<ContentSizeFitter>();
obj4.horizontalFit = (FitMode)2;
obj4.verticalFit = (FitMode)2;
TextMeshProUGUI obj5 = CreateText(val.transform, "00_Header", menu, 20f);
((TMP_Text)obj5).text = "<b>CREDENTIALS</b>";
((Graphic)obj5).color = new Color(0.95f, 0.93f, 0.88f, 1f);
return val.transform;
}
private static TextMeshProUGUI CreateText(Transform parent, string name, MainMenuController menu, float size)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_0013: Unknown result type (might be due to invalid IL or missing references)
//IL_0055: Unknown result type (might be due to invalid IL or missing references)
GameObject val = new GameObject(name);
((Transform)val.AddComponent<RectTransform>()).SetParent(parent, false);
TextMeshProUGUI val2 = val.AddComponent<TextMeshProUGUI>();
if ((Object)(object)menu.buildText != (Object)null)
{
((TMP_Text)val2).font = ((TMP_Text)menu.buildText).font;
}
((TMP_Text)val2).fontSize = size;
((Graphic)val2).color = new Color(0.86f, 0.84f, 0.8f, 1f);
((Graphic)val2).raycastTarget = false;
((TMP_Text)val2).enableWordWrapping = false;
((TMP_Text)val2).richText = true;
val.AddComponent<LayoutElement>().minWidth = 300f;
return val2;
}
public static string Hours(float hours)
{
hours = Mathf.Max(0f, hours);
int num = Mathf.FloorToInt(hours);
int value = Mathf.FloorToInt((hours - (float)num) * 60f);
if (num > 0)
{
return $"{num}h {value:00}m";
}
return $"{value}m";
}
}
[HarmonyPatch]
internal static class DialogGuard
{
private static ManualLogSource log;
private static readonly PropertyInfo ResponseInfo = typeof(DialogObject).GetProperty("ResponseInfo", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
private static ManualLogSource Log => log ?? (log = Logger.CreateLogSource("DialogGuard"));
[HarmonyPatch(typeof(DialogController), "Start")]
[HarmonyPrefix]
internal static void Protect()
{
foreach (DialogObject knownModDialog in Lib.Dialogs.KnownModDialogs)
{
try
{
Keep((Object)(object)knownModDialog.Preset);
object? obj = ResponseInfo?.GetValue(knownModDialog);
Keep((Object)((obj is Object) ? obj : null));
}
catch (Exception ex)
{
Log.LogError((object)ex.Message);
}
}
}
private static void Keep(Object o)
{
//IL_000b: Unknown result type (might be due to invalid IL or missing references)
//IL_0012: Unknown result type (might be due to invalid IL or missing references)
if (o != (Object)null)
{
o.hideFlags = (HideFlags)(o.hideFlags | 0x20);
}
}
[HarmonyPatch(typeof(EvidenceWitness), "GetDialogOptions", new Type[] { typeof(List<DataKey>) })]
[HarmonyPostfix]
private static void DropDead(List<DialogOption> __result)
{
//IL_0039: Unknown result type (might be due to invalid IL or missing references)
//IL_003f: Expected O, but got Unknown
if (__result == null)
{
return;
}
bool flag = default(bool);
for (int num = __result.Count - 1; num >= 0; num--)
{
DialogOption obj = __result[num];
DialogPreset val = ((obj != null) ? obj.preset : null);
if (val != null && !((SoCustomComparison)(object)val != (SoCustomComparison)null))
{
ManualLogSource obj2 = Log;
BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(52, 2, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Skipped a destroyed dialog option (msgID ");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(val.msgID);
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(", ranking ");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(val.ranking);
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(")");
}
obj2.LogWarning(val2);
__result.RemoveAt(num);
}
}
}
}
}
namespace DetectiveLicense
{
[HarmonyPatch]
internal static class Credentials
{
[HarmonyPostfix]
[HarmonyPatch(typeof(MainMenuController), "SetMenuComponent", new Type[] { typeof(Component) })]
private static void OnMenuComponent()
{
Refresh();
}
[HarmonyPostfix]
[HarmonyPatch(typeof(MainMenuController), "EnableMainMenu")]
private static void OnMenu()
{
Refresh();
}
private static void Refresh()
{
try
{
CredentialsCard.Refresh("10_Detective", "Detective", Lines);
}
catch (Exception ex)
{
Plugin.Logger.LogError((object)("Credentials card: " + ex));
}
}
private static List<string> Lines()
{
//IL_0054: Unknown result type (might be due to invalid IL or missing references)
List<string> list = new List<string>();
if (Plugin.Licensed)
{
list.Add("Private investigator's license");
}
float gameTime = SessionData.Instance.gameTime;
Dictionary<NewAddress, Vector2> guestPasses = GameplayController.Instance.guestPasses;
foreach (NewAddress item in CaseLocations.Get())
{
if (guestPasses.ContainsKey(item))
{
float x = guestPasses[item].x;
if (x > gameTime)
{
list.Add($"Crime scene permit: {((Object)item).name} ({CredentialsCard.Hours(x - gameTime)} left)");
}
}
}
return list;
}
}
internal static class Dialogs
{
private sealed class PermitLogic : IDialogLogic
{
public bool IsDialogShown(DialogPreset preset, Citizen saysTo, SideJob jobRef)
{
if (!Plugin.Licensed)
{
return IsCityHallDesk(saysTo);
}
return false;
}
public ForceSuccess ShouldDialogSucceedOverride(DialogController instance, DialogOption dialog, Citizen saysTo, NewNode where, Actor saidBy)
{
if (CaseLocations.Get().Count > 0)
{
return (ForceSuccess)1;
}
return (ForceSuccess)2;
}
public void OnDialogExecute(DialogController instance, Citizen saysTo, Interactable saysToInteractable, NewNode where, Actor saidBy, bool success, NewRoom roomRef, SideJob jobRef)
{
//IL_0090: Unknown result type (might be due to invalid IL or missing references)
//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
//IL_00b4: Expected O, but got Unknown
if (success)
{
float value = Plugin.PermitHours.Value;
int num = CaseLocations.IssuePermit(value);
Lib.GameMessage.Broadcast($"Crime scene permit: {num} location{((num == 1) ? "" : "s")}, {value:0.#} hours", (GameMessageType)0, (Icon)2, (Color?)InterfaceControls.Instance.messageGreen, 0f);
ManualLogSource logger = Plugin.Logger;
bool flag = default(bool);
BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(43, 2, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Permit issued for ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(num);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" case location(s), ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<float>(value);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" hours");
}
logger.LogInfo(val);
}
}
}
private sealed class RecordsLogic : IDialogLogic
{
public bool IsDialogShown(DialogPreset preset, Citizen saysTo, SideJob jobRef)
{
return IsCityHallDesk(saysTo);
}
public ForceSuccess ShouldDialogSucceedOverride(DialogController instance, DialogOption dialog, Citizen saysTo, NewNode where, Actor saidBy)
{
if (Plugin.Licensed)
{
return (ForceSuccess)1;
}
return (ForceSuccess)2;
}
public void OnDialogExecute(DialogController instance, Citizen saysTo, Interactable saysToInteractable, NewNode where, Actor saidBy, bool success, NewRoom roomRef, SideJob jobRef)
{
//IL_0095: Unknown result type (might be due to invalid IL or missing references)
//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
//IL_00b9: Expected O, but got Unknown
if (!success)
{
return;
}
Human val = Records.DeskLogin((Human)(object)saysTo);
if ((Object)(object)val == (Object)null)
{
Plugin.Logger.LogWarning((object)"Records: no lobby desk login found");
return;
}
GameplayController.Instance.AddPasscode(val.passcode, false);
Lib.GameMessage.Broadcast($"Public records: log in at a lobby desk as {val.GetInitialledName()} ({Plugin.PrintCost.Value} crows per printout)", (GameMessageType)0, (Icon)2, (Color?)InterfaceControls.Instance.messageGreen, 0f);
ManualLogSource logger = Plugin.Logger;
bool flag = default(bool);
BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(30, 2, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Records login given: ");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(val.GetCitizenName());
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" (asked ");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(((Human)saysTo).GetCitizenName());
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(")");
}
logger.LogInfo(val2);
}
}
private sealed class LicenseLogic : IDialogLogic
{
public bool IsDialogShown(DialogPreset preset, Citizen saysTo, SideJob jobRef)
{
if (!Plugin.Licensed)
{
return IsCityHallDesk(saysTo);
}
return false;
}
public ForceSuccess ShouldDialogSucceedOverride(DialogController instance, DialogOption dialog, Citizen saysTo, NewNode where, Actor saidBy)
{
return (ForceSuccess)1;
}
public void OnDialogExecute(DialogController instance, Citizen saysTo, Interactable saysToInteractable, NewNode where, Actor saidBy, bool success, NewRoom roomRef, SideJob jobRef)
{
//IL_0022: Unknown result type (might be due to invalid IL or missing references)
if (success)
{
Plugin.Licensed = true;
CaseLocations.Invalidate();
Lib.GameMessage.Broadcast("Private investigator's license acquired", (GameMessageType)0, (Icon)13, (Color?)InterfaceControls.Instance.messageGreen, 0f);
Plugin.Logger.LogInfo((object)"License acquired");
}
}
}
private static readonly HashSet<int> LoggedStaff = new HashSet<int>();
public static void Register()
{
Lib.Dialogs.Builder("DetectiveLicense_Permit").SetText("I need a crime scene permit.").SetDialogLogic((IDialogLogic)(object)new PermitLogic())
.ModifyDialogOptions((Action<DialogPreset>)delegate(DialogPreset p)
{
p.cost = Plugin.PermitCost.Value;
p.ranking = 6;
})
.AddResponse("Here's your permit. Don't touch anything you don't need to.", true, true)
.AddResponse("There aren't any open murder cases right now, Detective.", false, true)
.CreateAndRegister();
Lib.Dialogs.Builder("DetectiveLicense_License").SetText("I'd like a private investigator's license.").SetDialogLogic((IDialogLogic)(object)new LicenseLogic())
.ModifyDialogOptions((Action<DialogPreset>)delegate(DialogPreset p)
{
p.cost = Plugin.LicenseCost.Value;
p.ranking = 5;
})
.AddResponse("All in order. Welcome to the profession, Detective.", true, true)
.CreateAndRegister();
Lib.Dialogs.Builder("DetectiveLicense_Records").SetText("I need to check the public records.").SetDialogLogic((IDialogLogic)(object)new RecordsLogic())
.ModifyDialogOptions((Action<DialogPreset>)delegate(DialogPreset p)
{
p.ranking = 4;
})
.AddResponse("Use the terminal at the desk, my login's fine. Printouts are charged per page.", true, true)
.AddResponse("Records access is for licensed investigators only. You can apply for a license here.", false, true)
.CreateAndRegister();
}
internal static bool IsCityHallDesk(Citizen citizen)
{
//IL_0086: Unknown result type (might be due to invalid IL or missing references)
//IL_008d: Expected O, but got Unknown
if ((Object)(object)citizen == (Object)null)
{
return false;
}
Occupation job = ((Human)citizen).job;
object obj;
if (job == null)
{
obj = null;
}
else
{
Company employer = job.employer;
if (employer == null)
{
obj = null;
}
else
{
NewGameLocation placeOfBusiness = employer.placeOfBusiness;
obj = ((placeOfBusiness != null) ? placeOfBusiness.building : null);
}
}
NewBuilding val = (NewBuilding)obj;
if ((SoCustomComparison)(object)((val != null) ? val.preset : null) == (SoCustomComparison)null || ((SoCustomComparison)val.preset).presetName != "CityHall")
{
return false;
}
if (LoggedStaff.Add(((Human)citizen).humanID))
{
ManualLogSource logger = Plugin.Logger;
bool flag = default(bool);
BepInExDebugLogInterpolatedStringHandler val2 = new BepInExDebugLogInterpolatedStringHandler(30, 3, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("City Hall staff: ");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(((Human)citizen).GetCitizenName());
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" job=");
OccupationPreset preset = job.preset;
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>((preset != null) ? ((Object)preset).name : null);
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" atWork=");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<bool>(((Actor)citizen).isAtWork);
}
logger.LogDebug(val2);
}
if (!((Actor)citizen).isAtWork)
{
return false;
}
string value = Plugin.DeskJobs.Value;
if (string.IsNullOrWhiteSpace(value))
{
return true;
}
OccupationPreset preset2 = job.preset;
string jobName = ((preset2 != null) ? ((Object)preset2).name : null);
if (jobName != null)
{
return value.Split(',').Any((string j) => j.Trim().Equals(jobName, StringComparison.OrdinalIgnoreCase));
}
return false;
}
}
[BepInPlugin("sodmods.detectivelicense", "Detective License", "1.2.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class Plugin : BasePlugin
{
public const string Guid = "sodmods.detectivelicense";
internal static ManualLogSource Logger;
internal static ConfigEntry<int> PermitCost;
internal static ConfigEntry<float> PermitHours;
internal static ConfigEntry<int> LicenseCost;
internal static ConfigEntry<float> LookAroundBonus;
internal static ConfigEntry<float> TrustAuthorityBonus;
internal static ConfigEntry<float> DistrustAuthorityBonus;
internal static ConfigEntry<string> DeskJobs;
internal static ConfigEntry<int> PrintCost;
internal static bool Licensed;
public override void Load()
{
//IL_019c: Unknown result type (might be due to invalid IL or missing references)
Logger = ((BasePlugin)this).Log;
PermitCost = ((BasePlugin)this).Config.Bind<int>("Permit", "Cost", 100, "Price of a crime scene permit, in crows.");
PermitHours = ((BasePlugin)this).Config.Bind<float>("Permit", "Hours", 4f, "How many in-game hours a permit lasts (1 in-game hour is about 6.5 real minutes).");
LicenseCost = ((BasePlugin)this).Config.Bind<int>("License", "Cost", 1500, "Price of a private investigator's license, in crows.");
LookAroundBonus = ((BasePlugin)this).Config.Bind<float>("License", "LookAroundBonus", 0.3f, "Added to the chance a resident lets you look around, for licensed investigators (0.3 = +30%).");
TrustAuthorityBonus = ((BasePlugin)this).Config.Bind<float>("License", "TrustAuthorityBonus", 0.2f, "Extra chance on top of LookAroundBonus for residents who trust authority.");
DistrustAuthorityBonus = ((BasePlugin)this).Config.Bind<float>("License", "DistrustAuthorityBonus", -0.25f, "Change on top of LookAroundBonus for residents who distrust authority (negative = the license helps less).");
PrintCost = ((BasePlugin)this).Config.Bind<int>("Records", "PrintCost", 50, "Price of each citizen file printed at the City Hall lobby computers (license holders only), in crows.");
DeskJobs = ((BasePlugin)this).Config.Bind<string>("CityHall", "DeskJobs", "", "Comma-separated job names at City Hall who sell permits and licenses (e.g. Receptionist). Empty = any City Hall employee at work.");
Lib.SaveGame.OnAfterLoad += delegate(object? _, SaveGameArgs args)
{
//IL_0016: Unknown result type (might be due to invalid IL or missing references)
//IL_001c: Expected O, but got Unknown
Licensed = SaveData.Load(args);
ManualLogSource log = ((BasePlugin)this).Log;
bool flag = default(bool);
BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(24, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Save loaded: licensed = ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(Licensed);
}
log.LogInfo(val);
};
Lib.SaveGame.OnAfterSave += delegate(object? _, SaveGameArgs args)
{
SaveData.Save(args, Licensed);
};
Lib.SaveGame.OnBeforeNewGame += delegate
{
Licensed = false;
};
Dialogs.Register();
DialogGuard.Protect();
new Harmony("sodmods.detectivelicense").PatchAll(typeof(Plugin).Assembly);
((BasePlugin)this).Log.LogInfo((object)"Detective License loaded");
}
}
internal static class SaveData
{
private const string FileName = "detectivelicense.txt";
public static bool Load(SaveGameArgs args)
{
try
{
string saveGameDataPath = Lib.SaveGame.GetSaveGameDataPath(args, "detectivelicense.txt");
return File.Exists(saveGameDataPath) && File.ReadAllText(saveGameDataPath).Trim() == "licensed";
}
catch (Exception ex)
{
Plugin.Logger.LogError((object)("Failed to load license state: " + ex));
return false;
}
}
public static void Save(SaveGameArgs args, bool licensed)
{
try
{
string saveGameDataPath = Lib.SaveGame.GetSaveGameDataPath(args, "detectivelicense.txt");
if (licensed)
{
File.WriteAllText(saveGameDataPath, "licensed");
}
else if (File.Exists(saveGameDataPath))
{
File.Delete(saveGameDataPath);
}
}
catch (Exception ex)
{
Plugin.Logger.LogError((object)("Failed to save license state: " + ex));
}
}
}
internal static class CaseLocations
{
private static readonly HashSet<int> cachedIds = new HashSet<int>();
private static float cachedAt = -10f;
private static bool LogMurders;
public static List<NewAddress> Get()
{
List<NewAddress> list = new List<NewAddress>();
MurderController instance = MurderController.Instance;
if ((Object)(object)instance == (Object)null)
{
return list;
}
Collect(instance.activeMurders, list);
Collect(instance.inactiveMurders, list);
return list;
}
private static void Collect(List<Murder> murders, List<NewAddress> into)
{
//IL_001a: Unknown result type (might be due to invalid IL or missing references)
//IL_0021: Invalid comparison between Unknown and I4
//IL_0069: Unknown result type (might be due to invalid IL or missing references)
//IL_006f: Expected O, but got Unknown
//IL_009f: Unknown result type (might be due to invalid IL or missing references)
if (murders == null)
{
return;
}
bool flag = default(bool);
for (int i = 0; i < murders.Count; i++)
{
Murder val = murders[i];
if (val == null || (int)val.state == 9)
{
continue;
}
Human victim = val.victim;
Death val2 = ((victim != null) ? victim.death : null);
if (val2 == null || !val2.isDead || !val2.reported)
{
continue;
}
if (LogMurders)
{
ManualLogSource logger = Plugin.Logger;
BepInExDebugLogInterpolatedStringHandler val3 = new BepInExDebugLogInterpolatedStringHandler(60, 5, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("Open case: victim ");
((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<string>(val.victim.GetCitizenName());
((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(", state ");
((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<MurderState>(val.state);
((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(", ");
((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("location '");
NewGameLocation location = val.location;
((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<string>((location != null) ? ((Object)location).name : null);
((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("' (address: ");
NewGameLocation location2 = val.location;
((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<bool>((Object)(object)((location2 != null) ? location2.thisAsAddress : null) != (Object)null);
((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("), home '");
NewAddress home = val.victim.home;
((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<string>((home != null) ? ((Object)home).name : null);
((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("'");
}
logger.LogDebug(val3);
}
NewGameLocation location3 = val.location;
Add((location3 != null) ? location3.thisAsAddress : null, into);
Add(val.victim.home, into);
}
}
private static void Add(NewAddress address, List<NewAddress> into)
{
if ((Object)(object)address != (Object)null && !into.Contains(address))
{
into.Add(address);
}
}
public static bool Covers(NewAddress address)
{
if ((Object)(object)address == (Object)null)
{
return false;
}
float unscaledTime = Time.unscaledTime;
if (unscaledTime - cachedAt > 2f)
{
cachedAt = unscaledTime;
cachedIds.Clear();
foreach (NewAddress item in Get())
{
cachedIds.Add(item.id);
}
}
return cachedIds.Contains(address.id);
}
public static void Invalidate()
{
cachedAt = -10f;
}
public static int IssuePermit(float hours)
{
//IL_003c: Unknown result type (might be due to invalid IL or missing references)
//IL_0042: Expected O, but got Unknown
LogMurders = true;
List<NewAddress> list = Get();
LogMurders = false;
bool flag = default(bool);
foreach (NewAddress item in list)
{
GameplayController.Instance.AddGuestPass(item, hours);
ManualLogSource logger = Plugin.Logger;
BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(35, 3, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Permit covers '");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(((Object)item).name);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("' (id ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(item.id);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", building '");
NewBuilding building = ((NewGameLocation)item).building;
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>((building != null) ? ((Object)building).name : null);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("')");
}
logger.LogInfo(val);
}
return list.Count;
}
}
[HarmonyPatch(typeof(Human), "IsTrespassing")]
internal static class LicensedCaseAccessPatch
{
private static void Postfix(Human __instance, NewRoom room, ref int trespassEscalation, ref bool __result)
{
if (__result && Plugin.Licensed && ((Actor)__instance).isPlayer && !((Actor)__instance).inAirVent)
{
object address;
if (room == null)
{
address = null;
}
else
{
NewGameLocation gameLocation = room.gameLocation;
address = ((gameLocation != null) ? gameLocation.thisAsAddress : null);
}
if (CaseLocations.Covers((NewAddress)address))
{
__result = false;
trespassEscalation = 0;
}
}
}
}
[HarmonyPatch(typeof(UpgradeEffectController), "GetUpgradeEffect")]
internal static class UpgradeEffectPatch
{
private static void Postfix(Effect effect, ref float __result)
{
//IL_0008: Unknown result type (might be due to invalid IL or missing references)
//IL_000b: Invalid comparison between Unknown and I4
//IL_001c: Unknown result type (might be due to invalid IL or missing references)
//IL_001f: Invalid comparison between Unknown and I4
//IL_004f: Unknown result type (might be due to invalid IL or missing references)
//IL_0055: Expected O, but got Unknown
if (!Plugin.Licensed)
{
return;
}
if ((int)effect == 61)
{
__result = Math.Max(__result, 1f);
}
else
{
if ((int)effect != 64)
{
return;
}
if ((Object)(object)LookAroundTarget.Current != (Object)null)
{
float num = LookAroundTarget.Bonus(LookAroundTarget.Current);
__result += num;
ManualLogSource logger = Plugin.Logger;
bool flag = default(bool);
BepInExDebugLogInterpolatedStringHandler val = new BepInExDebugLogInterpolatedStringHandler(49, 3, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[LookAround] bonus +");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<float>(num, "P0");
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" applied for ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(LookAroundTarget.Current.GetCitizenName());
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" (modifier now ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<float>(__result, "0.00");
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(")");
}
logger.LogDebug(val);
}
else
{
Plugin.Logger.LogDebug((object)"[LookAround] guestPassIssueModifier read with no current target (bonus not applied)");
}
}
}
}
[HarmonyPatch(typeof(DialogController), "ExecuteDialog")]
internal static class LookAroundTarget
{
internal static Human Current;
private static void Prefix(DialogOption dialog, Interactable saysTo)
{
//IL_0023: Unknown result type (might be due to invalid IL or missing references)
//IL_002a: Invalid comparison between Unknown and I4
//IL_005d: Unknown result type (might be due to invalid IL or missing references)
//IL_0063: Expected O, but got Unknown
Current = null;
if ((SoCustomComparison)(object)((dialog != null) ? dialog.preset : null) != (SoCustomComparison)null && (int)dialog.preset.specialCase == 10)
{
object current;
if (saysTo == null)
{
current = null;
}
else
{
Actor isActor = saysTo.isActor;
current = ((isActor != null) ? ((Il2CppObjectBase)isActor).TryCast<Human>() : null);
}
Current = (Human)current;
DialogPreset preset = dialog.preset;
ManualLogSource logger = Plugin.Logger;
bool flag = default(bool);
BepInExDebugLogInterpolatedStringHandler val = new BepInExDebugLogInterpolatedStringHandler(88, 7, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[LookAround] asking ");
Human current2 = Current;
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>((current2 != null) ? current2.GetCitizenName() : null);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": baseChance=");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<float>(preset.baseChance, "0.00");
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" ");
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("useSuccessTest=");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(preset.useSuccessTest);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" traitRules=");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int?>(preset.modifySuccessChanceTraits?.Count);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" ");
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("trust=");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(HasTrait(Current, "Principle-TrustOfAuthority"));
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" distrust=");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(HasTrait(Current, "Principle-DistrustOfAuthority"));
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" ");
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("licensed=");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(Plugin.Licensed);
}
logger.LogDebug(val);
}
}
private static void Postfix(DialogOption dialog, bool __result)
{
//IL_001a: Unknown result type (might be due to invalid IL or missing references)
//IL_0021: Invalid comparison between Unknown and I4
//IL_002d: Unknown result type (might be due to invalid IL or missing references)
//IL_0033: Expected O, but got Unknown
if ((SoCustomComparison)(object)((dialog != null) ? dialog.preset : null) != (SoCustomComparison)null && (int)dialog.preset.specialCase == 10)
{
ManualLogSource logger = Plugin.Logger;
bool flag = default(bool);
BepInExDebugLogInterpolatedStringHandler val = new BepInExDebugLogInterpolatedStringHandler(21, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[LookAround] result: ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(__result ? "SUCCESS" : "refused");
}
logger.LogDebug(val);
}
}
private static void Finalizer()
{
Current = null;
}
internal static float Bonus(Human resident)
{
float num = Plugin.LookAroundBonus.Value;
if (HasTrait(resident, "Principle-TrustOfAuthority"))
{
num += Plugin.TrustAuthorityBonus.Value;
}
if (HasTrait(resident, "Principle-DistrustOfAuthority"))
{
num += Plugin.DistrustAuthorityBonus.Value;
}
return num;
}
private static bool HasTrait(Human human, string traitName)
{
List<Trait> val = ((human != null) ? human.characterTraits : null);
if (val == null)
{
return false;
}
for (int i = 0; i < val.Count; i++)
{
Trait obj = val[i];
if ((SoCustomComparison)(object)((obj != null) ? obj.trait : null) != (SoCustomComparison)null && ((Object)val[i].trait).name == traitName)
{
return true;
}
}
return false;
}
}
[HarmonyPatch]
internal static class Records
{
private const string LobbyPreset = "CityHallLobby";
private const string DatabaseAppName = "GovDatabase";
internal static bool IsLobby(NewAddress address)
{
if ((SoCustomComparison)(object)((address != null) ? address.addressPreset : null) != (SoCustomComparison)null)
{
return ((Object)address.addressPreset).name == "CityHallLobby";
}
return false;
}
private static NewAddress AddressOf(ComputerController cc)
{
if (cc == null)
{
return null;
}
InteractableController ic = cc.ic;
if (ic == null)
{
return null;
}
Interactable interactable = ic.interactable;
if (interactable == null)
{
return null;
}
NewNode node = interactable.node;
if (node == null)
{
return null;
}
NewGameLocation gameLocation = node.gameLocation;
if (gameLocation == null)
{
return null;
}
return gameLocation.thisAsAddress;
}
internal static Human DeskLogin(Human clerk)
{
List<Human> list = new List<Human>();
object obj;
if (clerk == null)
{
obj = null;
}
else
{
Occupation job = clerk.job;
if (job == null)
{
obj = null;
}
else
{
Company employer = job.employer;
if (employer == null)
{
obj = null;
}
else
{
NewGameLocation placeOfBusiness = employer.placeOfBusiness;
obj = ((placeOfBusiness != null) ? placeOfBusiness.building : null);
}
}
}
NewBuilding val = (NewBuilding)obj;
if ((Object)(object)val == (Object)null)
{
return null;
}
Enumerator<int, NewFloor> enumerator = val.floors.GetEnumerator();
while (enumerator.MoveNext())
{
Enumerator<NewAddress> enumerator2 = enumerator.Current.Value.addresses.GetEnumerator();
while (enumerator2.MoveNext())
{
NewAddress current = enumerator2.Current;
if (!IsLobby(current))
{
continue;
}
Enumerator<NewRoom> enumerator3 = ((NewGameLocation)current).rooms.GetEnumerator();
while (enumerator3.MoveNext())
{
Enumerator<NewNode> enumerator4 = enumerator3.Current.nodes.GetEnumerator();
while (enumerator4.MoveNext())
{
Enumerator<Interactable> enumerator5 = enumerator4.Current.interactables.GetEnumerator();
while (enumerator5.MoveNext())
{
Interactable current2 = enumerator5.Current;
if ((SoCustomComparison)(object)((current2 != null) ? current2.preset : null) == (SoCustomComparison)null || !current2.preset.isComputer || current2.furnitureParent == null)
{
continue;
}
Enumerator<OwnerKey, int> enumerator6 = current2.furnitureParent.ownerMap.GetEnumerator();
while (enumerator6.MoveNext())
{
KeyValuePair<OwnerKey, int> current3 = enumerator6.Current;
Human h = current3.Key.human;
if (!((Object)(object)h == (Object)null) && h.passcode != null)
{
if (h.humanID == clerk.humanID)
{
return h;
}
if (!list.Exists((Human o) => o.humanID == h.humanID))
{
list.Add(h);
}
}
}
}
}
}
}
}
if (list.Count <= 0)
{
return null;
}
return list[0];
}
[HarmonyPatch(typeof(DesktopApp), "UpdateIcons")]
[HarmonyPrefix]
private static void Install(DesktopApp __instance)
{
try
{
NewAddress val = AddressOf(((CruncherAppContent)__instance).controller);
if (!IsLobby(val))
{
return;
}
Enumerator<CruncherAppPreset> enumerator = ((CruncherAppContent)__instance).controller.ic.interactable.preset.additionalApps.GetEnumerator();
while (enumerator.MoveNext())
{
CruncherAppPreset current = enumerator.Current;
if (!((SoCustomComparison)(object)current == (SoCustomComparison)null) && !(((Object)current).name != "GovDatabase"))
{
List<AddressPreset> onlyInAddresses = current.onlyInAddresses;
bool flag = onlyInAddresses.Contains(val.addressPreset);
if (Plugin.Licensed && !flag)
{
onlyInAddresses.Add(val.addressPreset);
}
else if (!Plugin.Licensed && flag)
{
onlyInAddresses.Remove(val.addressPreset);
}
}
}
}
catch (Exception ex)
{
Plugin.Logger.LogError((object)("Records install failed: " + ex));
}
}
[HarmonyPatch(typeof(DatabaseApp), "OnPrintEntry")]
[HarmonyPrefix]
private static bool BeforePrint(DatabaseApp __instance, out bool __state)
{
//IL_008e: Unknown result type (might be due to invalid IL or missing references)
//IL_009e: Unknown result type (might be due to invalid IL or missing references)
//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
__state = false;
ComputerController controller = ((CruncherAppContent)__instance).controller;
if (!IsLobby(AddressOf(controller)))
{
return true;
}
int value = Plugin.PrintCost.Value;
__state = value > 0 && controller.printedDocument == null && controller.printTimer <= 0f;
if (__state && GameplayController.Instance.money < value)
{
__state = false;
AudioController.Instance.PlayWorldOneShot(AudioControls.Instance.computerInvalidPasscode, (Actor)(object)Player.Instance, controller.ic.interactable.node, controller.ic.interactable.wPos, (Interactable)null, (List<FMODParam>)null, 1f, (List<NewNode>)null, false, (SoundMaterialOverride)null, false);
Lib.GameMessage.Broadcast($"Records printout: {value} crows", (GameMessageType)0, (Icon)17, (Color?)InterfaceControls.Instance.messageRed, 0f);
return false;
}
return true;
}
[HarmonyPatch(typeof(DatabaseApp), "OnPrintEntry")]
[HarmonyPostfix]
private static void AfterPrint(DatabaseApp __instance, bool __state)
{
//IL_0038: Unknown result type (might be due to invalid IL or missing references)
//IL_003e: Expected O, but got Unknown
if (__state && ((CruncherAppContent)__instance).controller.printedDocument != null)
{
int value = Plugin.PrintCost.Value;
GameplayController.Instance.AddMoney(-value, true, "public records");
ManualLogSource logger = Plugin.Logger;
bool flag = default(bool);
BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(26, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Records printout: charged ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(value);
}
logger.LogInfo(val);
}
}
}
}