Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of TML Heat v1.0.0
Mods/TMLHeat.dll
Decompiled 12 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text.Json; using HarmonyLib; using Il2CppFishNet.Connection; using Il2CppFishNet.Object; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.Doors; using Il2CppScheduleOne.Economy; using Il2CppScheduleOne.Employees; using Il2CppScheduleOne.EntityFramework; using Il2CppScheduleOne.ItemFramework; using Il2CppScheduleOne.Messaging; using Il2CppScheduleOne.NPCs; using Il2CppScheduleOne.Networking; using Il2CppScheduleOne.PlayerScripts; using Il2CppScheduleOne.Police; using Il2CppScheduleOne.Property; using Il2CppSteamworks; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using S1API.Console; using S1API.Entities; using S1API.GameTime; using S1API.Internal.Abstraction; using S1API.Law; using S1API.Leveling; using S1API.Lifecycle; using S1API.Messaging; using S1API.Money; using S1API.PhoneApp; using S1API.Property; using S1API.Saveables; using S1API.UI; using S1API.Utils; using TMLHeat; using UnityEngine; using UnityEngine.AI; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Core), "TML Heat", "1.0.0", "ThatMaddLadd", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("TMLHeat")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+2211298c4493628c0d830ce9e794ecaff23ac061")] [assembly: AssemblyProduct("TMLHeat")] [assembly: AssemblyTitle("TMLHeat")] [assembly: AssemblyVersion("1.0.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 TMLHeat { internal static class Compat { public static T? As<T>(this Object? value) where T : Il2CppObjectBase { if (value == null) { return default(T); } return ((Il2CppObjectBase)value).TryCast<T>(); } } internal static class Json { private static readonly JsonSerializerOptions Options = new JsonSerializerOptions { IncludeFields = true }; public static string Write<T>(T value) { return JsonSerializer.Serialize(value, Options); } public static T? Read<T>(string json) where T : class { try { return JsonSerializer.Deserialize<T>(json, Options); } catch (Exception ex) when (((ex is JsonException || ex is NotSupportedException) ? 1 : 0) != 0) { return null; } } } public sealed class Core : MelonMod { internal static Instance Log { get; private set; } public override void OnInitializeMelon() { Log = ((MelonBase)this).LoggerInstance; Settings.Load(); Lang.Load(Settings.Language); GameLifecycle.OnPreLoad += Heat.Detach; GameLifecycle.OnLoadComplete += Heat.Attach; TimeManager.OnHourPass = (Action)Delegate.Combine(TimeManager.OnHourPass, new Action(Heat.OnHour)); TimeManager.OnDayPass = (Action)Delegate.Combine(TimeManager.OnDayPass, new Action(Heat.OnDay)); LevelManager.OnRankUp += delegate { Heat.CheckUnlock(force: false); }; } public override void OnUpdate() { Sync.Update(); } internal static void Debug(string message) { if (Settings.DebugLogs) { Log.Msg(message); } } } internal static class Heat { public const float Max = 100f; private const float WarningReset = 10f; private const float DealProximity = 8f; private const int HistoryDays = 7; private static readonly Random Rng = new Random(); private static readonly HashSet<NPC> Hooked = new HashSet<NPC>(); private static readonly Dictionary<string, int> LastWitnessed = new Dictionary<string, int>(); private static bool _bribeOffered; private static bool _forceSting; private const int LoyaltyBonusDays = 3; private const float TurfEvidenceWeight = 0.25f; private const int PatrolSize = 2; private static HeatData? Data => HeatSave.Instance?.Data; private static bool IsHost => !Sync.IsClient; private static int Today => TimeManager.ElapsedDays; public static bool Active { get { if (!Sync.IsClient) { return Data?.Unlocked ?? false; } return Sync.Mirror.Unlocked; } } public static float Value { get { if (!Sync.IsClient) { return Data?.Heat ?? 0f; } return Sync.Mirror.Heat; } } public static int Trust { get { if (!Sync.IsClient) { return Data?.Trust ?? 0; } return Sync.Mirror.Trust; } } public static int EvidenceCount { get { if (!Sync.IsClient) { return Data?.Evidence.Count ?? 0; } return Sync.Mirror.Evidence; } } public static int SnitchingCount { get { if (!Sync.IsClient) { return Data?.SnitchingEmployees.Count ?? 0; } return Sync.Mirror.Snitching; } } public static int DetainedCount { get { if (!Sync.IsClient) { return Data?.Detained.Count ?? 0; } return Sync.Mirror.Detained; } } public static bool FavourPending { get { if (!Sync.IsClient) { HeatData? data = Data; if (data == null) { return false; } return data.FavourDay >= 0; } return Sync.Mirror.FavourPending; } } public static bool TipOffReady { get { if (!Sync.IsClient) { if (Data != null) { return Today - Data.LastTipOffDay >= 1; } return false; } return Sync.Mirror.TipOffReady; } } public static IReadOnlyList<Informant> Informants { get { object obj; if (!Sync.IsClient) { obj = Data?.Informants; if (obj == null) { return new List<Informant>(); } } else { obj = Sync.Mirror.Informants; } return (IReadOnlyList<Informant>)obj; } } public static IReadOnlyList<ArrestedDealer> HeldDealers { get { object obj; if (!Sync.IsClient) { obj = Data?.ArrestedDealers; if (obj == null) { return new List<ArrestedDealer>(); } } else { obj = Sync.Mirror.Arrested; } return (IReadOnlyList<ArrestedDealer>)obj; } } public static IReadOnlyList<float> History { get { object obj; if (!Sync.IsClient) { obj = Data?.History; if (obj == null) { return new List<float>(); } } else { obj = Sync.Mirror.History; } return (IReadOnlyList<float>)obj; } } public static Raid? KnownRaid { get { if (!Sync.IsClient) { Raid raid = Data?.Raid; if (raid == null || !raid.Warned) { return null; } return raid; } return Sync.Mirror.Raid; } } public static float PersonalValue { get { Dictionary<string, float> dictionary = (Sync.IsClient ? Sync.Mirror.PlayerHeat : Data?.PlayerHeat); if (dictionary == null || !dictionary.TryGetValue(Sync.LocalCode, out var value)) { return 0f; } return value; } } public static PursuitLevel WantedLevel { get { //IL_000c: Unknown result type (might be due to invalid IL or missing references) Player local = Player.Local; if (local == null) { return (PursuitLevel)0; } return LawManager.GetWantedLevel(local); } } public static float BribeCost => Settings.BribeCostPerLevel * (float)WantedLevel; public static float CleanupCost => Settings.CleanerCost * (float)EvidenceCount; public static event Action? Changed; public static void NotifyChanged() { Sync.MarkDirty(); Heat.Changed?.Invoke(); } public static void Detach() { HeatSave.Instance?.Reset(); Hooked.Clear(); LastWitnessed.Clear(); _bribeOffered = false; _forceSting = false; } public static void HookNpcs() { int count = Hooked.Count; foreach (NPC npc in NPC.All.ToList()) { if (npc is Vance || npc is SwatOfficer || npc.ID.StartsWith("officer") || !Hooked.Add(npc)) { continue; } npc.OnNoticedDrugDealing += delegate(Player? player) { OnWitnessed(npc, player); }; if (npc.Customer.IsCustomer) { npc.Customer.OnDealCompleted += delegate { OnCustomerDeal(npc); }; } if (npc.Dealer.IsDealer) { HookDealer(npc); } } if (Hooked.Count > count) { Core.Debug($"Hooked {Hooked.Count - count} NPCs ({Hooked.Count} total)."); } } public static void Attach() { HookNpcs(); Vance.Refresh(); HeatApp.ApplyVisibility(); if (IsHost) { Raids.Stage(); CheckUnlock(force: false); RestoreHeld(); } Core.Debug(Status()); NotifyChanged(); } public static void CheckUnlock(bool force) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) if (!IsHost) { return; } HeatData data = Data; if (data != null && !data.Unlocked) { FullRank val = default(FullRank); ((FullRank)(ref val))..ctor((Rank)Settings.UnlockRank, Settings.UnlockTier); if (force || Settings.UnlockAtStart || !(LevelManager.CurrentRank < val)) { data.Unlocked = true; Vance.Notify(Strings.Intro, "", ""); HeatApp.ApplyVisibility(); Core.Debug("TML Heat unlocked."); NotifyChanged(); } } } public static void OnHour() { HeatApp.ApplyVisibility(); HookNpcs(); if (!Active) { return; } Player local = Player.Local; if (local != null) { CheckBribeOffer(local); HeatData data = Data; if (data != null && IsHost) { CheckEvidence(data); CheckEmployees(data); CheckRaid(data); CheckInvestigations(data); CheckPatrols(data); NotifyChanged(); } } } public static void OnDay() { if (!IsHost) { return; } HeatData data = Data; if (data == null || !data.Unlocked) { return; } data.History.Add(data.Heat); if (data.History.Count > 7) { data.History.RemoveAt(0); } Add(0f - Settings.HeatDecayPerDay); foreach (string item in data.PlayerHeat.Keys.ToList()) { AddPersonal(item, 0f - Settings.HeatDecayPerDay); } data.Evidence.RemoveAll((Evidence e) => Today - e.Day >= Settings.EvidenceLifetimeDays); data.Informants.RemoveAll((Informant i) => Today - i.Since >= Settings.InformantQuietDays); Add(Settings.InformantHeatPerDay * (float)data.Informants.Count); foreach (NPC item2 in NPC.All.ToList()) { if (item2.Customer.IsCustomer && item2.Relationship.IsUnlocked && item2.Relationship.Normalized < Settings.SnitchRelationship && Roll(Settings.SnitchChancePerDay)) { AddInformant(item2); } } ReleaseDetained(data); CheckDealers(data); CheckUndercover(data); CheckFavour(data); CheckBetrayal(data); ScheduleRaid(data, force: false); Core.Debug("New day. " + Status()); NotifyChanged(); } public static void HandleRequest(string playerCode, string action, string arg) { if (Active) { string text = Apply(action, arg, playerCode); if (text.Length > 0) { Vance.Notify(text, playerCode, ""); } Core.Debug($"Handled '{action}' from {playerCode}."); } } public static void ApplyEffect(string effect) { Player local = Player.Local; if (local == null) { return; } string[] array = effect.Split(':', 3); switch (array[0]) { case "clear": LawManager.ClearWantedLevel(local); break; case "sting": case "wanted": local.CrimeData.RecordLastKnownPosition(true); LawManager.SetWantedLevel(local, (PursuitLevel)2); break; case "investigate": TryInvestigate(local); break; case "arrest": { Player local2 = Player.Local; if (local2 != null) { local2.Arrest_Server(); } break; } case "raid": { if (array.Length == 3 && int.TryParse(array[1], out var result)) { PropertyWrapper currentProperty = local.CurrentProperty; if (((currentProperty != null) ? ((BaseProperty)currentProperty).PropertyName : null) == array[2]) { Hud.Countdown(array[2], result); } } break; } } } public static Snapshot Snapshot(List<SyncMessage> messages) { HeatData heatData = Data ?? new HeatData(); Snapshot obj = new Snapshot { Unlocked = heatData.Unlocked, Heat = heatData.Heat, Trust = heatData.Trust, Evidence = heatData.Evidence.Count, Snitching = heatData.SnitchingEmployees.Count, Detained = heatData.Detained.Count, FavourPending = (heatData.FavourDay >= 0), TipOffReady = (Today - heatData.LastTipOffDay >= 1), Informants = heatData.Informants, Arrested = heatData.ArrestedDealers, PlayerHeat = heatData.PlayerHeat, History = heatData.History }; Raid raid = heatData.Raid; obj.Raid = ((raid != null && raid.Warned) ? raid : null); obj.Messages = messages; return obj; } public static string Status() { //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: Unknown result type (might be due to invalid IL or missing references) if (Sync.IsClient) { return $"Client | {(Active ? "Unlocked" : "Locked")} | Crew heat {Value:0.#} | Your heat {PersonalValue:0.#} | Trust {Trust} | Wanted {WantedLevel}"; } HeatData data = Data; if (data == null) { return "No save loaded."; } Raid raid = data.Raid; string value = ((raid != null) ? $"{raid.Property} day {raid.Day} at {raid.Time:0000}{(raid.Warned ? " (warned)" : "")}" : "none"); return $"{(data.Unlocked ? "Unlocked" : "Locked")} | Crew heat {data.Heat:0.#} | Your heat {PersonalValue:0.#} | Trust {data.Trust} | Wanted {WantedLevel} | Evidence {data.Evidence.Count} | Informants {List<Informant>(data.Informants, (Informant i) => i.Name)} | Snitching staff {data.SnitchingEmployees.Count} | Detained {List<DetainedEmployee>(data.Detained, (DetainedEmployee d) => $"{d.Name} (day {d.ReleaseDay})")} | Held dealers {List<ArrestedDealer>(data.ArrestedDealers, (ArrestedDealer a) => a.Name + (a.Seized ? " (seized)" : ""))} | Undercover {data.Undercover?.Name ?? "none"} | Favour {((data.FavourDay >= 0) ? "pending" : "none")} | Betray streak {data.BetrayStreak} | Raid {value}{(Raids.Running ? " (in progress)" : "")} | Day {Today} {TimeManager.CurrentTime:0000}"; static string List<T>(IEnumerable<T> items, Func<T, string> name) where T : notnull { if (!items.Any()) { return "none"; } return string.Join(", ", items.Select(name)); } } public static void SetHeat(float value) { HeatData data = Data; if (data != null) { data.Heat = Math.Clamp(value, 0f, 100f); CheckWarnings(data); NotifyChanged(); } } public static void SetPersonalHeat(float value) { HeatData data = Data; if (data != null) { data.PlayerHeat[Sync.LocalCode] = Math.Clamp(value, 0f, 100f); NotifyChanged(); } } public static void SetTrust(int value) { HeatData data = Data; if (data != null) { data.Trust = Math.Max(0, value); NotifyChanged(); } } public static void ForceInformant(string npcId) { NPC val = (NPC)((npcId.Length > 0) ? ((object)NPC.Get(npcId)) : ((object)RandomNpc((NPC n) => n.Customer.IsCustomer && Informants.All((Informant i) => i.NpcId != n.ID)))); if (val == null) { Core.Log.Warning((npcId.Length > 0) ? ("No NPC with id '" + npcId + "'.") : "No customers left to turn."); return; } AddInformant(val); NotifyChanged(); } public static void ForceWitness(string npcId) { NPC val = (NPC)((npcId.Length > 0) ? ((object)NPC.Get(npcId)) : ((object)RandomNpc((NPC n) => !n.ID.StartsWith("officer") && !(n is Vance) && !(n is SwatOfficer)))); if (val != null) { Player local = Player.Local; if (local != null) { LastWitnessed.Remove(val.ID); OnWitnessed(val, local); return; } } Core.Log.Warning("No NPC with id '" + npcId + "'."); } public static void ForcePlayerDeal() { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) Player local = Player.Local; if (local != null) { RecordDeal(Settings.DealHeat, ((object)local.CurrentRegion/*cast due to .constrained prefix*/).ToString(), local.Name, Sync.LocalCode, local.Position); } } public static void ForceDealerDeal() { NPC val = RandomNpc((NPC n) => n.Dealer.IsDealer && n.Dealer.IsRecruited()) ?? RandomNpc((NPC n) => n.Dealer.IsDealer); if (val == null) { Core.Log.Warning("No dealers found."); } else { OnDealerDeal(val, forced: true); } } public static void AddEvidence(int count) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) HeatData data = Data; if (data == null) { return; } Player local = Player.Local; if (local != null) { for (int i = 0; i < count; i++) { data.Evidence.Add(NewEvidence(((object)local.CurrentRegion/*cast due to .constrained prefix*/).ToString(), Sync.LocalCode, local.Position)); } NotifyChanged(); } } public static void FindEvidenceNow() { HeatData data = Data; if (data == null || data.Evidence.Count == 0) { Core.Log.Warning("No evidence to find."); return; } FoundEvidence(data, data.Evidence[0]); NotifyChanged(); } public static void CheckEmployeesNow() { HeatData data = Data; if (data != null) { CheckEmployees(data); } NotifyChanged(); } public static void ForceRaid(string propertyName) { HeatData data = Data; if (data != null) { object obj; if (propertyName.Length <= 0) { Player local = Player.Local; PropertyWrapper val = ((local != null) ? local.CurrentProperty : null); obj = ((val != null) ? FindProperty(((BaseProperty)val).PropertyName) : RandomOwnedProperty()); } else { obj = FindProperty(propertyName); } Property val2 = (Property)obj; if ((Object)(object)val2 == (Object)null) { Core.Log.Warning((propertyName.Length > 0) ? ("You don't own a property called '" + propertyName + "'.") : "You don't own any properties to raid."); return; } data.Raid = null; Raids.Start(val2); NotifyChanged(); } } public static void ScheduleRaidNow() { HeatData data = Data; if (data != null) { data.Raid = null; ScheduleRaid(data, force: true); NotifyChanged(); } } public static void ForceInvestigation() { Player local = Player.Local; if (local != null) { Investigate(local); } } public static void ForceBribeOffer() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 Player local = Player.Local; if (local != null) { if ((int)LawManager.GetWantedLevel(local) < 2) { local.CrimeData.RecordLastKnownPosition(true); LawManager.SetWantedLevel(local, (PursuitLevel)2); } _bribeOffered = false; CheckBribeOffer(local); } } public static void SetWanted(int level) { Player local = Player.Local; if (local != null) { local.CrimeData.RecordLastKnownPosition(true); LawManager.SetWantedLevel(local, (PursuitLevel)Math.Clamp(level, 0, 4)); } } public static void ArmSting() { _forceSting = true; } public static void ResetSave() { HeatSave.Instance?.Reset(); _bribeOffered = false; NotifyChanged(); } internal static void FinishRaid(Property property, bool pursued) { HeatData data = Data; if (data == null) { return; } Add(0f - Settings.RaidHeatRelief); int num = Detain(data, property); string propertyName = property.PropertyName; if (pursued) { Vance.Notify(Strings.RaidInside(propertyName, num), "", ""); } else { float onlineBalance = Money.GetOnlineBalance(); float num2 = Math.Min(onlineBalance, Settings.RaidFineBase + onlineBalance * Settings.RaidFinePercent); if (num2 > 0f) { Money.CreateOnlineTransaction(Strings.SeizureTitle, 0f - num2, 1f, propertyName); } Vance.Notify(Strings.RaidSeized(propertyName, num2, num), "", ""); } Core.Debug($"Raid on {propertyName} finished. Pursued {pursued}, detained {num}."); NotifyChanged(); } private static void OnCustomerDeal(NPC customer) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) if (!Active || !IsHost) { return; } HeatData data = Data; if (data == null) { return; } Player val = NearestPlayer(customer.Position, 8f); if (!((Object)(object)val == (Object)null)) { RecordDeal(Settings.DealHeat, RegionOf(((Component)val).transform.position), val.PlayerCode, val.PlayerCode, ((Component)val).transform.position); if (data.Undercover?.NpcId == customer.ID) { BurnUndercover(data, val.PlayerCode); } } } private static void HookDealer(NPC npc) { Dealer val = ((Object?)(object)NPCManager.GetNPC(npc.ID)).As<Dealer>(); if (val != null) { val.OnCompleteDeal += DelegateSupport.ConvertDelegate<Action>((Delegate)(Action)delegate { OnDealerDeal(npc, forced: false); }); } } private static void OnDealerDeal(NPC npc, bool forced) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) if (Active && IsHost && (forced || npc.Dealer.IsRecruited())) { RecordDeal(Settings.DealerDealHeat, Strings.DealerTurf(npc.FullName), npc.FullName, npc.ID, npc.Position); } } private static void RecordDeal(float heat, string location, string who, string source, Vector3 position) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) HeatData data = Data; if (data != null) { Add(heat); AddPersonal(source, heat); data.Evidence.Add(NewEvidence(location, source, position)); Core.Debug($"{who} completed a deal in {location}, heat {Value:0.#}."); NotifyChanged(); } } private static Evidence NewEvidence(string region, string source, Vector3 position) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) return new Evidence { Region = region, Source = source, Day = Today, X = position.x, Y = position.y, Z = position.z }; } private static void OnWitnessed(NPC npc, Player? player) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) if (!Active || !IsHost || player == null) { return; } int num = Today * 24 + TimeManager.CurrentTime / 100; if (!LastWitnessed.TryGetValue(npc.ID, out var value) || value != num) { LastWitnessed[npc.ID] = num; Add(Settings.WitnessHeat); Player? obj = NearestPlayer(player.Position, 1f); AddPersonal(((obj != null) ? obj.PlayerCode : null) ?? "", Settings.WitnessHeat); Core.Debug($"{npc.FullName} saw {player.Name} deal, heat {Value:0.#}."); if (Roll(Settings.WitnessInformantChance)) { AddInformant(npc); } NotifyChanged(); } } private static void AddInformant(NPC npc) { HeatData data = Data; if (data != null && !data.Informants.Any((Informant i) => i.NpcId == npc.ID)) { data.Informants.Add(new Informant { NpcId = npc.ID, Name = npc.FullName, Since = Today }); Core.Debug(npc.FullName + " (" + npc.ID + ") is now an informant."); Vance.Notify(Strings.InformantTip(npc.FullName, Settings.PayOffCost), "", "", "payoff:" + npc.ID, "ignore"); } } private static void CheckEvidence(HeatData data) { float num = Math.Min(1f, (float)LawManager.ActiveOfficerCount / 4f); foreach (Evidence item in data.Evidence) { float num2 = 1f - (float)(Today - item.Day) / (float)Settings.EvidenceLifetimeDays; if (Roll(Settings.EvidenceFindChance * num2 * num)) { FoundEvidence(data, item); break; } } } private static void FoundEvidence(HeatData data, Evidence evidence) { data.Evidence.Remove(evidence); Add(Settings.EvidenceHeat); AddPersonal(evidence.Source, Settings.EvidenceHeat); Core.Debug($"Police found evidence in {evidence.Region}, heat {Value:0.#}."); Vance.Notify(Strings.EvidenceFound(evidence.Region), "", ""); } private static void ScheduleRaid(HeatData data, bool force) { if (data.Raid != null || Raids.Running || (!force && (data.Heat < Settings.RaidThreshold || !Roll(Settings.RaidChancePerDay)))) { return; } Property val = RandomOwnedProperty(); if (!((Object)(object)val == (Object)null)) { Raid raid = (data.Raid = new Raid { Property = val.PropertyName, Day = Today, Time = (force ? Math.Min(2359, TimeManager.CurrentTime + 100) : (Rng.Next(9, 21) * 100)), Warned = (data.Trust >= Settings.RaidWarningTrust) }); Core.Debug($"Raid scheduled on {raid.Property} at {raid.Time:0000}, warned {raid.Warned}."); if (raid.Warned) { Vance.Notify(Strings.RaidWarning(raid.Property, raid.Time), "", ""); } } } private static void CheckRaid(HeatData data) { Raid raid = data.Raid; if (raid != null && !Raids.Running && (Today != raid.Day || TimeManager.CurrentTime >= raid.Time)) { data.Raid = null; Property val = FindProperty(raid.Property); if (val != null) { Raids.Start(val); } } } private static void CheckInvestigations(HeatData data) { float investigationThreshold = Settings.InvestigationThreshold; Enumerator<Player> enumerator = Player.PlayerList.GetEnumerator(); while (enumerator.MoveNext()) { Player current = enumerator.Current; if (!((Object)(object)current == (Object)null) && data.PlayerHeat.TryGetValue(current.PlayerCode, out var value) && !(value < investigationThreshold) && Roll(Settings.InvestigationChance * Pressure(value, investigationThreshold))) { Vance.Notify("", current.PlayerCode, "investigate"); } } } private static void TryInvestigate(Player player) { if (!LawManager.IsPlayerWanted(player) && !player.IsSleeping && !player.IsArrested && player.CurrentProperty == null) { Investigate(player); } } private static void Investigate(Player player) { player.CrimeData.RecordLastKnownPosition(true); LawManager.SetWantedLevel(player, (PursuitLevel)1); Core.Debug("Police started an investigation."); } private static void CheckBribeOffer(Player player) { //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_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 PursuitLevel wantedLevel = LawManager.GetWantedLevel(player); if ((int)wantedLevel == 0) { _bribeOffered = false; } else if (!_bribeOffered && (int)wantedLevel >= 2) { _bribeOffered = true; Vance.Send(Strings.BribeOffer(BribeCost), Vance.Reply("bribe"), Vance.Reply("ignore")); } } private static bool Sting(HeatData data, string playerCode) { float num = Math.Max(Settings.StingChanceMin, Settings.StingChance - (float)data.Trust * Settings.StingTrustReduction); if (!_forceSting && !Roll(num)) { data.Trust++; Core.Debug($"Bribe paid, trust {data.Trust}."); return false; } _forceSting = false; data.Trust = 0; Add(Settings.StingHeat); AddPersonal(playerCode, Settings.StingHeat); Vance.Notify("", playerCode, "sting"); Core.Debug($"Bribe was a sting ({num:P0} chance)."); NotifyChanged(); return true; } private static void CheckWarnings(HeatData data) { int num = Settings.WarningLevels.Where((int level) => data.Heat >= (float)level).DefaultIfEmpty(0).Max(); if (num > data.LastWarning) { data.LastWarning = num; Vance.Notify(Strings.Warning(num), "", ""); } else if (data.Heat < (float)data.LastWarning - 10f) { data.LastWarning = num; } } private static Player? NearestPlayer(Vector3 position, float range) { //IL_0027: 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) Player result = null; float num = range; Enumerator<Player> enumerator = Player.PlayerList.GetEnumerator(); while (enumerator.MoveNext()) { Player current = enumerator.Current; if (!((Object)(object)current == (Object)null)) { float num2 = Vector3.Distance(((Component)current).transform.position, position); if (!(num2 > num)) { num = num2; result = current; } } } return result; } private static string RegionOf(Vector3 position) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_002f: 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) Player? obj = Player.All.OrderBy((Player p) => Vector3.Distance(p.Position, position)).FirstOrDefault(); return ((obj != null) ? ((object)obj.CurrentRegion/*cast due to .constrained prefix*/).ToString() : null) ?? ""; } private static Property? FindProperty(string name) { Enumerator<Property> enumerator = Property.OwnedProperties.GetEnumerator(); while (enumerator.MoveNext()) { Property current = enumerator.Current; if ((Object)(object)current != (Object)null && current.PropertyName == name) { return current; } } return null; } private static Property? RandomOwnedProperty() { List<Property> ownedProperties = Property.OwnedProperties; if (ownedProperties.Count != 0) { return ownedProperties[Rng.Next(ownedProperties.Count)]; } return null; } private static NPC? RandomNpc(Func<NPC, bool> filter) { return (from _ in NPC.All.Where(filter) orderby Rng.Next() select _).FirstOrDefault(); } private static bool TryPay(float cost) { if (Money.GetCashBalance() < cost) { return false; } Money.ChangeCashBalance(0f - cost, true, true); return true; } private static void Add(float amount) { HeatData data = Data; if (data != null) { data.Heat = Math.Clamp(data.Heat + amount, 0f, 100f); CheckWarnings(data); } } private static void AddPersonal(string playerCode, float amount) { if (playerCode.Length != 0) { HeatData data = Data; if (data != null) { data.PlayerHeat.TryGetValue(playerCode, out var value); data.PlayerHeat[playerCode] = Math.Clamp(value + amount, 0f, 100f); } } } private static float Pressure(float heat, float threshold) { return (heat - threshold + 1f) / (100f - threshold + 1f); } private static bool Roll(float chance) { return Rng.NextDouble() < (double)chance; } public static bool IsDealerHeld(string npcId) { if (IsHost) { return Data?.ArrestedDealers.Any((ArrestedDealer d) => d.NpcId == npcId) ?? false; } return false; } public static void ReleaseAll() { HeatData data = Data; if (data == null) { return; } foreach (DetainedEmployee item in data.Detained.ToList()) { ReleaseEmployee(data, item); } foreach (ArrestedDealer item2 in data.ArrestedDealers.ToList()) { FreeDealer(data, item2); } NotifyChanged(); } public static void ArrestDealerNow(string npcId) { HeatData data = Data; if (data == null) { return; } Dealer val = ((npcId.Length > 0) ? FindDealer(npcId) : ((IEnumerable<Dealer>)Dealer.AllPlayerDealers.ToArray()).FirstOrDefault((Func<Dealer, bool>)((Dealer d) => (Object)(object)d != (Object)null && data.ArrestedDealers.All((ArrestedDealer a) => a.NpcId != ((NPC)d).ID)))); if ((Object)(object)val == (Object)null) { Core.Log.Warning((npcId.Length > 0) ? ("No dealer with id '" + npcId + "'.") : "No recruited dealers to arrest."); return; } ArrestDealer(data, val); NotifyChanged(); } public static void SeizeHeldNow() { HeatData data = Data; if (data == null) { return; } foreach (ArrestedDealer item in data.ArrestedDealers.Where((ArrestedDealer d) => !d.Seized).ToList()) { SeizeDealer(data, item); } NotifyChanged(); } private static void RestoreHeld() { HeatData data = Data; if (data == null) { return; } foreach (DetainedEmployee item in data.Detained) { Employee val = FindEmployee(item.NpcId); if (val != null) { SetDetained(val, detained: true); } } foreach (ArrestedDealer arrestedDealer in data.ArrestedDealers) { Dealer? obj = FindDealer(arrestedDealer.NpcId); if (obj != null) { ((NPC)obj).SetVisible(false, true); } } } private static void CheckEmployees(HeatData data) { Enumerator<Property> enumerator = Property.OwnedProperties.GetEnumerator(); while (enumerator.MoveNext()) { Property current = enumerator.Current; if ((Object)(object)current == (Object)null) { continue; } Enumerator<Employee> enumerator2 = current.Employees.GetEnumerator(); while (enumerator2.MoveNext()) { Employee employee = enumerator2.Current; if ((Object)(object)employee == (Object)null || data.Detained.Any((DetainedEmployee d) => d.NpcId == ((NPC)employee).ID)) { continue; } EmployeeHome home = employee.GetHome(); if (home == null) { continue; } float num = Loyalty(data, ((NPC)employee).ID); bool flag = data.SnitchingEmployees.Contains(((NPC)employee).ID); if (home.GetCashSum() > 0f) { if (flag) { data.SnitchingEmployees.Remove(((NPC)employee).ID); } } else if (!Roll(num)) { Add(Settings.EmployeeSnitchHeat); Core.Debug($"{((NPC)employee).FullName}'s locker is empty, heat {Value:0.#} (loyalty {num:P0})."); if (!flag) { data.SnitchingEmployees.Add(((NPC)employee).ID); Vance.Notify(Strings.EmployeeSnitch(((NPC)employee).FullName, current.PropertyName), "", ""); } } } } } private static int Detain(HeatData data, Property property) { int releaseDay = Today + Rng.Next(Settings.DetainMinDays, Settings.DetainMaxDays + 1); int num = 0; Enumerator<Employee> enumerator = property.Employees.GetEnumerator(); while (enumerator.MoveNext()) { Employee employee = enumerator.Current; if (!((Object)(object)employee == (Object)null) && !data.Detained.Any((DetainedEmployee d) => d.NpcId == ((NPC)employee).ID)) { data.Detained.Add(new DetainedEmployee { NpcId = ((NPC)employee).ID, Name = ((NPC)employee).FullName, ReleaseDay = releaseDay }); data.SnitchingEmployees.Remove(((NPC)employee).ID); SetDetained(employee, detained: true); num++; } } return num; } private static void ReleaseDetained(HeatData data) { foreach (DetainedEmployee item in data.Detained.Where((DetainedEmployee d) => d.ReleaseDay <= Today).ToList()) { ReleaseEmployee(data, item); } } private static void ReleaseEmployee(HeatData data, DetainedEmployee entry) { data.Detained.Remove(entry); Employee val = FindEmployee(entry.NpcId); if (val != null) { SetDetained(val, detained: false); } ReturnFromCustody(data, entry.NpcId, entry.Name); } private static void SetDetained(Employee employee, bool detained) { employee.SetWaitOutside(detained); ((NPC)employee).SetVisible(!detained, true); } private static void CheckDealers(HeatData data) { foreach (ArrestedDealer item in data.ArrestedDealers.ToList()) { if (!item.Seized && item.Day < Today) { SeizeDealer(data, item); } if (item.ReleaseDay <= Today) { FreeDealer(data, item); } } Enumerator<Dealer> enumerator2 = Dealer.AllPlayerDealers.GetEnumerator(); while (enumerator2.MoveNext()) { Dealer current2 = enumerator2.Current; if ((Object)(object)current2 != (Object)null && current2.IsRecruited) { Loyalty(data, ((NPC)current2).ID); } } if (data.Heat < Settings.DealerArrestThreshold) { return; } float num = Pressure(data.Heat, Settings.DealerArrestThreshold); foreach (Dealer dealer in Dealer.AllPlayerDealers.ToArray()) { if (!((Object)(object)dealer == (Object)null) && dealer.IsRecruited && !data.ArrestedDealers.Any((ArrestedDealer a) => a.NpcId == ((NPC)dealer).ID)) { int num2 = data.Evidence.Count((Evidence e) => e.Source == ((NPC)dealer).ID); if (Roll(Settings.DealerArrestChance * num * (1f + (float)num2 * 0.25f))) { ArrestDealer(data, dealer); } } } } private static void ArrestDealer(HeatData data, Dealer dealer) { int num = Today + Rng.Next(Settings.DetainMinDays, Settings.DetainMaxDays + 1); data.ArrestedDealers.Add(new ArrestedDealer { NpcId = ((NPC)dealer).ID, Name = ((NPC)dealer).FullName, Day = Today, ReleaseDay = num }); ((NPC)dealer).SetVisible(false, true); Core.Debug($"{((NPC)dealer).FullName} was arrested until day {num}."); Vance.Notify(Strings.DealerArrested(((NPC)dealer).FullName, Settings.BailCost), "", "", "bail:" + ((NPC)dealer).ID, "ignore"); } private static void SeizeDealer(HeatData data, ArrestedDealer entry) { entry.Seized = true; Dealer val = FindDealer(entry.NpcId); if (val != null) { val.SetCash(0f); Enumerator<ItemSlot> enumerator = val.ItemSlots.GetEnumerator(); while (enumerator.MoveNext()) { ItemSlot current = enumerator.Current; if (current != null) { current.ClearStoredInstance(false); } } } Vance.Notify(Strings.DealerSeized(entry.Name), "", ""); Core.Debug(entry.Name + "'s cash and product were seized."); } private static void FreeDealer(HeatData data, ArrestedDealer entry) { data.ArrestedDealers.Remove(entry); Dealer? obj = FindDealer(entry.NpcId); if (obj != null) { ((NPC)obj).SetVisible(true, true); } if (entry.Seized) { ReturnFromCustody(data, entry.NpcId, entry.Name); } } private static void ReturnFromCustody(HeatData data, string npcId, string name) { float num = Loyalty(data, npcId); if (Roll(Settings.ReleaseInformantChance * (1f - num))) { NPC val = NPC.Get(npcId); if (val != null) { Vance.Notify(Strings.StaffTurned(name), "", ""); AddInformant(val); return; } } data.StaffSince[npcId] = data.StaffSince[npcId] - 3; Vance.Notify(Strings.StaffLoyal(name), "", ""); } private static float Loyalty(HeatData data, string npcId) { if (!data.StaffSince.TryGetValue(npcId, out var value)) { value = (data.StaffSince[npcId] = Today); } return Math.Min(1f, (float)(Today - value) / (float)Settings.LoyaltyDays) * Settings.MaxLoyalty; } private static Employee? FindEmployee(string id) { return ((Object?)(object)NPCManager.GetNPC(id)).As<Employee>(); } private static Dealer? FindDealer(string id) { return ((Object?)(object)NPCManager.GetNPC(id)).As<Dealer>(); } public static string PayOff(string npcId) { return Perform("payoff", npcId); } public static string LoseFile() { return Perform("losefile", ""); } public static string MakeItGoAway() { return Perform("bribe", ""); } public static string CallCleaner() { return Perform("cleaner", ""); } public static string DoFavour() { return Perform("favour", ""); } public static string DeclineFavour() { return Perform("decline", ""); } public static string TipOff() { return Perform("tipoff", ""); } public static string Bail(string npcId) { return Perform("bail", npcId); } public static void AskFavourNow() { HeatData data = Data; if (data != null) { data.FavourDay = -1; AskFavour(data); NotifyChanged(); } } public static void BetrayNow() { HeatData data = Data; if (data != null) { Betray(data); } NotifyChanged(); } private static string Perform(string action, string arg) { //IL_0156: Unknown result type (might be due to invalid IL or missing references) string text; switch (action) { case "payoff": if (Informants.All((Informant i) => i.NpcId != arg)) { text = Strings.AlreadyQuiet; break; } goto default; case "losefile": if (Value <= 0f) { text = Strings.FileClean; break; } goto default; case "bribe": if ((int)WantedLevel == 0) { text = Strings.NotWanted; break; } goto default; case "cleaner": if (EvidenceCount == 0) { text = Strings.NothingToClean; break; } goto default; case "favour": case "decline": if (!FavourPending) { text = Strings.FavourNone; break; } goto default; case "tipoff": if (!TipOffReady) { text = Strings.TipOffCooldown; break; } goto default; case "bail": if (HeldDealers.All((ArrestedDealer d) => d.NpcId != arg || d.Seized)) { text = Strings.DealerNotHeld; break; } goto default; default: text = null; break; } string text2 = text; if (text2 != null) { return text2; } float cost = action switch { "payoff" => Settings.PayOffCost, "losefile" => Settings.LoseFileCost, "bribe" => BribeCost, "cleaner" => CleanupCost, "favour" => Settings.FavourCost, "tipoff" => Settings.TipOffCost, "bail" => Settings.BailCost, _ => 0f, }; if (!TryPay(cost)) { return Strings.NotEnoughCash(cost); } if (!Sync.IsClient) { return Apply(action, arg, Sync.LocalCode); } Sync.Request(action, arg); return Strings.RequestSent; } private static string Apply(string action, string arg, string playerCode) { HeatData data = Data; if (data == null) { return ""; } string result; switch (action) { case "payoff": { Informant informant = data.Informants.FirstOrDefault((Informant i) => i.NpcId == arg); if (informant == null) { return Strings.AlreadyQuiet; } data.Informants.Remove(informant); result = Strings.PaidOff(informant.Name); break; } case "losefile": if (Sting(data, playerCode)) { return Strings.Sting; } Add(0f - Settings.LoseFileHeat); AddPersonal(playerCode, 0f - Settings.LoseFileHeat); result = Strings.FileLost; break; case "bribe": if (Sting(data, playerCode)) { return Strings.Sting; } Vance.Notify("", playerCode, "clear"); result = Strings.Cleared; break; case "cleaner": data.Evidence.Clear(); result = Strings.Cleaned; break; case "favour": data.FavourDay = -1; data.Trust += Settings.FavourTrust; result = Strings.FavourThanks; break; case "decline": data.FavourDay = -1; data.Trust = Math.Max(0, data.Trust - 1); result = Strings.FavourDeclined; break; case "tipoff": if (Sting(data, playerCode)) { return Strings.Sting; } data.LastTipOffDay = Today; Add(0f - Settings.TipOffHeat); result = Strings.TipOffDone; break; case "bail": { ArrestedDealer arrestedDealer = data.ArrestedDealers.FirstOrDefault((ArrestedDealer d) => d.NpcId == arg && !d.Seized); if (arrestedDealer == null) { return Strings.DealerNotHeld; } FreeDealer(data, arrestedDealer); result = Strings.DealerBailed(arrestedDealer.Name); break; } default: return ""; } Core.Debug($"{action} done for {playerCode}. {Status()}"); NotifyChanged(); return result; } private static void CheckFavour(HeatData data) { if (data.FavourDay >= 0 && data.FavourDay < Today) { data.FavourDay = -1; data.Trust = Math.Max(0, data.Trust - 1); Core.Debug("Vance's favour went unanswered."); } if (data.FavourDay < 0 && Roll(Settings.FavourChance)) { AskFavour(data); } } private static void AskFavour(HeatData data) { data.FavourDay = Today; Vance.Notify(Strings.FavourAsk(Settings.FavourCost), "", "", "favour", "decline"); } private static void CheckBetrayal(HeatData data) { data.BetrayStreak = ((data.Trust <= Settings.BetrayTrust && data.Heat >= Settings.BetrayHeat) ? (data.BetrayStreak + 1) : 0); if (data.BetrayStreak >= Settings.BetrayDays) { Betray(data); } } private static void Betray(HeatData data) { data.BetrayStreak = 0; data.Trust = 0; data.Raid = null; ScheduleRaid(data, force: true); Vance.Notify(Strings.Betrayal, "", ""); Core.Debug("Vance sold you out."); } public static bool IsNervous(string npcId) { if (IsHost) { HeatData data = Data; if (data != null && data.Unlocked && !(data.Heat < Settings.NervousThreshold)) { return StableRoll(npcId, Today) < (double)(Settings.NervousChance * Pressure(data.Heat, Settings.NervousThreshold)); } } return false; } public static int NervousCount() { return NPC.All.Count((NPC n) => n.Customer.IsCustomer && IsNervous(n.ID)); } public static void ForceUndercover(string npcId) { HeatData data = Data; if (data != null) { NPC val = (NPC)((npcId.Length > 0) ? ((object)NPC.Get(npcId)) : ((object)RandomNpc((NPC n) => n.Customer.IsCustomer && n.Relationship.IsUnlocked))); if (val == null) { Core.Log.Warning((npcId.Length > 0) ? ("No NPC with id '" + npcId + "'.") : "No unlocked customers to plant."); return; } PlantUndercover(data, val); NotifyChanged(); } } public static void StartPatrolNow() { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) HeatData data = Data; if (data != null) { Player local = Player.Local; if (local != null && !SendPatrol((data.Evidence.Count > 0) ? EvidencePosition(data.Evidence[Rng.Next(data.Evidence.Count)]) : local.Position)) { Core.Log.Warning("No patrol could be started."); } } } private static void CheckPatrols(HeatData data) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) if (!(data.Heat < Settings.PatrolThreshold) && data.Evidence.Count != 0 && Roll(Settings.PatrolChance * Pressure(data.Heat, Settings.PatrolThreshold))) { SendPatrol(EvidencePosition(data.Evidence[Rng.Next(data.Evidence.Count)])); } } private static bool SendPatrol(Vector3 spot) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) FootPatrolRoute val = (from r in LawManager.GetAllFootPatrolRoutes() orderby Vector3.Distance(r.Position, spot) select r).FirstOrDefault(); if (val == null || LawManager.StartFootPatrol(val, 2) == null) { return false; } Core.Debug("Sent a patrol to " + val.RouteName + "."); return true; } private static void CheckUndercover(HeatData data) { Undercover undercover = data.Undercover; if (undercover != null && Today - undercover.Since >= Settings.UndercoverDays) { data.Undercover = null; } if (data.Undercover == null && !(data.Heat < Settings.UndercoverThreshold) && Roll(Settings.UndercoverChance)) { NPC val = RandomNpc((NPC n) => n.Customer.IsCustomer && n.Relationship.IsUnlocked); if (val != null) { PlantUndercover(data, val); } } } private static void PlantUndercover(HeatData data, NPC npc) { data.Undercover = new Undercover { NpcId = npc.ID, Name = npc.FullName, Since = Today, Warned = (data.Trust >= Settings.UndercoverWarnTrust) }; Core.Debug($"{npc.FullName} ({npc.ID}) is now undercover, warned {data.Undercover.Warned}."); if (data.Undercover.Warned) { Vance.Notify(Strings.UndercoverWarning(npc.FullName), "", ""); } } private static void BurnUndercover(HeatData data, string playerCode) { string name = data.Undercover.Name; data.Undercover = null; Add(Settings.UndercoverHeat); AddPersonal(playerCode, Settings.UndercoverHeat); Vance.Notify("", playerCode, "wanted"); Vance.Notify(Strings.UndercoverBurned(name), "", ""); Core.Debug("Sold to undercover officer " + name + "."); NotifyChanged(); } private static Vector3 EvidencePosition(Evidence evidence) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) return new Vector3(evidence.X, evidence.Y, evidence.Z); } private static double StableRoll(string id, int day) { int num = day * 7919; foreach (char c in id) { num = num * 31 + c; } return (double)((num & 0x7FFFFFFF) % 10000) / 10000.0; } } public class HeatApp : PhoneApp { private static readonly Color Background = new Color(0.08f, 0.08f, 0.09f); private static readonly Color Accent = new Color(0.78f, 0.3f, 0.14f); private static readonly Color Subtle = new Color(0.62f, 0.62f, 0.66f); private static readonly Color BarBack = new Color(0.2f, 0.2f, 0.22f); private static readonly Color Cool = new Color(0.3f, 0.62f, 0.85f); private static readonly Color Hot = new Color(0.92f, 0.26f, 0.12f); private static Sprite? _icon; private static Transform? _iconRoot; private RectTransform? _status; private RectTransform? _actions; private string _note = ""; protected override string AppName => "TMLHeat"; protected override string AppTitle => Strings.AppTitle; protected override string IconLabel => Strings.AppTitle; protected override string IconFileName => "TMLHeat.png"; protected override void OnCreated() { ((PhoneApp)this).OnCreated(); if ((Object)(object)_icon == (Object)null) { _icon = BuildIcon(); } ((PhoneApp)this).SetIconSprite(_icon); } protected override void OnCreatedUI(GameObject container) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown GameObject val = UIFactory.Panel("Root", container.transform, Background, (Vector2?)null, (Vector2?)null, true); HorizontalLayoutGroup obj = val.AddComponent<HorizontalLayoutGroup>(); ((LayoutGroup)obj).padding = new RectOffset(36, 36, 28, 28); ((HorizontalOrVerticalLayoutGroup)obj).spacing = 36f; ((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = true; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = true; _status = Column("Status", val.transform); _actions = Column("Actions", val.transform); Heat.Changed += Refresh; Refresh(); } protected override void OnDestroyed() { Heat.Changed -= Refresh; ((PhoneApp)this).OnDestroyed(); } protected override void OnPhoneClosed() { _note = ""; } private void Refresh() { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_0207: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_025b: Unknown result type (might be due to invalid IL or missing references) //IL_0265: Unknown result type (might be due to invalid IL or missing references) //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_0460: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_status == (Object)null || (Object)(object)_actions == (Object)null) { return; } UIFactory.ClearChildren((Transform)(object)_status); UIFactory.ClearChildren((Transform)(object)_actions); Label((Transform)(object)_status, Strings.AppTitle, 26, (FontStyle)1, Color.white); Label((Transform)(object)_status, Strings.HeatLine(Heat.Value), 18, (FontStyle)0, Color.white); Bar((Transform)(object)_status, Heat.Value / 100f); Label((Transform)(object)_status, Strings.PersonalLine(Heat.PersonalValue), 15, (FontStyle)0, Color.white); Bar((Transform)(object)_status, Heat.PersonalValue / 100f); Label((Transform)(object)_status, Strings.HistoryLine, 13, (FontStyle)0, Subtle); HistoryChart((Transform)(object)_status, Heat.History.Append(Heat.Value).ToList()); Label((Transform)(object)_status, Strings.InformantsLine(Heat.Informants.Count), 15, (FontStyle)0, Subtle); Label((Transform)(object)_status, Strings.EvidenceLine(Heat.EvidenceCount), 15, (FontStyle)0, Subtle); Label((Transform)(object)_status, Strings.TrustLine(Heat.Trust), 15, (FontStyle)0, Subtle); if (Heat.SnitchingCount > 0) { Label((Transform)(object)_status, Strings.SnitchingLine(Heat.SnitchingCount), 15, (FontStyle)0, Hot); } if (Heat.DetainedCount > 0) { Label((Transform)(object)_status, Strings.DetainedLine(Heat.DetainedCount), 15, (FontStyle)0, Subtle); } if (Heat.HeldDealers.Count > 0) { Label((Transform)(object)_status, Strings.HeldLine(Heat.HeldDealers.Count), 15, (FontStyle)0, Hot); } if ((int)Heat.WantedLevel != 0) { Label((Transform)(object)_status, Strings.WantedLine(((object)Heat.WantedLevel/*cast due to .constrained prefix*/).ToString()), 15, (FontStyle)1, Hot); } Raid knownRaid = Heat.KnownRaid; if (knownRaid != null) { Label((Transform)(object)_status, Strings.RaidLine(knownRaid), 15, (FontStyle)1, Hot); } if (Sync.IsClient) { Label((Transform)(object)_status, Strings.SyncedLine, 13, (FontStyle)2, Subtle); } Label((Transform)(object)_actions, Strings.VanceTitle, 26, (FontStyle)1, Color.white); Label((Transform)(object)_actions, Strings.CashLine(Money.GetCashBalance()), 15, (FontStyle)0, Subtle); if ((int)Heat.WantedLevel != 0) { Action(Strings.GoAwayButton(Heat.BribeCost), Heat.MakeItGoAway); } Action(Strings.LoseFileButton(Settings.LoseFileCost), Heat.LoseFile); if (Heat.EvidenceCount > 0) { Action(Strings.CleanerButton(Heat.CleanupCost), Heat.CallCleaner); } if (Heat.TipOffReady) { Action(Strings.TipOffButton(Settings.TipOffCost), Heat.TipOff); } if (Heat.FavourPending) { Action(Strings.FavourButton(Settings.FavourCost), Heat.DoFavour); } foreach (ArrestedDealer item in Heat.HeldDealers.Where((ArrestedDealer d) => !d.Seized)) { string id = item.NpcId; Action(Strings.BailButton(item.Name, Settings.BailCost), () => Heat.Bail(id)); } foreach (Informant informant in Heat.Informants) { string id2 = informant.NpcId; Action(Strings.PayOffButton(informant.Name, Settings.PayOffCost), () => Heat.PayOff(id2)); } if (_note.Length > 0) { Label((Transform)(object)_actions, _note, 15, (FontStyle)2, Color.white); } UIFactory.FitContentHeight(_status); UIFactory.FitContentHeight(_actions); } private void Action(string label, Func<string> action) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) var (go, val, _) = UIFactory.RoundedButtonWithLabel("Action", label, (Transform)(object)_actions, Accent, 300f, 42f, 16, Color.white); Size(go, 42f); ButtonUtils.AddListener(val, (Action)delegate { _note = action(); Refresh(); }); } private static RectTransform Column(string name, Transform parent) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected O, but got Unknown //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) ScrollRect val2 = default(ScrollRect); RectTransform val = UIFactory.ScrollableVerticalList(name, parent, ref val2); val2.horizontal = false; val2.vertical = true; LayoutElement val3 = ((Component)val2).gameObject.GetComponent<LayoutElement>(); if ((Object)(object)val3 == (Object)null) { val3 = ((Component)val2).gameObject.AddComponent<LayoutElement>(); } val3.flexibleWidth = 1f; val3.flexibleHeight = 1f; VerticalLayoutGroup val4 = ((Component)val).GetComponent<VerticalLayoutGroup>(); if ((Object)(object)val4 == (Object)null) { val4 = ((Component)val).gameObject.AddComponent<VerticalLayoutGroup>(); } ((HorizontalOrVerticalLayoutGroup)val4).spacing = 10f; ((LayoutGroup)val4).padding = new RectOffset(0, 14, 0, 14); ((LayoutGroup)val4).childAlignment = (TextAnchor)0; ((HorizontalOrVerticalLayoutGroup)val4).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)val4).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)val4).childForceExpandWidth = true; ((HorizontalOrVerticalLayoutGroup)val4).childForceExpandHeight = false; ContentSizeFitter val5 = ((Component)val).GetComponent<ContentSizeFitter>(); if ((Object)(object)val5 == (Object)null) { val5 = ((Component)val).gameObject.AddComponent<ContentSizeFitter>(); } val5.horizontalFit = (FitMode)0; val5.verticalFit = (FitMode)2; val.anchorMin = new Vector2(0f, 1f); val.anchorMax = new Vector2(1f, 1f); val.pivot = new Vector2(0.5f, 1f); val.offsetMin = new Vector2(0f, val.offsetMin.y); val.offsetMax = new Vector2(0f, val.offsetMax.y); return val; } private static void Label(Transform parent, string text, int size, FontStyle style, Color color) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) ((Graphic)UIFactory.Text("Label", text, parent, size, (TextAnchor)3, style)).color = color; } private static void Bar(Transform parent, float fill) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) GameObject val = UIFactory.Panel("Bar", parent, BarBack, (Vector2?)null, (Vector2?)null, false); Size(val, 12f); if (!(fill <= 0f)) { RectTransform component = UIFactory.Panel("Fill", val.transform, Color.Lerp(Cool, Hot, fill), (Vector2?)null, (Vector2?)null, false).GetComponent<RectTransform>(); component.anchorMin = Vector2.zero; component.anchorMax = new Vector2(Mathf.Clamp01(fill), 1f); component.offsetMin = Vector2.zero; component.offsetMax = Vector2.zero; } } private static void HistoryChart(Transform parent, List<float> values) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("History"); val.AddComponent<RectTransform>(); val.transform.SetParent(parent, false); Size(val, 48f); HorizontalLayoutGroup obj = val.AddComponent<HorizontalLayoutGroup>(); ((HorizontalOrVerticalLayoutGroup)obj).spacing = 6f; ((LayoutGroup)obj).childAlignment = (TextAnchor)6; ((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = true; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = true; foreach (float value in values) { GameObject val2 = UIFactory.Panel("Day", val.transform, BarBack, (Vector2?)null, (Vector2?)null, false); float num = Mathf.Clamp01(value / 100f); if (!(num <= 0f)) { RectTransform component = UIFactory.Panel("Fill", val2.transform, Color.Lerp(Cool, Hot, num), (Vector2?)null, (Vector2?)null, false).GetComponent<RectTransform>(); component.anchorMin = Vector2.zero; component.anchorMax = new Vector2(1f, num); component.offsetMin = Vector2.zero; component.offsetMax = Vector2.zero; } } } internal static void ApplyVisibility() { if ((Object)(object)_iconRoot == (Object)null) { _iconRoot = ((IEnumerable<Transform>)Resources.FindObjectsOfTypeAll<Transform>()).FirstOrDefault((Func<Transform, bool>)delegate(Transform t) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) if (((Object)t).name == "AppIcons") { Scene scene = ((Component)t).gameObject.scene; return ((Scene)(ref scene)).IsValid(); } return false; }); } Transform val = (((Object)(object)_iconRoot == (Object)null) ? null : _iconRoot.Find("TMLHeat")); if ((Object)(object)val != (Object)null) { ((Component)val).gameObject.SetActive(Heat.Active); } } private static void Size(GameObject go, float height) { LayoutElement val = go.GetComponent<LayoutElement>(); if ((Object)(object)val == (Object)null) { val = go.AddComponent<LayoutElement>(); } val.minHeight = height; val.preferredHeight = height; } private static Sprite BuildIcon() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: 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_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(128, 128) { hideFlags = (HideFlags)32 }; Color val2 = default(Color); ((Color)(ref val2))..ctor(0.17f, 0.18f, 0.21f); Color val3 = default(Color); ((Color)(ref val3))..ctor(0.09f, 0.09f, 0.11f); Color val4 = default(Color); ((Color)(ref val4))..ctor(0.93f, 0.93f, 0.95f); Color val5 = default(Color); ((Color)(ref val5))..ctor(0.9f, 0.22f, 0.12f); for (int i = 0; i < 128; i++) { for (int j = 0; j < 128; j++) { float u = ((float)j + 0.5f) / 128f; float num = ((float)i + 0.5f) / 128f; Color val6 = Color.Lerp(val3, val2, num); val6 = Color.Lerp(val6, val4, Cover(Thermometer(u, num, 0.105f, 0.17f), 128)); val6 = Color.Lerp(val6, val3, Cover(Thermometer(u, num, 0.06f, 0.125f), 128)); float num2 = Mathf.Max(Thermometer(u, num, 0.06f, 0.125f), num - 0.6f); val6 = Color.Lerp(val6, val5, Cover(num2 + 0.012f, 128)); val6 = Color.Lerp(val6, val4, Cover(Ticks(u, num), 128)); val.SetPixel(j, i, val6); } } val.Apply(); Sprite obj = Sprite.Create(val, new Rect(0f, 0f, 128f, 128f), new Vector2(0.5f, 0.5f)); ((Object)obj).hideFlags = (HideFlags)32; return obj; } private static float Thermometer(float u, float v, float tube, float bulb) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Abs(u - 0.44f) - tube; float num2 = Mathf.Max(0.3f - v, v - 0.8f); float num3; Vector2 val; if (!(num2 > 0f)) { num3 = num; } else { val = new Vector2(Mathf.Max(num, 0f), num2); num3 = ((Vector2)(ref val)).magnitude + Mathf.Min(Mathf.Max(num, num2), 0f); } float num4 = num3; if (v > 0.8f) { val = new Vector2(u - 0.44f, v - 0.8f); num4 = ((Vector2)(ref val)).magnitude - tube; } val = new Vector2(u - 0.44f, v - 0.25f); float num5 = ((Vector2)(ref val)).magnitude - bulb; return Mathf.Min(num4, num5); } private static float Ticks(float u, float v) { float num = float.MaxValue; for (int i = 0; i < 4; i++) { float num2 = Mathf.Abs(v - (0.48f + (float)i * 0.09f)) - 0.012f; float num3 = Mathf.Abs(u - 0.66f) - 0.05f; num = Mathf.Min(num, Mathf.Max(num3, num2)); } return num; } private static float Cover(float distance, int size) { return Mathf.Clamp01(0.5f - distance * (float)size); } } public class HeatCommand : BaseConsoleCommand { private static readonly Dictionary<string, string> Help = new Dictionary<string, string> { ["status"] = "show everything", ["set <n>"] = "set heat (triggers warnings)", ["add <n>"] = "add heat", ["trust <n>"] = "set Vance's trust", ["unlock"] = "unlock now", ["deal"] = "simulate one of your deals", ["dealerdeal"] = "simulate a dealer's deal", ["witness [npcId]"] = "someone sees you deal", ["informant [npcId]"] = "turn someone into an informant", ["evidence [n]"] = "leave evidence where you stand", ["findevidence"] = "police find the oldest evidence", ["employees"] = "check lockers now", ["wanted <0-4>"] = "set your wanted level", ["bribeoffer"] = "get wanted and receive Vance's bribe offer", ["sting"] = "the next bribe will be a sting", ["investigate"] = "start an investigation", ["schedule"] = "schedule a raid for next hour", ["raid [property]"] = "raid now (your current property if inside)", ["countdown"] = "preview the raid countdown", ["release"] = "release detained staff and dealers", ["personal <n>"] = "set your own heat", ["favour"] = "Vance asks for a favour now", ["betray"] = "Vance sells you out now", ["arrestdealer [npcId]"] = "arrest one of your dealers", ["seize"] = "seize held dealers' cash and product now", ["undercover [npcId]"] = "plant an undercover officer", ["patrol"] = "send a patrol to your evidence", ["nervous"] = "count customers too nervous to order", ["lang <en|es>"] = "switch language until restart", ["hour"] = "run hourly checks", ["day"] = "run daily checks", ["sync"] = "co-op sync status", ["reset"] = "clear TML Heat data for this save", ["debug on|off"] = "log heat events" }; public override string CommandWord => "heat"; public override string CommandDescription => "TML Heat tools. Type 'heat help' for options."; public override string ExampleUsage => "heat set 80"; public override void ExecuteCommand(List<string> args) { string text = ((args.Count > 0) ? args[0].ToLowerInvariant() : "status"); string text2 = ((args.Count > 1) ? string.Join(" ", args.Skip(1)) : ""); if (text == "help") { foreach (var (value, value2) in Help) { Core.Log.Msg($"heat {value,-18} {value2}"); } return; } if (text == "sync") { Core.Log.Msg(Sync.Status); return; } if (HeatSave.Instance == null || Player.Local == null) { Core.Log.Warning("Load a save first."); return; } if (Sync.IsClient && text != "status" && text != "countdown" && text != "wanted" && text != "debug") { Core.Log.Warning("Only the host can change TML Heat. Run this on the host."); return; } switch (text) { case "set": Heat.SetHeat(Number(text2)); break; case "add": Heat.SetHeat(Heat.Value + Number(text2)); break; case "trust": Heat.SetTrust((int)Number(text2)); break; case "unlock": Heat.CheckUnlock(force: true); break; case "deal": Heat.ForcePlayerDeal(); break; case "dealerdeal": Heat.ForceDealerDeal(); break; case "witness": Heat.ForceWitness(text2); break; case "informant": Heat.ForceInformant(text2); break; case "evidence": Heat.AddEvidence(Math.Max(1, (int)Number(text2))); break; case "findevidence": Heat.FindEvidenceNow(); break; case "employees": Heat.CheckEmployeesNow(); break; case "wanted": Heat.SetWanted((int)Number(text2)); break; case "bribeoffer": Heat.ForceBribeOffer(); break; case "sting": Heat.ArmSting(); Core.Log.Msg("The next bribe will be a sting."); break; case "investigate": Heat.ForceInvestigation(); break; case "schedule": Heat.ScheduleRaidNow(); break; case "raid": Heat.ForceRaid(text2); break; case "countdown": Hud.Countdown("Test", Settings.RaidCountdown); break; case "release": Heat.ReleaseAll(); break; case "personal": Heat.SetPersonalHeat(Number(text2)); break; case "favour": Heat.AskFavourNow(); break; case "betray": Heat.BetrayNow(); break; case "arrestdealer": Heat.ArrestDealerNow(text2); break; case "seize": Heat.SeizeHeldNow(); break; case "undercover": Heat.ForceUndercover(text2); break; case "patrol": Heat.StartPatrolNow(); break; case "nervous": Core.Log.Msg($"{Heat.NervousCount()} customers are too nervous to order today."); break; case "lang": Lang.Load((text2.Length > 0) ? text2.ToLowerInvariant() : "en"); Heat.NotifyChanged(); break; case "hour": Heat.OnHour(); break; case "day": Heat.OnDay(); break; case "reset": Heat.ResetSave(); break; case "debug": Settings.DebugLogs = text2 != "off"; Core.Log.Msg("Debug logs " + (Settings.DebugLogs ? "on" : "off")); break; default: Core.Log.Warning("Unknown option '" + text + "'. Type 'heat help'."); return; case "status": break; } Core.Log.Msg(Heat.Status()); } private static float Number(string text) { if (!float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return 0f; } return result; } } public class HeatSave : Saveable { [SaveableField("tmlheat")] private HeatData _data = new HeatData(); internal static HeatSave? Instance { get; private set; } internal HeatData Data => _data; public HeatSave() { Instance = this; } internal void Reset() { _data = new HeatData(); } protected override void OnLoaded() { if (_data == null) { _data = new HeatData(); } Heat.NotifyChanged(); } } public class HeatData { public float Heat; public int Trust; public bool Unlocked; public List<Evidence> Evidence = new List<Evidence>(); public List<Informant> Informants = new List<Informant>(); public Raid? Raid; public int LastWarning; public List<string> SnitchingEmployees = new List<string>(); public List<DetainedEmployee> Detained = new List<DetainedEmployee>(); public Dictionary<string, float> PlayerHeat = new Dictionary<string, float>(); public List<float> History = new List<float>(); public Dictionary<string, int> StaffSince = new Dictionary<string, int>(); public List<ArrestedDealer> ArrestedDealers = new List<ArrestedDealer>(); public Undercover? Undercover; public int FavourDay = -1; public int LastTipOffDay = -99; public int BetrayStreak; } public class Evidence { public string Region = ""; public string Source = ""; public int Day; public float X; public float Y; public float Z; } public class Informant { public string NpcId = ""; public string Name = ""; public int Since; } public class Raid { public string Property = ""; public int Day; public int Time; public bool Warned; } public class DetainedEmployee { public string NpcId = ""; public string Name = ""; public int ReleaseDay; } public class ArrestedDealer { public string NpcId = ""; public string Name = ""; public int Day; public int ReleaseDay; public bool Seized; } public class Undercover { public string NpcId = ""; public string Name = ""; public int Since; public bool Warned; } public class Snapshot { public bool Unlocked; public float Heat; public int Trust; public int Evidence; public int Snitching; public int Detained; public bool FavourPending; public bool TipOffReady; public List<Informant> Informants = new List<Informant>(); public List<ArrestedDealer> Arrested = new List<ArrestedDealer>(); public Dictionary<string, float> PlayerHeat = new Dictionary<string, float>(); public List<float> History = new List<float>(); public Raid? Raid; public List<SyncMessage> Messages = new List<SyncMessage>(); } public class SyncMessage { public int Id; public string Target = ""; public string Text = ""; public string Effect = ""; public string[] Replies = Array.Empty<string>(); } internal static class Hud { private static readonly Color Banner = new Color(0.08f, 0.08f, 0.09f, 0.7f); private static GameObject? _root; private static Text? _label; private static int _run; public static void Countdown(string property, int seconds) { MelonCoroutines.Start(Run(property, seconds, ++_run)); } private static IEnumerator Run(string property, int seconds, int run) { Ensure(); _root.SetActive(true); int remaining = seconds; while (remaining > 0 && run == _run) { _label.text = Strings.RaidCountdown(property, remaining); yield return (object)new WaitForSeconds(1f); remaining--; } if (run == _run) { _label.text = Strings.RaidBreach; yield return (object)new WaitForSeconds(1.5f); if (run == _run) { _root.SetActive(false); } } } private static void Ensure() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null)) { _root = new GameObject("TMLHeatHud"); Object.DontDestroyOnLoad((Object)(object)_root); Canvas obj = _root.AddComponent<Canvas>(); obj.renderMode = (RenderMode)0; obj.sortingOrder = 500; CanvasScaler obj2 = _root.AddComponent<CanvasScaler>(); obj2.uiScaleMode = (ScaleMode)1; obj2.referenceResolution = new Vector2(1920f, 1080f); GameObject val = UIFactory.Panel("Banner", _root.transform, Banner, (Vector2?)new Vector2(0.38f, 0.91f), (Vector2?)new Vector2(0.62f, 0.95f), false); Stretch(val.GetComponent<RectTransform>()); _label = UIFactory.Text("Label", "", val.transform, 20, (TextAnchor)4, (FontStyle)0); ((Graphic)_label).color = new Color(1f, 0.55f, 0.5f); RectTransform component = ((Component)_label).GetComponent<RectTransform>(); component.anchorMin = Vector2.zero; component.anchorMax = Vector2.one; Stretch(component); } } private static void Stretch(RectTransform rect) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) rect.offsetMin = Vector2.zero; rect.offsetMax = Vector2.zero; } } internal static class Lang { private const string Fallback = "en"; private static Dictionary<string, string> _fallback = new Dictionary<string, string>(); private static Dictionary<string, string> _active = new Dictionary<string, string>(); public static string Folder => Path.Combine(MelonEnvironment.UserDataDirectory, "TMLHeat", "lang"); public static bool Exists(string language) { if (!File.Exists(Path.Combine(Folder, language + ".json"))) { return Embedded(language) != null; } return true; } public static void Load(string language) { Directory.CreateDirectory(Folder); string[] array = new string[2] { "en", "es" }; for (int i = 0; i < array.Length; i++) { Export(array[i]); } _fallback = Read("en"); _active = ((language == "en") ? _fallback : Read(language)); if (_active.Count == 0) { Core.Log.Warning("No translations found for '" + language + "'. Using English."); _active = _fallback; } } public static string T(string key) { if (!_active.TryGetValue(key, out string value)) { if (!_fallback.TryGetValue(key, out string value2)) { return key; } return value2; } return value; } public static string T(string key, params object[] args) { return string.Format(CultureInfo.InvariantCulture, T(key), args); } private static Dictionary<string, string> Read(string language) { Dictionary<string, string> dictionary = Parse(Embedded(language)) ?? new Dictionary<string, string>(); string text = Path.Combine(Folder, language + ".json"); if (!File.Exists(text)) { return dictionary; } Dictionary<string, string> dictionary2 = Parse(File.ReadAllText(text)); if (dictionary2 == null) { Core.Log.Warning(text + " isn't valid JSON. Using the built-in text."); return dictionary; } foreach (KeyValuePair<string, string> item in dictionary2) { item.Deconstruct(out var key, out var value); string key2 = key; string value2 = value; dictionary[key2] = value2; } return dictionary; } private static void Export(string language) { string path = Path.Combine(Folder, language + ".json"); if (!File.Exists(path)) { string text = Embedded(language); if (text != null) { File.WriteAllText(path, text); } } } private static string? Embedded(string language) { using Stream stream = typeof(Lang).Assembly.GetManifestResourceStream("TMLHeat.lang." + language + ".json"); if (stream == null) { return null; } using StreamReader streamReader = new StreamReader(stream); return streamReader.ReadToEnd(); } private static Dictionary<string, string>? Parse(string? json) { if (string.IsNullOrEmpty(json)) { return null; } return Json.Read<Dictionary<string, string>>(json); } } [HarmonyPatch(typeof(Customer), "ShouldTryGenerateDeal")] internal static class NervousCustomerPatch { private static bool Prefix(Customer __instance, ref bool __result) { NPC component = ((Component)__instance).GetComponent<NPC>(); if ((Object)(object)component == (Object)null || !Heat.IsNervous(component.ID)) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(Dealer), "ShouldAcceptContract")] internal static class HeldDealerPatch { private static bool Prefix(Dealer __instance, ref bool __result) { if (!Heat.IsDealerHeld(((NPC)__instance).ID)) { return true; } __result = false; return false; } } internal static class Raids { public static readonly Vector3 Staging = new Vector3(-50f, 1.06f, 70f); private const float SightRange = 16f; private const float ArrestRange = 2.2f; private const float ChaseRefresh = 0.5f; private const float RetargetDelay = 2.5f; private const int MaxWaypoints = 16; private const string SpeedControl = "tmlheat_raid"; private const float RaidSpeed = 0.85f; private const float StageTimeout = 30f; private const float MinimumSweep = 20f; private static readonly Random Rng = new Random(); private static readonly List<SwatOfficer> Squad = new List<SwatOfficer>(); private static readonly List<(DoorController Door, bool Openable)> Doors = new List<(DoorController, bool)>(); public static bool Running { get; private set; } public static void Start(Property property) { if (!Running) { Running = true; MelonCoroutines.Start(Guarded(property)); } } private static IEnumerator Guarded(Property property) { try { IEnumerator run = Run(property); while (run.MoveNext()) { yield return run.Current; } } finally { if (Squad.Count > 0 || Doors.Count > 0) { Withdraw(); } Running = false; } } public static void Stage() { MelonCoroutines.Start(StageWhenSpawned()); } private static IEnumerator StageWhenSpawned() { float deadline = Time.realtimeSinceStartup + 30f; while (Time.realtimeSinceStartup < deadline && !NPC.All.OfType<SwatOfficer>().All(Spawned)) { yield return (object)new WaitForSeconds(1f); } if (Running) { yield break; } foreach (SwatOfficer item in NPC.All.OfType<SwatOfficer>().Where(Spawned)) { Deploy(item, deployed: false, ""); } Core.Debug($"Staged {NPC.All.OfType<SwatOfficer>().Count(Spawned)} SWAT officers."); Heat.HookNpcs(); } private static IEnumerator Run(Property property) { string name = property.PropertyName; Core.Debug($"Raid on {name} in {Settings.RaidCountdown}s."); Vance.Notify("", "", $"raid:{Settings.RaidCountdown}:{name}"); yield return (object)new WaitForSeconds((float)Settings.RaidCountdown); Breach(property); List<Vector3> waypoints = Waypoints(property); HashSet<string> spotted = new HashSet<string>(); HashSet<string> arrested = new HashSet<string>(); Dictionary<SwatOfficer, float> nextMove = new Dictionary<SwatOfficer, float>(); float end = Time.time + (float)Settings.RaidDuration; int visited = 0; float diagnoseAt = Time.time + 1.5f; float minimumEnd = Time.time + 20f; while (Time.time < end && Squad.Count > 0) { List<Player> list = Targets(property, arrested); foreach (SwatOfficer officer in Squad) { var (player, num) = (from p in list select (p: p, Vector3.Distance(((NPC)officer).Position, ((Component)p).transform.position)) into t where t.Item2 <= 16f orderby t.Item2 select t).FirstOrDefault(); float value; if ((Object)(object)player != (Object)null) { spotted.Add(player.PlayerCode); if (num <= 2.2f && arrested.Add(player.PlayerCode)) { Safely(delegate { //IL_001b: Unknown result type (might be due to invalid IL or missing references) ((NPC)officer).Movement.FacePoint(((Component)player).transform.position); }); Vance.Notify("", player.PlayerCode, "arrest"); Core.Debug($"{((NPC)officer).FullName} arrested {player.PlayerCode} in {name}."); } else { Reach(officer, ((Component)player).transform.position); nextMove[officer] = Time.time + 0.5f; } } else if (visited < waypoints.Count && !Moving(officer) && (!nextMove.TryGetValue(officer, out value) || !(Time.time < value))) { Reach(officer, waypoints[visited++]); nextMove[officer] = Time.time + 2.5f; } } if (diagnoseAt > 0f && Time.time >= diagnoseAt) { diagnoseAt = 0f; Diagnose(property, waypoints); } if (list.Count == 0 && visited >= waypoints.Count && Time.time >= minimumEnd && !Squad.Any(Moving) && nextMove.Values.All((float t) => Time.time >= t)) { break; } yield return (object)new WaitForSeconds(0.5f); } foreach (string item in spotted.Except(arrested)) { Vance.Notify("", item, "wanted"); } Withdraw(); Heat.FinishRaid(property, spotted.Count > 0); } private static void Breach(Property property) { //IL_000d: 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) Vector3 entrance = property.SpawnPoint.position; string gun = GunPath(); List<SwatOfficer> list = (from o in NPC.All.OfType<SwatOfficer>().Where(Spawned) orderby ((NPC)o).ID select o).Take(Settings.RaidOfficers).ToList(); for (int num = 0; num < list.Count; num++) { float angle = (float)num * (float)Math.PI * 2f / (float)list.Count; Deploy(list[num], deployed: true, gun); SwatOfficer officer = list[num]; Safely(delegate { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) Live(officer).Movement.Warp(entrance + new Vector3(Mathf.Cos(angle), 0f, Mathf.Sin(angle)) * 1.5f); }); Squad.Add(list[num]); } if (Squad.Count == 0) { Core.Log.Warning("No SWAT officers were available for the raid."); } foreach (DoorController item in Object.FindObjectsOfType<DoorController>()) { if (!((Object)(object)item == (Object)null) && Near(property, ((Component)item).transform)) { Doors.Add((item, item.OpenableByNPCs)); item.OpenableByNPCs = true; item.SetIsOpen_Server(true, (EDoorSide)1, false); } } Core.Debug($"Breached {property.PropertyName} with {Squad.Count} officers, {Doors.Count} doors opened."); } private static void Diagnose(Property property, List<Vector3> waypoints) { if (!Settings.DebugLogs) { return; } Core.Debug($"Raid waypoints: {waypoints.Count}. Players inside: {Targets(property, new HashSet<string>()).Count}."); foreach (SwatOfficer item in Squad) { NPC? obj = Live(item); NavMeshAgent val = ((obj != null) ? obj.Movement._agent : null); Core.Debug(((Object)(object)val == (Object)null) ? (((NPC)item).FullName + ": no agent.") : $"{((NPC)item).FullName}: enabled {((Behaviour)val).enabled}, on navmesh {val.isOnNavMesh}, stopped {val.isOnNavMesh && val.isStopped}, path {val.hasPath}, speed {val.speed:0.0}, moving {Moving(item)}."); } } private static void Deploy(SwatOfficer officer, bool deployed, string gun) { NPC npc = Live(officer); if (npc == null) { return; } MSGConversation mSGConversation = npc.MSGConversation; if (mSGConversation != null && mSGConversation.uiCreated) { mSGConversation.SetEntryVisibility(false); } if (deployed) { Safely(delegate { npc.Movement.SetAgentEnabled(true); }); if (gun.Length > 0) { npc.SetEquippable_Networked_Return((NetworkConnection)null, gun); } npc.SendEquippableMessage_Networked((NetworkConnection)null, "Raise"); ((NPC)officer).Movement.AddSpeedControl("tmlheat_raid", 60, 0.85f); } else { npc.SendEquippableMessage_Networked((NetworkConnection)null, "Lower"); npc.SetEquippable_Networked_Return((NetworkConnection)null, ""); ((NPC)officer).Movement.RemoveSpeedControl("tmlheat_raid"); Safely(delegate { npc.Movement.Stop(); }); } npc.SetVisible(deployed, true); } private static NPC? Live(SwatOfficer officer) { if (!((Object)(object)((NPC)officer).gameObject == (Object)null)) { return ((NPC)officer).gameObject.GetComponent<NPC>(); } return null; } private static bool Moving(SwatOfficer officer) { try { NPC? obj = Live(officer); return obj != null && obj.Movement.IsMoving; } catch (Exception) { return false; } } private static void Reach(SwatOfficer officer, Vector3 target) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) NPC npc = Live(officer); if (npc == null) { return; } List<Vector3> list = new List<Vector3> { target }; NavMeshHit val = default(NavMeshHit); if (NavMesh.SamplePosition(target, ref val, 3f, -1)) { list.Add(((NavMeshHit)(ref val)).position); } list.AddRange(from d in Doors where (Object)(object)d.Door != (Object)null select ((Component)d.Door).transform.position into p orderby Vector3.Distance(p, target) select p); foreach (Vector3 point in list) { bool reachable = false; Safely(delegate { //IL_0012: Unknown result type (might be due to invalid IL or missing references) reachable = npc.Movement.CanGetTo(point, 2.2f); }); if (reachable) { Go(officer, point); return; } } Core.Debug($"{((NPC)officer).FullName} can't find a route towards {target}."); Go(officer, target); } private static void Go(SwatOfficer officer, Vector3 destination) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) Safely(delegate { //IL_0016: Unknown result type (might be due to invalid IL or missing references) NPC? obj = Live(officer); if (obj != null) { obj.Movement.SetDestination(destination); } }); } private static void Safely(Action action) { try { action(); } catch (Exception ex) { Core.Debug("Raid officer action failed: " + ex.Message); } } private static bool Spawned(SwatOfficer officer) { NPC val = Live(officer); if (val != null) { return ((NetworkBehaviour)val).IsSpawned; } return false; } private static string GunPath() { Enumerator<PoliceOfficer> enumerator = PoliceOfficer.Officers.GetEnumerator(); while (enumerator.MoveNext()) { PoliceOfficer current = enumerator.Current; if ((Object)(object)current != (Object)null && (Object)(object)current.GunPrefab != (Object)null) { return current.GunPrefab.AssetPath; } } return ""; } private static List<Player> Targets(Property property, HashSet<string> arrested) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) List<Player> list = new List<Player>(); Enumerator<Player> enumerator = Player.PlayerList.GetEnumerator(); while (enumerator.MoveNext()) { Player current = enumerator.Current; if ((Object)(object)current != (Object)null && !arrested.Contains(current.PlayerCode) && property.DoBoundsContainPoint(((Component)current).transform.position)) { list.Add(current); } } return list; } private static List<Vector3> Waypoints(Property property) { //IL_001b: 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_0064: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) List<Vector3> list = new List<Vector3>(); if ((Object)(object)property.InteriorSpawnPoint != (Object)null) { list.Add(property.InteriorSpawnPoint.position); } if ((Object)(object)property.NPCSpawnPoint != (Object)null) { list.Add(property.NPCSpawnPoint.position); } foreach (Transform item in (Il2CppArrayBase<Transform>)(object)property.EmployeeIdlePoints) { if ((Object)(object)item != (Object)null) { list.Add(item.position); } } Enumerator<BuildableItem> enumerator2 = property.BuildableItems.GetEnumerator(); while (enumerator2.MoveNext()) { BuildableItem current2 = enumerator2.Current; if ((Object)(object)current2 != (Object)null) { list.Add(((Component)current2).transform.position); } } IEnumerable<Vector3> second = (from _ in ((IEnumerable<Vector3>)list).Where((Func<Vector3, bool>)property.DoBoundsContainPoint) orderby Rng.Next() select _).Take(16); return (from d in Doors where (Object)(object)d.Door != (Object)null select ((Component)d.Door).transform.position).Concat(second).ToList(); } private static bool Near(Property property, Transform door) { //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_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0011: 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_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) Vector3 position = door.position; if (!property.DoBoundsContainPoint(position) && !property.DoBoundsContainPoint(position + door.forward * 1.5f) && !property.DoBoundsContainPoint(position - door.forward * 1.5f) && !property.DoBoundsContainPoint(position + door.right * 1.5f)) { return property.DoBoundsContainPoint(position - door.right * 1.5f); } return true; } private static void Withdraw() { fore